Changeset 41752
- Timestamp:
- 10/04/2017 09:00:15 PM (7 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/.jshintrc
r39898 r41752 22 22 "jQuery": false, 23 23 "JSON": false, 24 "wp": false 24 "wp": false, 25 "export": false, 26 "module": false, 27 "require": false 25 28 } 26 29 } -
trunk/Gruntfile.js
r41751 r41752 1 1 /* jshint node:true */ 2 2 /* globals Set */ 3 var webpackConfig = require( './webpack.config.prod' ); 4 var webpackDevConfig = require( './webpack.config.dev' ); 5 3 6 module.exports = function(grunt) { 4 7 var path = require('path'), … … 8 11 BUILD_DIR = 'build/', 9 12 BANNER_TEXT = '/*! This file is auto-generated */', 10 autoprefixer = require('autoprefixer'), 11 mediaConfig = {}, 12 mediaBuilds = ['audiovideo', 'grid', 'models', 'views']; 13 autoprefixer = require( 'autoprefixer' ); 13 14 14 15 // Load tasks. … … 16 17 // Load legacy utils 17 18 grunt.util = require('grunt-legacy-util'); 18 19 mediaBuilds.forEach( function ( build ) {20 var path = SOURCE_DIR + 'wp-includes/js/media';21 mediaConfig[ build ] = { files : {} };22 mediaConfig[ build ].files[ path + '-' + build + '.js' ] = [ path + '/' + build + '.manifest.js' ];23 } );24 19 25 20 // Project configuration. … … 178 173 } 179 174 }, 180 browserify: mediaConfig,181 175 sass: { 182 176 colors: { … … 339 333 }, 340 334 media: { 341 options: {342 browserify: true343 },344 335 src: [ 345 336 SOURCE_DIR + 'wp-includes/js/media/**/*.js' … … 554 545 } 555 546 }, 556 547 webpack: { 548 prod: webpackConfig, 549 dev: webpackDevConfig 550 }, 557 551 concat: { 558 552 tinymce: { … … 720 714 }, 721 715 config: { 722 files: 'Gruntfile.js' 716 files: [ 717 'Gruntfile.js', 718 'webpack-dev.config.js', 719 'webpack.config.js' 720 ] 723 721 }, 724 722 colors: { … … 757 755 758 756 // Register tasks. 757 758 // Webpack task. 759 grunt.loadNpmTasks( 'grunt-webpack' ); 759 760 760 761 // RTL task. … … 781 782 782 783 grunt.registerTask( 'watch', function() { 783 if ( ! this.args.length || this.args.indexOf( 'browserify' ) > -1 ) { 784 grunt.config( 'browserify.options', { 785 browserifyOptions: { 786 debug: true 787 }, 788 watch: true 789 } ); 790 791 grunt.task.run( 'browserify' ); 784 if ( ! this.args.length || this.args.indexOf( 'webpack' ) > -1 ) { 785 786 grunt.task.run( 'webpack:dev' ); 792 787 } 793 788 … … 800 795 801 796 grunt.registerTask( 'precommit:js', [ 802 ' browserify',797 'webpack:prod', 803 798 'jshint:corejs', 804 799 'uglify:masonry', … … 979 974 var src; 980 975 981 if ( [ 'all', 'rtl', ' browserify' ].indexOf( target ) === -1 ) {976 if ( [ 'all', 'rtl', 'webpack' ].indexOf( target ) === -1 ) { 982 977 return; 983 978 } -
trunk/package.json
r41351 r41752 16 16 "grunt": "~0.4.5", 17 17 "grunt-banner": "^0.6.0", 18 "grunt-browserify": "~5.0.0",19 18 "grunt-contrib-clean": "~1.0.0", 20 19 "grunt-contrib-compress": "~1.3.0", … … 37 36 "grunt-sass": "~1.2.1", 38 37 "ink-docstrap": "^1.3.0", 39 "matchdep": "~1.0.0" 38 "grunt-webpack": "^3.0.2", 39 "matchdep": "~1.0.0", 40 "webpack": "^3.6.0", 41 "webpack-dev-server": "^2.9.1" 40 42 } 41 43 } -
trunk/src/wp-includes/js/media-audiovideo.js
r41351 r41752 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 var media = wp.media, 3 71 baseSettings = window._wpmejsSettings || {}, … … 269 337 }; 270 338 271 media.model.PostMedia = require( './models/post-media.js' ); 272 media.controller.AudioDetails = require( './controllers/audio-details.js' ); 273 media.controller.VideoDetails = require( './controllers/video-details.js' ); 274 media.view.MediaFrame.MediaDetails = require( './views/frame/media-details.js' ); 275 media.view.MediaFrame.AudioDetails = require( './views/frame/audio-details.js' ); 276 media.view.MediaFrame.VideoDetails = require( './views/frame/video-details.js' ); 277 media.view.MediaDetails = require( './views/media-details.js' ); 278 media.view.AudioDetails = require( './views/audio-details.js' ); 279 media.view.VideoDetails = require( './views/video-details.js' ); 280 281 },{"./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){ 339 media.model.PostMedia = __webpack_require__( 1 ); 340 media.controller.AudioDetails = __webpack_require__( 2 ); 341 media.controller.VideoDetails = __webpack_require__( 3 ); 342 media.view.MediaFrame.MediaDetails = __webpack_require__( 4 ); 343 media.view.MediaFrame.AudioDetails = __webpack_require__( 5 ); 344 media.view.MediaFrame.VideoDetails = __webpack_require__( 6 ); 345 media.view.MediaDetails = __webpack_require__( 7 ); 346 media.view.AudioDetails = __webpack_require__( 8 ); 347 media.view.VideoDetails = __webpack_require__( 9 ); 348 349 350 /***/ }), 351 /* 1 */ 352 /***/ (function(module, exports) { 353 354 /** 355 * wp.media.model.PostMedia 356 * 357 * Shared model class for audio and video. Updates the model after 358 * "Add Audio|Video Source" and "Replace Audio|Video" states return 359 * 360 * @memberOf wp.media.model 361 * 362 * @class 363 * @augments Backbone.Model 364 */ 365 var PostMedia = Backbone.Model.extend(/** @lends wp.media.model.PostMedia.prototype */{ 366 initialize: function() { 367 this.attachment = false; 368 }, 369 370 setSource: function( attachment ) { 371 this.attachment = attachment; 372 this.extension = attachment.get( 'filename' ).split('.').pop(); 373 374 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { 375 this.unset( 'src' ); 376 } 377 378 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { 379 this.set( this.extension, this.attachment.get( 'url' ) ); 380 } else { 381 this.unset( this.extension ); 382 } 383 }, 384 385 changeAttachment: function( attachment ) { 386 this.setSource( attachment ); 387 388 this.unset( 'src' ); 389 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) { 390 this.unset( ext ); 391 }, this ); 392 } 393 }); 394 395 module.exports = PostMedia; 396 397 398 /***/ }), 399 /* 2 */ 400 /***/ (function(module, exports) { 401 282 402 var State = wp.media.controller.State, 283 403 l10n = wp.media.view.l10n, … … 314 434 module.exports = AudioDetails; 315 435 316 },{}],3:[function(require,module,exports){ 436 437 /***/ }), 438 /* 3 */ 439 /***/ (function(module, exports) { 440 317 441 /** 318 442 * wp.media.controller.VideoDetails … … 349 473 module.exports = VideoDetails; 350 474 351 },{}],4:[function(require,module,exports){ 352 /** 353 * wp.media.model.PostMedia 354 * 355 * Shared model class for audio and video. Updates the model after 356 * "Add Audio|Video Source" and "Replace Audio|Video" states return 357 * 358 * @memberOf wp.media.model 359 * 360 * @class 361 * @augments Backbone.Model 362 */ 363 var PostMedia = Backbone.Model.extend(/** @lends wp.media.model.PostMedia.prototype */{ 364 initialize: function() { 365 this.attachment = false; 366 }, 367 368 setSource: function( attachment ) { 369 this.attachment = attachment; 370 this.extension = attachment.get( 'filename' ).split('.').pop(); 371 372 if ( this.get( 'src' ) && this.extension === this.get( 'src' ).split('.').pop() ) { 373 this.unset( 'src' ); 374 } 375 376 if ( _.contains( wp.media.view.settings.embedExts, this.extension ) ) { 377 this.set( this.extension, this.attachment.get( 'url' ) ); 378 } else { 379 this.unset( this.extension ); 380 } 381 }, 382 383 changeAttachment: function( attachment ) { 384 this.setSource( attachment ); 385 386 this.unset( 'src' ); 387 _.each( _.without( wp.media.view.settings.embedExts, this.extension ), function( ext ) { 388 this.unset( ext ); 389 }, this ); 390 } 391 }); 392 393 module.exports = PostMedia; 394 395 },{}],5:[function(require,module,exports){ 396 var MediaDetails = wp.media.view.MediaDetails, 397 AudioDetails; 398 399 /** 400 * wp.media.view.AudioDetails 401 * 402 * @memberOf wp.media.view 403 * 404 * @class 405 * @augments wp.media.view.MediaDetails 406 * @augments wp.media.view.Settings.AttachmentDisplay 407 * @augments wp.media.view.Settings 408 * @augments wp.media.View 409 * @augments wp.Backbone.View 410 * @augments Backbone.View 411 */ 412 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.AudioDetails.prototype */{ 413 className: 'audio-details', 414 template: wp.template('audio-details'), 415 416 setMedia: function() { 417 var audio = this.$('.wp-audio-shortcode'); 418 419 if ( audio.find( 'source' ).length ) { 420 if ( audio.is(':hidden') ) { 421 audio.show(); 422 } 423 this.media = MediaDetails.prepareSrc( audio.get(0) ); 424 } else { 425 audio.hide(); 426 this.media = false; 427 } 428 429 return this; 430 } 431 }); 432 433 module.exports = AudioDetails; 434 435 },{}],6:[function(require,module,exports){ 436 var MediaDetails = wp.media.view.MediaFrame.MediaDetails, 437 MediaLibrary = wp.media.controller.MediaLibrary, 438 439 l10n = wp.media.view.l10n, 440 AudioDetails; 441 442 /** 443 * wp.media.view.MediaFrame.AudioDetails 444 * 445 * @memberOf wp.media.view.MediaFrame 446 * 447 * @class 448 * @augments wp.media.view.MediaFrame.MediaDetails 449 * @augments wp.media.view.MediaFrame.Select 450 * @augments wp.media.view.MediaFrame 451 * @augments wp.media.view.Frame 452 * @augments wp.media.View 453 * @augments wp.Backbone.View 454 * @augments Backbone.View 455 * @mixes wp.media.controller.StateMachine 456 */ 457 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{ 458 defaults: { 459 id: 'audio', 460 url: '', 461 menu: 'audio-details', 462 content: 'audio-details', 463 toolbar: 'audio-details', 464 type: 'link', 465 title: l10n.audioDetailsTitle, 466 priority: 120 467 }, 468 469 initialize: function( options ) { 470 options.DetailsView = wp.media.view.AudioDetails; 471 options.cancelText = l10n.audioDetailsCancel; 472 options.addText = l10n.audioAddSourceTitle; 473 474 MediaDetails.prototype.initialize.call( this, options ); 475 }, 476 477 bindHandlers: function() { 478 MediaDetails.prototype.bindHandlers.apply( this, arguments ); 479 480 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); 481 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); 482 }, 483 484 createStates: function() { 485 this.states.add([ 486 new wp.media.controller.AudioDetails( { 487 media: this.media 488 } ), 489 490 new MediaLibrary( { 491 type: 'audio', 492 id: 'replace-audio', 493 title: l10n.audioReplaceTitle, 494 toolbar: 'replace-audio', 495 media: this.media, 496 menu: 'audio-details' 497 } ), 498 499 new MediaLibrary( { 500 type: 'audio', 501 id: 'add-audio-source', 502 title: l10n.audioAddSourceTitle, 503 toolbar: 'add-audio-source', 504 media: this.media, 505 menu: false 506 } ) 507 ]); 508 } 509 }); 510 511 module.exports = AudioDetails; 512 513 },{}],7:[function(require,module,exports){ 475 476 /***/ }), 477 /* 4 */ 478 /***/ (function(module, exports) { 479 514 480 var Select = wp.media.view.MediaFrame.Select, 515 481 l10n = wp.media.view.l10n, … … 643 609 module.exports = MediaDetails; 644 610 645 },{}],8:[function(require,module,exports){ 611 612 /***/ }), 613 /* 5 */ 614 /***/ (function(module, exports) { 615 616 var MediaDetails = wp.media.view.MediaFrame.MediaDetails, 617 MediaLibrary = wp.media.controller.MediaLibrary, 618 619 l10n = wp.media.view.l10n, 620 AudioDetails; 621 622 /** 623 * wp.media.view.MediaFrame.AudioDetails 624 * 625 * @memberOf wp.media.view.MediaFrame 626 * 627 * @class 628 * @augments wp.media.view.MediaFrame.MediaDetails 629 * @augments wp.media.view.MediaFrame.Select 630 * @augments wp.media.view.MediaFrame 631 * @augments wp.media.view.Frame 632 * @augments wp.media.View 633 * @augments wp.Backbone.View 634 * @augments Backbone.View 635 * @mixes wp.media.controller.StateMachine 636 */ 637 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{ 638 defaults: { 639 id: 'audio', 640 url: '', 641 menu: 'audio-details', 642 content: 'audio-details', 643 toolbar: 'audio-details', 644 type: 'link', 645 title: l10n.audioDetailsTitle, 646 priority: 120 647 }, 648 649 initialize: function( options ) { 650 options.DetailsView = wp.media.view.AudioDetails; 651 options.cancelText = l10n.audioDetailsCancel; 652 options.addText = l10n.audioAddSourceTitle; 653 654 MediaDetails.prototype.initialize.call( this, options ); 655 }, 656 657 bindHandlers: function() { 658 MediaDetails.prototype.bindHandlers.apply( this, arguments ); 659 660 this.on( 'toolbar:render:replace-audio', this.renderReplaceToolbar, this ); 661 this.on( 'toolbar:render:add-audio-source', this.renderAddSourceToolbar, this ); 662 }, 663 664 createStates: function() { 665 this.states.add([ 666 new wp.media.controller.AudioDetails( { 667 media: this.media 668 } ), 669 670 new MediaLibrary( { 671 type: 'audio', 672 id: 'replace-audio', 673 title: l10n.audioReplaceTitle, 674 toolbar: 'replace-audio', 675 media: this.media, 676 menu: 'audio-details' 677 } ), 678 679 new MediaLibrary( { 680 type: 'audio', 681 id: 'add-audio-source', 682 title: l10n.audioAddSourceTitle, 683 toolbar: 'add-audio-source', 684 media: this.media, 685 menu: false 686 } ) 687 ]); 688 } 689 }); 690 691 module.exports = AudioDetails; 692 693 694 /***/ }), 695 /* 6 */ 696 /***/ (function(module, exports) { 697 646 698 var MediaDetails = wp.media.view.MediaFrame.MediaDetails, 647 699 MediaLibrary = wp.media.controller.MediaLibrary, … … 780 832 module.exports = VideoDetails; 781 833 782 },{}],9:[function(require,module,exports){ 834 835 /***/ }), 836 /* 7 */ 837 /***/ (function(module, exports) { 838 783 839 /* global MediaElementPlayer */ 784 840 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, … … 950 1006 module.exports = MediaDetails; 951 1007 952 },{}],10:[function(require,module,exports){ 1008 1009 /***/ }), 1010 /* 8 */ 1011 /***/ (function(module, exports) { 1012 1013 var MediaDetails = wp.media.view.MediaDetails, 1014 AudioDetails; 1015 1016 /** 1017 * wp.media.view.AudioDetails 1018 * 1019 * @memberOf wp.media.view 1020 * 1021 * @class 1022 * @augments wp.media.view.MediaDetails 1023 * @augments wp.media.view.Settings.AttachmentDisplay 1024 * @augments wp.media.view.Settings 1025 * @augments wp.media.View 1026 * @augments wp.Backbone.View 1027 * @augments Backbone.View 1028 */ 1029 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.AudioDetails.prototype */{ 1030 className: 'audio-details', 1031 template: wp.template('audio-details'), 1032 1033 setMedia: function() { 1034 var audio = this.$('.wp-audio-shortcode'); 1035 1036 if ( audio.find( 'source' ).length ) { 1037 if ( audio.is(':hidden') ) { 1038 audio.show(); 1039 } 1040 this.media = MediaDetails.prepareSrc( audio.get(0) ); 1041 } else { 1042 audio.hide(); 1043 this.media = false; 1044 } 1045 1046 return this; 1047 } 1048 }); 1049 1050 module.exports = AudioDetails; 1051 1052 1053 /***/ }), 1054 /* 9 */ 1055 /***/ (function(module, exports) { 1056 953 1057 var MediaDetails = wp.media.view.MediaDetails, 954 1058 VideoDetails; … … 995 1099 module.exports = VideoDetails; 996 1100 997 },{}]},{},[1]); 1101 1102 /***/ }) 1103 /******/ ]); -
trunk/src/wp-includes/js/media-grid.js
r41351 r41752 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 var media = wp.media; 81 82 media.controller.EditAttachmentMetadata = __webpack_require__( 11 ); 83 media.view.MediaFrame.Manage = __webpack_require__( 12 ); 84 media.view.Attachment.Details.TwoColumn = __webpack_require__( 13 ); 85 media.view.MediaFrame.Manage.Router = __webpack_require__( 14 ); 86 media.view.EditImage.Details = __webpack_require__( 15 ); 87 media.view.MediaFrame.EditAttachments = __webpack_require__( 16 ); 88 media.view.SelectModeToggleButton = __webpack_require__( 17 ); 89 media.view.DeleteSelectedButton = __webpack_require__( 18 ); 90 media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 19 ); 91 92 93 /***/ }), 94 /* 11 */ 95 /***/ (function(module, exports) { 96 2 97 var l10n = wp.media.view.l10n, 3 98 EditAttachmentMetadata; … … 29 124 module.exports = EditAttachmentMetadata; 30 125 31 },{}],2:[function(require,module,exports){ 32 var media = wp.media; 33 34 media.controller.EditAttachmentMetadata = require( './controllers/edit-attachment-metadata.js' ); 35 media.view.MediaFrame.Manage = require( './views/frame/manage.js' ); 36 media.view.Attachment.Details.TwoColumn = require( './views/attachment/details-two-column.js' ); 37 media.view.MediaFrame.Manage.Router = require( './routers/manage.js' ); 38 media.view.EditImage.Details = require( './views/edit-image-details.js' ); 39 media.view.MediaFrame.EditAttachments = require( './views/frame/edit-attachments.js' ); 40 media.view.SelectModeToggleButton = require( './views/button/select-mode-toggle.js' ); 41 media.view.DeleteSelectedButton = require( './views/button/delete-selected.js' ); 42 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' ); 43 44 },{"./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){ 126 127 /***/ }), 128 /* 12 */ 129 /***/ (function(module, exports) { 130 131 var MediaFrame = wp.media.view.MediaFrame, 132 Library = wp.media.controller.Library, 133 134 $ = Backbone.$, 135 Manage; 136 137 /** 138 * wp.media.view.MediaFrame.Manage 139 * 140 * A generic management frame workflow. 141 * 142 * Used in the media grid view. 143 * 144 * @memberOf wp.media.view.MediaFrame 145 * 146 * @class 147 * @augments wp.media.view.MediaFrame 148 * @augments wp.media.view.Frame 149 * @augments wp.media.View 150 * @augments wp.Backbone.View 151 * @augments Backbone.View 152 * @mixes wp.media.controller.StateMachine 153 */ 154 Manage = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Manage.prototype */{ 155 /** 156 * @constructs 157 */ 158 initialize: function() { 159 _.defaults( this.options, { 160 title: '', 161 modal: false, 162 selection: [], 163 library: {}, // Options hash for the query to the media library. 164 multiple: 'add', 165 state: 'library', 166 uploader: true, 167 mode: [ 'grid', 'edit' ] 168 }); 169 170 this.$body = $( document.body ); 171 this.$window = $( window ); 172 this.$adminBar = $( '#wpadminbar' ); 173 // Store the Add New button for later reuse in wp.media.view.UploaderInline. 174 this.$uploaderToggler = $( '.page-title-action' ) 175 .attr( 'aria-expanded', 'false' ) 176 .on( 'click', _.bind( this.addNewClickHandler, this ) ); 177 178 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) ); 179 180 // Ensure core and media grid view UI is enabled. 181 this.$el.addClass('wp-core-ui'); 182 183 // Force the uploader off if the upload limit has been exceeded or 184 // if the browser isn't supported. 185 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 186 this.options.uploader = false; 187 } 188 189 // Initialize a window-wide uploader. 190 if ( this.options.uploader ) { 191 this.uploader = new wp.media.view.UploaderWindow({ 192 controller: this, 193 uploader: { 194 dropzone: document.body, 195 container: document.body 196 } 197 }).render(); 198 this.uploader.ready(); 199 $('body').append( this.uploader.el ); 200 201 this.options.uploader = false; 202 } 203 204 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router(); 205 206 // Call 'initialize' directly on the parent class. 207 MediaFrame.prototype.initialize.apply( this, arguments ); 208 209 // Append the frame view directly the supplied container. 210 this.$el.appendTo( this.options.container ); 211 212 this.createStates(); 213 this.bindRegionModeHandlers(); 214 this.render(); 215 this.bindSearchHandler(); 216 217 wp.media.frames.browse = this; 218 }, 219 220 bindSearchHandler: function() { 221 var search = this.$( '#media-search-input' ), 222 searchView = this.browserView.toolbar.get( 'search' ).$el, 223 listMode = this.$( '.view-list' ), 224 225 input = _.throttle( function (e) { 226 var val = $( e.currentTarget ).val(), 227 url = ''; 228 229 if ( val ) { 230 url += '?search=' + val; 231 this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } ); 232 } 233 }, 1000 ); 234 235 // Update the URL when entering search string (at most once per second) 236 search.on( 'input', _.bind( input, this ) ); 237 238 this.gridRouter 239 .on( 'route:search', function () { 240 var href = window.location.href; 241 if ( href.indexOf( 'mode=' ) > -1 ) { 242 href = href.replace( /mode=[^&]+/g, 'mode=list' ); 243 } else { 244 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; 245 } 246 href = href.replace( 'search=', 's=' ); 247 listMode.prop( 'href', href ); 248 }) 249 .on( 'route:reset', function() { 250 searchView.val( '' ).trigger( 'input' ); 251 }); 252 }, 253 254 /** 255 * Create the default states for the frame. 256 */ 257 createStates: function() { 258 var options = this.options; 259 260 if ( this.options.states ) { 261 return; 262 } 263 264 // Add the default states. 265 this.states.add([ 266 new Library({ 267 library: wp.media.query( options.library ), 268 multiple: options.multiple, 269 title: options.title, 270 content: 'browse', 271 toolbar: 'select', 272 contentUserSetting: false, 273 filterable: 'all', 274 autoSelect: false 275 }) 276 ]); 277 }, 278 279 /** 280 * Bind region mode activation events to proper handlers. 281 */ 282 bindRegionModeHandlers: function() { 283 this.on( 'content:create:browse', this.browseContent, this ); 284 285 // Handle a frame-level event for editing an attachment. 286 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 287 288 this.on( 'select:activate', this.bindKeydown, this ); 289 this.on( 'select:deactivate', this.unbindKeydown, this ); 290 }, 291 292 handleKeydown: function( e ) { 293 if ( 27 === e.which ) { 294 e.preventDefault(); 295 this.deactivateMode( 'select' ).activateMode( 'edit' ); 296 } 297 }, 298 299 bindKeydown: function() { 300 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) ); 301 }, 302 303 unbindKeydown: function() { 304 this.$body.off( 'keydown.select' ); 305 }, 306 307 fixPosition: function() { 308 var $browser, $toolbar; 309 if ( ! this.isModeActive( 'select' ) ) { 310 return; 311 } 312 313 $browser = this.$('.attachments-browser'); 314 $toolbar = $browser.find('.media-toolbar'); 315 316 // Offset doesn't appear to take top margin into account, hence +16 317 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) { 318 $browser.addClass( 'fixed' ); 319 $toolbar.css('width', $browser.width() + 'px'); 320 } else { 321 $browser.removeClass( 'fixed' ); 322 $toolbar.css('width', ''); 323 } 324 }, 325 326 /** 327 * Click handler for the `Add New` button. 328 */ 329 addNewClickHandler: function( event ) { 330 event.preventDefault(); 331 this.trigger( 'toggle:upload:attachment' ); 332 333 if ( this.uploader ) { 334 this.uploader.refresh(); 335 } 336 }, 337 338 /** 339 * Open the Edit Attachment modal. 340 */ 341 openEditAttachmentModal: function( model ) { 342 // Create a new EditAttachment frame, passing along the library and the attachment model. 343 if ( wp.media.frames.edit ) { 344 wp.media.frames.edit.open().trigger( 'refresh', model ); 345 } else { 346 wp.media.frames.edit = wp.media( { 347 frame: 'edit-attachments', 348 controller: this, 349 library: this.state().get('library'), 350 model: model 351 } ); 352 } 353 }, 354 355 /** 356 * Create an attachments browser view within the content region. 357 * 358 * @param {Object} contentRegion Basic object with a `view` property, which 359 * should be set with the proper region view. 360 * @this wp.media.controller.Region 361 */ 362 browseContent: function( contentRegion ) { 363 var state = this.state(); 364 365 // Browse our library of attachments. 366 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({ 367 controller: this, 368 collection: state.get('library'), 369 selection: state.get('selection'), 370 model: state, 371 sortable: state.get('sortable'), 372 search: state.get('searchable'), 373 filters: state.get('filterable'), 374 date: state.get('date'), 375 display: state.get('displaySettings'), 376 dragInfo: state.get('dragInfo'), 377 sidebar: 'errors', 378 379 suggestedWidth: state.get('suggestedWidth'), 380 suggestedHeight: state.get('suggestedHeight'), 381 382 AttachmentView: state.get('AttachmentView'), 383 384 scrollElement: document 385 }); 386 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) ); 387 388 this.errors = wp.Uploader.errors; 389 this.errors.on( 'add remove reset', this.sidebarVisibility, this ); 390 }, 391 392 sidebarVisibility: function() { 393 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length ); 394 }, 395 396 bindDeferred: function() { 397 if ( ! this.browserView.dfd ) { 398 return; 399 } 400 this.browserView.dfd.done( _.bind( this.startHistory, this ) ); 401 }, 402 403 startHistory: function() { 404 // Verify pushState support and activate 405 if ( window.history && window.history.pushState ) { 406 if ( Backbone.History.started ) { 407 Backbone.history.stop(); 408 } 409 Backbone.history.start( { 410 root: window._wpMediaGridSettings.adminUrl, 411 pushState: true 412 } ); 413 } 414 } 415 }); 416 417 module.exports = Manage; 418 419 420 /***/ }), 421 /* 13 */ 422 /***/ (function(module, exports) { 423 424 var Details = wp.media.view.Attachment.Details, 425 TwoColumn; 426 427 /** 428 * wp.media.view.Attachment.Details.TwoColumn 429 * 430 * A similar view to media.view.Attachment.Details 431 * for use in the Edit Attachment modal. 432 * 433 * @memberOf wp.media.view.Attachment.Details 434 * 435 * @class 436 * @augments wp.media.view.Attachment.Details 437 * @augments wp.media.view.Attachment 438 * @augments wp.media.View 439 * @augments wp.Backbone.View 440 * @augments Backbone.View 441 */ 442 TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{ 443 template: wp.template( 'attachment-details-two-column' ), 444 445 initialize: function() { 446 this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) ); 447 448 Details.prototype.initialize.apply( this, arguments ); 449 }, 450 451 editAttachment: function( event ) { 452 if ( event ) { 453 event.preventDefault(); 454 } 455 this.controller.content.mode( 'edit-image' ); 456 }, 457 458 /** 459 * Noop this from parent class, doesn't apply here. 460 */ 461 toggleSelectionHandler: function() {}, 462 463 render: function() { 464 Details.prototype.render.apply( this, arguments ); 465 466 wp.media.mixin.removeAllPlayers(); 467 this.$( 'audio, video' ).each( function (i, elem) { 468 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 469 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 470 } ); 471 } 472 }); 473 474 module.exports = TwoColumn; 475 476 477 /***/ }), 478 /* 14 */ 479 /***/ (function(module, exports) { 480 45 481 /** 46 482 * wp.media.view.MediaFrame.Manage.Router … … 111 547 module.exports = Router; 112 548 113 },{}],4:[function(require,module,exports){ 114 var Details = wp.media.view.Attachment.Details, 115 TwoColumn; 549 550 /***/ }), 551 /* 15 */ 552 /***/ (function(module, exports) { 553 554 var View = wp.media.View, 555 EditImage = wp.media.view.EditImage, 556 Details; 116 557 117 558 /** 118 * wp.media.view.Attachment.Details.TwoColumn 119 * 120 * A similar view to media.view.Attachment.Details 121 * for use in the Edit Attachment modal. 122 * 123 * @memberOf wp.media.view.Attachment.Details 559 * wp.media.view.EditImage.Details 560 * 561 * @memberOf wp.media.view.EditImage 124 562 * 125 563 * @class 126 * @augments wp.media.view.Attachment.Details 127 * @augments wp.media.view.Attachment 564 * @augments wp.media.view.EditImage 128 565 * @augments wp.media.View 129 566 * @augments wp.Backbone.View 130 567 * @augments Backbone.View 131 568 */ 132 TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{ 133 template: wp.template( 'attachment-details-two-column' ), 134 135 initialize: function() { 136 this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) ); 137 138 Details.prototype.initialize.apply( this, arguments ); 139 }, 140 141 editAttachment: function( event ) { 142 if ( event ) { 143 event.preventDefault(); 144 } 145 this.controller.content.mode( 'edit-image' ); 146 }, 147 148 /** 149 * Noop this from parent class, doesn't apply here. 150 */ 151 toggleSelectionHandler: function() {}, 152 153 render: function() { 154 Details.prototype.render.apply( this, arguments ); 155 156 wp.media.mixin.removeAllPlayers(); 157 this.$( 'audio, video' ).each( function (i, elem) { 158 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 159 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 160 } ); 569 Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{ 570 initialize: function( options ) { 571 this.editor = window.imageEdit; 572 this.frame = options.frame; 573 this.controller = options.controller; 574 View.prototype.initialize.apply( this, arguments ); 575 }, 576 577 back: function() { 578 this.frame.content.mode( 'edit-metadata' ); 579 }, 580 581 save: function() { 582 this.model.fetch().done( _.bind( function() { 583 this.frame.content.mode( 'edit-metadata' ); 584 }, this ) ); 161 585 } 162 586 }); 163 587 164 module.exports = TwoColumn; 165 166 },{}],5:[function(require,module,exports){ 167 var Button = wp.media.view.Button, 168 DeleteSelected = wp.media.view.DeleteSelectedButton, 169 DeleteSelectedPermanently; 588 module.exports = Details; 589 590 591 /***/ }), 592 /* 16 */ 593 /***/ (function(module, exports) { 594 595 var Frame = wp.media.view.Frame, 596 MediaFrame = wp.media.view.MediaFrame, 597 598 $ = jQuery, 599 EditAttachments; 170 600 171 601 /** 172 * wp.media.view.DeleteSelectedPermanentlyButton 173 * 174 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 175 * 176 * @memberOf wp.media.view 602 * wp.media.view.MediaFrame.EditAttachments 603 * 604 * A frame for editing the details of a specific media item. 605 * 606 * Opens in a modal by default. 607 * 608 * Requires an attachment model to be passed in the options hash under `model`. 609 * 610 * @memberOf wp.media.view.MediaFrame 177 611 * 178 612 * @class 179 * @augments wp.media.view.DeleteSelectedButton 180 * @augments wp.media.view.Button 613 * @augments wp.media.view.Frame 181 614 * @augments wp.media.View 182 615 * @augments wp.Backbone.View 183 616 * @augments Backbone.View 617 * @mixes wp.media.controller.StateMachine 184 618 */ 185 DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{ 619 EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAttachments.prototype */{ 620 621 className: 'edit-attachment-frame', 622 template: wp.template( 'edit-attachment-frame' ), 623 regions: [ 'title', 'content' ], 624 625 events: { 626 'click .left': 'previousMediaItem', 627 'click .right': 'nextMediaItem' 628 }, 629 186 630 initialize: function() { 187 DeleteSelected.prototype.initialize.apply( this, arguments ); 188 this.controller.on( 'select:activate', this.selectActivate, this ); 189 this.controller.on( 'select:deactivate', this.selectDeactivate, this ); 190 }, 191 192 filterChange: function( model ) { 193 this.canShow = ( 'trash' === model.get( 'status' ) ); 194 }, 195 196 selectActivate: function() { 197 this.toggleDisabled(); 198 this.$el.toggleClass( 'hidden', ! this.canShow ); 199 }, 200 201 selectDeactivate: function() { 202 this.toggleDisabled(); 203 this.$el.addClass( 'hidden' ); 204 }, 205 206 render: function() { 207 Button.prototype.render.apply( this, arguments ); 208 this.selectActivate(); 209 return this; 631 Frame.prototype.initialize.apply( this, arguments ); 632 633 _.defaults( this.options, { 634 modal: true, 635 state: 'edit-attachment' 636 }); 637 638 this.controller = this.options.controller; 639 this.gridRouter = this.controller.gridRouter; 640 this.library = this.options.library; 641 642 if ( this.options.model ) { 643 this.model = this.options.model; 644 } 645 646 this.bindHandlers(); 647 this.createStates(); 648 this.createModal(); 649 650 this.title.mode( 'default' ); 651 this.toggleNav(); 652 }, 653 654 bindHandlers: function() { 655 // Bind default title creation. 656 this.on( 'title:create:default', this.createTitle, this ); 657 658 this.on( 'content:create:edit-metadata', this.editMetadataMode, this ); 659 this.on( 'content:create:edit-image', this.editImageMode, this ); 660 this.on( 'content:render:edit-image', this.editImageModeRender, this ); 661 this.on( 'refresh', this.rerender, this ); 662 this.on( 'close', this.detach ); 663 664 this.bindModelHandlers(); 665 this.listenTo( this.gridRouter, 'route:search', this.close, this ); 666 }, 667 668 bindModelHandlers: function() { 669 // Close the modal if the attachment is deleted. 670 this.listenTo( this.model, 'change:status destroy', this.close, this ); 671 }, 672 673 createModal: function() { 674 // Initialize modal container view. 675 if ( this.options.modal ) { 676 this.modal = new wp.media.view.Modal({ 677 controller: this, 678 title: this.options.title 679 }); 680 681 this.modal.on( 'open', _.bind( function () { 682 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) ); 683 }, this ) ); 684 685 // Completely destroy the modal DOM element when closing it. 686 this.modal.on( 'close', _.bind( function() { 687 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ 688 // Restore the original focus item if possible 689 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); 690 this.resetRoute(); 691 }, this ) ); 692 693 // Set this frame as the modal's content. 694 this.modal.content( this ); 695 this.modal.open(); 696 } 697 }, 698 699 /** 700 * Add the default states to the frame. 701 */ 702 createStates: function() { 703 this.states.add([ 704 new wp.media.controller.EditAttachmentMetadata({ 705 model: this.model, 706 library: this.library 707 }) 708 ]); 709 }, 710 711 /** 712 * Content region rendering callback for the `edit-metadata` mode. 713 * 714 * @param {Object} contentRegion Basic object with a `view` property, which 715 * should be set with the proper region view. 716 */ 717 editMetadataMode: function( contentRegion ) { 718 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({ 719 controller: this, 720 model: this.model 721 }); 722 723 /** 724 * Attach a subview to display fields added via the 725 * `attachment_fields_to_edit` filter. 726 */ 727 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({ 728 controller: this, 729 model: this.model 730 }) ); 731 732 // Update browser url when navigating media details, except on load. 733 if ( this.model && ! this.model.get( 'skipHistory' ) ) { 734 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); 735 } 736 }, 737 738 /** 739 * Render the EditImage view into the frame's content region. 740 * 741 * @param {Object} contentRegion Basic object with a `view` property, which 742 * should be set with the proper region view. 743 */ 744 editImageMode: function( contentRegion ) { 745 var editImageController = new wp.media.controller.EditImage( { 746 model: this.model, 747 frame: this 748 } ); 749 // Noop some methods. 750 editImageController._toolbar = function() {}; 751 editImageController._router = function() {}; 752 editImageController._menu = function() {}; 753 754 contentRegion.view = new wp.media.view.EditImage.Details( { 755 model: this.model, 756 frame: this, 757 controller: editImageController 758 } ); 759 760 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) ); 761 762 }, 763 764 editImageModeRender: function( view ) { 765 view.on( 'ready', view.loadEditor ); 766 }, 767 768 toggleNav: function() { 769 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() ); 770 this.$('.right').toggleClass( 'disabled', ! this.hasNext() ); 771 }, 772 773 /** 774 * Rerender the view. 775 */ 776 rerender: function( model ) { 777 this.stopListening( this.model ); 778 779 this.model = model; 780 781 this.bindModelHandlers(); 782 783 // Only rerender the `content` region. 784 if ( this.content.mode() !== 'edit-metadata' ) { 785 this.content.mode( 'edit-metadata' ); 786 } else { 787 this.content.render(); 788 } 789 790 this.toggleNav(); 791 }, 792 793 /** 794 * Click handler to switch to the previous media item. 795 */ 796 previousMediaItem: function() { 797 if ( ! this.hasPrevious() ) { 798 this.$( '.left' ).blur(); 799 return; 800 } 801 this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) ); 802 this.$( '.left' ).focus(); 803 }, 804 805 /** 806 * Click handler to switch to the next media item. 807 */ 808 nextMediaItem: function() { 809 if ( ! this.hasNext() ) { 810 this.$( '.right' ).blur(); 811 return; 812 } 813 this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) ); 814 this.$( '.right' ).focus(); 815 }, 816 817 getCurrentIndex: function() { 818 return this.library.indexOf( this.model ); 819 }, 820 821 hasNext: function() { 822 return ( this.getCurrentIndex() + 1 ) < this.library.length; 823 }, 824 825 hasPrevious: function() { 826 return ( this.getCurrentIndex() - 1 ) > -1; 827 }, 828 /** 829 * Respond to the keyboard events: right arrow, left arrow, except when 830 * focus is in a textarea or input field. 831 */ 832 keyEvent: function( event ) { 833 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) { 834 return; 835 } 836 837 // The right arrow key 838 if ( 39 === event.keyCode ) { 839 this.nextMediaItem(); 840 } 841 // The left arrow key 842 if ( 37 === event.keyCode ) { 843 this.previousMediaItem(); 844 } 845 }, 846 847 resetRoute: function() { 848 var searchTerm = this.controller.browserView.toolbar.get( 'search' ).$el.val(), 849 url = '' !== searchTerm ? '?search=' + searchTerm : ''; 850 this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } ); 210 851 } 211 852 }); 212 853 213 module.exports = DeleteSelectedPermanently; 214 215 },{}],6:[function(require,module,exports){ 216 var Button = wp.media.view.Button, 217 l10n = wp.media.view.l10n, 218 DeleteSelected; 219 220 /** 221 * wp.media.view.DeleteSelectedButton 222 * 223 * A button that handles bulk Delete/Trash logic 224 * 225 * @memberOf wp.media.view 226 * 227 * @class 228 * @augments wp.media.view.Button 229 * @augments wp.media.View 230 * @augments wp.Backbone.View 231 * @augments Backbone.View 232 */ 233 DeleteSelected = Button.extend(/** @lends wp.media.view.DeleteSelectedButton.prototype */{ 234 initialize: function() { 235 Button.prototype.initialize.apply( this, arguments ); 236 if ( this.options.filters ) { 237 this.options.filters.model.on( 'change', this.filterChange, this ); 238 } 239 this.controller.on( 'selection:toggle', this.toggleDisabled, this ); 240 }, 241 242 filterChange: function( model ) { 243 if ( 'trash' === model.get( 'status' ) ) { 244 this.model.set( 'text', l10n.untrashSelected ); 245 } else if ( wp.media.view.settings.mediaTrash ) { 246 this.model.set( 'text', l10n.trashSelected ); 247 } else { 248 this.model.set( 'text', l10n.deleteSelected ); 249 } 250 }, 251 252 toggleDisabled: function() { 253 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 254 }, 255 256 render: function() { 257 Button.prototype.render.apply( this, arguments ); 258 if ( this.controller.isModeActive( 'select' ) ) { 259 this.$el.addClass( 'delete-selected-button' ); 260 } else { 261 this.$el.addClass( 'delete-selected-button hidden' ); 262 } 263 this.toggleDisabled(); 264 return this; 265 } 266 }); 267 268 module.exports = DeleteSelected; 269 270 },{}],7:[function(require,module,exports){ 854 module.exports = EditAttachments; 855 856 857 /***/ }), 858 /* 17 */ 859 /***/ (function(module, exports) { 860 271 861 272 862 var Button = wp.media.view.Button, … … 345 935 module.exports = SelectModeToggle; 346 936 347 },{}],8:[function(require,module,exports){ 348 var View = wp.media.View, 349 EditImage = wp.media.view.EditImage, 350 Details; 937 938 /***/ }), 939 /* 18 */ 940 /***/ (function(module, exports) { 941 942 var Button = wp.media.view.Button, 943 l10n = wp.media.view.l10n, 944 DeleteSelected; 351 945 352 946 /** 353 * wp.media.view.EditImage.Details 354 * 355 * @memberOf wp.media.view.EditImage 947 * wp.media.view.DeleteSelectedButton 948 * 949 * A button that handles bulk Delete/Trash logic 950 * 951 * @memberOf wp.media.view 356 952 * 357 953 * @class 358 * @augments wp.media.view. EditImage954 * @augments wp.media.view.Button 359 955 * @augments wp.media.View 360 956 * @augments wp.Backbone.View 361 957 * @augments Backbone.View 362 958 */ 363 Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{ 364 initialize: function( options ) { 365 this.editor = window.imageEdit; 366 this.frame = options.frame; 367 this.controller = options.controller; 368 View.prototype.initialize.apply( this, arguments ); 369 }, 370 371 back: function() { 372 this.frame.content.mode( 'edit-metadata' ); 373 }, 374 375 save: function() { 376 this.model.fetch().done( _.bind( function() { 377 this.frame.content.mode( 'edit-metadata' ); 378 }, this ) ); 959 DeleteSelected = Button.extend(/** @lends wp.media.view.DeleteSelectedButton.prototype */{ 960 initialize: function() { 961 Button.prototype.initialize.apply( this, arguments ); 962 if ( this.options.filters ) { 963 this.options.filters.model.on( 'change', this.filterChange, this ); 964 } 965 this.controller.on( 'selection:toggle', this.toggleDisabled, this ); 966 }, 967 968 filterChange: function( model ) { 969 if ( 'trash' === model.get( 'status' ) ) { 970 this.model.set( 'text', l10n.untrashSelected ); 971 } else if ( wp.media.view.settings.mediaTrash ) { 972 this.model.set( 'text', l10n.trashSelected ); 973 } else { 974 this.model.set( 'text', l10n.deleteSelected ); 975 } 976 }, 977 978 toggleDisabled: function() { 979 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 980 }, 981 982 render: function() { 983 Button.prototype.render.apply( this, arguments ); 984 if ( this.controller.isModeActive( 'select' ) ) { 985 this.$el.addClass( 'delete-selected-button' ); 986 } else { 987 this.$el.addClass( 'delete-selected-button hidden' ); 988 } 989 this.toggleDisabled(); 990 return this; 379 991 } 380 992 }); 381 993 382 module.exports = Details; 383 384 },{}],9:[function(require,module,exports){ 385 var Frame = wp.media.view.Frame, 386 MediaFrame = wp.media.view.MediaFrame, 387 388 $ = jQuery, 389 EditAttachments; 994 module.exports = DeleteSelected; 995 996 997 /***/ }), 998 /* 19 */ 999 /***/ (function(module, exports) { 1000 1001 var Button = wp.media.view.Button, 1002 DeleteSelected = wp.media.view.DeleteSelectedButton, 1003 DeleteSelectedPermanently; 390 1004 391 1005 /** 392 * wp.media.view.MediaFrame.EditAttachments 393 * 394 * A frame for editing the details of a specific media item. 395 * 396 * Opens in a modal by default. 397 * 398 * Requires an attachment model to be passed in the options hash under `model`. 399 * 400 * @memberOf wp.media.view.MediaFrame 1006 * wp.media.view.DeleteSelectedPermanentlyButton 1007 * 1008 * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic 1009 * 1010 * @memberOf wp.media.view 401 1011 * 402 1012 * @class 403 * @augments wp.media.view.Frame 1013 * @augments wp.media.view.DeleteSelectedButton 1014 * @augments wp.media.view.Button 404 1015 * @augments wp.media.View 405 1016 * @augments wp.Backbone.View 406 1017 * @augments Backbone.View 407 * @mixes wp.media.controller.StateMachine408 1018 */ 409 EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAttachments.prototype */{ 410 411 className: 'edit-attachment-frame', 412 template: wp.template( 'edit-attachment-frame' ), 413 regions: [ 'title', 'content' ], 414 415 events: { 416 'click .left': 'previousMediaItem', 417 'click .right': 'nextMediaItem' 418 }, 419 1019 DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{ 420 1020 initialize: function() { 421 Frame.prototype.initialize.apply( this, arguments ); 422 423 _.defaults( this.options, { 424 modal: true, 425 state: 'edit-attachment' 426 }); 427 428 this.controller = this.options.controller; 429 this.gridRouter = this.controller.gridRouter; 430 this.library = this.options.library; 431 432 if ( this.options.model ) { 433 this.model = this.options.model; 434 } 435 436 this.bindHandlers(); 437 this.createStates(); 438 this.createModal(); 439 440 this.title.mode( 'default' ); 441 this.toggleNav(); 442 }, 443 444 bindHandlers: function() { 445 // Bind default title creation. 446 this.on( 'title:create:default', this.createTitle, this ); 447 448 this.on( 'content:create:edit-metadata', this.editMetadataMode, this ); 449 this.on( 'content:create:edit-image', this.editImageMode, this ); 450 this.on( 'content:render:edit-image', this.editImageModeRender, this ); 451 this.on( 'refresh', this.rerender, this ); 452 this.on( 'close', this.detach ); 453 454 this.bindModelHandlers(); 455 this.listenTo( this.gridRouter, 'route:search', this.close, this ); 456 }, 457 458 bindModelHandlers: function() { 459 // Close the modal if the attachment is deleted. 460 this.listenTo( this.model, 'change:status destroy', this.close, this ); 461 }, 462 463 createModal: function() { 464 // Initialize modal container view. 465 if ( this.options.modal ) { 466 this.modal = new wp.media.view.Modal({ 467 controller: this, 468 title: this.options.title 469 }); 470 471 this.modal.on( 'open', _.bind( function () { 472 $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) ); 473 }, this ) ); 474 475 // Completely destroy the modal DOM element when closing it. 476 this.modal.on( 'close', _.bind( function() { 477 $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ 478 // Restore the original focus item if possible 479 $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); 480 this.resetRoute(); 481 }, this ) ); 482 483 // Set this frame as the modal's content. 484 this.modal.content( this ); 485 this.modal.open(); 486 } 487 }, 488 489 /** 490 * Add the default states to the frame. 491 */ 492 createStates: function() { 493 this.states.add([ 494 new wp.media.controller.EditAttachmentMetadata({ 495 model: this.model, 496 library: this.library 497 }) 498 ]); 499 }, 500 501 /** 502 * Content region rendering callback for the `edit-metadata` mode. 503 * 504 * @param {Object} contentRegion Basic object with a `view` property, which 505 * should be set with the proper region view. 506 */ 507 editMetadataMode: function( contentRegion ) { 508 contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({ 509 controller: this, 510 model: this.model 511 }); 512 513 /** 514 * Attach a subview to display fields added via the 515 * `attachment_fields_to_edit` filter. 516 */ 517 contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({ 518 controller: this, 519 model: this.model 520 }) ); 521 522 // Update browser url when navigating media details, except on load. 523 if ( this.model && ! this.model.get( 'skipHistory' ) ) { 524 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); 525 } 526 }, 527 528 /** 529 * Render the EditImage view into the frame's content region. 530 * 531 * @param {Object} contentRegion Basic object with a `view` property, which 532 * should be set with the proper region view. 533 */ 534 editImageMode: function( contentRegion ) { 535 var editImageController = new wp.media.controller.EditImage( { 536 model: this.model, 537 frame: this 538 } ); 539 // Noop some methods. 540 editImageController._toolbar = function() {}; 541 editImageController._router = function() {}; 542 editImageController._menu = function() {}; 543 544 contentRegion.view = new wp.media.view.EditImage.Details( { 545 model: this.model, 546 frame: this, 547 controller: editImageController 548 } ); 549 550 this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) ); 551 552 }, 553 554 editImageModeRender: function( view ) { 555 view.on( 'ready', view.loadEditor ); 556 }, 557 558 toggleNav: function() { 559 this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() ); 560 this.$('.right').toggleClass( 'disabled', ! this.hasNext() ); 561 }, 562 563 /** 564 * Rerender the view. 565 */ 566 rerender: function( model ) { 567 this.stopListening( this.model ); 568 569 this.model = model; 570 571 this.bindModelHandlers(); 572 573 // Only rerender the `content` region. 574 if ( this.content.mode() !== 'edit-metadata' ) { 575 this.content.mode( 'edit-metadata' ); 576 } else { 577 this.content.render(); 578 } 579 580 this.toggleNav(); 581 }, 582 583 /** 584 * Click handler to switch to the previous media item. 585 */ 586 previousMediaItem: function() { 587 if ( ! this.hasPrevious() ) { 588 this.$( '.left' ).blur(); 589 return; 590 } 591 this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) ); 592 this.$( '.left' ).focus(); 593 }, 594 595 /** 596 * Click handler to switch to the next media item. 597 */ 598 nextMediaItem: function() { 599 if ( ! this.hasNext() ) { 600 this.$( '.right' ).blur(); 601 return; 602 } 603 this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) ); 604 this.$( '.right' ).focus(); 605 }, 606 607 getCurrentIndex: function() { 608 return this.library.indexOf( this.model ); 609 }, 610 611 hasNext: function() { 612 return ( this.getCurrentIndex() + 1 ) < this.library.length; 613 }, 614 615 hasPrevious: function() { 616 return ( this.getCurrentIndex() - 1 ) > -1; 617 }, 618 /** 619 * Respond to the keyboard events: right arrow, left arrow, except when 620 * focus is in a textarea or input field. 621 */ 622 keyEvent: function( event ) { 623 if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) { 624 return; 625 } 626 627 // The right arrow key 628 if ( 39 === event.keyCode ) { 629 this.nextMediaItem(); 630 } 631 // The left arrow key 632 if ( 37 === event.keyCode ) { 633 this.previousMediaItem(); 634 } 635 }, 636 637 resetRoute: function() { 638 var searchTerm = this.controller.browserView.toolbar.get( 'search' ).$el.val(), 639 url = '' !== searchTerm ? '?search=' + searchTerm : ''; 640 this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } ); 1021 DeleteSelected.prototype.initialize.apply( this, arguments ); 1022 this.controller.on( 'select:activate', this.selectActivate, this ); 1023 this.controller.on( 'select:deactivate', this.selectDeactivate, this ); 1024 }, 1025 1026 filterChange: function( model ) { 1027 this.canShow = ( 'trash' === model.get( 'status' ) ); 1028 }, 1029 1030 selectActivate: function() { 1031 this.toggleDisabled(); 1032 this.$el.toggleClass( 'hidden', ! this.canShow ); 1033 }, 1034 1035 selectDeactivate: function() { 1036 this.toggleDisabled(); 1037 this.$el.addClass( 'hidden' ); 1038 }, 1039 1040 render: function() { 1041 Button.prototype.render.apply( this, arguments ); 1042 this.selectActivate(); 1043 return this; 641 1044 } 642 1045 }); 643 1046 644 module.exports = EditAttachments; 645 646 },{}],10:[function(require,module,exports){ 647 var MediaFrame = wp.media.view.MediaFrame, 648 Library = wp.media.controller.Library, 649 650 $ = Backbone.$, 651 Manage; 652 653 /** 654 * wp.media.view.MediaFrame.Manage 655 * 656 * A generic management frame workflow. 657 * 658 * Used in the media grid view. 659 * 660 * @memberOf wp.media.view.MediaFrame 661 * 662 * @class 663 * @augments wp.media.view.MediaFrame 664 * @augments wp.media.view.Frame 665 * @augments wp.media.View 666 * @augments wp.Backbone.View 667 * @augments Backbone.View 668 * @mixes wp.media.controller.StateMachine 669 */ 670 Manage = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Manage.prototype */{ 671 /** 672 * @constructs 673 */ 674 initialize: function() { 675 _.defaults( this.options, { 676 title: '', 677 modal: false, 678 selection: [], 679 library: {}, // Options hash for the query to the media library. 680 multiple: 'add', 681 state: 'library', 682 uploader: true, 683 mode: [ 'grid', 'edit' ] 684 }); 685 686 this.$body = $( document.body ); 687 this.$window = $( window ); 688 this.$adminBar = $( '#wpadminbar' ); 689 // Store the Add New button for later reuse in wp.media.view.UploaderInline. 690 this.$uploaderToggler = $( '.page-title-action' ) 691 .attr( 'aria-expanded', 'false' ) 692 .on( 'click', _.bind( this.addNewClickHandler, this ) ); 693 694 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) ); 695 696 // Ensure core and media grid view UI is enabled. 697 this.$el.addClass('wp-core-ui'); 698 699 // Force the uploader off if the upload limit has been exceeded or 700 // if the browser isn't supported. 701 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 702 this.options.uploader = false; 703 } 704 705 // Initialize a window-wide uploader. 706 if ( this.options.uploader ) { 707 this.uploader = new wp.media.view.UploaderWindow({ 708 controller: this, 709 uploader: { 710 dropzone: document.body, 711 container: document.body 712 } 713 }).render(); 714 this.uploader.ready(); 715 $('body').append( this.uploader.el ); 716 717 this.options.uploader = false; 718 } 719 720 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router(); 721 722 // Call 'initialize' directly on the parent class. 723 MediaFrame.prototype.initialize.apply( this, arguments ); 724 725 // Append the frame view directly the supplied container. 726 this.$el.appendTo( this.options.container ); 727 728 this.createStates(); 729 this.bindRegionModeHandlers(); 730 this.render(); 731 this.bindSearchHandler(); 732 733 wp.media.frames.browse = this; 734 }, 735 736 bindSearchHandler: function() { 737 var search = this.$( '#media-search-input' ), 738 searchView = this.browserView.toolbar.get( 'search' ).$el, 739 listMode = this.$( '.view-list' ), 740 741 input = _.throttle( function (e) { 742 var val = $( e.currentTarget ).val(), 743 url = ''; 744 745 if ( val ) { 746 url += '?search=' + val; 747 this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } ); 748 } 749 }, 1000 ); 750 751 // Update the URL when entering search string (at most once per second) 752 search.on( 'input', _.bind( input, this ) ); 753 754 this.gridRouter 755 .on( 'route:search', function () { 756 var href = window.location.href; 757 if ( href.indexOf( 'mode=' ) > -1 ) { 758 href = href.replace( /mode=[^&]+/g, 'mode=list' ); 759 } else { 760 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; 761 } 762 href = href.replace( 'search=', 's=' ); 763 listMode.prop( 'href', href ); 764 }) 765 .on( 'route:reset', function() { 766 searchView.val( '' ).trigger( 'input' ); 767 }); 768 }, 769 770 /** 771 * Create the default states for the frame. 772 */ 773 createStates: function() { 774 var options = this.options; 775 776 if ( this.options.states ) { 777 return; 778 } 779 780 // Add the default states. 781 this.states.add([ 782 new Library({ 783 library: wp.media.query( options.library ), 784 multiple: options.multiple, 785 title: options.title, 786 content: 'browse', 787 toolbar: 'select', 788 contentUserSetting: false, 789 filterable: 'all', 790 autoSelect: false 791 }) 792 ]); 793 }, 794 795 /** 796 * Bind region mode activation events to proper handlers. 797 */ 798 bindRegionModeHandlers: function() { 799 this.on( 'content:create:browse', this.browseContent, this ); 800 801 // Handle a frame-level event for editing an attachment. 802 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 803 804 this.on( 'select:activate', this.bindKeydown, this ); 805 this.on( 'select:deactivate', this.unbindKeydown, this ); 806 }, 807 808 handleKeydown: function( e ) { 809 if ( 27 === e.which ) { 810 e.preventDefault(); 811 this.deactivateMode( 'select' ).activateMode( 'edit' ); 812 } 813 }, 814 815 bindKeydown: function() { 816 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) ); 817 }, 818 819 unbindKeydown: function() { 820 this.$body.off( 'keydown.select' ); 821 }, 822 823 fixPosition: function() { 824 var $browser, $toolbar; 825 if ( ! this.isModeActive( 'select' ) ) { 826 return; 827 } 828 829 $browser = this.$('.attachments-browser'); 830 $toolbar = $browser.find('.media-toolbar'); 831 832 // Offset doesn't appear to take top margin into account, hence +16 833 if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) { 834 $browser.addClass( 'fixed' ); 835 $toolbar.css('width', $browser.width() + 'px'); 836 } else { 837 $browser.removeClass( 'fixed' ); 838 $toolbar.css('width', ''); 839 } 840 }, 841 842 /** 843 * Click handler for the `Add New` button. 844 */ 845 addNewClickHandler: function( event ) { 846 event.preventDefault(); 847 this.trigger( 'toggle:upload:attachment' ); 848 849 if ( this.uploader ) { 850 this.uploader.refresh(); 851 } 852 }, 853 854 /** 855 * Open the Edit Attachment modal. 856 */ 857 openEditAttachmentModal: function( model ) { 858 // Create a new EditAttachment frame, passing along the library and the attachment model. 859 if ( wp.media.frames.edit ) { 860 wp.media.frames.edit.open().trigger( 'refresh', model ); 861 } else { 862 wp.media.frames.edit = wp.media( { 863 frame: 'edit-attachments', 864 controller: this, 865 library: this.state().get('library'), 866 model: model 867 } ); 868 } 869 }, 870 871 /** 872 * Create an attachments browser view within the content region. 873 * 874 * @param {Object} contentRegion Basic object with a `view` property, which 875 * should be set with the proper region view. 876 * @this wp.media.controller.Region 877 */ 878 browseContent: function( contentRegion ) { 879 var state = this.state(); 880 881 // Browse our library of attachments. 882 this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({ 883 controller: this, 884 collection: state.get('library'), 885 selection: state.get('selection'), 886 model: state, 887 sortable: state.get('sortable'), 888 search: state.get('searchable'), 889 filters: state.get('filterable'), 890 date: state.get('date'), 891 display: state.get('displaySettings'), 892 dragInfo: state.get('dragInfo'), 893 sidebar: 'errors', 894 895 suggestedWidth: state.get('suggestedWidth'), 896 suggestedHeight: state.get('suggestedHeight'), 897 898 AttachmentView: state.get('AttachmentView'), 899 900 scrollElement: document 901 }); 902 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) ); 903 904 this.errors = wp.Uploader.errors; 905 this.errors.on( 'add remove reset', this.sidebarVisibility, this ); 906 }, 907 908 sidebarVisibility: function() { 909 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length ); 910 }, 911 912 bindDeferred: function() { 913 if ( ! this.browserView.dfd ) { 914 return; 915 } 916 this.browserView.dfd.done( _.bind( this.startHistory, this ) ); 917 }, 918 919 startHistory: function() { 920 // Verify pushState support and activate 921 if ( window.history && window.history.pushState ) { 922 if ( Backbone.History.started ) { 923 Backbone.history.stop(); 924 } 925 Backbone.history.start( { 926 root: window._wpMediaGridSettings.adminUrl, 927 pushState: true 928 } ); 929 } 930 } 931 }); 932 933 module.exports = Manage; 934 935 },{}]},{},[2]); 1047 module.exports = DeleteSelectedPermanently; 1048 1049 1050 /***/ }) 1051 /******/ ]); -
trunk/src/wp-includes/js/media-models.js
r41386 r41752 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 var $ = jQuery, 3 72 Attachment, Attachments, l10n, media; … … 66 135 delete l10n.settings; 67 136 68 Attachment = media.model.Attachment = require( './models/attachment.js');69 Attachments = media.model.Attachments = require( './models/attachments.js');70 71 media.model.Query = require( './models/query.js');72 media.model.PostImage = require( './models/post-image.js');73 media.model.Selection = require( './models/selection.js');137 Attachment = media.model.Attachment = __webpack_require__( 21 ); 138 Attachments = media.model.Attachments = __webpack_require__( 22 ); 139 140 media.model.Query = __webpack_require__( 23 ); 141 media.model.PostImage = __webpack_require__( 24 ); 142 media.model.Selection = __webpack_require__( 25 ); 74 143 75 144 /** … … 239 308 }); 240 309 241 },{"./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){ 310 311 /***/ }), 312 313 /***/ 21: 314 /***/ (function(module, exports) { 315 242 316 var $ = Backbone.$, 243 317 Attachment; … … 410 484 module.exports = Attachment; 411 485 412 },{}],3:[function(require,module,exports){ 486 487 /***/ }), 488 489 /***/ 22: 490 /***/ (function(module, exports) { 491 413 492 /** 414 493 * wp.media.model.Attachments … … 955 1034 module.exports = Attachments; 956 1035 957 },{}],4:[function(require,module,exports){ 1036 1037 /***/ }), 1038 1039 /***/ 23: 1040 /***/ (function(module, exports) { 1041 1042 var Attachments = wp.media.model.Attachments, 1043 Query; 1044 1045 /** 1046 * wp.media.model.Query 1047 * 1048 * A collection of attachments that match the supplied query arguments. 1049 * 1050 * Note: Do NOT change this.args after the query has been initialized. 1051 * Things will break. 1052 * 1053 * @memberOf wp.media.model 1054 * 1055 * @class 1056 * @augments wp.media.model.Attachments 1057 * @augments Backbone.Collection 1058 * 1059 * @param {array} [models] Models to initialize with the collection. 1060 * @param {object} [options] Options hash. 1061 * @param {object} [options.args] Attachments query arguments. 1062 * @param {object} [options.args.posts_per_page] 1063 */ 1064 Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{ 1065 /** 1066 * @param {array} [models=[]] Array of initial models to populate the collection. 1067 * @param {object} [options={}] 1068 */ 1069 initialize: function( models, options ) { 1070 var allowed; 1071 1072 options = options || {}; 1073 Attachments.prototype.initialize.apply( this, arguments ); 1074 1075 this.args = options.args; 1076 this._hasMore = true; 1077 this.created = new Date(); 1078 1079 this.filters.order = function( attachment ) { 1080 var orderby = this.props.get('orderby'), 1081 order = this.props.get('order'); 1082 1083 if ( ! this.comparator ) { 1084 return true; 1085 } 1086 1087 // We want any items that can be placed before the last 1088 // item in the set. If we add any items after the last 1089 // item, then we can't guarantee the set is complete. 1090 if ( this.length ) { 1091 return 1 !== this.comparator( attachment, this.last(), { ties: true }); 1092 1093 // Handle the case where there are no items yet and 1094 // we're sorting for recent items. In that case, we want 1095 // changes that occurred after we created the query. 1096 } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) { 1097 return attachment.get( orderby ) >= this.created; 1098 1099 // If we're sorting by menu order and we have no items, 1100 // accept any items that have the default menu order (0). 1101 } else if ( 'ASC' === order && 'menuOrder' === orderby ) { 1102 return attachment.get( orderby ) === 0; 1103 } 1104 1105 // Otherwise, we don't want any items yet. 1106 return false; 1107 }; 1108 1109 // Observe the central `wp.Uploader.queue` collection to watch for 1110 // new matches for the query. 1111 // 1112 // Only observe when a limited number of query args are set. There 1113 // are no filters for other properties, so observing will result in 1114 // false positives in those queries. 1115 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ]; 1116 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { 1117 this.observe( wp.Uploader.queue ); 1118 } 1119 }, 1120 /** 1121 * Whether there are more attachments that haven't been sync'd from the server 1122 * that match the collection's query. 1123 * 1124 * @returns {boolean} 1125 */ 1126 hasMore: function() { 1127 return this._hasMore; 1128 }, 1129 /** 1130 * Fetch more attachments from the server for the collection. 1131 * 1132 * @param {object} [options={}] 1133 * @returns {Promise} 1134 */ 1135 more: function( options ) { 1136 var query = this; 1137 1138 // If there is already a request pending, return early with the Deferred object. 1139 if ( this._more && 'pending' === this._more.state() ) { 1140 return this._more; 1141 } 1142 1143 if ( ! this.hasMore() ) { 1144 return jQuery.Deferred().resolveWith( this ).promise(); 1145 } 1146 1147 options = options || {}; 1148 options.remove = false; 1149 1150 return this._more = this.fetch( options ).done( function( resp ) { 1151 if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { 1152 query._hasMore = false; 1153 } 1154 }); 1155 }, 1156 /** 1157 * Overrides Backbone.Collection.sync 1158 * Overrides wp.media.model.Attachments.sync 1159 * 1160 * @param {String} method 1161 * @param {Backbone.Model} model 1162 * @param {Object} [options={}] 1163 * @returns {Promise} 1164 */ 1165 sync: function( method, model, options ) { 1166 var args, fallback; 1167 1168 // Overload the read method so Attachment.fetch() functions correctly. 1169 if ( 'read' === method ) { 1170 options = options || {}; 1171 options.context = this; 1172 options.data = _.extend( options.data || {}, { 1173 action: 'query-attachments', 1174 post_id: wp.media.model.settings.post.id 1175 }); 1176 1177 // Clone the args so manipulation is non-destructive. 1178 args = _.clone( this.args ); 1179 1180 // Determine which page to query. 1181 if ( -1 !== args.posts_per_page ) { 1182 args.paged = Math.round( this.length / args.posts_per_page ) + 1; 1183 } 1184 1185 options.data.query = args; 1186 return wp.media.ajax( options ); 1187 1188 // Otherwise, fall back to Backbone.sync() 1189 } else { 1190 /** 1191 * Call wp.media.model.Attachments.sync or Backbone.sync 1192 */ 1193 fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; 1194 return fallback.sync.apply( this, arguments ); 1195 } 1196 } 1197 }, /** @lends wp.media.model.Query */{ 1198 /** 1199 * @readonly 1200 */ 1201 defaultProps: { 1202 orderby: 'date', 1203 order: 'DESC' 1204 }, 1205 /** 1206 * @readonly 1207 */ 1208 defaultArgs: { 1209 posts_per_page: 40 1210 }, 1211 /** 1212 * @readonly 1213 */ 1214 orderby: { 1215 allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ], 1216 /** 1217 * A map of JavaScript orderby values to their WP_Query equivalents. 1218 * @type {Object} 1219 */ 1220 valuemap: { 1221 'id': 'ID', 1222 'uploadedTo': 'parent', 1223 'menuOrder': 'menu_order ID' 1224 } 1225 }, 1226 /** 1227 * A map of JavaScript query properties to their WP_Query equivalents. 1228 * 1229 * @readonly 1230 */ 1231 propmap: { 1232 'search': 's', 1233 'type': 'post_mime_type', 1234 'perPage': 'posts_per_page', 1235 'menuOrder': 'menu_order', 1236 'uploadedTo': 'post_parent', 1237 'status': 'post_status', 1238 'include': 'post__in', 1239 'exclude': 'post__not_in' 1240 }, 1241 /** 1242 * Creates and returns an Attachments Query collection given the properties. 1243 * 1244 * Caches query objects and reuses where possible. 1245 * 1246 * @static 1247 * @method 1248 * 1249 * @param {object} [props] 1250 * @param {Object} [props.cache=true] Whether to use the query cache or not. 1251 * @param {Object} [props.order] 1252 * @param {Object} [props.orderby] 1253 * @param {Object} [props.include] 1254 * @param {Object} [props.exclude] 1255 * @param {Object} [props.s] 1256 * @param {Object} [props.post_mime_type] 1257 * @param {Object} [props.posts_per_page] 1258 * @param {Object} [props.menu_order] 1259 * @param {Object} [props.post_parent] 1260 * @param {Object} [props.post_status] 1261 * @param {Object} [options] 1262 * 1263 * @returns {wp.media.model.Query} A new Attachments Query collection. 1264 */ 1265 get: (function(){ 1266 /** 1267 * @static 1268 * @type Array 1269 */ 1270 var queries = []; 1271 1272 /** 1273 * @returns {Query} 1274 */ 1275 return function( props, options ) { 1276 var args = {}, 1277 orderby = Query.orderby, 1278 defaults = Query.defaultProps, 1279 query, 1280 cache = !! props.cache || _.isUndefined( props.cache ); 1281 1282 // Remove the `query` property. This isn't linked to a query, 1283 // this *is* the query. 1284 delete props.query; 1285 delete props.cache; 1286 1287 // Fill default args. 1288 _.defaults( props, defaults ); 1289 1290 // Normalize the order. 1291 props.order = props.order.toUpperCase(); 1292 if ( 'DESC' !== props.order && 'ASC' !== props.order ) { 1293 props.order = defaults.order.toUpperCase(); 1294 } 1295 1296 // Ensure we have a valid orderby value. 1297 if ( ! _.contains( orderby.allowed, props.orderby ) ) { 1298 props.orderby = defaults.orderby; 1299 } 1300 1301 _.each( [ 'include', 'exclude' ], function( prop ) { 1302 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) { 1303 props[ prop ] = [ props[ prop ] ]; 1304 } 1305 } ); 1306 1307 // Generate the query `args` object. 1308 // Correct any differing property names. 1309 _.each( props, function( value, prop ) { 1310 if ( _.isNull( value ) ) { 1311 return; 1312 } 1313 1314 args[ Query.propmap[ prop ] || prop ] = value; 1315 }); 1316 1317 // Fill any other default query args. 1318 _.defaults( args, Query.defaultArgs ); 1319 1320 // `props.orderby` does not always map directly to `args.orderby`. 1321 // Substitute exceptions specified in orderby.keymap. 1322 args.orderby = orderby.valuemap[ props.orderby ] || props.orderby; 1323 1324 // Search the query cache for a matching query. 1325 if ( cache ) { 1326 query = _.find( queries, function( query ) { 1327 return _.isEqual( query.args, args ); 1328 }); 1329 } else { 1330 queries = []; 1331 } 1332 1333 // Otherwise, create a new query and add it to the cache. 1334 if ( ! query ) { 1335 query = new Query( [], _.extend( options || {}, { 1336 props: props, 1337 args: args 1338 } ) ); 1339 queries.push( query ); 1340 } 1341 1342 return query; 1343 }; 1344 }()) 1345 }); 1346 1347 module.exports = Query; 1348 1349 1350 /***/ }), 1351 1352 /***/ 24: 1353 /***/ (function(module, exports) { 1354 958 1355 /** 959 1356 * wp.media.model.PostImage … … 1111 1508 module.exports = PostImage; 1112 1509 1113 },{}],5:[function(require,module,exports){ 1114 var Attachments = wp.media.model.Attachments, 1115 Query; 1116 1117 /** 1118 * wp.media.model.Query 1119 * 1120 * A collection of attachments that match the supplied query arguments. 1121 * 1122 * Note: Do NOT change this.args after the query has been initialized. 1123 * Things will break. 1124 * 1125 * @memberOf wp.media.model 1126 * 1127 * @class 1128 * @augments wp.media.model.Attachments 1129 * @augments Backbone.Collection 1130 * 1131 * @param {array} [models] Models to initialize with the collection. 1132 * @param {object} [options] Options hash. 1133 * @param {object} [options.args] Attachments query arguments. 1134 * @param {object} [options.args.posts_per_page] 1135 */ 1136 Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{ 1137 /** 1138 * @param {array} [models=[]] Array of initial models to populate the collection. 1139 * @param {object} [options={}] 1140 */ 1141 initialize: function( models, options ) { 1142 var allowed; 1143 1144 options = options || {}; 1145 Attachments.prototype.initialize.apply( this, arguments ); 1146 1147 this.args = options.args; 1148 this._hasMore = true; 1149 this.created = new Date(); 1150 1151 this.filters.order = function( attachment ) { 1152 var orderby = this.props.get('orderby'), 1153 order = this.props.get('order'); 1154 1155 if ( ! this.comparator ) { 1156 return true; 1157 } 1158 1159 // We want any items that can be placed before the last 1160 // item in the set. If we add any items after the last 1161 // item, then we can't guarantee the set is complete. 1162 if ( this.length ) { 1163 return 1 !== this.comparator( attachment, this.last(), { ties: true }); 1164 1165 // Handle the case where there are no items yet and 1166 // we're sorting for recent items. In that case, we want 1167 // changes that occurred after we created the query. 1168 } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) { 1169 return attachment.get( orderby ) >= this.created; 1170 1171 // If we're sorting by menu order and we have no items, 1172 // accept any items that have the default menu order (0). 1173 } else if ( 'ASC' === order && 'menuOrder' === orderby ) { 1174 return attachment.get( orderby ) === 0; 1175 } 1176 1177 // Otherwise, we don't want any items yet. 1178 return false; 1179 }; 1180 1181 // Observe the central `wp.Uploader.queue` collection to watch for 1182 // new matches for the query. 1183 // 1184 // Only observe when a limited number of query args are set. There 1185 // are no filters for other properties, so observing will result in 1186 // false positives in those queries. 1187 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ]; 1188 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { 1189 this.observe( wp.Uploader.queue ); 1190 } 1191 }, 1192 /** 1193 * Whether there are more attachments that haven't been sync'd from the server 1194 * that match the collection's query. 1195 * 1196 * @returns {boolean} 1197 */ 1198 hasMore: function() { 1199 return this._hasMore; 1200 }, 1201 /** 1202 * Fetch more attachments from the server for the collection. 1203 * 1204 * @param {object} [options={}] 1205 * @returns {Promise} 1206 */ 1207 more: function( options ) { 1208 var query = this; 1209 1210 // If there is already a request pending, return early with the Deferred object. 1211 if ( this._more && 'pending' === this._more.state() ) { 1212 return this._more; 1213 } 1214 1215 if ( ! this.hasMore() ) { 1216 return jQuery.Deferred().resolveWith( this ).promise(); 1217 } 1218 1219 options = options || {}; 1220 options.remove = false; 1221 1222 return this._more = this.fetch( options ).done( function( resp ) { 1223 if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { 1224 query._hasMore = false; 1225 } 1226 }); 1227 }, 1228 /** 1229 * Overrides Backbone.Collection.sync 1230 * Overrides wp.media.model.Attachments.sync 1231 * 1232 * @param {String} method 1233 * @param {Backbone.Model} model 1234 * @param {Object} [options={}] 1235 * @returns {Promise} 1236 */ 1237 sync: function( method, model, options ) { 1238 var args, fallback; 1239 1240 // Overload the read method so Attachment.fetch() functions correctly. 1241 if ( 'read' === method ) { 1242 options = options || {}; 1243 options.context = this; 1244 options.data = _.extend( options.data || {}, { 1245 action: 'query-attachments', 1246 post_id: wp.media.model.settings.post.id 1247 }); 1248 1249 // Clone the args so manipulation is non-destructive. 1250 args = _.clone( this.args ); 1251 1252 // Determine which page to query. 1253 if ( -1 !== args.posts_per_page ) { 1254 args.paged = Math.round( this.length / args.posts_per_page ) + 1; 1255 } 1256 1257 options.data.query = args; 1258 return wp.media.ajax( options ); 1259 1260 // Otherwise, fall back to Backbone.sync() 1261 } else { 1262 /** 1263 * Call wp.media.model.Attachments.sync or Backbone.sync 1264 */ 1265 fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; 1266 return fallback.sync.apply( this, arguments ); 1267 } 1268 } 1269 }, /** @lends wp.media.model.Query */{ 1270 /** 1271 * @readonly 1272 */ 1273 defaultProps: { 1274 orderby: 'date', 1275 order: 'DESC' 1276 }, 1277 /** 1278 * @readonly 1279 */ 1280 defaultArgs: { 1281 posts_per_page: 40 1282 }, 1283 /** 1284 * @readonly 1285 */ 1286 orderby: { 1287 allowed: [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ], 1288 /** 1289 * A map of JavaScript orderby values to their WP_Query equivalents. 1290 * @type {Object} 1291 */ 1292 valuemap: { 1293 'id': 'ID', 1294 'uploadedTo': 'parent', 1295 'menuOrder': 'menu_order ID' 1296 } 1297 }, 1298 /** 1299 * A map of JavaScript query properties to their WP_Query equivalents. 1300 * 1301 * @readonly 1302 */ 1303 propmap: { 1304 'search': 's', 1305 'type': 'post_mime_type', 1306 'perPage': 'posts_per_page', 1307 'menuOrder': 'menu_order', 1308 'uploadedTo': 'post_parent', 1309 'status': 'post_status', 1310 'include': 'post__in', 1311 'exclude': 'post__not_in' 1312 }, 1313 /** 1314 * Creates and returns an Attachments Query collection given the properties. 1315 * 1316 * Caches query objects and reuses where possible. 1317 * 1318 * @static 1319 * @method 1320 * 1321 * @param {object} [props] 1322 * @param {Object} [props.cache=true] Whether to use the query cache or not. 1323 * @param {Object} [props.order] 1324 * @param {Object} [props.orderby] 1325 * @param {Object} [props.include] 1326 * @param {Object} [props.exclude] 1327 * @param {Object} [props.s] 1328 * @param {Object} [props.post_mime_type] 1329 * @param {Object} [props.posts_per_page] 1330 * @param {Object} [props.menu_order] 1331 * @param {Object} [props.post_parent] 1332 * @param {Object} [props.post_status] 1333 * @param {Object} [options] 1334 * 1335 * @returns {wp.media.model.Query} A new Attachments Query collection. 1336 */ 1337 get: (function(){ 1338 /** 1339 * @static 1340 * @type Array 1341 */ 1342 var queries = []; 1343 1344 /** 1345 * @returns {Query} 1346 */ 1347 return function( props, options ) { 1348 var args = {}, 1349 orderby = Query.orderby, 1350 defaults = Query.defaultProps, 1351 query, 1352 cache = !! props.cache || _.isUndefined( props.cache ); 1353 1354 // Remove the `query` property. This isn't linked to a query, 1355 // this *is* the query. 1356 delete props.query; 1357 delete props.cache; 1358 1359 // Fill default args. 1360 _.defaults( props, defaults ); 1361 1362 // Normalize the order. 1363 props.order = props.order.toUpperCase(); 1364 if ( 'DESC' !== props.order && 'ASC' !== props.order ) { 1365 props.order = defaults.order.toUpperCase(); 1366 } 1367 1368 // Ensure we have a valid orderby value. 1369 if ( ! _.contains( orderby.allowed, props.orderby ) ) { 1370 props.orderby = defaults.orderby; 1371 } 1372 1373 _.each( [ 'include', 'exclude' ], function( prop ) { 1374 if ( props[ prop ] && ! _.isArray( props[ prop ] ) ) { 1375 props[ prop ] = [ props[ prop ] ]; 1376 } 1377 } ); 1378 1379 // Generate the query `args` object. 1380 // Correct any differing property names. 1381 _.each( props, function( value, prop ) { 1382 if ( _.isNull( value ) ) { 1383 return; 1384 } 1385 1386 args[ Query.propmap[ prop ] || prop ] = value; 1387 }); 1388 1389 // Fill any other default query args. 1390 _.defaults( args, Query.defaultArgs ); 1391 1392 // `props.orderby` does not always map directly to `args.orderby`. 1393 // Substitute exceptions specified in orderby.keymap. 1394 args.orderby = orderby.valuemap[ props.orderby ] || props.orderby; 1395 1396 // Search the query cache for a matching query. 1397 if ( cache ) { 1398 query = _.find( queries, function( query ) { 1399 return _.isEqual( query.args, args ); 1400 }); 1401 } else { 1402 queries = []; 1403 } 1404 1405 // Otherwise, create a new query and add it to the cache. 1406 if ( ! query ) { 1407 query = new Query( [], _.extend( options || {}, { 1408 props: props, 1409 args: args 1410 } ) ); 1411 queries.push( query ); 1412 } 1413 1414 return query; 1415 }; 1416 }()) 1417 }); 1418 1419 module.exports = Query; 1420 1421 },{}],6:[function(require,module,exports){ 1510 1511 /***/ }), 1512 1513 /***/ 25: 1514 /***/ (function(module, exports) { 1515 1422 1516 var Attachments = wp.media.model.Attachments, 1423 1517 Selection; … … 1518 1612 module.exports = Selection; 1519 1613 1520 },{}]},{},[1]); 1614 1615 /***/ }) 1616 1617 /******/ }); -
trunk/src/wp-includes/js/media-views.js
r41557 r41752 1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 2 var Selection = wp.media.model.Selection, 3 Library = wp.media.controller.Library, 4 CollectionAdd; 1 /******/ (function(modules) { // webpackBootstrap 2 /******/ // The module cache 3 /******/ var installedModules = {}; 4 /******/ 5 /******/ // The require function 6 /******/ function __webpack_require__(moduleId) { 7 /******/ 8 /******/ // Check if module is in cache 9 /******/ if(installedModules[moduleId]) { 10 /******/ return installedModules[moduleId].exports; 11 /******/ } 12 /******/ // Create a new module (and put it into the cache) 13 /******/ var module = installedModules[moduleId] = { 14 /******/ i: moduleId, 15 /******/ l: false, 16 /******/ exports: {} 17 /******/ }; 18 /******/ 19 /******/ // Execute the module function 20 /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 /******/ 22 /******/ // Flag the module as loaded 23 /******/ module.l = true; 24 /******/ 25 /******/ // Return the exports of the module 26 /******/ return module.exports; 27 /******/ } 28 /******/ 29 /******/ 30 /******/ // expose the modules object (__webpack_modules__) 31 /******/ __webpack_require__.m = modules; 32 /******/ 33 /******/ // expose the module cache 34 /******/ __webpack_require__.c = installedModules; 35 /******/ 36 /******/ // define getter function for harmony exports 37 /******/ __webpack_require__.d = function(exports, name, getter) { 38 /******/ if(!__webpack_require__.o(exports, name)) { 39 /******/ Object.defineProperty(exports, name, { 40 /******/ configurable: false, 41 /******/ enumerable: true, 42 /******/ get: getter 43 /******/ }); 44 /******/ } 45 /******/ }; 46 /******/ 47 /******/ // getDefaultExport function for compatibility with non-harmony modules 48 /******/ __webpack_require__.n = function(module) { 49 /******/ var getter = module && module.__esModule ? 50 /******/ function getDefault() { return module['default']; } : 51 /******/ function getModuleExports() { return module; }; 52 /******/ __webpack_require__.d(getter, 'a', getter); 53 /******/ return getter; 54 /******/ }; 55 /******/ 56 /******/ // Object.prototype.hasOwnProperty.call 57 /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 /******/ 59 /******/ // __webpack_public_path__ 60 /******/ __webpack_require__.p = ""; 61 /******/ 62 /******/ // Load entry module and return exports 63 /******/ return __webpack_require__(__webpack_require__.s = 26); 64 /******/ }) 65 /************************************************************************/ 66 /******/ (Array(26).concat([ 67 /* 26 */ 68 /***/ (function(module, exports, __webpack_require__) { 69 70 var media = wp.media, 71 $ = jQuery, 72 l10n; 73 74 media.isTouchDevice = ( 'ontouchend' in document ); 75 76 // Link any localized strings. 77 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 78 79 // Link any settings. 80 media.view.settings = l10n.settings || {}; 81 delete l10n.settings; 82 83 // Copy the `post` setting over to the model settings. 84 media.model.settings.post = media.view.settings.post; 85 86 // Check if the browser supports CSS 3.0 transitions 87 $.support.transition = (function(){ 88 var style = document.documentElement.style, 89 transitions = { 90 WebkitTransition: 'webkitTransitionEnd', 91 MozTransition: 'transitionend', 92 OTransition: 'oTransitionEnd otransitionend', 93 transition: 'transitionend' 94 }, transition; 95 96 transition = _.find( _.keys( transitions ), function( transition ) { 97 return ! _.isUndefined( style[ transition ] ); 98 }); 99 100 return transition && { 101 end: transitions[ transition ] 102 }; 103 }()); 5 104 6 105 /** 7 * wp.media.controller.CollectionAdd 8 * 9 * A state for adding attachments to a collection (e.g. video playlist). 106 * A shared event bus used to provide events into 107 * the media workflows that 3rd-party devs can use to hook 108 * in. 109 */ 110 media.events = _.extend( {}, Backbone.Events ); 111 112 /** 113 * Makes it easier to bind events using transitions. 114 * 115 * @param {string} selector 116 * @param {Number} sensitivity 117 * @returns {Promise} 118 */ 119 media.transition = function( selector, sensitivity ) { 120 var deferred = $.Deferred(); 121 122 sensitivity = sensitivity || 2000; 123 124 if ( $.support.transition ) { 125 if ( ! (selector instanceof $) ) { 126 selector = $( selector ); 127 } 128 129 // Resolve the deferred when the first element finishes animating. 130 selector.first().one( $.support.transition.end, deferred.resolve ); 131 132 // Just in case the event doesn't trigger, fire a callback. 133 _.delay( deferred.resolve, sensitivity ); 134 135 // Otherwise, execute on the spot. 136 } else { 137 deferred.resolve(); 138 } 139 140 return deferred.promise(); 141 }; 142 143 media.controller.Region = __webpack_require__( 27 ); 144 media.controller.StateMachine = __webpack_require__( 28 ); 145 media.controller.State = __webpack_require__( 29 ); 146 147 media.selectionSync = __webpack_require__( 30 ); 148 media.controller.Library = __webpack_require__( 31 ); 149 media.controller.ImageDetails = __webpack_require__( 32 ); 150 media.controller.GalleryEdit = __webpack_require__( 33 ); 151 media.controller.GalleryAdd = __webpack_require__( 34 ); 152 media.controller.CollectionEdit = __webpack_require__( 35 ); 153 media.controller.CollectionAdd = __webpack_require__( 36 ); 154 media.controller.FeaturedImage = __webpack_require__( 37 ); 155 media.controller.ReplaceImage = __webpack_require__( 38 ); 156 media.controller.EditImage = __webpack_require__( 39 ); 157 media.controller.MediaLibrary = __webpack_require__( 40 ); 158 media.controller.Embed = __webpack_require__( 41 ); 159 media.controller.Cropper = __webpack_require__( 42 ); 160 media.controller.CustomizeImageCropper = __webpack_require__( 43 ); 161 media.controller.SiteIconCropper = __webpack_require__( 44 ); 162 163 media.View = __webpack_require__( 45 ); 164 media.view.Frame = __webpack_require__( 46 ); 165 media.view.MediaFrame = __webpack_require__( 47 ); 166 media.view.MediaFrame.Select = __webpack_require__( 48 ); 167 media.view.MediaFrame.Post = __webpack_require__( 49 ); 168 media.view.MediaFrame.ImageDetails = __webpack_require__( 50 ); 169 media.view.Modal = __webpack_require__( 51 ); 170 media.view.FocusManager = __webpack_require__( 52 ); 171 media.view.UploaderWindow = __webpack_require__( 53 ); 172 media.view.EditorUploader = __webpack_require__( 54 ); 173 media.view.UploaderInline = __webpack_require__( 55 ); 174 media.view.UploaderStatus = __webpack_require__( 56 ); 175 media.view.UploaderStatusError = __webpack_require__( 57 ); 176 media.view.Toolbar = __webpack_require__( 58 ); 177 media.view.Toolbar.Select = __webpack_require__( 59 ); 178 media.view.Toolbar.Embed = __webpack_require__( 60 ); 179 media.view.Button = __webpack_require__( 61 ); 180 media.view.ButtonGroup = __webpack_require__( 62 ); 181 media.view.PriorityList = __webpack_require__( 63 ); 182 media.view.MenuItem = __webpack_require__( 64 ); 183 media.view.Menu = __webpack_require__( 65 ); 184 media.view.RouterItem = __webpack_require__( 66 ); 185 media.view.Router = __webpack_require__( 67 ); 186 media.view.Sidebar = __webpack_require__( 68 ); 187 media.view.Attachment = __webpack_require__( 69 ); 188 media.view.Attachment.Library = __webpack_require__( 70 ); 189 media.view.Attachment.EditLibrary = __webpack_require__( 71 ); 190 media.view.Attachments = __webpack_require__( 72 ); 191 media.view.Search = __webpack_require__( 73 ); 192 media.view.AttachmentFilters = __webpack_require__( 74 ); 193 media.view.DateFilter = __webpack_require__( 75 ); 194 media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 ); 195 media.view.AttachmentFilters.All = __webpack_require__( 77 ); 196 media.view.AttachmentsBrowser = __webpack_require__( 78 ); 197 media.view.Selection = __webpack_require__( 79 ); 198 media.view.Attachment.Selection = __webpack_require__( 80 ); 199 media.view.Attachments.Selection = __webpack_require__( 81 ); 200 media.view.Attachment.EditSelection = __webpack_require__( 82 ); 201 media.view.Settings = __webpack_require__( 83 ); 202 media.view.Settings.AttachmentDisplay = __webpack_require__( 84 ); 203 media.view.Settings.Gallery = __webpack_require__( 85 ); 204 media.view.Settings.Playlist = __webpack_require__( 86 ); 205 media.view.Attachment.Details = __webpack_require__( 87 ); 206 media.view.AttachmentCompat = __webpack_require__( 88 ); 207 media.view.Iframe = __webpack_require__( 89 ); 208 media.view.Embed = __webpack_require__( 90 ); 209 media.view.Label = __webpack_require__( 91 ); 210 media.view.EmbedUrl = __webpack_require__( 92 ); 211 media.view.EmbedLink = __webpack_require__( 93 ); 212 media.view.EmbedImage = __webpack_require__( 94 ); 213 media.view.ImageDetails = __webpack_require__( 95 ); 214 media.view.Cropper = __webpack_require__( 96 ); 215 media.view.SiteIconCropper = __webpack_require__( 97 ); 216 media.view.SiteIconPreview = __webpack_require__( 98 ); 217 media.view.EditImage = __webpack_require__( 99 ); 218 media.view.Spinner = __webpack_require__( 100 ); 219 220 221 /***/ }), 222 /* 27 */ 223 /***/ (function(module, exports) { 224 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. 10 237 * 11 238 * @memberOf wp.media.controller 12 239 * 13 240 * @class 14 * @augments wp.media.controller.Library 15 * @augments wp.media.controller.State 16 * @augments Backbone.Model 17 * 18 * @param {object} [attributes] The attributes hash passed to the state. 19 * @param {string} [attributes.id=library] Unique identifier. 20 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 21 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 22 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 23 * If one is not supplied, a collection of attachments of the specified type will be created. 24 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 25 * Accepts 'all', 'uploaded', or 'unattached'. 26 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 27 * @param {string} [attributes.content=upload] Initial mode for the content region. 28 * Overridden by persistent user setting if 'contentUserSetting' is true. 29 * @param {string} [attributes.router=browse] Initial mode for the router region. 30 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 31 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 32 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 33 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 34 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 35 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 36 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 37 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 38 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 39 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 241 * 242 * @param {object} options Options hash for the region. 243 * @param {string} options.id Unique identifier for the region. 244 * @param {Backbone.View} options.view A parent view the region exists within. 245 * @param {string} options.selector jQuery selector for the region within the parent view. 40 246 */ 41 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{ 42 defaults: _.defaults( { 43 // Selection defaults. @see media.model.Selection 44 multiple: 'add', 45 // Attachments browser defaults. @see media.view.AttachmentsBrowser 46 filterable: 'uploaded', 47 48 priority: 100, 49 syncSelection: false 50 }, Library.prototype.defaults ), 51 52 /** 53 * @since 3.9.0 54 */ 55 initialize: function() { 56 var collectionType = this.get('collectionType'); 57 58 if ( 'video' === this.get( 'type' ) ) { 59 collectionType = 'video-' + collectionType; 60 } 61 62 this.set( 'id', collectionType + '-library' ); 63 this.set( 'toolbar', collectionType + '-add' ); 64 this.set( 'menu', collectionType ); 65 66 // If we haven't been provided a `library`, create a `Selection`. 67 if ( ! this.get('library') ) { 68 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 69 } 70 Library.prototype.initialize.apply( this, arguments ); 71 }, 72 73 /** 74 * @since 3.9.0 75 */ 76 activate: function() { 77 var library = this.get('library'), 78 editLibrary = this.get('editLibrary'), 79 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 80 81 if ( editLibrary && editLibrary !== edit ) { 82 library.unobserve( editLibrary ); 83 } 84 85 // Accepts attachments that exist in the original library and 86 // that do not exist in gallery's library. 87 library.validator = function( attachment ) { 88 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 89 }; 90 91 // Reset the library to ensure that all attachments are re-added 92 // to the collection. Do so silently, as calling `observe` will 93 // trigger the `reset` event. 94 library.reset( library.mirroring.models, { silent: true }); 95 library.observe( edit ); 96 this.set('editLibrary', edit); 97 98 Library.prototype.activate.apply( this, arguments ); 247 var Region = function( options ) { 248 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 249 }; 250 251 // Use Backbone's self-propagating `extend` inheritance method. 252 Region.extend = Backbone.Model.extend; 253 254 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{ 255 /** 256 * Activate a mode. 257 * 258 * @since 3.5.0 259 * 260 * @param {string} mode 261 * 262 * @fires Region#activate 263 * @fires Region#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 wp.media.controller.Region#deactivate 280 */ 281 this.trigger('deactivate'); 282 283 this._mode = mode; 284 this.render( mode ); 285 286 /** 287 * Region mode activation event. 288 * 289 * @event wp.media.controller.Region#activate 290 */ 291 this.trigger('activate'); 292 return this; 293 }, 294 /** 295 * Render a mode. 296 * 297 * @since 3.5.0 298 * 299 * @param {string} mode 300 * 301 * @fires Region#create 302 * @fires Region#render 303 * 304 * @returns {wp.media.controller.Region} Returns itself to allow chaining 305 */ 306 render: function( mode ) { 307 // If the mode isn't active, activate it. 308 if ( mode && mode !== this._mode ) { 309 return this.mode( mode ); 310 } 311 312 var set = { view: null }, 313 view; 314 315 /** 316 * Create region view event. 317 * 318 * Region view creation takes place in an event callback on the frame. 319 * 320 * @event wp.media.controller.Region#create 321 * @type {object} 322 * @property {object} view 323 */ 324 this.trigger( 'create', set ); 325 view = set.view; 326 327 /** 328 * Render region view event. 329 * 330 * Region view creation takes place in an event callback on the frame. 331 * 332 * @event wp.media.controller.Region#render 333 * @type {object} 334 */ 335 this.trigger( 'render', view ); 336 if ( view ) { 337 this.set( view ); 338 } 339 return this; 340 }, 341 342 /** 343 * Get the region's view. 344 * 345 * @since 3.5.0 346 * 347 * @returns {wp.media.View} 348 */ 349 get: function() { 350 return this.view.views.first( this.selector ); 351 }, 352 353 /** 354 * Set the region's view as a subview of the frame. 355 * 356 * @since 3.5.0 357 * 358 * @param {Array|Object} views 359 * @param {Object} [options={}] 360 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 361 */ 362 set: function( views, options ) { 363 if ( options ) { 364 options.add = false; 365 } 366 return this.view.views.set( this.selector, views, options ); 367 }, 368 369 /** 370 * Trigger regional view events on the frame. 371 * 372 * @since 3.5.0 373 * 374 * @param {string} event 375 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 376 */ 377 trigger: function( event ) { 378 var base, args; 379 380 if ( ! this._mode ) { 381 return; 382 } 383 384 args = _.toArray( arguments ); 385 base = this.id + ':' + event; 386 387 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 388 args[0] = base + ':' + this._mode; 389 this.view.trigger.apply( this.view, args ); 390 391 // Trigger `{this.id}:{event}` event on the frame. 392 args[0] = base; 393 this.view.trigger.apply( this.view, args ); 394 return this; 99 395 } 100 396 }); 101 397 102 module.exports = CollectionAdd; 103 104 },{}],2:[function(require,module,exports){ 105 var Library = wp.media.controller.Library, 106 l10n = wp.media.view.l10n, 107 $ = jQuery, 108 CollectionEdit; 398 module.exports = Region; 399 400 401 /***/ }), 402 /* 28 */ 403 /***/ (function(module, exports) { 109 404 110 405 /** 111 * wp.media.controller.CollectionEdit 112 * 113 * A state for editing a collection, which is used by audio and video playlists, 114 * and can be used for other collections. 406 * wp.media.controller.StateMachine 407 * 408 * A state machine keeps track of state. It is in one state at a time, 409 * and can change from one state to another. 410 * 411 * States are stored as models in a Backbone collection. 115 412 * 116 413 * @memberOf wp.media.controller 117 414 * 415 * @since 3.5.0 416 * 118 417 * @class 119 * @augments wp.media.controller.Library120 * @augments wp.media.controller.State121 418 * @augments Backbone.Model 122 * 123 * @param {object} [attributes] The attributes hash passed to the state. 124 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 125 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 126 * If one is not supplied, an empty media.model.Selection collection is created. 127 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 128 * @param {string} [attributes.content=browse] Initial mode for the content region. 129 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 130 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 131 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 132 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 133 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 134 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 135 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 136 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 137 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 138 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 139 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 140 * Defaults to false for this state, because the library passed in *is* the selection. 141 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 142 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 143 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 144 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 145 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 419 * @mixin 420 * @mixes Backbone.Events 421 * 422 * @param {Array} states 146 423 */ 147 CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{ 148 defaults: { 149 multiple: false, 150 sortable: true, 151 date: false, 152 searchable: false, 153 content: 'browse', 154 describe: true, 155 dragInfo: true, 156 idealColumnWidth: 170, 157 editing: false, 158 priority: 60, 159 SettingsView: false, 160 syncSelection: false 161 }, 162 163 /** 164 * @since 3.9.0 165 */ 166 initialize: function() { 167 var collectionType = this.get('collectionType'); 168 169 if ( 'video' === this.get( 'type' ) ) { 170 collectionType = 'video-' + collectionType; 171 } 172 173 this.set( 'id', collectionType + '-edit' ); 174 this.set( 'toolbar', collectionType + '-edit' ); 175 176 // If we haven't been provided a `library`, create a `Selection`. 177 if ( ! this.get('library') ) { 178 this.set( 'library', new wp.media.model.Selection() ); 179 } 180 // The single `Attachment` view to be used in the `Attachments` view. 181 if ( ! this.get('AttachmentView') ) { 182 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 183 } 184 Library.prototype.initialize.apply( this, arguments ); 185 }, 186 187 /** 188 * @since 3.9.0 189 */ 190 activate: function() { 191 var library = this.get('library'); 192 193 // Limit the library to images only. 194 library.props.set( 'type', this.get( 'type' ) ); 195 196 // Watch for uploaded attachments. 197 this.get('library').observe( wp.Uploader.queue ); 198 199 this.frame.on( 'content:render:browse', this.renderSettings, this ); 200 201 Library.prototype.activate.apply( this, arguments ); 202 }, 203 204 /** 205 * @since 3.9.0 206 */ 207 deactivate: function() { 208 // Stop watching for uploaded attachments. 209 this.get('library').unobserve( wp.Uploader.queue ); 210 211 this.frame.off( 'content:render:browse', this.renderSettings, this ); 212 213 Library.prototype.deactivate.apply( this, arguments ); 214 }, 215 216 /** 217 * Render the collection embed settings view in the browser sidebar. 424 var StateMachine = function( states ) { 425 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 426 this.states = new Backbone.Collection( states ); 427 }; 428 429 // Use Backbone's self-propagating `extend` inheritance method. 430 StateMachine.extend = Backbone.Model.extend; 431 432 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{ 433 /** 434 * Fetch a state. 218 435 * 219 * @todo This is against the pattern elsewhere in media. Typically the frame 220 * is responsible for adding region mode callbacks. Explain. 436 * If no `id` is provided, returns the active state. 221 437 * 222 * @since 3.9.0438 * Implicitly creates states. 223 439 * 224 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 225 */ 226 renderSettings: function( attachmentsBrowserView ) { 227 var library = this.get('library'), 228 collectionType = this.get('collectionType'), 229 dragInfoText = this.get('dragInfoText'), 230 SettingsView = this.get('SettingsView'), 231 obj = {}; 232 233 if ( ! library || ! attachmentsBrowserView ) { 234 return; 235 } 236 237 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 238 239 obj[ collectionType ] = new SettingsView({ 240 controller: this, 241 model: library[ collectionType ], 242 priority: 40 243 }); 244 245 attachmentsBrowserView.sidebar.set( obj ); 246 247 if ( dragInfoText ) { 248 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 249 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 250 priority: -40 251 }) ); 252 } 253 254 // Add the 'Reverse order' button to the toolbar. 255 attachmentsBrowserView.toolbar.set( 'reverse', { 256 text: l10n.reverseOrder, 257 priority: 80, 258 259 click: function() { 260 library.reset( library.toArray().reverse() ); 261 } 262 }); 440 * Ensure that the `states` collection exists so the `StateMachine` 441 * can be used as a mixin. 442 * 443 * @since 3.5.0 444 * 445 * @param {string} id 446 * @returns {wp.media.controller.State} Returns a State model 447 * from the StateMachine collection 448 */ 449 state: function( id ) { 450 this.states = this.states || new Backbone.Collection(); 451 452 // Default to the active state. 453 id = id || this._state; 454 455 if ( id && ! this.states.get( id ) ) { 456 this.states.add({ id: id }); 457 } 458 return this.states.get( id ); 459 }, 460 461 /** 462 * Sets the active state. 463 * 464 * Bail if we're trying to select the current state, if we haven't 465 * created the `states` collection, or are trying to select a state 466 * that does not exist. 467 * 468 * @since 3.5.0 469 * 470 * @param {string} id 471 * 472 * @fires wp.media.controller.State#deactivate 473 * @fires wp.media.controller.State#activate 474 * 475 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 476 */ 477 setState: function( id ) { 478 var previous = this.state(); 479 480 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 481 return this; 482 } 483 484 if ( previous ) { 485 previous.trigger('deactivate'); 486 this._lastState = previous.id; 487 } 488 489 this._state = id; 490 this.state().trigger('activate'); 491 492 return this; 493 }, 494 495 /** 496 * Returns the previous active state. 497 * 498 * Call the `state()` method with no parameters to retrieve the current 499 * active state. 500 * 501 * @since 3.5.0 502 * 503 * @returns {wp.media.controller.State} Returns a State model 504 * from the StateMachine collection 505 */ 506 lastState: function() { 507 if ( this._lastState ) { 508 return this.state( this._lastState ); 509 } 263 510 } 264 511 }); 265 512 266 module.exports = CollectionEdit; 267 268 },{}],3:[function(require,module,exports){ 513 // Map all event binding and triggering on a StateMachine to its `states` collection. 514 _.each([ 'on', 'off', 'trigger' ], function( method ) { 515 /** 516 * @function on 517 * @memberOf wp.media.controller.StateMachine 518 * @instance 519 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 520 */ 521 /** 522 * @function off 523 * @memberOf wp.media.controller.StateMachine 524 * @instance 525 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 526 */ 527 /** 528 * @function trigger 529 * @memberOf wp.media.controller.StateMachine 530 * @instance 531 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 532 */ 533 StateMachine.prototype[ method ] = function() { 534 // Ensure that the `states` collection exists so the `StateMachine` 535 // can be used as a mixin. 536 this.states = this.states || new Backbone.Collection(); 537 // Forward the method to the `states` collection. 538 this.states[ method ].apply( this.states, arguments ); 539 return this; 540 }; 541 }); 542 543 module.exports = StateMachine; 544 545 546 /***/ }), 547 /* 29 */ 548 /***/ (function(module, exports) { 549 550 /** 551 * wp.media.controller.State 552 * 553 * A state is a step in a workflow that when set will trigger the controllers 554 * for the regions to be updated as specified in the frame. 555 * 556 * A state has an event-driven lifecycle: 557 * 558 * 'ready' triggers when a state is added to a state machine's collection. 559 * 'activate' triggers when a state is activated by a state machine. 560 * 'deactivate' triggers when a state is deactivated by a state machine. 561 * 'reset' is not triggered automatically. It should be invoked by the 562 * proper controller to reset the state to its default. 563 * 564 * @memberOf wp.media.controller 565 * 566 * @class 567 * @augments Backbone.Model 568 */ 569 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{ 570 /** 571 * Constructor. 572 * 573 * @since 3.5.0 574 */ 575 constructor: function() { 576 this.on( 'activate', this._preActivate, this ); 577 this.on( 'activate', this.activate, this ); 578 this.on( 'activate', this._postActivate, this ); 579 this.on( 'deactivate', this._deactivate, this ); 580 this.on( 'deactivate', this.deactivate, this ); 581 this.on( 'reset', this.reset, this ); 582 this.on( 'ready', this._ready, this ); 583 this.on( 'ready', this.ready, this ); 584 /** 585 * Call parent constructor with passed arguments 586 */ 587 Backbone.Model.apply( this, arguments ); 588 this.on( 'change:menu', this._updateMenu, this ); 589 }, 590 /** 591 * Ready event callback. 592 * 593 * @abstract 594 * @since 3.5.0 595 */ 596 ready: function() {}, 597 598 /** 599 * Activate event callback. 600 * 601 * @abstract 602 * @since 3.5.0 603 */ 604 activate: function() {}, 605 606 /** 607 * Deactivate event callback. 608 * 609 * @abstract 610 * @since 3.5.0 611 */ 612 deactivate: function() {}, 613 614 /** 615 * Reset event callback. 616 * 617 * @abstract 618 * @since 3.5.0 619 */ 620 reset: function() {}, 621 622 /** 623 * @access private 624 * @since 3.5.0 625 */ 626 _ready: function() { 627 this._updateMenu(); 628 }, 629 630 /** 631 * @access private 632 * @since 3.5.0 633 */ 634 _preActivate: function() { 635 this.active = true; 636 }, 637 638 /** 639 * @access private 640 * @since 3.5.0 641 */ 642 _postActivate: function() { 643 this.on( 'change:menu', this._menu, this ); 644 this.on( 'change:titleMode', this._title, this ); 645 this.on( 'change:content', this._content, this ); 646 this.on( 'change:toolbar', this._toolbar, this ); 647 648 this.frame.on( 'title:render:default', this._renderTitle, this ); 649 650 this._title(); 651 this._menu(); 652 this._toolbar(); 653 this._content(); 654 this._router(); 655 }, 656 657 /** 658 * @access private 659 * @since 3.5.0 660 */ 661 _deactivate: function() { 662 this.active = false; 663 664 this.frame.off( 'title:render:default', this._renderTitle, this ); 665 666 this.off( 'change:menu', this._menu, this ); 667 this.off( 'change:titleMode', this._title, this ); 668 this.off( 'change:content', this._content, this ); 669 this.off( 'change:toolbar', this._toolbar, this ); 670 }, 671 672 /** 673 * @access private 674 * @since 3.5.0 675 */ 676 _title: function() { 677 this.frame.title.render( this.get('titleMode') || 'default' ); 678 }, 679 680 /** 681 * @access private 682 * @since 3.5.0 683 */ 684 _renderTitle: function( view ) { 685 view.$el.text( this.get('title') || '' ); 686 }, 687 688 /** 689 * @access private 690 * @since 3.5.0 691 */ 692 _router: function() { 693 var router = this.frame.router, 694 mode = this.get('router'), 695 view; 696 697 this.frame.$el.toggleClass( 'hide-router', ! mode ); 698 if ( ! mode ) { 699 return; 700 } 701 702 this.frame.router.render( mode ); 703 704 view = router.get(); 705 if ( view && view.select ) { 706 view.select( this.frame.content.mode() ); 707 } 708 }, 709 710 /** 711 * @access private 712 * @since 3.5.0 713 */ 714 _menu: function() { 715 var menu = this.frame.menu, 716 mode = this.get('menu'), 717 view; 718 719 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 720 if ( ! mode ) { 721 return; 722 } 723 724 menu.mode( mode ); 725 726 view = menu.get(); 727 if ( view && view.select ) { 728 view.select( this.id ); 729 } 730 }, 731 732 /** 733 * @access private 734 * @since 3.5.0 735 */ 736 _updateMenu: function() { 737 var previous = this.previous('menu'), 738 menu = this.get('menu'); 739 740 if ( previous ) { 741 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 742 } 743 744 if ( menu ) { 745 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 746 } 747 }, 748 749 /** 750 * Create a view in the media menu for the state. 751 * 752 * @access private 753 * @since 3.5.0 754 * 755 * @param {media.view.Menu} view The menu view. 756 */ 757 _renderMenu: function( view ) { 758 var menuItem = this.get('menuItem'), 759 title = this.get('title'), 760 priority = this.get('priority'); 761 762 if ( ! menuItem && title ) { 763 menuItem = { text: title }; 764 765 if ( priority ) { 766 menuItem.priority = priority; 767 } 768 } 769 770 if ( ! menuItem ) { 771 return; 772 } 773 774 view.set( this.id, menuItem ); 775 } 776 }); 777 778 _.each(['toolbar','content'], function( region ) { 779 /** 780 * @access private 781 */ 782 State.prototype[ '_' + region ] = function() { 783 var mode = this.get( region ); 784 if ( mode ) { 785 this.frame[ region ].render( mode ); 786 } 787 }; 788 }); 789 790 module.exports = State; 791 792 793 /***/ }), 794 /* 30 */ 795 /***/ (function(module, exports) { 796 797 /** 798 * wp.media.selectionSync 799 * 800 * Sync an attachments selection in a state with another state. 801 * 802 * Allows for selecting multiple images in the Add Media workflow, and then 803 * switching to the Insert Gallery workflow while preserving the attachments selection. 804 * 805 * @memberOf wp.media 806 * 807 * @mixin 808 */ 809 var selectionSync = { 810 /** 811 * @since 3.5.0 812 */ 813 syncSelection: function() { 814 var selection = this.get('selection'), 815 manager = this.frame._selection; 816 817 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 818 return; 819 } 820 821 // If the selection supports multiple items, validate the stored 822 // attachments based on the new selection's conditions. Record 823 // the attachments that are not included; we'll maintain a 824 // reference to those. Other attachments are considered in flux. 825 if ( selection.multiple ) { 826 selection.reset( [], { silent: true }); 827 selection.validateAll( manager.attachments ); 828 manager.difference = _.difference( manager.attachments.models, selection.models ); 829 } 830 831 // Sync the selection's single item with the master. 832 selection.single( manager.single ); 833 }, 834 835 /** 836 * Record the currently active attachments, which is a combination 837 * of the selection's attachments and the set of selected 838 * attachments that this specific selection considered invalid. 839 * Reset the difference and record the single attachment. 840 * 841 * @since 3.5.0 842 */ 843 recordSelection: function() { 844 var selection = this.get('selection'), 845 manager = this.frame._selection; 846 847 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 848 return; 849 } 850 851 if ( selection.multiple ) { 852 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 853 manager.difference = []; 854 } else { 855 manager.attachments.add( selection.toArray() ); 856 } 857 858 manager.single = selection._single; 859 } 860 }; 861 862 module.exports = selectionSync; 863 864 865 /***/ }), 866 /* 31 */ 867 /***/ (function(module, exports) { 868 269 869 var l10n = wp.media.view.l10n, 270 Cropper; 870 getUserSetting = window.getUserSetting, 871 setUserSetting = window.setUserSetting, 872 Library; 271 873 272 874 /** 273 * wp.media.controller. Cropper274 * 275 * A state for c ropping an image.875 * wp.media.controller.Library 876 * 877 * A state for choosing an attachment or group of attachments from the media library. 276 878 * 277 879 * @memberOf wp.media.controller … … 280 882 * @augments wp.media.controller.State 281 883 * @augments Backbone.Model 884 * @mixes media.selectionSync 885 * 886 * @param {object} [attributes] The attributes hash passed to the state. 887 * @param {string} [attributes.id=library] Unique identifier. 888 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 889 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 890 * If one is not supplied, a collection of all attachments will be created. 891 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 892 * If the 'selection' attribute is a plain JS object, 893 * a Selection will be created using its values as the selection instance's `props` model. 894 * Otherwise, it will copy the library's `props` model. 895 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 896 * @param {string} [attributes.content=upload] Initial mode for the content region. 897 * Overridden by persistent user setting if 'contentUserSetting' is true. 898 * @param {string} [attributes.menu=default] Initial mode for the menu region. 899 * @param {string} [attributes.router=browse] Initial mode for the router region. 900 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 901 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 902 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 903 * Accepts 'all', 'uploaded', or 'unattached'. 904 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 905 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 906 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 907 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 908 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 282 909 */ 283 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{910 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{ 284 911 defaults: { 285 id: 'cropper', 286 title: l10n.cropImage, 287 // Region mode defaults. 288 toolbar: 'crop', 289 content: 'crop', 290 router: false, 291 canSkipCrop: false, 292 293 // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state. 294 doCropArgs: {} 295 }, 296 912 id: 'library', 913 title: l10n.mediaLibraryTitle, 914 multiple: false, 915 content: 'upload', 916 menu: 'default', 917 router: 'browse', 918 toolbar: 'select', 919 searchable: true, 920 filterable: false, 921 sortable: true, 922 autoSelect: true, 923 describe: false, 924 contentUserSetting: true, 925 syncSelection: true 926 }, 927 928 /** 929 * If a library isn't provided, query all media items. 930 * If a selection instance isn't provided, create one. 931 * 932 * @since 3.5.0 933 */ 934 initialize: function() { 935 var selection = this.get('selection'), 936 props; 937 938 if ( ! this.get('library') ) { 939 this.set( 'library', wp.media.query() ); 940 } 941 942 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 943 props = selection; 944 945 if ( ! props ) { 946 props = this.get('library').props.toJSON(); 947 props = _.omit( props, 'orderby', 'query' ); 948 } 949 950 this.set( 'selection', new wp.media.model.Selection( null, { 951 multiple: this.get('multiple'), 952 props: props 953 }) ); 954 } 955 956 this.resetDisplays(); 957 }, 958 959 /** 960 * @since 3.5.0 961 */ 297 962 activate: function() { 298 this.frame.on( 'content:create:crop', this.createCropContent, this ); 299 this.frame.on( 'close', this.removeCropper, this ); 300 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 301 }, 302 963 this.syncSelection(); 964 965 wp.Uploader.queue.on( 'add', this.uploading, this ); 966 967 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 968 969 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 970 this.frame.on( 'content:activate', this.saveContentMode, this ); 971 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 972 } 973 }, 974 975 /** 976 * @since 3.5.0 977 */ 303 978 deactivate: function() { 304 this.frame.toolbar.mode('browse'); 305 }, 306 307 createCropContent: function() { 308 this.cropperView = new wp.media.view.Cropper({ 309 controller: this, 310 attachment: this.get('selection').first() 311 }); 312 this.cropperView.on('image-loaded', this.createCropToolbar, this); 313 this.frame.content.set(this.cropperView); 314 315 }, 316 removeCropper: function() { 317 this.imgSelect.cancelSelection(); 318 this.imgSelect.setOptions({remove: true}); 319 this.imgSelect.update(); 320 this.cropperView.remove(); 321 }, 322 createCropToolbar: function() { 323 var canSkipCrop, toolbarOptions; 324 325 canSkipCrop = this.get('canSkipCrop') || false; 326 327 toolbarOptions = { 328 controller: this.frame, 329 items: { 330 insert: { 331 style: 'primary', 332 text: l10n.cropImage, 333 priority: 80, 334 requires: { library: false, selection: false }, 335 336 click: function() { 337 var controller = this.controller, 338 selection; 339 340 selection = controller.state().get('selection').first(); 341 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 342 343 this.$el.text(l10n.cropping); 344 this.$el.attr('disabled', true); 345 346 controller.state().doCrop( selection ).done( function( croppedImage ) { 347 controller.trigger('cropped', croppedImage ); 348 controller.close(); 349 }).fail( function() { 350 controller.trigger('content:error:crop'); 351 }); 352 } 353 } 979 this.recordSelection(); 980 981 this.frame.off( 'content:activate', this.saveContentMode, this ); 982 983 // Unbind all event handlers that use this state as the context 984 // from the selection. 985 this.get('selection').off( null, null, this ); 986 987 wp.Uploader.queue.off( null, null, this ); 988 }, 989 990 /** 991 * Reset the library to its initial state. 992 * 993 * @since 3.5.0 994 */ 995 reset: function() { 996 this.get('selection').reset(); 997 this.resetDisplays(); 998 this.refreshContent(); 999 }, 1000 1001 /** 1002 * Reset the attachment display settings defaults to the site options. 1003 * 1004 * If site options don't define them, fall back to a persistent user setting. 1005 * 1006 * @since 3.5.0 1007 */ 1008 resetDisplays: function() { 1009 var defaultProps = wp.media.view.settings.defaultProps; 1010 this._displays = []; 1011 this._defaultDisplaySettings = { 1012 align: getUserSetting( 'align', defaultProps.align ) || 'none', 1013 size: getUserSetting( 'imgsize', defaultProps.size ) || 'medium', 1014 link: getUserSetting( 'urlbutton', defaultProps.link ) || 'none' 1015 }; 1016 }, 1017 1018 /** 1019 * Create a model to represent display settings (alignment, etc.) for an attachment. 1020 * 1021 * @since 3.5.0 1022 * 1023 * @param {wp.media.model.Attachment} attachment 1024 * @returns {Backbone.Model} 1025 */ 1026 display: function( attachment ) { 1027 var displays = this._displays; 1028 1029 if ( ! displays[ attachment.cid ] ) { 1030 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 1031 } 1032 return displays[ attachment.cid ]; 1033 }, 1034 1035 /** 1036 * Given an attachment, create attachment display settings properties. 1037 * 1038 * @since 3.6.0 1039 * 1040 * @param {wp.media.model.Attachment} attachment 1041 * @returns {Object} 1042 */ 1043 defaultDisplaySettings: function( attachment ) { 1044 var settings = _.clone( this._defaultDisplaySettings ); 1045 1046 if ( settings.canEmbed = this.canEmbed( attachment ) ) { 1047 settings.link = 'embed'; 1048 } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) { 1049 settings.link = 'file'; 1050 } 1051 1052 return settings; 1053 }, 1054 1055 /** 1056 * Whether an attachment is image. 1057 * 1058 * @since 4.4.1 1059 * 1060 * @param {wp.media.model.Attachment} attachment 1061 * @returns {Boolean} 1062 */ 1063 isImageAttachment: function( attachment ) { 1064 // If uploading, we know the filename but not the mime type. 1065 if ( attachment.get('uploading') ) { 1066 return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') ); 1067 } 1068 1069 return attachment.get('type') === 'image'; 1070 }, 1071 1072 /** 1073 * Whether an attachment can be embedded (audio or video). 1074 * 1075 * @since 3.6.0 1076 * 1077 * @param {wp.media.model.Attachment} attachment 1078 * @returns {Boolean} 1079 */ 1080 canEmbed: function( attachment ) { 1081 // If uploading, we know the filename but not the mime type. 1082 if ( ! attachment.get('uploading') ) { 1083 var type = attachment.get('type'); 1084 if ( type !== 'audio' && type !== 'video' ) { 1085 return false; 354 1086 } 355 }; 356 357 if ( canSkipCrop ) { 358 _.extend( toolbarOptions.items, { 359 skip: { 360 style: 'secondary', 361 text: l10n.skipCropping, 362 priority: 70, 363 requires: { library: false, selection: false }, 364 click: function() { 365 var selection = this.controller.state().get('selection').first(); 366 this.controller.state().cropperView.remove(); 367 this.controller.trigger('skippedcrop', selection); 368 this.controller.close(); 369 } 370 } 371 }); 372 } 373 374 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 375 }, 376 377 doCrop: function( attachment ) { 378 return wp.ajax.post( 'custom-header-crop', _.extend( 379 {}, 380 this.defaults.doCropArgs, 381 { 382 nonce: attachment.get( 'nonces' ).edit, 383 id: attachment.get( 'id' ), 384 cropDetails: attachment.get( 'cropDetails' ) 385 } 386 ) ); 1087 } 1088 1089 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1090 }, 1091 1092 1093 /** 1094 * If the state is active, no items are selected, and the current 1095 * content mode is not an option in the state's router (provided 1096 * the state has a router), reset the content mode to the default. 1097 * 1098 * @since 3.5.0 1099 */ 1100 refreshContent: function() { 1101 var selection = this.get('selection'), 1102 frame = this.frame, 1103 router = frame.router.get(), 1104 mode = frame.content.mode(); 1105 1106 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1107 this.frame.content.render( this.get('content') ); 1108 } 1109 }, 1110 1111 /** 1112 * Callback handler when an attachment is uploaded. 1113 * 1114 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 1115 * 1116 * Adds any uploading attachments to the selection. 1117 * 1118 * If the state only supports one attachment to be selected and multiple 1119 * attachments are uploaded, the last attachment in the upload queue will 1120 * be selected. 1121 * 1122 * @since 3.5.0 1123 * 1124 * @param {wp.media.model.Attachment} attachment 1125 */ 1126 uploading: function( attachment ) { 1127 var content = this.frame.content; 1128 1129 if ( 'upload' === content.mode() ) { 1130 this.frame.content.mode('browse'); 1131 } 1132 1133 if ( this.get( 'autoSelect' ) ) { 1134 this.get('selection').add( attachment ); 1135 this.frame.trigger( 'library:selection:add' ); 1136 } 1137 }, 1138 1139 /** 1140 * Persist the mode of the content region as a user setting. 1141 * 1142 * @since 3.5.0 1143 */ 1144 saveContentMode: function() { 1145 if ( 'browse' !== this.get('router') ) { 1146 return; 1147 } 1148 1149 var mode = this.frame.content.mode(), 1150 view = this.frame.router.get(); 1151 1152 if ( view && view.get( mode ) ) { 1153 setUserSetting( 'libraryContent', mode ); 1154 } 387 1155 } 388 1156 }); 389 1157 390 module.exports = Cropper; 391 392 },{}],4:[function(require,module,exports){ 393 var Controller = wp.media.controller, 394 CustomizeImageCropper; 1158 // Make selectionSync available on any Media Library state. 1159 _.extend( Library.prototype, wp.media.selectionSync ); 1160 1161 module.exports = Library; 1162 1163 1164 /***/ }), 1165 /* 32 */ 1166 /***/ (function(module, exports) { 1167 1168 var State = wp.media.controller.State, 1169 Library = wp.media.controller.Library, 1170 l10n = wp.media.view.l10n, 1171 ImageDetails; 395 1172 396 1173 /** 397 * wp.media.controller.CustomizeImageCropper 398 * 399 * @memberOf wp.media.controller 400 * 401 * A state for cropping an image. 402 * 403 * @class 404 * @augments wp.media.controller.Cropper 405 * @augments wp.media.controller.State 406 * @augments Backbone.Model 407 */ 408 CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{ 409 doCrop: function( attachment ) { 410 var cropDetails = attachment.get( 'cropDetails' ), 411 control = this.get( 'control' ), 412 ratio = cropDetails.width / cropDetails.height; 413 414 // Use crop measurements when flexible in both directions. 415 if ( control.params.flex_width && control.params.flex_height ) { 416 cropDetails.dst_width = cropDetails.width; 417 cropDetails.dst_height = cropDetails.height; 418 419 // Constrain flexible side based on image ratio and size of the fixed side. 420 } else { 421 cropDetails.dst_width = control.params.flex_width ? control.params.height * ratio : control.params.width; 422 cropDetails.dst_height = control.params.flex_height ? control.params.width / ratio : control.params.height; 423 } 424 425 return wp.ajax.post( 'crop-image', { 426 wp_customize: 'on', 427 nonce: attachment.get( 'nonces' ).edit, 428 id: attachment.get( 'id' ), 429 context: control.id, 430 cropDetails: cropDetails 431 } ); 432 } 433 }); 434 435 module.exports = CustomizeImageCropper; 436 437 },{}],5:[function(require,module,exports){ 438 var l10n = wp.media.view.l10n, 439 EditImage; 440 441 /** 442 * wp.media.controller.EditImage 443 * 444 * A state for editing (cropping, etc.) an image. 1174 * wp.media.controller.ImageDetails 1175 * 1176 * A state for editing the attachment display settings of an image that's been 1177 * inserted into the editor. 445 1178 * 446 1179 * @memberOf wp.media.controller … … 450 1183 * @augments Backbone.Model 451 1184 * 452 * @param {object} attributes The attributes hash passed to the state. 453 * @param {wp.media.model.Attachment} attributes.model The attachment. 454 * @param {string} [attributes.id=edit-image] Unique identifier. 455 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 456 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 457 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 458 * @param {string} [attributes.menu=false] Initial mode for the menu region. 459 * @param {string} [attributes.url] Unused. @todo Consider removal. 1185 * @param {object} [attributes] The attributes hash passed to the state. 1186 * @param {string} [attributes.id=image-details] Unique identifier. 1187 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 1188 * @param {wp.media.model.Attachment} attributes.image The image's model. 1189 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 1190 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 1191 * @param {string|false} [attributes.router=false] Initial mode for the router region. 1192 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1193 * @param {boolean} [attributes.editing=false] Unused. 1194 * @param {int} [attributes.priority=60] Unused. 1195 * 1196 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1197 * however this may not do anything. 460 1198 */ 461 EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{ 462 defaults: { 463 id: 'edit-image', 464 title: l10n.editImage, 465 menu: false, 466 toolbar: 'edit-image', 467 content: 'edit-image', 468 url: '' 469 }, 1199 ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{ 1200 defaults: _.defaults({ 1201 id: 'image-details', 1202 title: l10n.imageDetailsTitle, 1203 content: 'image-details', 1204 menu: false, 1205 router: false, 1206 toolbar: 'image-details', 1207 editing: false, 1208 priority: 60 1209 }, Library.prototype.defaults ), 470 1210 471 1211 /** 472 1212 * @since 3.9.0 1213 * 1214 * @param options Attributes 1215 */ 1216 initialize: function( options ) { 1217 this.image = options.image; 1218 State.prototype.initialize.apply( this, arguments ); 1219 }, 1220 1221 /** 1222 * @since 3.9.0 473 1223 */ 474 1224 activate: function() { 475 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) ); 476 }, 477 478 /** 479 * @since 3.9.0 480 */ 481 deactivate: function() { 482 this.frame.off( 'toolbar:render:edit-image' ); 483 }, 484 485 /** 486 * @since 3.9.0 487 */ 488 toolbar: function() { 489 var frame = this.frame, 490 lastState = frame.lastState(), 491 previous = lastState && lastState.id; 492 493 frame.toolbar.set( new wp.media.view.Toolbar({ 494 controller: frame, 495 items: { 496 back: { 497 style: 'primary', 498 text: l10n.back, 499 priority: 20, 500 click: function() { 501 if ( previous ) { 502 frame.setState( previous ); 503 } else { 504 frame.close(); 505 } 506 } 507 } 508 } 509 }) ); 1225 this.frame.modal.$el.addClass('image-details'); 510 1226 } 511 1227 }); 512 1228 513 module.exports = EditImage; 514 515 },{}],6:[function(require,module,exports){ 516 var l10n = wp.media.view.l10n, 517 $ = Backbone.$, 518 Embed; 519 520 /** 521 * wp.media.controller.Embed 522 * 523 * A state for embedding media from a URL. 524 * 525 * @memberOf wp.media.controller 526 * 527 * @class 528 * @augments wp.media.controller.State 529 * @augments Backbone.Model 530 * 531 * @param {object} attributes The attributes hash passed to the state. 532 * @param {string} [attributes.id=embed] Unique identifier. 533 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 534 * @param {string} [attributes.content=embed] Initial mode for the content region. 535 * @param {string} [attributes.menu=default] Initial mode for the menu region. 536 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 537 * @param {string} [attributes.menu=false] Initial mode for the menu region. 538 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 539 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 540 * @param {string} [attributes.url] The embed URL. 541 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 542 */ 543 Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{ 544 defaults: { 545 id: 'embed', 546 title: l10n.insertFromUrlTitle, 547 content: 'embed', 548 menu: 'default', 549 toolbar: 'main-embed', 550 priority: 120, 551 type: 'link', 552 url: '', 553 metadata: {} 554 }, 555 556 // The amount of time used when debouncing the scan. 557 sensitivity: 400, 558 559 initialize: function(options) { 560 this.metadata = options.metadata; 561 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 562 this.props = new Backbone.Model( this.metadata || { url: '' }); 563 this.props.on( 'change:url', this.debouncedScan, this ); 564 this.props.on( 'change:url', this.refresh, this ); 565 this.on( 'scan', this.scanImage, this ); 566 }, 567 568 /** 569 * Trigger a scan of the embedded URL's content for metadata required to embed. 570 * 571 * @fires wp.media.controller.Embed#scan 572 */ 573 scan: function() { 574 var scanners, 575 embed = this, 576 attributes = { 577 type: 'link', 578 scanners: [] 579 }; 580 581 // Scan is triggered with the list of `attributes` to set on the 582 // state, useful for the 'type' attribute and 'scanners' attribute, 583 // an array of promise objects for asynchronous scan operations. 584 if ( this.props.get('url') ) { 585 this.trigger( 'scan', attributes ); 586 } 587 588 if ( attributes.scanners.length ) { 589 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 590 scanners.always( function() { 591 if ( embed.get('scanners') === scanners ) { 592 embed.set( 'loading', false ); 593 } 594 }); 595 } else { 596 attributes.scanners = null; 597 } 598 599 attributes.loading = !! attributes.scanners; 600 this.set( attributes ); 601 }, 602 /** 603 * Try scanning the embed as an image to discover its dimensions. 604 * 605 * @param {Object} attributes 606 */ 607 scanImage: function( attributes ) { 608 var frame = this.frame, 609 state = this, 610 url = this.props.get('url'), 611 image = new Image(), 612 deferred = $.Deferred(); 613 614 attributes.scanners.push( deferred.promise() ); 615 616 // Try to load the image and find its width/height. 617 image.onload = function() { 618 deferred.resolve(); 619 620 if ( state !== frame.state() || url !== state.props.get('url') ) { 621 return; 622 } 623 624 state.set({ 625 type: 'image' 626 }); 627 628 state.props.set({ 629 width: image.width, 630 height: image.height 631 }); 632 }; 633 634 image.onerror = deferred.reject; 635 image.src = url; 636 }, 637 638 refresh: function() { 639 this.frame.toolbar.get().refresh(); 640 }, 641 642 reset: function() { 643 this.props.clear().set({ url: '' }); 644 645 if ( this.active ) { 646 this.refresh(); 647 } 648 } 649 }); 650 651 module.exports = Embed; 652 653 },{}],7:[function(require,module,exports){ 654 var Attachment = wp.media.model.Attachment, 655 Library = wp.media.controller.Library, 656 l10n = wp.media.view.l10n, 657 FeaturedImage; 658 659 /** 660 * wp.media.controller.FeaturedImage 661 * 662 * A state for selecting a featured image for a post. 663 * 664 * @memberOf wp.media.controller 665 * 666 * @class 667 * @augments wp.media.controller.Library 668 * @augments wp.media.controller.State 669 * @augments Backbone.Model 670 * 671 * @param {object} [attributes] The attributes hash passed to the state. 672 * @param {string} [attributes.id=featured-image] Unique identifier. 673 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 674 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 675 * If one is not supplied, a collection of all images will be created. 676 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 677 * @param {string} [attributes.content=upload] Initial mode for the content region. 678 * Overridden by persistent user setting if 'contentUserSetting' is true. 679 * @param {string} [attributes.menu=default] Initial mode for the menu region. 680 * @param {string} [attributes.router=browse] Initial mode for the router region. 681 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 682 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 683 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 684 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 685 * Accepts 'all', 'uploaded', or 'unattached'. 686 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 687 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 688 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 689 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 690 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 691 */ 692 FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{ 693 defaults: _.defaults({ 694 id: 'featured-image', 695 title: l10n.setFeaturedImageTitle, 696 multiple: false, 697 filterable: 'uploaded', 698 toolbar: 'featured-image', 699 priority: 60, 700 syncSelection: true 701 }, Library.prototype.defaults ), 702 703 /** 704 * @since 3.5.0 705 */ 706 initialize: function() { 707 var library, comparator; 708 709 // If we haven't been provided a `library`, create a `Selection`. 710 if ( ! this.get('library') ) { 711 this.set( 'library', wp.media.query({ type: 'image' }) ); 712 } 713 714 Library.prototype.initialize.apply( this, arguments ); 715 716 library = this.get('library'); 717 comparator = library.comparator; 718 719 // Overload the library's comparator to push items that are not in 720 // the mirrored query to the front of the aggregate collection. 721 library.comparator = function( a, b ) { 722 var aInQuery = !! this.mirroring.get( a.cid ), 723 bInQuery = !! this.mirroring.get( b.cid ); 724 725 if ( ! aInQuery && bInQuery ) { 726 return -1; 727 } else if ( aInQuery && ! bInQuery ) { 728 return 1; 729 } else { 730 return comparator.apply( this, arguments ); 731 } 732 }; 733 734 // Add all items in the selection to the library, so any featured 735 // images that are not initially loaded still appear. 736 library.observe( this.get('selection') ); 737 }, 738 739 /** 740 * @since 3.5.0 741 */ 742 activate: function() { 743 this.updateSelection(); 744 this.frame.on( 'open', this.updateSelection, this ); 745 746 Library.prototype.activate.apply( this, arguments ); 747 }, 748 749 /** 750 * @since 3.5.0 751 */ 752 deactivate: function() { 753 this.frame.off( 'open', this.updateSelection, this ); 754 755 Library.prototype.deactivate.apply( this, arguments ); 756 }, 757 758 /** 759 * @since 3.5.0 760 */ 761 updateSelection: function() { 762 var selection = this.get('selection'), 763 id = wp.media.view.settings.post.featuredImageId, 764 attachment; 765 766 if ( '' !== id && -1 !== id ) { 767 attachment = Attachment.get( id ); 768 attachment.fetch(); 769 } 770 771 selection.reset( attachment ? [ attachment ] : [] ); 772 } 773 }); 774 775 module.exports = FeaturedImage; 776 777 },{}],8:[function(require,module,exports){ 778 var Selection = wp.media.model.Selection, 779 Library = wp.media.controller.Library, 780 l10n = wp.media.view.l10n, 781 GalleryAdd; 782 783 /** 784 * wp.media.controller.GalleryAdd 785 * 786 * A state for selecting more images to add to a gallery. 787 * 788 * @memberOf wp.media.controller 789 * 790 * @class 791 * @augments wp.media.controller.Library 792 * @augments wp.media.controller.State 793 * @augments Backbone.Model 794 * 795 * @param {object} [attributes] The attributes hash passed to the state. 796 * @param {string} [attributes.id=gallery-library] Unique identifier. 797 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 798 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 799 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 800 * If one is not supplied, a collection of all images will be created. 801 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 802 * Accepts 'all', 'uploaded', or 'unattached'. 803 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 804 * @param {string} [attributes.content=upload] Initial mode for the content region. 805 * Overridden by persistent user setting if 'contentUserSetting' is true. 806 * @param {string} [attributes.router=browse] Initial mode for the router region. 807 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 808 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 809 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 810 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 811 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 812 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 813 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 814 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 815 */ 816 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{ 817 defaults: _.defaults({ 818 id: 'gallery-library', 819 title: l10n.addToGalleryTitle, 820 multiple: 'add', 821 filterable: 'uploaded', 822 menu: 'gallery', 823 toolbar: 'gallery-add', 824 priority: 100, 825 syncSelection: false 826 }, Library.prototype.defaults ), 827 828 /** 829 * @since 3.5.0 830 */ 831 initialize: function() { 832 // If a library wasn't supplied, create a library of images. 833 if ( ! this.get('library') ) { 834 this.set( 'library', wp.media.query({ type: 'image' }) ); 835 } 836 837 Library.prototype.initialize.apply( this, arguments ); 838 }, 839 840 /** 841 * @since 3.5.0 842 */ 843 activate: function() { 844 var library = this.get('library'), 845 edit = this.frame.state('gallery-edit').get('library'); 846 847 if ( this.editLibrary && this.editLibrary !== edit ) { 848 library.unobserve( this.editLibrary ); 849 } 850 851 // Accepts attachments that exist in the original library and 852 // that do not exist in gallery's library. 853 library.validator = function( attachment ) { 854 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 855 }; 856 857 // Reset the library to ensure that all attachments are re-added 858 // to the collection. Do so silently, as calling `observe` will 859 // trigger the `reset` event. 860 library.reset( library.mirroring.models, { silent: true }); 861 library.observe( edit ); 862 this.editLibrary = edit; 863 864 Library.prototype.activate.apply( this, arguments ); 865 } 866 }); 867 868 module.exports = GalleryAdd; 869 870 },{}],9:[function(require,module,exports){ 1229 module.exports = ImageDetails; 1230 1231 1232 /***/ }), 1233 /* 33 */ 1234 /***/ (function(module, exports) { 1235 871 1236 var Library = wp.media.controller.Library, 872 1237 l10n = wp.media.view.l10n, … … 1012 1377 module.exports = GalleryEdit; 1013 1378 1014 },{}],10:[function(require,module,exports){ 1015 var State = wp.media.controller.State, 1379 1380 /***/ }), 1381 /* 34 */ 1382 /***/ (function(module, exports) { 1383 1384 var Selection = wp.media.model.Selection, 1016 1385 Library = wp.media.controller.Library, 1017 1386 l10n = wp.media.view.l10n, 1018 ImageDetails;1387 GalleryAdd; 1019 1388 1020 1389 /** 1021 * wp.media.controller.ImageDetails 1022 * 1023 * A state for editing the attachment display settings of an image that's been 1024 * inserted into the editor. 1025 * 1026 * @memberOf wp.media.controller 1027 * 1028 * @class 1029 * @augments wp.media.controller.State 1030 * @augments Backbone.Model 1031 * 1032 * @param {object} [attributes] The attributes hash passed to the state. 1033 * @param {string} [attributes.id=image-details] Unique identifier. 1034 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 1035 * @param {wp.media.model.Attachment} attributes.image The image's model. 1036 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 1037 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 1038 * @param {string|false} [attributes.router=false] Initial mode for the router region. 1039 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1040 * @param {boolean} [attributes.editing=false] Unused. 1041 * @param {int} [attributes.priority=60] Unused. 1042 * 1043 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1044 * however this may not do anything. 1045 */ 1046 ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{ 1047 defaults: _.defaults({ 1048 id: 'image-details', 1049 title: l10n.imageDetailsTitle, 1050 content: 'image-details', 1051 menu: false, 1052 router: false, 1053 toolbar: 'image-details', 1054 editing: false, 1055 priority: 60 1056 }, Library.prototype.defaults ), 1057 1058 /** 1059 * @since 3.9.0 1060 * 1061 * @param options Attributes 1062 */ 1063 initialize: function( options ) { 1064 this.image = options.image; 1065 State.prototype.initialize.apply( this, arguments ); 1066 }, 1067 1068 /** 1069 * @since 3.9.0 1070 */ 1071 activate: function() { 1072 this.frame.modal.$el.addClass('image-details'); 1073 } 1074 }); 1075 1076 module.exports = ImageDetails; 1077 1078 },{}],11:[function(require,module,exports){ 1079 var l10n = wp.media.view.l10n, 1080 getUserSetting = window.getUserSetting, 1081 setUserSetting = window.setUserSetting, 1082 Library; 1083 1084 /** 1085 * wp.media.controller.Library 1086 * 1087 * A state for choosing an attachment or group of attachments from the media library. 1088 * 1089 * @memberOf wp.media.controller 1090 * 1091 * @class 1092 * @augments wp.media.controller.State 1093 * @augments Backbone.Model 1094 * @mixes media.selectionSync 1095 * 1096 * @param {object} [attributes] The attributes hash passed to the state. 1097 * @param {string} [attributes.id=library] Unique identifier. 1098 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 1099 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1100 * If one is not supplied, a collection of all attachments will be created. 1101 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 1102 * If the 'selection' attribute is a plain JS object, 1103 * a Selection will be created using its values as the selection instance's `props` model. 1104 * Otherwise, it will copy the library's `props` model. 1105 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1106 * @param {string} [attributes.content=upload] Initial mode for the content region. 1107 * Overridden by persistent user setting if 'contentUserSetting' is true. 1108 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1109 * @param {string} [attributes.router=browse] Initial mode for the router region. 1110 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 1111 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1112 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1113 * Accepts 'all', 'uploaded', or 'unattached'. 1114 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1115 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1116 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1117 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1118 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1119 */ 1120 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{ 1121 defaults: { 1122 id: 'library', 1123 title: l10n.mediaLibraryTitle, 1124 multiple: false, 1125 content: 'upload', 1126 menu: 'default', 1127 router: 'browse', 1128 toolbar: 'select', 1129 searchable: true, 1130 filterable: false, 1131 sortable: true, 1132 autoSelect: true, 1133 describe: false, 1134 contentUserSetting: true, 1135 syncSelection: true 1136 }, 1137 1138 /** 1139 * If a library isn't provided, query all media items. 1140 * If a selection instance isn't provided, create one. 1141 * 1142 * @since 3.5.0 1143 */ 1144 initialize: function() { 1145 var selection = this.get('selection'), 1146 props; 1147 1148 if ( ! this.get('library') ) { 1149 this.set( 'library', wp.media.query() ); 1150 } 1151 1152 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 1153 props = selection; 1154 1155 if ( ! props ) { 1156 props = this.get('library').props.toJSON(); 1157 props = _.omit( props, 'orderby', 'query' ); 1158 } 1159 1160 this.set( 'selection', new wp.media.model.Selection( null, { 1161 multiple: this.get('multiple'), 1162 props: props 1163 }) ); 1164 } 1165 1166 this.resetDisplays(); 1167 }, 1168 1169 /** 1170 * @since 3.5.0 1171 */ 1172 activate: function() { 1173 this.syncSelection(); 1174 1175 wp.Uploader.queue.on( 'add', this.uploading, this ); 1176 1177 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 1178 1179 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 1180 this.frame.on( 'content:activate', this.saveContentMode, this ); 1181 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 1182 } 1183 }, 1184 1185 /** 1186 * @since 3.5.0 1187 */ 1188 deactivate: function() { 1189 this.recordSelection(); 1190 1191 this.frame.off( 'content:activate', this.saveContentMode, this ); 1192 1193 // Unbind all event handlers that use this state as the context 1194 // from the selection. 1195 this.get('selection').off( null, null, this ); 1196 1197 wp.Uploader.queue.off( null, null, this ); 1198 }, 1199 1200 /** 1201 * Reset the library to its initial state. 1202 * 1203 * @since 3.5.0 1204 */ 1205 reset: function() { 1206 this.get('selection').reset(); 1207 this.resetDisplays(); 1208 this.refreshContent(); 1209 }, 1210 1211 /** 1212 * Reset the attachment display settings defaults to the site options. 1213 * 1214 * If site options don't define them, fall back to a persistent user setting. 1215 * 1216 * @since 3.5.0 1217 */ 1218 resetDisplays: function() { 1219 var defaultProps = wp.media.view.settings.defaultProps; 1220 this._displays = []; 1221 this._defaultDisplaySettings = { 1222 align: getUserSetting( 'align', defaultProps.align ) || 'none', 1223 size: getUserSetting( 'imgsize', defaultProps.size ) || 'medium', 1224 link: getUserSetting( 'urlbutton', defaultProps.link ) || 'none' 1225 }; 1226 }, 1227 1228 /** 1229 * Create a model to represent display settings (alignment, etc.) for an attachment. 1230 * 1231 * @since 3.5.0 1232 * 1233 * @param {wp.media.model.Attachment} attachment 1234 * @returns {Backbone.Model} 1235 */ 1236 display: function( attachment ) { 1237 var displays = this._displays; 1238 1239 if ( ! displays[ attachment.cid ] ) { 1240 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 1241 } 1242 return displays[ attachment.cid ]; 1243 }, 1244 1245 /** 1246 * Given an attachment, create attachment display settings properties. 1247 * 1248 * @since 3.6.0 1249 * 1250 * @param {wp.media.model.Attachment} attachment 1251 * @returns {Object} 1252 */ 1253 defaultDisplaySettings: function( attachment ) { 1254 var settings = _.clone( this._defaultDisplaySettings ); 1255 1256 if ( settings.canEmbed = this.canEmbed( attachment ) ) { 1257 settings.link = 'embed'; 1258 } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) { 1259 settings.link = 'file'; 1260 } 1261 1262 return settings; 1263 }, 1264 1265 /** 1266 * Whether an attachment is image. 1267 * 1268 * @since 4.4.1 1269 * 1270 * @param {wp.media.model.Attachment} attachment 1271 * @returns {Boolean} 1272 */ 1273 isImageAttachment: function( attachment ) { 1274 // If uploading, we know the filename but not the mime type. 1275 if ( attachment.get('uploading') ) { 1276 return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') ); 1277 } 1278 1279 return attachment.get('type') === 'image'; 1280 }, 1281 1282 /** 1283 * Whether an attachment can be embedded (audio or video). 1284 * 1285 * @since 3.6.0 1286 * 1287 * @param {wp.media.model.Attachment} attachment 1288 * @returns {Boolean} 1289 */ 1290 canEmbed: function( attachment ) { 1291 // If uploading, we know the filename but not the mime type. 1292 if ( ! attachment.get('uploading') ) { 1293 var type = attachment.get('type'); 1294 if ( type !== 'audio' && type !== 'video' ) { 1295 return false; 1296 } 1297 } 1298 1299 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1300 }, 1301 1302 1303 /** 1304 * If the state is active, no items are selected, and the current 1305 * content mode is not an option in the state's router (provided 1306 * the state has a router), reset the content mode to the default. 1307 * 1308 * @since 3.5.0 1309 */ 1310 refreshContent: function() { 1311 var selection = this.get('selection'), 1312 frame = this.frame, 1313 router = frame.router.get(), 1314 mode = frame.content.mode(); 1315 1316 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1317 this.frame.content.render( this.get('content') ); 1318 } 1319 }, 1320 1321 /** 1322 * Callback handler when an attachment is uploaded. 1323 * 1324 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 1325 * 1326 * Adds any uploading attachments to the selection. 1327 * 1328 * If the state only supports one attachment to be selected and multiple 1329 * attachments are uploaded, the last attachment in the upload queue will 1330 * be selected. 1331 * 1332 * @since 3.5.0 1333 * 1334 * @param {wp.media.model.Attachment} attachment 1335 */ 1336 uploading: function( attachment ) { 1337 var content = this.frame.content; 1338 1339 if ( 'upload' === content.mode() ) { 1340 this.frame.content.mode('browse'); 1341 } 1342 1343 if ( this.get( 'autoSelect' ) ) { 1344 this.get('selection').add( attachment ); 1345 this.frame.trigger( 'library:selection:add' ); 1346 } 1347 }, 1348 1349 /** 1350 * Persist the mode of the content region as a user setting. 1351 * 1352 * @since 3.5.0 1353 */ 1354 saveContentMode: function() { 1355 if ( 'browse' !== this.get('router') ) { 1356 return; 1357 } 1358 1359 var mode = this.frame.content.mode(), 1360 view = this.frame.router.get(); 1361 1362 if ( view && view.get( mode ) ) { 1363 setUserSetting( 'libraryContent', mode ); 1364 } 1365 } 1366 }); 1367 1368 // Make selectionSync available on any Media Library state. 1369 _.extend( Library.prototype, wp.media.selectionSync ); 1370 1371 module.exports = Library; 1372 1373 },{}],12:[function(require,module,exports){ 1374 /** 1375 * wp.media.controller.MediaLibrary 1390 * wp.media.controller.GalleryAdd 1391 * 1392 * A state for selecting more images to add to a gallery. 1376 1393 * 1377 1394 * @memberOf wp.media.controller … … 1381 1398 * @augments wp.media.controller.State 1382 1399 * @augments Backbone.Model 1400 * 1401 * @param {object} [attributes] The attributes hash passed to the state. 1402 * @param {string} [attributes.id=gallery-library] Unique identifier. 1403 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 1404 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1405 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1406 * If one is not supplied, a collection of all images will be created. 1407 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1408 * Accepts 'all', 'uploaded', or 'unattached'. 1409 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1410 * @param {string} [attributes.content=upload] Initial mode for the content region. 1411 * Overridden by persistent user setting if 'contentUserSetting' is true. 1412 * @param {string} [attributes.router=browse] Initial mode for the router region. 1413 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1414 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1415 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1416 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1417 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1418 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1419 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1420 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1383 1421 */ 1384 var Library = wp.media.controller.Library, 1385 MediaLibrary; 1386 1387 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{ 1422 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{ 1388 1423 defaults: _.defaults({ 1389 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1390 filterable: 'uploaded', 1391 1392 displaySettings: false, 1393 priority: 80, 1394 syncSelection: false 1424 id: 'gallery-library', 1425 title: l10n.addToGalleryTitle, 1426 multiple: 'add', 1427 filterable: 'uploaded', 1428 menu: 'gallery', 1429 toolbar: 'gallery-add', 1430 priority: 100, 1431 syncSelection: false 1395 1432 }, Library.prototype.defaults ), 1396 1433 1397 1434 /** 1398 * @since 3.9.0 1399 * 1400 * @param options 1401 */ 1402 initialize: function( options ) { 1403 this.media = options.media; 1404 this.type = options.type; 1405 this.set( 'library', wp.media.query({ type: this.type }) ); 1435 * @since 3.5.0 1436 */ 1437 initialize: function() { 1438 // If a library wasn't supplied, create a library of images. 1439 if ( ! this.get('library') ) { 1440 this.set( 'library', wp.media.query({ type: 'image' }) ); 1441 } 1406 1442 1407 1443 Library.prototype.initialize.apply( this, arguments ); … … 1409 1445 1410 1446 /** 1411 * @since 3. 9.01447 * @since 3.5.0 1412 1448 */ 1413 1449 activate: function() { 1414 // @todo this should use this.frame. 1415 if ( wp.media.frame.lastMime ) { 1416 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 1417 delete wp.media.frame.lastMime; 1418 } 1450 var library = this.get('library'), 1451 edit = this.frame.state('gallery-edit').get('library'); 1452 1453 if ( this.editLibrary && this.editLibrary !== edit ) { 1454 library.unobserve( this.editLibrary ); 1455 } 1456 1457 // Accepts attachments that exist in the original library and 1458 // that do not exist in gallery's library. 1459 library.validator = function( attachment ) { 1460 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1461 }; 1462 1463 // Reset the library to ensure that all attachments are re-added 1464 // to the collection. Do so silently, as calling `observe` will 1465 // trigger the `reset` event. 1466 library.reset( library.mirroring.models, { silent: true }); 1467 library.observe( edit ); 1468 this.editLibrary = edit; 1469 1419 1470 Library.prototype.activate.apply( this, arguments ); 1420 1471 } 1421 1472 }); 1422 1473 1423 module.exports = MediaLibrary; 1424 1425 },{}],13:[function(require,module,exports){ 1474 module.exports = GalleryAdd; 1475 1476 1477 /***/ }), 1478 /* 35 */ 1479 /***/ (function(module, exports) { 1480 1481 var Library = wp.media.controller.Library, 1482 l10n = wp.media.view.l10n, 1483 $ = jQuery, 1484 CollectionEdit; 1485 1426 1486 /** 1427 * wp.media.controller.Region 1428 * 1429 * A region is a persistent application layout area. 1430 * 1431 * A region assumes one mode at any time, and can be switched to another. 1432 * 1433 * When mode changes, events are triggered on the region's parent view. 1434 * The parent view will listen to specific events and fill the region with an 1435 * appropriate view depending on mode. For example, a frame listens for the 1436 * 'browse' mode t be activated on the 'content' view and then fills the region 1437 * with an AttachmentsBrowser view. 1487 * wp.media.controller.CollectionEdit 1488 * 1489 * A state for editing a collection, which is used by audio and video playlists, 1490 * and can be used for other collections. 1438 1491 * 1439 1492 * @memberOf wp.media.controller 1440 1493 * 1441 1494 * @class 1442 * 1443 * @param {object} options Options hash for the region. 1444 * @param {string} options.id Unique identifier for the region. 1445 * @param {Backbone.View} options.view A parent view the region exists within. 1446 * @param {string} options.selector jQuery selector for the region within the parent view. 1495 * @augments wp.media.controller.Library 1496 * @augments wp.media.controller.State 1497 * @augments Backbone.Model 1498 * 1499 * @param {object} [attributes] The attributes hash passed to the state. 1500 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 1501 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 1502 * If one is not supplied, an empty media.model.Selection collection is created. 1503 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1504 * @param {string} [attributes.content=browse] Initial mode for the content region. 1505 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 1506 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1507 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1508 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1509 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 1510 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1511 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 1512 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1513 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1514 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1515 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1516 * Defaults to false for this state, because the library passed in *is* the selection. 1517 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 1518 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1519 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1520 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1521 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1447 1522 */ 1448 var Region = function( options ) { 1449 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 1450 }; 1451 1452 // Use Backbone's self-propagating `extend` inheritance method. 1453 Region.extend = Backbone.Model.extend; 1454 1455 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{ 1456 /** 1457 * Activate a mode. 1523 CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{ 1524 defaults: { 1525 multiple: false, 1526 sortable: true, 1527 date: false, 1528 searchable: false, 1529 content: 'browse', 1530 describe: true, 1531 dragInfo: true, 1532 idealColumnWidth: 170, 1533 editing: false, 1534 priority: 60, 1535 SettingsView: false, 1536 syncSelection: false 1537 }, 1538 1539 /** 1540 * @since 3.9.0 1541 */ 1542 initialize: function() { 1543 var collectionType = this.get('collectionType'); 1544 1545 if ( 'video' === this.get( 'type' ) ) { 1546 collectionType = 'video-' + collectionType; 1547 } 1548 1549 this.set( 'id', collectionType + '-edit' ); 1550 this.set( 'toolbar', collectionType + '-edit' ); 1551 1552 // If we haven't been provided a `library`, create a `Selection`. 1553 if ( ! this.get('library') ) { 1554 this.set( 'library', new wp.media.model.Selection() ); 1555 } 1556 // The single `Attachment` view to be used in the `Attachments` view. 1557 if ( ! this.get('AttachmentView') ) { 1558 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1559 } 1560 Library.prototype.initialize.apply( this, arguments ); 1561 }, 1562 1563 /** 1564 * @since 3.9.0 1565 */ 1566 activate: function() { 1567 var library = this.get('library'); 1568 1569 // Limit the library to images only. 1570 library.props.set( 'type', this.get( 'type' ) ); 1571 1572 // Watch for uploaded attachments. 1573 this.get('library').observe( wp.Uploader.queue ); 1574 1575 this.frame.on( 'content:render:browse', this.renderSettings, this ); 1576 1577 Library.prototype.activate.apply( this, arguments ); 1578 }, 1579 1580 /** 1581 * @since 3.9.0 1582 */ 1583 deactivate: function() { 1584 // Stop watching for uploaded attachments. 1585 this.get('library').unobserve( wp.Uploader.queue ); 1586 1587 this.frame.off( 'content:render:browse', this.renderSettings, this ); 1588 1589 Library.prototype.deactivate.apply( this, arguments ); 1590 }, 1591 1592 /** 1593 * Render the collection embed settings view in the browser sidebar. 1458 1594 * 1459 * @since 3.5.0 1595 * @todo This is against the pattern elsewhere in media. Typically the frame 1596 * is responsible for adding region mode callbacks. Explain. 1460 1597 * 1461 * @ param {string} mode1598 * @since 3.9.0 1462 1599 * 1463 * @fires Region#activate 1464 * @fires Region#deactivate 1465 * 1466 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 1467 */ 1468 mode: function( mode ) { 1469 if ( ! mode ) { 1470 return this._mode; 1471 } 1472 // Bail if we're trying to change to the current mode. 1473 if ( mode === this._mode ) { 1474 return this; 1475 } 1476 1477 /** 1478 * Region mode deactivation event. 1479 * 1480 * @event wp.media.controller.Region#deactivate 1481 */ 1482 this.trigger('deactivate'); 1483 1484 this._mode = mode; 1485 this.render( mode ); 1486 1487 /** 1488 * Region mode activation event. 1489 * 1490 * @event wp.media.controller.Region#activate 1491 */ 1492 this.trigger('activate'); 1493 return this; 1494 }, 1495 /** 1496 * Render a mode. 1497 * 1498 * @since 3.5.0 1499 * 1500 * @param {string} mode 1501 * 1502 * @fires Region#create 1503 * @fires Region#render 1504 * 1505 * @returns {wp.media.controller.Region} Returns itself to allow chaining 1506 */ 1507 render: function( mode ) { 1508 // If the mode isn't active, activate it. 1509 if ( mode && mode !== this._mode ) { 1510 return this.mode( mode ); 1511 } 1512 1513 var set = { view: null }, 1514 view; 1515 1516 /** 1517 * Create region view event. 1518 * 1519 * Region view creation takes place in an event callback on the frame. 1520 * 1521 * @event wp.media.controller.Region#create 1522 * @type {object} 1523 * @property {object} view 1524 */ 1525 this.trigger( 'create', set ); 1526 view = set.view; 1527 1528 /** 1529 * Render region view event. 1530 * 1531 * Region view creation takes place in an event callback on the frame. 1532 * 1533 * @event wp.media.controller.Region#render 1534 * @type {object} 1535 */ 1536 this.trigger( 'render', view ); 1537 if ( view ) { 1538 this.set( view ); 1539 } 1540 return this; 1541 }, 1542 1543 /** 1544 * Get the region's view. 1545 * 1546 * @since 3.5.0 1547 * 1548 * @returns {wp.media.View} 1549 */ 1550 get: function() { 1551 return this.view.views.first( this.selector ); 1552 }, 1553 1554 /** 1555 * Set the region's view as a subview of the frame. 1556 * 1557 * @since 3.5.0 1558 * 1559 * @param {Array|Object} views 1560 * @param {Object} [options={}] 1561 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 1562 */ 1563 set: function( views, options ) { 1564 if ( options ) { 1565 options.add = false; 1566 } 1567 return this.view.views.set( this.selector, views, options ); 1568 }, 1569 1570 /** 1571 * Trigger regional view events on the frame. 1572 * 1573 * @since 3.5.0 1574 * 1575 * @param {string} event 1576 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 1577 */ 1578 trigger: function( event ) { 1579 var base, args; 1580 1581 if ( ! this._mode ) { 1600 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 1601 */ 1602 renderSettings: function( attachmentsBrowserView ) { 1603 var library = this.get('library'), 1604 collectionType = this.get('collectionType'), 1605 dragInfoText = this.get('dragInfoText'), 1606 SettingsView = this.get('SettingsView'), 1607 obj = {}; 1608 1609 if ( ! library || ! attachmentsBrowserView ) { 1582 1610 return; 1583 1611 } 1584 1612 1585 args = _.toArray( arguments ); 1586 base = this.id + ':' + event; 1587 1588 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 1589 args[0] = base + ':' + this._mode; 1590 this.view.trigger.apply( this.view, args ); 1591 1592 // Trigger `{this.id}:{event}` event on the frame. 1593 args[0] = base; 1594 this.view.trigger.apply( this.view, args ); 1595 return this; 1613 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 1614 1615 obj[ collectionType ] = new SettingsView({ 1616 controller: this, 1617 model: library[ collectionType ], 1618 priority: 40 1619 }); 1620 1621 attachmentsBrowserView.sidebar.set( obj ); 1622 1623 if ( dragInfoText ) { 1624 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 1625 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 1626 priority: -40 1627 }) ); 1628 } 1629 1630 // Add the 'Reverse order' button to the toolbar. 1631 attachmentsBrowserView.toolbar.set( 'reverse', { 1632 text: l10n.reverseOrder, 1633 priority: 80, 1634 1635 click: function() { 1636 library.reset( library.toArray().reverse() ); 1637 } 1638 }); 1596 1639 } 1597 1640 }); 1598 1641 1599 module.exports = Region; 1600 1601 },{}],14:[function(require,module,exports){ 1642 module.exports = CollectionEdit; 1643 1644 1645 /***/ }), 1646 /* 36 */ 1647 /***/ (function(module, exports) { 1648 1649 var Selection = wp.media.model.Selection, 1650 Library = wp.media.controller.Library, 1651 CollectionAdd; 1652 1653 /** 1654 * wp.media.controller.CollectionAdd 1655 * 1656 * A state for adding attachments to a collection (e.g. video playlist). 1657 * 1658 * @memberOf wp.media.controller 1659 * 1660 * @class 1661 * @augments wp.media.controller.Library 1662 * @augments wp.media.controller.State 1663 * @augments Backbone.Model 1664 * 1665 * @param {object} [attributes] The attributes hash passed to the state. 1666 * @param {string} [attributes.id=library] Unique identifier. 1667 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 1668 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1669 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1670 * If one is not supplied, a collection of attachments of the specified type will be created. 1671 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1672 * Accepts 'all', 'uploaded', or 'unattached'. 1673 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1674 * @param {string} [attributes.content=upload] Initial mode for the content region. 1675 * Overridden by persistent user setting if 'contentUserSetting' is true. 1676 * @param {string} [attributes.router=browse] Initial mode for the router region. 1677 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1678 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1679 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1680 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1681 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1682 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1683 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1684 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1685 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1686 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1687 */ 1688 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{ 1689 defaults: _.defaults( { 1690 // Selection defaults. @see media.model.Selection 1691 multiple: 'add', 1692 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1693 filterable: 'uploaded', 1694 1695 priority: 100, 1696 syncSelection: false 1697 }, Library.prototype.defaults ), 1698 1699 /** 1700 * @since 3.9.0 1701 */ 1702 initialize: function() { 1703 var collectionType = this.get('collectionType'); 1704 1705 if ( 'video' === this.get( 'type' ) ) { 1706 collectionType = 'video-' + collectionType; 1707 } 1708 1709 this.set( 'id', collectionType + '-library' ); 1710 this.set( 'toolbar', collectionType + '-add' ); 1711 this.set( 'menu', collectionType ); 1712 1713 // If we haven't been provided a `library`, create a `Selection`. 1714 if ( ! this.get('library') ) { 1715 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 1716 } 1717 Library.prototype.initialize.apply( this, arguments ); 1718 }, 1719 1720 /** 1721 * @since 3.9.0 1722 */ 1723 activate: function() { 1724 var library = this.get('library'), 1725 editLibrary = this.get('editLibrary'), 1726 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 1727 1728 if ( editLibrary && editLibrary !== edit ) { 1729 library.unobserve( editLibrary ); 1730 } 1731 1732 // Accepts attachments that exist in the original library and 1733 // that do not exist in gallery's library. 1734 library.validator = function( attachment ) { 1735 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1736 }; 1737 1738 // Reset the library to ensure that all attachments are re-added 1739 // to the collection. Do so silently, as calling `observe` will 1740 // trigger the `reset` event. 1741 library.reset( library.mirroring.models, { silent: true }); 1742 library.observe( edit ); 1743 this.set('editLibrary', edit); 1744 1745 Library.prototype.activate.apply( this, arguments ); 1746 } 1747 }); 1748 1749 module.exports = CollectionAdd; 1750 1751 1752 /***/ }), 1753 /* 37 */ 1754 /***/ (function(module, exports) { 1755 1756 var Attachment = wp.media.model.Attachment, 1757 Library = wp.media.controller.Library, 1758 l10n = wp.media.view.l10n, 1759 FeaturedImage; 1760 1761 /** 1762 * wp.media.controller.FeaturedImage 1763 * 1764 * A state for selecting a featured image for a post. 1765 * 1766 * @memberOf wp.media.controller 1767 * 1768 * @class 1769 * @augments wp.media.controller.Library 1770 * @augments wp.media.controller.State 1771 * @augments Backbone.Model 1772 * 1773 * @param {object} [attributes] The attributes hash passed to the state. 1774 * @param {string} [attributes.id=featured-image] Unique identifier. 1775 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 1776 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1777 * If one is not supplied, a collection of all images will be created. 1778 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1779 * @param {string} [attributes.content=upload] Initial mode for the content region. 1780 * Overridden by persistent user setting if 'contentUserSetting' is true. 1781 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1782 * @param {string} [attributes.router=browse] Initial mode for the router region. 1783 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 1784 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1785 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1786 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1787 * Accepts 'all', 'uploaded', or 'unattached'. 1788 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1789 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1790 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1791 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1792 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1793 */ 1794 FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{ 1795 defaults: _.defaults({ 1796 id: 'featured-image', 1797 title: l10n.setFeaturedImageTitle, 1798 multiple: false, 1799 filterable: 'uploaded', 1800 toolbar: 'featured-image', 1801 priority: 60, 1802 syncSelection: true 1803 }, Library.prototype.defaults ), 1804 1805 /** 1806 * @since 3.5.0 1807 */ 1808 initialize: function() { 1809 var library, comparator; 1810 1811 // If we haven't been provided a `library`, create a `Selection`. 1812 if ( ! this.get('library') ) { 1813 this.set( 'library', wp.media.query({ type: 'image' }) ); 1814 } 1815 1816 Library.prototype.initialize.apply( this, arguments ); 1817 1818 library = this.get('library'); 1819 comparator = library.comparator; 1820 1821 // Overload the library's comparator to push items that are not in 1822 // the mirrored query to the front of the aggregate collection. 1823 library.comparator = function( a, b ) { 1824 var aInQuery = !! this.mirroring.get( a.cid ), 1825 bInQuery = !! this.mirroring.get( b.cid ); 1826 1827 if ( ! aInQuery && bInQuery ) { 1828 return -1; 1829 } else if ( aInQuery && ! bInQuery ) { 1830 return 1; 1831 } else { 1832 return comparator.apply( this, arguments ); 1833 } 1834 }; 1835 1836 // Add all items in the selection to the library, so any featured 1837 // images that are not initially loaded still appear. 1838 library.observe( this.get('selection') ); 1839 }, 1840 1841 /** 1842 * @since 3.5.0 1843 */ 1844 activate: function() { 1845 this.updateSelection(); 1846 this.frame.on( 'open', this.updateSelection, this ); 1847 1848 Library.prototype.activate.apply( this, arguments ); 1849 }, 1850 1851 /** 1852 * @since 3.5.0 1853 */ 1854 deactivate: function() { 1855 this.frame.off( 'open', this.updateSelection, this ); 1856 1857 Library.prototype.deactivate.apply( this, arguments ); 1858 }, 1859 1860 /** 1861 * @since 3.5.0 1862 */ 1863 updateSelection: function() { 1864 var selection = this.get('selection'), 1865 id = wp.media.view.settings.post.featuredImageId, 1866 attachment; 1867 1868 if ( '' !== id && -1 !== id ) { 1869 attachment = Attachment.get( id ); 1870 attachment.fetch(); 1871 } 1872 1873 selection.reset( attachment ? [ attachment ] : [] ); 1874 } 1875 }); 1876 1877 module.exports = FeaturedImage; 1878 1879 1880 /***/ }), 1881 /* 38 */ 1882 /***/ (function(module, exports) { 1883 1602 1884 var Library = wp.media.controller.Library, 1603 1885 l10n = wp.media.view.l10n, … … 1709 1991 module.exports = ReplaceImage; 1710 1992 1711 },{}],15:[function(require,module,exports){ 1993 1994 /***/ }), 1995 /* 39 */ 1996 /***/ (function(module, exports) { 1997 1998 var l10n = wp.media.view.l10n, 1999 EditImage; 2000 2001 /** 2002 * wp.media.controller.EditImage 2003 * 2004 * A state for editing (cropping, etc.) an image. 2005 * 2006 * @memberOf wp.media.controller 2007 * 2008 * @class 2009 * @augments wp.media.controller.State 2010 * @augments Backbone.Model 2011 * 2012 * @param {object} attributes The attributes hash passed to the state. 2013 * @param {wp.media.model.Attachment} attributes.model The attachment. 2014 * @param {string} [attributes.id=edit-image] Unique identifier. 2015 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 2016 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 2017 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 2018 * @param {string} [attributes.menu=false] Initial mode for the menu region. 2019 * @param {string} [attributes.url] Unused. @todo Consider removal. 2020 */ 2021 EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{ 2022 defaults: { 2023 id: 'edit-image', 2024 title: l10n.editImage, 2025 menu: false, 2026 toolbar: 'edit-image', 2027 content: 'edit-image', 2028 url: '' 2029 }, 2030 2031 /** 2032 * @since 3.9.0 2033 */ 2034 activate: function() { 2035 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) ); 2036 }, 2037 2038 /** 2039 * @since 3.9.0 2040 */ 2041 deactivate: function() { 2042 this.frame.off( 'toolbar:render:edit-image' ); 2043 }, 2044 2045 /** 2046 * @since 3.9.0 2047 */ 2048 toolbar: function() { 2049 var frame = this.frame, 2050 lastState = frame.lastState(), 2051 previous = lastState && lastState.id; 2052 2053 frame.toolbar.set( new wp.media.view.Toolbar({ 2054 controller: frame, 2055 items: { 2056 back: { 2057 style: 'primary', 2058 text: l10n.back, 2059 priority: 20, 2060 click: function() { 2061 if ( previous ) { 2062 frame.setState( previous ); 2063 } else { 2064 frame.close(); 2065 } 2066 } 2067 } 2068 } 2069 }) ); 2070 } 2071 }); 2072 2073 module.exports = EditImage; 2074 2075 2076 /***/ }), 2077 /* 40 */ 2078 /***/ (function(module, exports) { 2079 2080 /** 2081 * wp.media.controller.MediaLibrary 2082 * 2083 * @memberOf wp.media.controller 2084 * 2085 * @class 2086 * @augments wp.media.controller.Library 2087 * @augments wp.media.controller.State 2088 * @augments Backbone.Model 2089 */ 2090 var Library = wp.media.controller.Library, 2091 MediaLibrary; 2092 2093 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{ 2094 defaults: _.defaults({ 2095 // Attachments browser defaults. @see media.view.AttachmentsBrowser 2096 filterable: 'uploaded', 2097 2098 displaySettings: false, 2099 priority: 80, 2100 syncSelection: false 2101 }, Library.prototype.defaults ), 2102 2103 /** 2104 * @since 3.9.0 2105 * 2106 * @param options 2107 */ 2108 initialize: function( options ) { 2109 this.media = options.media; 2110 this.type = options.type; 2111 this.set( 'library', wp.media.query({ type: this.type }) ); 2112 2113 Library.prototype.initialize.apply( this, arguments ); 2114 }, 2115 2116 /** 2117 * @since 3.9.0 2118 */ 2119 activate: function() { 2120 // @todo this should use this.frame. 2121 if ( wp.media.frame.lastMime ) { 2122 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 2123 delete wp.media.frame.lastMime; 2124 } 2125 Library.prototype.activate.apply( this, arguments ); 2126 } 2127 }); 2128 2129 module.exports = MediaLibrary; 2130 2131 2132 /***/ }), 2133 /* 41 */ 2134 /***/ (function(module, exports) { 2135 2136 var l10n = wp.media.view.l10n, 2137 $ = Backbone.$, 2138 Embed; 2139 2140 /** 2141 * wp.media.controller.Embed 2142 * 2143 * A state for embedding media from a URL. 2144 * 2145 * @memberOf wp.media.controller 2146 * 2147 * @class 2148 * @augments wp.media.controller.State 2149 * @augments Backbone.Model 2150 * 2151 * @param {object} attributes The attributes hash passed to the state. 2152 * @param {string} [attributes.id=embed] Unique identifier. 2153 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 2154 * @param {string} [attributes.content=embed] Initial mode for the content region. 2155 * @param {string} [attributes.menu=default] Initial mode for the menu region. 2156 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 2157 * @param {string} [attributes.menu=false] Initial mode for the menu region. 2158 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 2159 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 2160 * @param {string} [attributes.url] The embed URL. 2161 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 2162 */ 2163 Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{ 2164 defaults: { 2165 id: 'embed', 2166 title: l10n.insertFromUrlTitle, 2167 content: 'embed', 2168 menu: 'default', 2169 toolbar: 'main-embed', 2170 priority: 120, 2171 type: 'link', 2172 url: '', 2173 metadata: {} 2174 }, 2175 2176 // The amount of time used when debouncing the scan. 2177 sensitivity: 400, 2178 2179 initialize: function(options) { 2180 this.metadata = options.metadata; 2181 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 2182 this.props = new Backbone.Model( this.metadata || { url: '' }); 2183 this.props.on( 'change:url', this.debouncedScan, this ); 2184 this.props.on( 'change:url', this.refresh, this ); 2185 this.on( 'scan', this.scanImage, this ); 2186 }, 2187 2188 /** 2189 * Trigger a scan of the embedded URL's content for metadata required to embed. 2190 * 2191 * @fires wp.media.controller.Embed#scan 2192 */ 2193 scan: function() { 2194 var scanners, 2195 embed = this, 2196 attributes = { 2197 type: 'link', 2198 scanners: [] 2199 }; 2200 2201 // Scan is triggered with the list of `attributes` to set on the 2202 // state, useful for the 'type' attribute and 'scanners' attribute, 2203 // an array of promise objects for asynchronous scan operations. 2204 if ( this.props.get('url') ) { 2205 this.trigger( 'scan', attributes ); 2206 } 2207 2208 if ( attributes.scanners.length ) { 2209 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 2210 scanners.always( function() { 2211 if ( embed.get('scanners') === scanners ) { 2212 embed.set( 'loading', false ); 2213 } 2214 }); 2215 } else { 2216 attributes.scanners = null; 2217 } 2218 2219 attributes.loading = !! attributes.scanners; 2220 this.set( attributes ); 2221 }, 2222 /** 2223 * Try scanning the embed as an image to discover its dimensions. 2224 * 2225 * @param {Object} attributes 2226 */ 2227 scanImage: function( attributes ) { 2228 var frame = this.frame, 2229 state = this, 2230 url = this.props.get('url'), 2231 image = new Image(), 2232 deferred = $.Deferred(); 2233 2234 attributes.scanners.push( deferred.promise() ); 2235 2236 // Try to load the image and find its width/height. 2237 image.onload = function() { 2238 deferred.resolve(); 2239 2240 if ( state !== frame.state() || url !== state.props.get('url') ) { 2241 return; 2242 } 2243 2244 state.set({ 2245 type: 'image' 2246 }); 2247 2248 state.props.set({ 2249 width: image.width, 2250 height: image.height 2251 }); 2252 }; 2253 2254 image.onerror = deferred.reject; 2255 image.src = url; 2256 }, 2257 2258 refresh: function() { 2259 this.frame.toolbar.get().refresh(); 2260 }, 2261 2262 reset: function() { 2263 this.props.clear().set({ url: '' }); 2264 2265 if ( this.active ) { 2266 this.refresh(); 2267 } 2268 } 2269 }); 2270 2271 module.exports = Embed; 2272 2273 2274 /***/ }), 2275 /* 42 */ 2276 /***/ (function(module, exports) { 2277 2278 var l10n = wp.media.view.l10n, 2279 Cropper; 2280 2281 /** 2282 * wp.media.controller.Cropper 2283 * 2284 * A state for cropping an image. 2285 * 2286 * @memberOf wp.media.controller 2287 * 2288 * @class 2289 * @augments wp.media.controller.State 2290 * @augments Backbone.Model 2291 */ 2292 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{ 2293 defaults: { 2294 id: 'cropper', 2295 title: l10n.cropImage, 2296 // Region mode defaults. 2297 toolbar: 'crop', 2298 content: 'crop', 2299 router: false, 2300 canSkipCrop: false, 2301 2302 // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state. 2303 doCropArgs: {} 2304 }, 2305 2306 activate: function() { 2307 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2308 this.frame.on( 'close', this.removeCropper, this ); 2309 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2310 }, 2311 2312 deactivate: function() { 2313 this.frame.toolbar.mode('browse'); 2314 }, 2315 2316 createCropContent: function() { 2317 this.cropperView = new wp.media.view.Cropper({ 2318 controller: this, 2319 attachment: this.get('selection').first() 2320 }); 2321 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2322 this.frame.content.set(this.cropperView); 2323 2324 }, 2325 removeCropper: function() { 2326 this.imgSelect.cancelSelection(); 2327 this.imgSelect.setOptions({remove: true}); 2328 this.imgSelect.update(); 2329 this.cropperView.remove(); 2330 }, 2331 createCropToolbar: function() { 2332 var canSkipCrop, toolbarOptions; 2333 2334 canSkipCrop = this.get('canSkipCrop') || false; 2335 2336 toolbarOptions = { 2337 controller: this.frame, 2338 items: { 2339 insert: { 2340 style: 'primary', 2341 text: l10n.cropImage, 2342 priority: 80, 2343 requires: { library: false, selection: false }, 2344 2345 click: function() { 2346 var controller = this.controller, 2347 selection; 2348 2349 selection = controller.state().get('selection').first(); 2350 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 2351 2352 this.$el.text(l10n.cropping); 2353 this.$el.attr('disabled', true); 2354 2355 controller.state().doCrop( selection ).done( function( croppedImage ) { 2356 controller.trigger('cropped', croppedImage ); 2357 controller.close(); 2358 }).fail( function() { 2359 controller.trigger('content:error:crop'); 2360 }); 2361 } 2362 } 2363 } 2364 }; 2365 2366 if ( canSkipCrop ) { 2367 _.extend( toolbarOptions.items, { 2368 skip: { 2369 style: 'secondary', 2370 text: l10n.skipCropping, 2371 priority: 70, 2372 requires: { library: false, selection: false }, 2373 click: function() { 2374 var selection = this.controller.state().get('selection').first(); 2375 this.controller.state().cropperView.remove(); 2376 this.controller.trigger('skippedcrop', selection); 2377 this.controller.close(); 2378 } 2379 } 2380 }); 2381 } 2382 2383 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 2384 }, 2385 2386 doCrop: function( attachment ) { 2387 return wp.ajax.post( 'custom-header-crop', _.extend( 2388 {}, 2389 this.defaults.doCropArgs, 2390 { 2391 nonce: attachment.get( 'nonces' ).edit, 2392 id: attachment.get( 'id' ), 2393 cropDetails: attachment.get( 'cropDetails' ) 2394 } 2395 ) ); 2396 } 2397 }); 2398 2399 module.exports = Cropper; 2400 2401 2402 /***/ }), 2403 /* 43 */ 2404 /***/ (function(module, exports) { 2405 2406 var Controller = wp.media.controller, 2407 CustomizeImageCropper; 2408 2409 /** 2410 * wp.media.controller.CustomizeImageCropper 2411 * 2412 * @memberOf wp.media.controller 2413 * 2414 * A state for cropping an image. 2415 * 2416 * @class 2417 * @augments wp.media.controller.Cropper 2418 * @augments wp.media.controller.State 2419 * @augments Backbone.Model 2420 */ 2421 CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{ 2422 doCrop: function( attachment ) { 2423 var cropDetails = attachment.get( 'cropDetails' ), 2424 control = this.get( 'control' ), 2425 ratio = cropDetails.width / cropDetails.height; 2426 2427 // Use crop measurements when flexible in both directions. 2428 if ( control.params.flex_width && control.params.flex_height ) { 2429 cropDetails.dst_width = cropDetails.width; 2430 cropDetails.dst_height = cropDetails.height; 2431 2432 // Constrain flexible side based on image ratio and size of the fixed side. 2433 } else { 2434 cropDetails.dst_width = control.params.flex_width ? control.params.height * ratio : control.params.width; 2435 cropDetails.dst_height = control.params.flex_height ? control.params.width / ratio : control.params.height; 2436 } 2437 2438 return wp.ajax.post( 'crop-image', { 2439 wp_customize: 'on', 2440 nonce: attachment.get( 'nonces' ).edit, 2441 id: attachment.get( 'id' ), 2442 context: control.id, 2443 cropDetails: cropDetails 2444 } ); 2445 } 2446 }); 2447 2448 module.exports = CustomizeImageCropper; 2449 2450 2451 /***/ }), 2452 /* 44 */ 2453 /***/ (function(module, exports) { 2454 1712 2455 var Controller = wp.media.controller, 1713 2456 SiteIconCropper; … … 1760 2503 module.exports = SiteIconCropper; 1761 2504 1762 },{}],16:[function(require,module,exports){ 2505 2506 /***/ }), 2507 /* 45 */ 2508 /***/ (function(module, exports) { 2509 1763 2510 /** 1764 * wp.media.controller.StateMachine 1765 * 1766 * A state machine keeps track of state. It is in one state at a time, 1767 * and can change from one state to another. 1768 * 1769 * States are stored as models in a Backbone collection. 1770 * 1771 * @memberOf wp.media.controller 1772 * 1773 * @since 3.5.0 2511 * wp.media.View 2512 * 2513 * The base view class for media. 2514 * 2515 * Undelegating events, removing events from the model, and 2516 * removing events from the controller mirror the code for 2517 * `Backbone.View.dispose` in Backbone 0.9.8 development. 2518 * 2519 * This behavior has since been removed, and should not be used 2520 * outside of the media manager. 2521 * 2522 * @memberOf wp.media 1774 2523 * 1775 2524 * @class 1776 * @augments Backbone.Model 1777 * @mixin 1778 * @mixes Backbone.Events 1779 * 1780 * @param {Array} states 2525 * @augments wp.Backbone.View 2526 * @augments Backbone.View 1781 2527 */ 1782 var StateMachine = function( states ){1783 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates.1784 this.states = new Backbone.Collection( states );1785 };1786 1787 // Use Backbone's self-propagating `extend` inheritance method. 1788 StateMachine.extend = Backbone.Model.extend; 1789 1790 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{ 1791 /**1792 * Fetch a state.2528 var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{ 2529 constructor: function( options ) { 2530 if ( options && options.controller ) { 2531 this.controller = options.controller; 2532 } 2533 wp.Backbone.View.apply( this, arguments ); 2534 }, 2535 /** 2536 * @todo The internal comment mentions this might have been a stop-gap 2537 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 2538 * care of this in Backbone.View now. 1793 2539 * 1794 * If no `id` is provided, returns the active state. 1795 * 1796 * Implicitly creates states. 1797 * 1798 * Ensure that the `states` collection exists so the `StateMachine` 1799 * can be used as a mixin. 1800 * 1801 * @since 3.5.0 1802 * 1803 * @param {string} id 1804 * @returns {wp.media.controller.State} Returns a State model 1805 * from the StateMachine collection 1806 */ 1807 state: function( id ) { 1808 this.states = this.states || new Backbone.Collection(); 1809 1810 // Default to the active state. 1811 id = id || this._state; 1812 1813 if ( id && ! this.states.get( id ) ) { 1814 this.states.add({ id: id }); 1815 } 1816 return this.states.get( id ); 1817 }, 1818 1819 /** 1820 * Sets the active state. 1821 * 1822 * Bail if we're trying to select the current state, if we haven't 1823 * created the `states` collection, or are trying to select a state 1824 * that does not exist. 1825 * 1826 * @since 3.5.0 1827 * 1828 * @param {string} id 1829 * 1830 * @fires wp.media.controller.State#deactivate 1831 * @fires wp.media.controller.State#activate 1832 * 1833 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 1834 */ 1835 setState: function( id ) { 1836 var previous = this.state(); 1837 1838 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 1839 return this; 1840 } 1841 1842 if ( previous ) { 1843 previous.trigger('deactivate'); 1844 this._lastState = previous.id; 1845 } 1846 1847 this._state = id; 1848 this.state().trigger('activate'); 2540 * @returns {wp.media.View} Returns itself to allow chaining 2541 */ 2542 dispose: function() { 2543 // Undelegating events, removing events from the model, and 2544 // removing events from the controller mirror the code for 2545 // `Backbone.View.dispose` in Backbone 0.9.8 development. 2546 this.undelegateEvents(); 2547 2548 if ( this.model && this.model.off ) { 2549 this.model.off( null, null, this ); 2550 } 2551 2552 if ( this.collection && this.collection.off ) { 2553 this.collection.off( null, null, this ); 2554 } 2555 2556 // Unbind controller events. 2557 if ( this.controller && this.controller.off ) { 2558 this.controller.off( null, null, this ); 2559 } 1849 2560 1850 2561 return this; 1851 2562 }, 1852 1853 /** 1854 * Returns the previous active state. 1855 * 1856 * Call the `state()` method with no parameters to retrieve the current 1857 * active state. 1858 * 1859 * @since 3.5.0 1860 * 1861 * @returns {wp.media.controller.State} Returns a State model 1862 * from the StateMachine collection 1863 */ 1864 lastState: function() { 1865 if ( this._lastState ) { 1866 return this.state( this._lastState ); 1867 } 2563 /** 2564 * @returns {wp.media.View} Returns itself to allow chaining 2565 */ 2566 remove: function() { 2567 this.dispose(); 2568 /** 2569 * call 'remove' directly on the parent class 2570 */ 2571 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 1868 2572 } 1869 2573 }); 1870 2574 1871 // Map all event binding and triggering on a StateMachine to its `states` collection. 1872 _.each([ 'on', 'off', 'trigger' ], function( method ) { 1873 /** 1874 * @function on 1875 * @memberOf wp.media.controller.StateMachine 2575 module.exports = View; 2576 2577 2578 /***/ }), 2579 /* 46 */ 2580 /***/ (function(module, exports) { 2581 2582 /** 2583 * wp.media.view.Frame 2584 * 2585 * A frame is a composite view consisting of one or more regions and one or more 2586 * states. 2587 * 2588 * @memberOf wp.media.view 2589 * 2590 * @see wp.media.controller.State 2591 * @see wp.media.controller.Region 2592 * 2593 * @class 2594 * @augments wp.media.View 2595 * @augments wp.Backbone.View 2596 * @augments Backbone.View 2597 * @mixes wp.media.controller.StateMachine 2598 */ 2599 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{ 2600 initialize: function() { 2601 _.defaults( this.options, { 2602 mode: [ 'select' ] 2603 }); 2604 this._createRegions(); 2605 this._createStates(); 2606 this._createModes(); 2607 }, 2608 2609 _createRegions: function() { 2610 // Clone the regions array. 2611 this.regions = this.regions ? this.regions.slice() : []; 2612 2613 // Initialize regions. 2614 _.each( this.regions, function( region ) { 2615 this[ region ] = new wp.media.controller.Region({ 2616 view: this, 2617 id: region, 2618 selector: '.media-frame-' + region 2619 }); 2620 }, this ); 2621 }, 2622 /** 2623 * Create the frame's states. 2624 * 2625 * @see wp.media.controller.State 2626 * @see wp.media.controller.StateMachine 2627 * 2628 * @fires wp.media.controller.State#ready 2629 */ 2630 _createStates: function() { 2631 // Create the default `states` collection. 2632 this.states = new Backbone.Collection( null, { 2633 model: wp.media.controller.State 2634 }); 2635 2636 // Ensure states have a reference to the frame. 2637 this.states.on( 'add', function( model ) { 2638 model.frame = this; 2639 model.trigger('ready'); 2640 }, this ); 2641 2642 if ( this.options.states ) { 2643 this.states.add( this.options.states ); 2644 } 2645 }, 2646 2647 /** 2648 * A frame can be in a mode or multiple modes at one time. 2649 * 2650 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 2651 */ 2652 _createModes: function() { 2653 // Store active "modes" that the frame is in. Unrelated to region modes. 2654 this.activeModes = new Backbone.Collection(); 2655 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 2656 2657 _.each( this.options.mode, function( mode ) { 2658 this.activateMode( mode ); 2659 }, this ); 2660 }, 2661 /** 2662 * Reset all states on the frame to their defaults. 2663 * 2664 * @returns {wp.media.view.Frame} Returns itself to allow chaining 2665 */ 2666 reset: function() { 2667 this.states.invoke( 'trigger', 'reset' ); 2668 return this; 2669 }, 2670 /** 2671 * Map activeMode collection events to the frame. 2672 */ 2673 triggerModeEvents: function( model, collection, options ) { 2674 var collectionEvent, 2675 modeEventMap = { 2676 add: 'activate', 2677 remove: 'deactivate' 2678 }, 2679 eventToTrigger; 2680 // Probably a better way to do this. 2681 _.each( options, function( value, key ) { 2682 if ( value ) { 2683 collectionEvent = key; 2684 } 2685 } ); 2686 2687 if ( ! _.has( modeEventMap, collectionEvent ) ) { 2688 return; 2689 } 2690 2691 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 2692 this.trigger( eventToTrigger ); 2693 }, 2694 /** 2695 * Activate a mode on the frame. 2696 * 2697 * @param string mode Mode ID. 2698 * @returns {this} Returns itself to allow chaining. 2699 */ 2700 activateMode: function( mode ) { 2701 // Bail if the mode is already active. 2702 if ( this.isModeActive( mode ) ) { 2703 return; 2704 } 2705 this.activeModes.add( [ { id: mode } ] ); 2706 // Add a CSS class to the frame so elements can be styled for the mode. 2707 this.$el.addClass( 'mode-' + mode ); 2708 2709 return this; 2710 }, 2711 /** 2712 * Deactivate a mode on the frame. 2713 * 2714 * @param string mode Mode ID. 2715 * @returns {this} Returns itself to allow chaining. 2716 */ 2717 deactivateMode: function( mode ) { 2718 // Bail if the mode isn't active. 2719 if ( ! this.isModeActive( mode ) ) { 2720 return this; 2721 } 2722 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 2723 this.$el.removeClass( 'mode-' + mode ); 2724 /** 2725 * Frame mode deactivation event. 2726 * 2727 * @event wp.media.view.Frame#{mode}:deactivate 2728 */ 2729 this.trigger( mode + ':deactivate' ); 2730 2731 return this; 2732 }, 2733 /** 2734 * Check if a mode is enabled on the frame. 2735 * 2736 * @param string mode Mode ID. 2737 * @return bool 2738 */ 2739 isModeActive: function( mode ) { 2740 return Boolean( this.activeModes.where( { id: mode } ).length ); 2741 } 2742 }); 2743 2744 // Make the `Frame` a `StateMachine`. 2745 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 2746 2747 module.exports = Frame; 2748 2749 2750 /***/ }), 2751 /* 47 */ 2752 /***/ (function(module, exports) { 2753 2754 var Frame = wp.media.view.Frame, 2755 $ = jQuery, 2756 MediaFrame; 2757 2758 /** 2759 * wp.media.view.MediaFrame 2760 * 2761 * The frame used to create the media modal. 2762 * 2763 * @memberOf wp.media.view 2764 * 2765 * @class 2766 * @augments wp.media.view.Frame 2767 * @augments wp.media.View 2768 * @augments wp.Backbone.View 2769 * @augments Backbone.View 2770 * @mixes wp.media.controller.StateMachine 2771 */ 2772 MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{ 2773 className: 'media-frame', 2774 template: wp.template('media-frame'), 2775 regions: ['menu','title','content','toolbar','router'], 2776 2777 events: { 2778 'click div.media-frame-title h1': 'toggleMenu' 2779 }, 2780 2781 /** 2782 * @constructs 2783 */ 2784 initialize: function() { 2785 Frame.prototype.initialize.apply( this, arguments ); 2786 2787 _.defaults( this.options, { 2788 title: '', 2789 modal: true, 2790 uploader: true 2791 }); 2792 2793 // Ensure core UI is enabled. 2794 this.$el.addClass('wp-core-ui'); 2795 2796 // Initialize modal container view. 2797 if ( this.options.modal ) { 2798 this.modal = new wp.media.view.Modal({ 2799 controller: this, 2800 title: this.options.title 2801 }); 2802 2803 this.modal.content( this ); 2804 } 2805 2806 // Force the uploader off if the upload limit has been exceeded or 2807 // if the browser isn't supported. 2808 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 2809 this.options.uploader = false; 2810 } 2811 2812 // Initialize window-wide uploader. 2813 if ( this.options.uploader ) { 2814 this.uploader = new wp.media.view.UploaderWindow({ 2815 controller: this, 2816 uploader: { 2817 dropzone: this.modal ? this.modal.$el : this.$el, 2818 container: this.$el 2819 } 2820 }); 2821 this.views.set( '.media-frame-uploader', this.uploader ); 2822 } 2823 2824 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 2825 2826 // Bind default title creation. 2827 this.on( 'title:create:default', this.createTitle, this ); 2828 this.title.mode('default'); 2829 2830 this.on( 'title:render', function( view ) { 2831 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 2832 }); 2833 2834 // Bind default menu. 2835 this.on( 'menu:create:default', this.createMenu, this ); 2836 }, 2837 /** 2838 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2839 */ 2840 render: function() { 2841 // Activate the default state if no active state exists. 2842 if ( ! this.state() && this.options.state ) { 2843 this.setState( this.options.state ); 2844 } 2845 /** 2846 * call 'render' directly on the parent class 2847 */ 2848 return Frame.prototype.render.apply( this, arguments ); 2849 }, 2850 /** 2851 * @param {Object} title 2852 * @this wp.media.controller.Region 2853 */ 2854 createTitle: function( title ) { 2855 title.view = new wp.media.View({ 2856 controller: this, 2857 tagName: 'h1' 2858 }); 2859 }, 2860 /** 2861 * @param {Object} menu 2862 * @this wp.media.controller.Region 2863 */ 2864 createMenu: function( menu ) { 2865 menu.view = new wp.media.view.Menu({ 2866 controller: this 2867 }); 2868 }, 2869 2870 toggleMenu: function() { 2871 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 2872 }, 2873 2874 /** 2875 * @param {Object} toolbar 2876 * @this wp.media.controller.Region 2877 */ 2878 createToolbar: function( toolbar ) { 2879 toolbar.view = new wp.media.view.Toolbar({ 2880 controller: this 2881 }); 2882 }, 2883 /** 2884 * @param {Object} router 2885 * @this wp.media.controller.Region 2886 */ 2887 createRouter: function( router ) { 2888 router.view = new wp.media.view.Router({ 2889 controller: this 2890 }); 2891 }, 2892 /** 2893 * @param {Object} options 2894 */ 2895 createIframeStates: function( options ) { 2896 var settings = wp.media.view.settings, 2897 tabs = settings.tabs, 2898 tabUrl = settings.tabUrl, 2899 $postId; 2900 2901 if ( ! tabs || ! tabUrl ) { 2902 return; 2903 } 2904 2905 // Add the post ID to the tab URL if it exists. 2906 $postId = $('#post_ID'); 2907 if ( $postId.length ) { 2908 tabUrl += '&post_id=' + $postId.val(); 2909 } 2910 2911 // Generate the tab states. 2912 _.each( tabs, function( title, id ) { 2913 this.state( 'iframe:' + id ).set( _.defaults({ 2914 tab: id, 2915 src: tabUrl + '&tab=' + id, 2916 title: title, 2917 content: 'iframe', 2918 menu: 'default' 2919 }, options ) ); 2920 }, this ); 2921 2922 this.on( 'content:create:iframe', this.iframeContent, this ); 2923 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 2924 this.on( 'menu:render:default', this.iframeMenu, this ); 2925 this.on( 'open', this.hijackThickbox, this ); 2926 this.on( 'close', this.restoreThickbox, this ); 2927 }, 2928 2929 /** 2930 * @param {Object} content 2931 * @this wp.media.controller.Region 2932 */ 2933 iframeContent: function( content ) { 2934 this.$el.addClass('hide-toolbar'); 2935 content.view = new wp.media.view.Iframe({ 2936 controller: this 2937 }); 2938 }, 2939 2940 iframeContentCleanup: function() { 2941 this.$el.removeClass('hide-toolbar'); 2942 }, 2943 2944 iframeMenu: function( view ) { 2945 var views = {}; 2946 2947 if ( ! view ) { 2948 return; 2949 } 2950 2951 _.each( wp.media.view.settings.tabs, function( title, id ) { 2952 views[ 'iframe:' + id ] = { 2953 text: this.state( 'iframe:' + id ).get('title'), 2954 priority: 200 2955 }; 2956 }, this ); 2957 2958 view.set( views ); 2959 }, 2960 2961 hijackThickbox: function() { 2962 var frame = this; 2963 2964 if ( ! window.tb_remove || this._tb_remove ) { 2965 return; 2966 } 2967 2968 this._tb_remove = window.tb_remove; 2969 window.tb_remove = function() { 2970 frame.close(); 2971 frame.reset(); 2972 frame.setState( frame.options.state ); 2973 frame._tb_remove.call( window ); 2974 }; 2975 }, 2976 2977 restoreThickbox: function() { 2978 if ( ! this._tb_remove ) { 2979 return; 2980 } 2981 2982 window.tb_remove = this._tb_remove; 2983 delete this._tb_remove; 2984 } 2985 }); 2986 2987 // Map some of the modal's methods to the frame. 2988 _.each(['open','close','attach','detach','escape'], function( method ) { 2989 /** 2990 * @function open 2991 * @memberOf wp.media.view.MediaFrame 1876 2992 * @instance 1877 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1878 */ 1879 /** 1880 * @function off 1881 * @memberOf wp.media.controller.StateMachine 2993 * 2994 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2995 */ 2996 /** 2997 * @function close 2998 * @memberOf wp.media.view.MediaFrame 1882 2999 * @instance 1883 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1884 */ 1885 /** 1886 * @function trigger 1887 * @memberOf wp.media.controller.StateMachine 3000 * 3001 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 3002 */ 3003 /** 3004 * @function attach 3005 * @memberOf wp.media.view.MediaFrame 1888 3006 * @instance 1889 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1890 */ 1891 StateMachine.prototype[ method ] = function() { 1892 // Ensure that the `states` collection exists so the `StateMachine` 1893 // can be used as a mixin. 1894 this.states = this.states || new Backbone.Collection(); 1895 // Forward the method to the `states` collection. 1896 this.states[ method ].apply( this.states, arguments ); 3007 * 3008 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 3009 */ 3010 /** 3011 * @function detach 3012 * @memberOf wp.media.view.MediaFrame 3013 * @instance 3014 * 3015 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 3016 */ 3017 /** 3018 * @function escape 3019 * @memberOf wp.media.view.MediaFrame 3020 * @instance 3021 * 3022 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 3023 */ 3024 MediaFrame.prototype[ method ] = function() { 3025 if ( this.modal ) { 3026 this.modal[ method ].apply( this.modal, arguments ); 3027 } 1897 3028 return this; 1898 3029 }; 1899 3030 }); 1900 3031 1901 module.exports = StateMachine; 1902 1903 },{}],17:[function(require,module,exports){ 3032 module.exports = MediaFrame; 3033 3034 3035 /***/ }), 3036 /* 48 */ 3037 /***/ (function(module, exports) { 3038 3039 var MediaFrame = wp.media.view.MediaFrame, 3040 l10n = wp.media.view.l10n, 3041 Select; 3042 1904 3043 /** 1905 * wp.media.controller.State 1906 * 1907 * A state is a step in a workflow that when set will trigger the controllers 1908 * for the regions to be updated as specified in the frame. 1909 * 1910 * A state has an event-driven lifecycle: 1911 * 1912 * 'ready' triggers when a state is added to a state machine's collection. 1913 * 'activate' triggers when a state is activated by a state machine. 1914 * 'deactivate' triggers when a state is deactivated by a state machine. 1915 * 'reset' is not triggered automatically. It should be invoked by the 1916 * proper controller to reset the state to its default. 1917 * 1918 * @memberOf wp.media.controller 3044 * wp.media.view.MediaFrame.Select 3045 * 3046 * A frame for selecting an item or items from the media library. 3047 * 3048 * @memberOf wp.media.view.MediaFrame 1919 3049 * 1920 3050 * @class 1921 * @augments Backbone.Model 3051 * @augments wp.media.view.MediaFrame 3052 * @augments wp.media.view.Frame 3053 * @augments wp.media.View 3054 * @augments wp.Backbone.View 3055 * @augments Backbone.View 3056 * @mixes wp.media.controller.StateMachine 1922 3057 */ 1923 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{ 1924 /** 1925 * Constructor. 3058 Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{ 3059 initialize: function() { 3060 // Call 'initialize' directly on the parent class. 3061 MediaFrame.prototype.initialize.apply( this, arguments ); 3062 3063 _.defaults( this.options, { 3064 selection: [], 3065 library: {}, 3066 multiple: false, 3067 state: 'library' 3068 }); 3069 3070 this.createSelection(); 3071 this.createStates(); 3072 this.bindHandlers(); 3073 }, 3074 3075 /** 3076 * Attach a selection collection to the frame. 1926 3077 * 1927 * @since 3.5.0 1928 */ 1929 constructor: function() { 1930 this.on( 'activate', this._preActivate, this ); 1931 this.on( 'activate', this.activate, this ); 1932 this.on( 'activate', this._postActivate, this ); 1933 this.on( 'deactivate', this._deactivate, this ); 1934 this.on( 'deactivate', this.deactivate, this ); 1935 this.on( 'reset', this.reset, this ); 1936 this.on( 'ready', this._ready, this ); 1937 this.on( 'ready', this.ready, this ); 1938 /** 1939 * Call parent constructor with passed arguments 1940 */ 1941 Backbone.Model.apply( this, arguments ); 1942 this.on( 'change:menu', this._updateMenu, this ); 1943 }, 1944 /** 1945 * Ready event callback. 3078 * A selection is a collection of attachments used for a specific purpose 3079 * by a media frame. e.g. Selecting an attachment (or many) to insert into 3080 * post content. 1946 3081 * 1947 * @abstract 1948 * @since 3.5.0 1949 */ 1950 ready: function() {}, 1951 1952 /** 1953 * Activate event callback. 3082 * @see media.model.Selection 3083 */ 3084 createSelection: function() { 3085 var selection = this.options.selection; 3086 3087 if ( ! (selection instanceof wp.media.model.Selection) ) { 3088 this.options.selection = new wp.media.model.Selection( selection, { 3089 multiple: this.options.multiple 3090 }); 3091 } 3092 3093 this._selection = { 3094 attachments: new wp.media.model.Attachments(), 3095 difference: [] 3096 }; 3097 }, 3098 3099 /** 3100 * Create the default states on the frame. 3101 */ 3102 createStates: function() { 3103 var options = this.options; 3104 3105 if ( this.options.states ) { 3106 return; 3107 } 3108 3109 // Add the default states. 3110 this.states.add([ 3111 // Main states. 3112 new wp.media.controller.Library({ 3113 library: wp.media.query( options.library ), 3114 multiple: options.multiple, 3115 title: options.title, 3116 priority: 20 3117 }) 3118 ]); 3119 }, 3120 3121 /** 3122 * Bind region mode event callbacks. 1954 3123 * 1955 * @abstract 1956 * @since 3.5.0 1957 */ 1958 activate: function() {}, 1959 1960 /** 1961 * Deactivate event callback. 3124 * @see media.controller.Region.render 3125 */ 3126 bindHandlers: function() { 3127 this.on( 'router:create:browse', this.createRouter, this ); 3128 this.on( 'router:render:browse', this.browseRouter, this ); 3129 this.on( 'content:create:browse', this.browseContent, this ); 3130 this.on( 'content:render:upload', this.uploadContent, this ); 3131 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 3132 }, 3133 3134 /** 3135 * Render callback for the router region in the `browse` mode. 1962 3136 * 1963 * @abstract 1964 * @since 3.5.0 1965 */ 1966 deactivate: function() {}, 1967 1968 /** 1969 * Reset event callback. 3137 * @param {wp.media.view.Router} routerView 3138 */ 3139 browseRouter: function( routerView ) { 3140 routerView.set({ 3141 upload: { 3142 text: l10n.uploadFilesTitle, 3143 priority: 20 3144 }, 3145 browse: { 3146 text: l10n.mediaLibraryTitle, 3147 priority: 40 3148 } 3149 }); 3150 }, 3151 3152 /** 3153 * Render callback for the content region in the `browse` mode. 1970 3154 * 1971 * @abstract 1972 * @since 3.5.0 1973 */ 1974 reset: function() {}, 1975 1976 /** 1977 * @access private 1978 * @since 3.5.0 1979 */ 1980 _ready: function() { 1981 this._updateMenu(); 1982 }, 1983 1984 /** 1985 * @access private 1986 * @since 3.5.0 1987 */ 1988 _preActivate: function() { 1989 this.active = true; 1990 }, 1991 1992 /** 1993 * @access private 1994 * @since 3.5.0 1995 */ 1996 _postActivate: function() { 1997 this.on( 'change:menu', this._menu, this ); 1998 this.on( 'change:titleMode', this._title, this ); 1999 this.on( 'change:content', this._content, this ); 2000 this.on( 'change:toolbar', this._toolbar, this ); 2001 2002 this.frame.on( 'title:render:default', this._renderTitle, this ); 2003 2004 this._title(); 2005 this._menu(); 2006 this._toolbar(); 2007 this._content(); 2008 this._router(); 2009 }, 2010 2011 /** 2012 * @access private 2013 * @since 3.5.0 2014 */ 2015 _deactivate: function() { 2016 this.active = false; 2017 2018 this.frame.off( 'title:render:default', this._renderTitle, this ); 2019 2020 this.off( 'change:menu', this._menu, this ); 2021 this.off( 'change:titleMode', this._title, this ); 2022 this.off( 'change:content', this._content, this ); 2023 this.off( 'change:toolbar', this._toolbar, this ); 2024 }, 2025 2026 /** 2027 * @access private 2028 * @since 3.5.0 2029 */ 2030 _title: function() { 2031 this.frame.title.render( this.get('titleMode') || 'default' ); 2032 }, 2033 2034 /** 2035 * @access private 2036 * @since 3.5.0 2037 */ 2038 _renderTitle: function( view ) { 2039 view.$el.text( this.get('title') || '' ); 2040 }, 2041 2042 /** 2043 * @access private 2044 * @since 3.5.0 2045 */ 2046 _router: function() { 2047 var router = this.frame.router, 2048 mode = this.get('router'), 2049 view; 2050 2051 this.frame.$el.toggleClass( 'hide-router', ! mode ); 2052 if ( ! mode ) { 2053 return; 2054 } 2055 2056 this.frame.router.render( mode ); 2057 2058 view = router.get(); 2059 if ( view && view.select ) { 2060 view.select( this.frame.content.mode() ); 2061 } 2062 }, 2063 2064 /** 2065 * @access private 2066 * @since 3.5.0 2067 */ 2068 _menu: function() { 2069 var menu = this.frame.menu, 2070 mode = this.get('menu'), 2071 view; 2072 2073 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 2074 if ( ! mode ) { 2075 return; 2076 } 2077 2078 menu.mode( mode ); 2079 2080 view = menu.get(); 2081 if ( view && view.select ) { 2082 view.select( this.id ); 2083 } 2084 }, 2085 2086 /** 2087 * @access private 2088 * @since 3.5.0 2089 */ 2090 _updateMenu: function() { 2091 var previous = this.previous('menu'), 2092 menu = this.get('menu'); 2093 2094 if ( previous ) { 2095 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 2096 } 2097 2098 if ( menu ) { 2099 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 2100 } 2101 }, 2102 2103 /** 2104 * Create a view in the media menu for the state. 3155 * @param {wp.media.controller.Region} contentRegion 3156 */ 3157 browseContent: function( contentRegion ) { 3158 var state = this.state(); 3159 3160 this.$el.removeClass('hide-toolbar'); 3161 3162 // Browse our library of attachments. 3163 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 3164 controller: this, 3165 collection: state.get('library'), 3166 selection: state.get('selection'), 3167 model: state, 3168 sortable: state.get('sortable'), 3169 search: state.get('searchable'), 3170 filters: state.get('filterable'), 3171 date: state.get('date'), 3172 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 3173 dragInfo: state.get('dragInfo'), 3174 3175 idealColumnWidth: state.get('idealColumnWidth'), 3176 suggestedWidth: state.get('suggestedWidth'), 3177 suggestedHeight: state.get('suggestedHeight'), 3178 3179 AttachmentView: state.get('AttachmentView') 3180 }); 3181 }, 3182 3183 /** 3184 * Render callback for the content region in the `upload` mode. 3185 */ 3186 uploadContent: function() { 3187 this.$el.removeClass( 'hide-toolbar' ); 3188 this.content.set( new wp.media.view.UploaderInline({ 3189 controller: this 3190 }) ); 3191 }, 3192 3193 /** 3194 * Toolbars 2105 3195 * 2106 * @access private 2107 * @since 3.5.0 2108 * 2109 * @param {media.view.Menu} view The menu view. 2110 */ 2111 _renderMenu: function( view ) { 2112 var menuItem = this.get('menuItem'), 2113 title = this.get('title'), 2114 priority = this.get('priority'); 2115 2116 if ( ! menuItem && title ) { 2117 menuItem = { text: title }; 2118 2119 if ( priority ) { 2120 menuItem.priority = priority; 2121 } 2122 } 2123 2124 if ( ! menuItem ) { 2125 return; 2126 } 2127 2128 view.set( this.id, menuItem ); 3196 * @param {Object} toolbar 3197 * @param {Object} [options={}] 3198 * @this wp.media.controller.Region 3199 */ 3200 createSelectToolbar: function( toolbar, options ) { 3201 options = options || this.options.button || {}; 3202 options.controller = this; 3203 3204 toolbar.view = new wp.media.view.Toolbar.Select( options ); 2129 3205 } 2130 3206 }); 2131 3207 2132 _.each(['toolbar','content'], function( region ) { 2133 /** 2134 * @access private 2135 */ 2136 State.prototype[ '_' + region ] = function() { 2137 var mode = this.get( region ); 2138 if ( mode ) { 2139 this.frame[ region ].render( mode ); 2140 } 2141 }; 3208 module.exports = Select; 3209 3210 3211 /***/ }), 3212 /* 49 */ 3213 /***/ (function(module, exports) { 3214 3215 var Select = wp.media.view.MediaFrame.Select, 3216 Library = wp.media.controller.Library, 3217 l10n = wp.media.view.l10n, 3218 Post; 3219 3220 /** 3221 * wp.media.view.MediaFrame.Post 3222 * 3223 * The frame for manipulating media on the Edit Post page. 3224 * 3225 * @memberOf wp.media.view.MediaFrame 3226 * 3227 * @class 3228 * @augments wp.media.view.MediaFrame.Select 3229 * @augments wp.media.view.MediaFrame 3230 * @augments wp.media.view.Frame 3231 * @augments wp.media.View 3232 * @augments wp.Backbone.View 3233 * @augments Backbone.View 3234 * @mixes wp.media.controller.StateMachine 3235 */ 3236 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{ 3237 initialize: function() { 3238 this.counts = { 3239 audio: { 3240 count: wp.media.view.settings.attachmentCounts.audio, 3241 state: 'playlist' 3242 }, 3243 video: { 3244 count: wp.media.view.settings.attachmentCounts.video, 3245 state: 'video-playlist' 3246 } 3247 }; 3248 3249 _.defaults( this.options, { 3250 multiple: true, 3251 editing: false, 3252 state: 'insert', 3253 metadata: {} 3254 }); 3255 3256 // Call 'initialize' directly on the parent class. 3257 Select.prototype.initialize.apply( this, arguments ); 3258 this.createIframeStates(); 3259 3260 }, 3261 3262 /** 3263 * Create the default states. 3264 */ 3265 createStates: function() { 3266 var options = this.options; 3267 3268 this.states.add([ 3269 // Main states. 3270 new Library({ 3271 id: 'insert', 3272 title: l10n.insertMediaTitle, 3273 priority: 20, 3274 toolbar: 'main-insert', 3275 filterable: 'all', 3276 library: wp.media.query( options.library ), 3277 multiple: options.multiple ? 'reset' : false, 3278 editable: true, 3279 3280 // If the user isn't allowed to edit fields, 3281 // can they still edit it locally? 3282 allowLocalEdits: true, 3283 3284 // Show the attachment display settings. 3285 displaySettings: true, 3286 // Update user settings when users adjust the 3287 // attachment display settings. 3288 displayUserSettings: true 3289 }), 3290 3291 new Library({ 3292 id: 'gallery', 3293 title: l10n.createGalleryTitle, 3294 priority: 40, 3295 toolbar: 'main-gallery', 3296 filterable: 'uploaded', 3297 multiple: 'add', 3298 editable: false, 3299 3300 library: wp.media.query( _.defaults({ 3301 type: 'image' 3302 }, options.library ) ) 3303 }), 3304 3305 // Embed states. 3306 new wp.media.controller.Embed( { metadata: options.metadata } ), 3307 3308 new wp.media.controller.EditImage( { model: options.editImage } ), 3309 3310 // Gallery states. 3311 new wp.media.controller.GalleryEdit({ 3312 library: options.selection, 3313 editing: options.editing, 3314 menu: 'gallery' 3315 }), 3316 3317 new wp.media.controller.GalleryAdd(), 3318 3319 new Library({ 3320 id: 'playlist', 3321 title: l10n.createPlaylistTitle, 3322 priority: 60, 3323 toolbar: 'main-playlist', 3324 filterable: 'uploaded', 3325 multiple: 'add', 3326 editable: false, 3327 3328 library: wp.media.query( _.defaults({ 3329 type: 'audio' 3330 }, options.library ) ) 3331 }), 3332 3333 // Playlist states. 3334 new wp.media.controller.CollectionEdit({ 3335 type: 'audio', 3336 collectionType: 'playlist', 3337 title: l10n.editPlaylistTitle, 3338 SettingsView: wp.media.view.Settings.Playlist, 3339 library: options.selection, 3340 editing: options.editing, 3341 menu: 'playlist', 3342 dragInfoText: l10n.playlistDragInfo, 3343 dragInfo: false 3344 }), 3345 3346 new wp.media.controller.CollectionAdd({ 3347 type: 'audio', 3348 collectionType: 'playlist', 3349 title: l10n.addToPlaylistTitle 3350 }), 3351 3352 new Library({ 3353 id: 'video-playlist', 3354 title: l10n.createVideoPlaylistTitle, 3355 priority: 60, 3356 toolbar: 'main-video-playlist', 3357 filterable: 'uploaded', 3358 multiple: 'add', 3359 editable: false, 3360 3361 library: wp.media.query( _.defaults({ 3362 type: 'video' 3363 }, options.library ) ) 3364 }), 3365 3366 new wp.media.controller.CollectionEdit({ 3367 type: 'video', 3368 collectionType: 'playlist', 3369 title: l10n.editVideoPlaylistTitle, 3370 SettingsView: wp.media.view.Settings.Playlist, 3371 library: options.selection, 3372 editing: options.editing, 3373 menu: 'video-playlist', 3374 dragInfoText: l10n.videoPlaylistDragInfo, 3375 dragInfo: false 3376 }), 3377 3378 new wp.media.controller.CollectionAdd({ 3379 type: 'video', 3380 collectionType: 'playlist', 3381 title: l10n.addToVideoPlaylistTitle 3382 }) 3383 ]); 3384 3385 if ( wp.media.view.settings.post.featuredImageId ) { 3386 this.states.add( new wp.media.controller.FeaturedImage() ); 3387 } 3388 }, 3389 3390 bindHandlers: function() { 3391 var handlers, checkCounts; 3392 3393 Select.prototype.bindHandlers.apply( this, arguments ); 3394 3395 this.on( 'activate', this.activate, this ); 3396 3397 // Only bother checking media type counts if one of the counts is zero 3398 checkCounts = _.find( this.counts, function( type ) { 3399 return type.count === 0; 3400 } ); 3401 3402 if ( typeof checkCounts !== 'undefined' ) { 3403 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 3404 } 3405 3406 this.on( 'menu:create:gallery', this.createMenu, this ); 3407 this.on( 'menu:create:playlist', this.createMenu, this ); 3408 this.on( 'menu:create:video-playlist', this.createMenu, this ); 3409 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 3410 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 3411 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 3412 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 3413 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 3414 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 3415 3416 handlers = { 3417 menu: { 3418 'default': 'mainMenu', 3419 'gallery': 'galleryMenu', 3420 'playlist': 'playlistMenu', 3421 'video-playlist': 'videoPlaylistMenu' 3422 }, 3423 3424 content: { 3425 'embed': 'embedContent', 3426 'edit-image': 'editImageContent', 3427 'edit-selection': 'editSelectionContent' 3428 }, 3429 3430 toolbar: { 3431 'main-insert': 'mainInsertToolbar', 3432 'main-gallery': 'mainGalleryToolbar', 3433 'gallery-edit': 'galleryEditToolbar', 3434 'gallery-add': 'galleryAddToolbar', 3435 'main-playlist': 'mainPlaylistToolbar', 3436 'playlist-edit': 'playlistEditToolbar', 3437 'playlist-add': 'playlistAddToolbar', 3438 'main-video-playlist': 'mainVideoPlaylistToolbar', 3439 'video-playlist-edit': 'videoPlaylistEditToolbar', 3440 'video-playlist-add': 'videoPlaylistAddToolbar' 3441 } 3442 }; 3443 3444 _.each( handlers, function( regionHandlers, region ) { 3445 _.each( regionHandlers, function( callback, handler ) { 3446 this.on( region + ':render:' + handler, this[ callback ], this ); 3447 }, this ); 3448 }, this ); 3449 }, 3450 3451 activate: function() { 3452 // Hide menu items for states tied to particular media types if there are no items 3453 _.each( this.counts, function( type ) { 3454 if ( type.count < 1 ) { 3455 this.menuItemVisibility( type.state, 'hide' ); 3456 } 3457 }, this ); 3458 }, 3459 3460 mediaTypeCounts: function( model, attr ) { 3461 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 3462 this.counts[ attr ].count++; 3463 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 3464 } 3465 }, 3466 3467 // Menus 3468 /** 3469 * @param {wp.Backbone.View} view 3470 */ 3471 mainMenu: function( view ) { 3472 view.set({ 3473 'library-separator': new wp.media.View({ 3474 className: 'separator', 3475 priority: 100 3476 }) 3477 }); 3478 }, 3479 3480 menuItemVisibility: function( state, visibility ) { 3481 var menu = this.menu.get(); 3482 if ( visibility === 'hide' ) { 3483 menu.hide( state ); 3484 } else if ( visibility === 'show' ) { 3485 menu.show( state ); 3486 } 3487 }, 3488 /** 3489 * @param {wp.Backbone.View} view 3490 */ 3491 galleryMenu: function( view ) { 3492 var lastState = this.lastState(), 3493 previous = lastState && lastState.id, 3494 frame = this; 3495 3496 view.set({ 3497 cancel: { 3498 text: l10n.cancelGalleryTitle, 3499 priority: 20, 3500 click: function() { 3501 if ( previous ) { 3502 frame.setState( previous ); 3503 } else { 3504 frame.close(); 3505 } 3506 3507 // Keep focus inside media modal 3508 // after canceling a gallery 3509 this.controller.modal.focusManager.focus(); 3510 } 3511 }, 3512 separateCancel: new wp.media.View({ 3513 className: 'separator', 3514 priority: 40 3515 }) 3516 }); 3517 }, 3518 3519 playlistMenu: function( view ) { 3520 var lastState = this.lastState(), 3521 previous = lastState && lastState.id, 3522 frame = this; 3523 3524 view.set({ 3525 cancel: { 3526 text: l10n.cancelPlaylistTitle, 3527 priority: 20, 3528 click: function() { 3529 if ( previous ) { 3530 frame.setState( previous ); 3531 } else { 3532 frame.close(); 3533 } 3534 } 3535 }, 3536 separateCancel: new wp.media.View({ 3537 className: 'separator', 3538 priority: 40 3539 }) 3540 }); 3541 }, 3542 3543 videoPlaylistMenu: function( view ) { 3544 var lastState = this.lastState(), 3545 previous = lastState && lastState.id, 3546 frame = this; 3547 3548 view.set({ 3549 cancel: { 3550 text: l10n.cancelVideoPlaylistTitle, 3551 priority: 20, 3552 click: function() { 3553 if ( previous ) { 3554 frame.setState( previous ); 3555 } else { 3556 frame.close(); 3557 } 3558 } 3559 }, 3560 separateCancel: new wp.media.View({ 3561 className: 'separator', 3562 priority: 40 3563 }) 3564 }); 3565 }, 3566 3567 // Content 3568 embedContent: function() { 3569 var view = new wp.media.view.Embed({ 3570 controller: this, 3571 model: this.state() 3572 }).render(); 3573 3574 this.content.set( view ); 3575 3576 if ( ! wp.media.isTouchDevice ) { 3577 view.url.focus(); 3578 } 3579 }, 3580 3581 editSelectionContent: function() { 3582 var state = this.state(), 3583 selection = state.get('selection'), 3584 view; 3585 3586 view = new wp.media.view.AttachmentsBrowser({ 3587 controller: this, 3588 collection: selection, 3589 selection: selection, 3590 model: state, 3591 sortable: true, 3592 search: false, 3593 date: false, 3594 dragInfo: true, 3595 3596 AttachmentView: wp.media.view.Attachments.EditSelection 3597 }).render(); 3598 3599 view.toolbar.set( 'backToLibrary', { 3600 text: l10n.returnToLibrary, 3601 priority: -100, 3602 3603 click: function() { 3604 this.controller.content.mode('browse'); 3605 } 3606 }); 3607 3608 // Browse our library of attachments. 3609 this.content.set( view ); 3610 3611 // Trigger the controller to set focus 3612 this.trigger( 'edit:selection', this ); 3613 }, 3614 3615 editImageContent: function() { 3616 var image = this.state().get('image'), 3617 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 3618 3619 this.content.set( view ); 3620 3621 // after creating the wrapper view, load the actual editor via an ajax call 3622 view.loadEditor(); 3623 3624 }, 3625 3626 // Toolbars 3627 3628 /** 3629 * @param {wp.Backbone.View} view 3630 */ 3631 selectionStatusToolbar: function( view ) { 3632 var editable = this.state().get('editable'); 3633 3634 view.set( 'selection', new wp.media.view.Selection({ 3635 controller: this, 3636 collection: this.state().get('selection'), 3637 priority: -40, 3638 3639 // If the selection is editable, pass the callback to 3640 // switch the content mode. 3641 editable: editable && function() { 3642 this.controller.content.mode('edit-selection'); 3643 } 3644 }).render() ); 3645 }, 3646 3647 /** 3648 * @param {wp.Backbone.View} view 3649 */ 3650 mainInsertToolbar: function( view ) { 3651 var controller = this; 3652 3653 this.selectionStatusToolbar( view ); 3654 3655 view.set( 'insert', { 3656 style: 'primary', 3657 priority: 80, 3658 text: l10n.insertIntoPost, 3659 requires: { selection: true }, 3660 3661 /** 3662 * @callback 3663 * @fires wp.media.controller.State#insert 3664 */ 3665 click: function() { 3666 var state = controller.state(), 3667 selection = state.get('selection'); 3668 3669 controller.close(); 3670 state.trigger( 'insert', selection ).reset(); 3671 } 3672 }); 3673 }, 3674 3675 /** 3676 * @param {wp.Backbone.View} view 3677 */ 3678 mainGalleryToolbar: function( view ) { 3679 var controller = this; 3680 3681 this.selectionStatusToolbar( view ); 3682 3683 view.set( 'gallery', { 3684 style: 'primary', 3685 text: l10n.createNewGallery, 3686 priority: 60, 3687 requires: { selection: true }, 3688 3689 click: function() { 3690 var selection = controller.state().get('selection'), 3691 edit = controller.state('gallery-edit'), 3692 models = selection.where({ type: 'image' }); 3693 3694 edit.set( 'library', new wp.media.model.Selection( models, { 3695 props: selection.props.toJSON(), 3696 multiple: true 3697 }) ); 3698 3699 this.controller.setState('gallery-edit'); 3700 3701 // Keep focus inside media modal 3702 // after jumping to gallery view 3703 this.controller.modal.focusManager.focus(); 3704 } 3705 }); 3706 }, 3707 3708 mainPlaylistToolbar: function( view ) { 3709 var controller = this; 3710 3711 this.selectionStatusToolbar( view ); 3712 3713 view.set( 'playlist', { 3714 style: 'primary', 3715 text: l10n.createNewPlaylist, 3716 priority: 100, 3717 requires: { selection: true }, 3718 3719 click: function() { 3720 var selection = controller.state().get('selection'), 3721 edit = controller.state('playlist-edit'), 3722 models = selection.where({ type: 'audio' }); 3723 3724 edit.set( 'library', new wp.media.model.Selection( models, { 3725 props: selection.props.toJSON(), 3726 multiple: true 3727 }) ); 3728 3729 this.controller.setState('playlist-edit'); 3730 3731 // Keep focus inside media modal 3732 // after jumping to playlist view 3733 this.controller.modal.focusManager.focus(); 3734 } 3735 }); 3736 }, 3737 3738 mainVideoPlaylistToolbar: function( view ) { 3739 var controller = this; 3740 3741 this.selectionStatusToolbar( view ); 3742 3743 view.set( 'video-playlist', { 3744 style: 'primary', 3745 text: l10n.createNewVideoPlaylist, 3746 priority: 100, 3747 requires: { selection: true }, 3748 3749 click: function() { 3750 var selection = controller.state().get('selection'), 3751 edit = controller.state('video-playlist-edit'), 3752 models = selection.where({ type: 'video' }); 3753 3754 edit.set( 'library', new wp.media.model.Selection( models, { 3755 props: selection.props.toJSON(), 3756 multiple: true 3757 }) ); 3758 3759 this.controller.setState('video-playlist-edit'); 3760 3761 // Keep focus inside media modal 3762 // after jumping to video playlist view 3763 this.controller.modal.focusManager.focus(); 3764 } 3765 }); 3766 }, 3767 3768 featuredImageToolbar: function( toolbar ) { 3769 this.createSelectToolbar( toolbar, { 3770 text: l10n.setFeaturedImage, 3771 state: this.options.state 3772 }); 3773 }, 3774 3775 mainEmbedToolbar: function( toolbar ) { 3776 toolbar.view = new wp.media.view.Toolbar.Embed({ 3777 controller: this 3778 }); 3779 }, 3780 3781 galleryEditToolbar: function() { 3782 var editing = this.state().get('editing'); 3783 this.toolbar.set( new wp.media.view.Toolbar({ 3784 controller: this, 3785 items: { 3786 insert: { 3787 style: 'primary', 3788 text: editing ? l10n.updateGallery : l10n.insertGallery, 3789 priority: 80, 3790 requires: { library: true }, 3791 3792 /** 3793 * @fires wp.media.controller.State#update 3794 */ 3795 click: function() { 3796 var controller = this.controller, 3797 state = controller.state(); 3798 3799 controller.close(); 3800 state.trigger( 'update', state.get('library') ); 3801 3802 // Restore and reset the default state. 3803 controller.setState( controller.options.state ); 3804 controller.reset(); 3805 } 3806 } 3807 } 3808 }) ); 3809 }, 3810 3811 galleryAddToolbar: function() { 3812 this.toolbar.set( new wp.media.view.Toolbar({ 3813 controller: this, 3814 items: { 3815 insert: { 3816 style: 'primary', 3817 text: l10n.addToGallery, 3818 priority: 80, 3819 requires: { selection: true }, 3820 3821 /** 3822 * @fires wp.media.controller.State#reset 3823 */ 3824 click: function() { 3825 var controller = this.controller, 3826 state = controller.state(), 3827 edit = controller.state('gallery-edit'); 3828 3829 edit.get('library').add( state.get('selection').models ); 3830 state.trigger('reset'); 3831 controller.setState('gallery-edit'); 3832 } 3833 } 3834 } 3835 }) ); 3836 }, 3837 3838 playlistEditToolbar: function() { 3839 var editing = this.state().get('editing'); 3840 this.toolbar.set( new wp.media.view.Toolbar({ 3841 controller: this, 3842 items: { 3843 insert: { 3844 style: 'primary', 3845 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 3846 priority: 80, 3847 requires: { library: true }, 3848 3849 /** 3850 * @fires wp.media.controller.State#update 3851 */ 3852 click: function() { 3853 var controller = this.controller, 3854 state = controller.state(); 3855 3856 controller.close(); 3857 state.trigger( 'update', state.get('library') ); 3858 3859 // Restore and reset the default state. 3860 controller.setState( controller.options.state ); 3861 controller.reset(); 3862 } 3863 } 3864 } 3865 }) ); 3866 }, 3867 3868 playlistAddToolbar: function() { 3869 this.toolbar.set( new wp.media.view.Toolbar({ 3870 controller: this, 3871 items: { 3872 insert: { 3873 style: 'primary', 3874 text: l10n.addToPlaylist, 3875 priority: 80, 3876 requires: { selection: true }, 3877 3878 /** 3879 * @fires wp.media.controller.State#reset 3880 */ 3881 click: function() { 3882 var controller = this.controller, 3883 state = controller.state(), 3884 edit = controller.state('playlist-edit'); 3885 3886 edit.get('library').add( state.get('selection').models ); 3887 state.trigger('reset'); 3888 controller.setState('playlist-edit'); 3889 } 3890 } 3891 } 3892 }) ); 3893 }, 3894 3895 videoPlaylistEditToolbar: function() { 3896 var editing = this.state().get('editing'); 3897 this.toolbar.set( new wp.media.view.Toolbar({ 3898 controller: this, 3899 items: { 3900 insert: { 3901 style: 'primary', 3902 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 3903 priority: 140, 3904 requires: { library: true }, 3905 3906 click: function() { 3907 var controller = this.controller, 3908 state = controller.state(), 3909 library = state.get('library'); 3910 3911 library.type = 'video'; 3912 3913 controller.close(); 3914 state.trigger( 'update', library ); 3915 3916 // Restore and reset the default state. 3917 controller.setState( controller.options.state ); 3918 controller.reset(); 3919 } 3920 } 3921 } 3922 }) ); 3923 }, 3924 3925 videoPlaylistAddToolbar: function() { 3926 this.toolbar.set( new wp.media.view.Toolbar({ 3927 controller: this, 3928 items: { 3929 insert: { 3930 style: 'primary', 3931 text: l10n.addToVideoPlaylist, 3932 priority: 140, 3933 requires: { selection: true }, 3934 3935 click: function() { 3936 var controller = this.controller, 3937 state = controller.state(), 3938 edit = controller.state('video-playlist-edit'); 3939 3940 edit.get('library').add( state.get('selection').models ); 3941 state.trigger('reset'); 3942 controller.setState('video-playlist-edit'); 3943 } 3944 } 3945 } 3946 }) ); 3947 } 2142 3948 }); 2143 3949 2144 module.exports = State; 2145 2146 },{}],18:[function(require,module,exports){ 3950 module.exports = Post; 3951 3952 3953 /***/ }), 3954 /* 50 */ 3955 /***/ (function(module, exports) { 3956 3957 var Select = wp.media.view.MediaFrame.Select, 3958 l10n = wp.media.view.l10n, 3959 ImageDetails; 3960 2147 3961 /** 2148 * wp.media.selectionSync 2149 * 2150 * Sync an attachments selection in a state with another state. 2151 * 2152 * Allows for selecting multiple images in the Add Media workflow, and then 2153 * switching to the Insert Gallery workflow while preserving the attachments selection. 2154 * 2155 * @memberOf wp.media 2156 * 2157 * @mixin 3962 * wp.media.view.MediaFrame.ImageDetails 3963 * 3964 * A media frame for manipulating an image that's already been inserted 3965 * into a post. 3966 * 3967 * @memberOf wp.media.view.MediaFrame 3968 * 3969 * @class 3970 * @augments wp.media.view.MediaFrame.Select 3971 * @augments wp.media.view.MediaFrame 3972 * @augments wp.media.view.Frame 3973 * @augments wp.media.View 3974 * @augments wp.Backbone.View 3975 * @augments Backbone.View 3976 * @mixes wp.media.controller.StateMachine 2158 3977 */ 2159 var selectionSync = { 2160 /** 2161 * @since 3.5.0 2162 */ 2163 syncSelection: function() { 2164 var selection = this.get('selection'), 2165 manager = this.frame._selection; 2166 2167 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 3978 ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{ 3979 defaults: { 3980 id: 'image', 3981 url: '', 3982 menu: 'image-details', 3983 content: 'image-details', 3984 toolbar: 'image-details', 3985 type: 'link', 3986 title: l10n.imageDetailsTitle, 3987 priority: 120 3988 }, 3989 3990 initialize: function( options ) { 3991 this.image = new wp.media.model.PostImage( options.metadata ); 3992 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 3993 Select.prototype.initialize.apply( this, arguments ); 3994 }, 3995 3996 bindHandlers: function() { 3997 Select.prototype.bindHandlers.apply( this, arguments ); 3998 this.on( 'menu:create:image-details', this.createMenu, this ); 3999 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 4000 this.on( 'content:render:edit-image', this.editImageContent, this ); 4001 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 4002 // override the select toolbar 4003 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 4004 }, 4005 4006 createStates: function() { 4007 this.states.add([ 4008 new wp.media.controller.ImageDetails({ 4009 image: this.image, 4010 editable: false 4011 }), 4012 new wp.media.controller.ReplaceImage({ 4013 id: 'replace-image', 4014 library: wp.media.query( { type: 'image' } ), 4015 image: this.image, 4016 multiple: false, 4017 title: l10n.imageReplaceTitle, 4018 toolbar: 'replace', 4019 priority: 80, 4020 displaySettings: true 4021 }), 4022 new wp.media.controller.EditImage( { 4023 image: this.image, 4024 selection: this.options.selection 4025 } ) 4026 ]); 4027 }, 4028 4029 imageDetailsContent: function( options ) { 4030 options.view = new wp.media.view.ImageDetails({ 4031 controller: this, 4032 model: this.state().image, 4033 attachment: this.state().image.attachment 4034 }); 4035 }, 4036 4037 editImageContent: function() { 4038 var state = this.state(), 4039 model = state.get('image'), 4040 view; 4041 4042 if ( ! model ) { 2168 4043 return; 2169 4044 } 2170 4045 2171 // If the selection supports multiple items, validate the stored 2172 // attachments based on the new selection's conditions. Record 2173 // the attachments that are not included; we'll maintain a 2174 // reference to those. Other attachments are considered in flux. 2175 if ( selection.multiple ) { 2176 selection.reset( [], { silent: true }); 2177 selection.validateAll( manager.attachments ); 2178 manager.difference = _.difference( manager.attachments.models, selection.models ); 2179 } 2180 2181 // Sync the selection's single item with the master. 2182 selection.single( manager.single ); 2183 }, 2184 2185 /** 2186 * Record the currently active attachments, which is a combination 2187 * of the selection's attachments and the set of selected 2188 * attachments that this specific selection considered invalid. 2189 * Reset the difference and record the single attachment. 2190 * 2191 * @since 3.5.0 2192 */ 2193 recordSelection: function() { 2194 var selection = this.get('selection'), 2195 manager = this.frame._selection; 2196 2197 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2198 return; 2199 } 2200 2201 if ( selection.multiple ) { 2202 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 2203 manager.difference = []; 2204 } else { 2205 manager.attachments.add( selection.toArray() ); 2206 } 2207 2208 manager.single = selection._single; 4046 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 4047 4048 this.content.set( view ); 4049 4050 // after bringing in the frame, load the actual editor via an ajax call 4051 view.loadEditor(); 4052 4053 }, 4054 4055 renderImageDetailsToolbar: function() { 4056 this.toolbar.set( new wp.media.view.Toolbar({ 4057 controller: this, 4058 items: { 4059 select: { 4060 style: 'primary', 4061 text: l10n.update, 4062 priority: 80, 4063 4064 click: function() { 4065 var controller = this.controller, 4066 state = controller.state(); 4067 4068 controller.close(); 4069 4070 // not sure if we want to use wp.media.string.image which will create a shortcode or 4071 // perhaps wp.html.string to at least to build the <img /> 4072 state.trigger( 'update', controller.image.toJSON() ); 4073 4074 // Restore and reset the default state. 4075 controller.setState( controller.options.state ); 4076 controller.reset(); 4077 } 4078 } 4079 } 4080 }) ); 4081 }, 4082 4083 renderReplaceImageToolbar: function() { 4084 var frame = this, 4085 lastState = frame.lastState(), 4086 previous = lastState && lastState.id; 4087 4088 this.toolbar.set( new wp.media.view.Toolbar({ 4089 controller: this, 4090 items: { 4091 back: { 4092 text: l10n.back, 4093 priority: 20, 4094 click: function() { 4095 if ( previous ) { 4096 frame.setState( previous ); 4097 } else { 4098 frame.close(); 4099 } 4100 } 4101 }, 4102 4103 replace: { 4104 style: 'primary', 4105 text: l10n.replace, 4106 priority: 80, 4107 requires: { selection: true }, 4108 4109 click: function() { 4110 var controller = this.controller, 4111 state = controller.state(), 4112 selection = state.get( 'selection' ), 4113 attachment = selection.single(); 4114 4115 controller.close(); 4116 4117 controller.image.changeAttachment( attachment, state.display( attachment ) ); 4118 4119 // not sure if we want to use wp.media.string.image which will create a shortcode or 4120 // perhaps wp.html.string to at least to build the <img /> 4121 state.trigger( 'replace', controller.image.toJSON() ); 4122 4123 // Restore and reset the default state. 4124 controller.setState( controller.options.state ); 4125 controller.reset(); 4126 } 4127 } 4128 } 4129 }) ); 2209 4130 } 2210 }; 2211 2212 module.exports = selectionSync; 2213 2214 },{}],19:[function(require,module,exports){ 2215 var media = wp.media, 2216 $ = jQuery, 2217 l10n; 2218 2219 media.isTouchDevice = ( 'ontouchend' in document ); 2220 2221 // Link any localized strings. 2222 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 2223 2224 // Link any settings. 2225 media.view.settings = l10n.settings || {}; 2226 delete l10n.settings; 2227 2228 // Copy the `post` setting over to the model settings. 2229 media.model.settings.post = media.view.settings.post; 2230 2231 // Check if the browser supports CSS 3.0 transitions 2232 $.support.transition = (function(){ 2233 var style = document.documentElement.style, 2234 transitions = { 2235 WebkitTransition: 'webkitTransitionEnd', 2236 MozTransition: 'transitionend', 2237 OTransition: 'oTransitionEnd otransitionend', 2238 transition: 'transitionend' 2239 }, transition; 2240 2241 transition = _.find( _.keys( transitions ), function( transition ) { 2242 return ! _.isUndefined( style[ transition ] ); 2243 }); 2244 2245 return transition && { 2246 end: transitions[ transition ] 2247 }; 2248 }()); 4131 4132 }); 4133 4134 module.exports = ImageDetails; 4135 4136 4137 /***/ }), 4138 /* 51 */ 4139 /***/ (function(module, exports) { 4140 4141 var $ = jQuery, 4142 Modal; 2249 4143 2250 4144 /** 2251 * A shared event bus used to provide events into 2252 * the media workflows that 3rd-party devs can use to hook 2253 * in. 2254 */ 2255 media.events = _.extend( {}, Backbone.Events ); 2256 2257 /** 2258 * Makes it easier to bind events using transitions. 2259 * 2260 * @param {string} selector 2261 * @param {Number} sensitivity 2262 * @returns {Promise} 2263 */ 2264 media.transition = function( selector, sensitivity ) { 2265 var deferred = $.Deferred(); 2266 2267 sensitivity = sensitivity || 2000; 2268 2269 if ( $.support.transition ) { 2270 if ( ! (selector instanceof $) ) { 2271 selector = $( selector ); 2272 } 2273 2274 // Resolve the deferred when the first element finishes animating. 2275 selector.first().one( $.support.transition.end, deferred.resolve ); 2276 2277 // Just in case the event doesn't trigger, fire a callback. 2278 _.delay( deferred.resolve, sensitivity ); 2279 2280 // Otherwise, execute on the spot. 2281 } else { 2282 deferred.resolve(); 2283 } 2284 2285 return deferred.promise(); 2286 }; 2287 2288 media.controller.Region = require( './controllers/region.js' ); 2289 media.controller.StateMachine = require( './controllers/state-machine.js' ); 2290 media.controller.State = require( './controllers/state.js' ); 2291 2292 media.selectionSync = require( './utils/selection-sync.js' ); 2293 media.controller.Library = require( './controllers/library.js' ); 2294 media.controller.ImageDetails = require( './controllers/image-details.js' ); 2295 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' ); 2296 media.controller.GalleryAdd = require( './controllers/gallery-add.js' ); 2297 media.controller.CollectionEdit = require( './controllers/collection-edit.js' ); 2298 media.controller.CollectionAdd = require( './controllers/collection-add.js' ); 2299 media.controller.FeaturedImage = require( './controllers/featured-image.js' ); 2300 media.controller.ReplaceImage = require( './controllers/replace-image.js' ); 2301 media.controller.EditImage = require( './controllers/edit-image.js' ); 2302 media.controller.MediaLibrary = require( './controllers/media-library.js' ); 2303 media.controller.Embed = require( './controllers/embed.js' ); 2304 media.controller.Cropper = require( './controllers/cropper.js' ); 2305 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' ); 2306 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' ); 2307 2308 media.View = require( './views/view.js' ); 2309 media.view.Frame = require( './views/frame.js' ); 2310 media.view.MediaFrame = require( './views/media-frame.js' ); 2311 media.view.MediaFrame.Select = require( './views/frame/select.js' ); 2312 media.view.MediaFrame.Post = require( './views/frame/post.js' ); 2313 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' ); 2314 media.view.Modal = require( './views/modal.js' ); 2315 media.view.FocusManager = require( './views/focus-manager.js' ); 2316 media.view.UploaderWindow = require( './views/uploader/window.js' ); 2317 media.view.EditorUploader = require( './views/uploader/editor.js' ); 2318 media.view.UploaderInline = require( './views/uploader/inline.js' ); 2319 media.view.UploaderStatus = require( './views/uploader/status.js' ); 2320 media.view.UploaderStatusError = require( './views/uploader/status-error.js' ); 2321 media.view.Toolbar = require( './views/toolbar.js' ); 2322 media.view.Toolbar.Select = require( './views/toolbar/select.js' ); 2323 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' ); 2324 media.view.Button = require( './views/button.js' ); 2325 media.view.ButtonGroup = require( './views/button-group.js' ); 2326 media.view.PriorityList = require( './views/priority-list.js' ); 2327 media.view.MenuItem = require( './views/menu-item.js' ); 2328 media.view.Menu = require( './views/menu.js' ); 2329 media.view.RouterItem = require( './views/router-item.js' ); 2330 media.view.Router = require( './views/router.js' ); 2331 media.view.Sidebar = require( './views/sidebar.js' ); 2332 media.view.Attachment = require( './views/attachment.js' ); 2333 media.view.Attachment.Library = require( './views/attachment/library.js' ); 2334 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' ); 2335 media.view.Attachments = require( './views/attachments.js' ); 2336 media.view.Search = require( './views/search.js' ); 2337 media.view.AttachmentFilters = require( './views/attachment-filters.js' ); 2338 media.view.DateFilter = require( './views/attachment-filters/date.js' ); 2339 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' ); 2340 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' ); 2341 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' ); 2342 media.view.Selection = require( './views/selection.js' ); 2343 media.view.Attachment.Selection = require( './views/attachment/selection.js' ); 2344 media.view.Attachments.Selection = require( './views/attachments/selection.js' ); 2345 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' ); 2346 media.view.Settings = require( './views/settings.js' ); 2347 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' ); 2348 media.view.Settings.Gallery = require( './views/settings/gallery.js' ); 2349 media.view.Settings.Playlist = require( './views/settings/playlist.js' ); 2350 media.view.Attachment.Details = require( './views/attachment/details.js' ); 2351 media.view.AttachmentCompat = require( './views/attachment-compat.js' ); 2352 media.view.Iframe = require( './views/iframe.js' ); 2353 media.view.Embed = require( './views/embed.js' ); 2354 media.view.Label = require( './views/label.js' ); 2355 media.view.EmbedUrl = require( './views/embed/url.js' ); 2356 media.view.EmbedLink = require( './views/embed/link.js' ); 2357 media.view.EmbedImage = require( './views/embed/image.js' ); 2358 media.view.ImageDetails = require( './views/image-details.js' ); 2359 media.view.Cropper = require( './views/cropper.js' ); 2360 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' ); 2361 media.view.SiteIconPreview = require( './views/site-icon-preview.js' ); 2362 media.view.EditImage = require( './views/edit-image.js' ); 2363 media.view.Spinner = require( './views/spinner.js' ); 2364 2365 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){ 2366 var View = wp.media.View, 2367 AttachmentCompat; 2368 2369 /** 2370 * wp.media.view.AttachmentCompat 2371 * 2372 * A view to display fields added via the `attachment_fields_to_edit` filter. 4145 * wp.media.view.Modal 4146 * 4147 * A modal view, which the media modal uses as its default container. 2373 4148 * 2374 4149 * @memberOf wp.media.view … … 2379 4154 * @augments Backbone.View 2380 4155 */ 2381 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{ 2382 tagName: 'form', 2383 className: 'compat-item', 4156 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ 4157 tagName: 'div', 4158 template: wp.template('media-modal'), 4159 4160 attributes: { 4161 tabindex: 0 4162 }, 2384 4163 2385 4164 events: { 2386 ' submit': 'preventDefault',2387 ' change input': 'save',2388 'change select': 'save',2389 'change textarea': 'save' 2390 },4165 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 4166 'keydown': 'keydown' 4167 }, 4168 4169 clickedOpenerEl: null, 2391 4170 2392 4171 initialize: function() { 2393 this.listenTo( this.model, 'change:compat', this.render ); 2394 }, 2395 /** 2396 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 4172 _.defaults( this.options, { 4173 container: document.body, 4174 title: '', 4175 propagate: true, 4176 freeze: true 4177 }); 4178 4179 this.focusManager = new wp.media.view.FocusManager({ 4180 el: this.el 4181 }); 4182 }, 4183 /** 4184 * @returns {Object} 4185 */ 4186 prepare: function() { 4187 return { 4188 title: this.options.title 4189 }; 4190 }, 4191 4192 /** 4193 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4194 */ 4195 attach: function() { 4196 if ( this.views.attached ) { 4197 return this; 4198 } 4199 4200 if ( ! this.views.rendered ) { 4201 this.render(); 4202 } 4203 4204 this.$el.appendTo( this.options.container ); 4205 4206 // Manually mark the view as attached and trigger ready. 4207 this.views.attached = true; 4208 this.views.ready(); 4209 4210 return this.propagate('attach'); 4211 }, 4212 4213 /** 4214 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4215 */ 4216 detach: function() { 4217 if ( this.$el.is(':visible') ) { 4218 this.close(); 4219 } 4220 4221 this.$el.detach(); 4222 this.views.attached = false; 4223 return this.propagate('detach'); 4224 }, 4225 4226 /** 4227 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4228 */ 4229 open: function() { 4230 var $el = this.$el, 4231 options = this.options, 4232 mceEditor; 4233 4234 if ( $el.is(':visible') ) { 4235 return this; 4236 } 4237 4238 this.clickedOpenerEl = document.activeElement; 4239 4240 if ( ! this.views.attached ) { 4241 this.attach(); 4242 } 4243 4244 // If the `freeze` option is set, record the window's scroll position. 4245 if ( options.freeze ) { 4246 this._freeze = { 4247 scrollTop: $( window ).scrollTop() 4248 }; 4249 } 4250 4251 // Disable page scrolling. 4252 $( 'body' ).addClass( 'modal-open' ); 4253 4254 $el.show(); 4255 4256 // Try to close the onscreen keyboard 4257 if ( 'ontouchend' in document ) { 4258 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 4259 mceEditor.iframeElement.focus(); 4260 mceEditor.iframeElement.blur(); 4261 4262 setTimeout( function() { 4263 mceEditor.iframeElement.blur(); 4264 }, 100 ); 4265 } 4266 } 4267 4268 this.$el.focus(); 4269 4270 return this.propagate('open'); 4271 }, 4272 4273 /** 4274 * @param {Object} options 4275 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4276 */ 4277 close: function( options ) { 4278 var freeze = this._freeze; 4279 4280 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 4281 return this; 4282 } 4283 4284 // Enable page scrolling. 4285 $( 'body' ).removeClass( 'modal-open' ); 4286 4287 // Hide modal and remove restricted media modal tab focus once it's closed 4288 this.$el.hide().undelegate( 'keydown' ); 4289 4290 // Put focus back in useful location once modal is closed. 4291 if ( null !== this.clickedOpenerEl ) { 4292 this.clickedOpenerEl.focus(); 4293 } else { 4294 $( '#wpbody-content' ).focus(); 4295 } 4296 4297 this.propagate('close'); 4298 4299 // If the `freeze` option is set, restore the container's scroll position. 4300 if ( freeze ) { 4301 $( window ).scrollTop( freeze.scrollTop ); 4302 } 4303 4304 if ( options && options.escape ) { 4305 this.propagate('escape'); 4306 } 4307 4308 return this; 4309 }, 4310 /** 4311 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4312 */ 4313 escape: function() { 4314 return this.close({ escape: true }); 4315 }, 4316 /** 4317 * @param {Object} event 4318 */ 4319 escapeHandler: function( event ) { 4320 event.preventDefault(); 4321 this.escape(); 4322 }, 4323 4324 /** 4325 * @param {Array|Object} content Views to register to '.media-modal-content' 4326 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4327 */ 4328 content: function( content ) { 4329 this.views.set( '.media-modal-content', content ); 4330 return this; 4331 }, 4332 4333 /** 4334 * Triggers a modal event and if the `propagate` option is set, 4335 * forwards events to the modal's controller. 4336 * 4337 * @param {string} id 4338 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4339 */ 4340 propagate: function( id ) { 4341 this.trigger( id ); 4342 4343 if ( this.options.propagate ) { 4344 this.controller.trigger( id ); 4345 } 4346 4347 return this; 4348 }, 4349 /** 4350 * @param {Object} event 4351 */ 4352 keydown: function( event ) { 4353 // Close the modal when escape is pressed. 4354 if ( 27 === event.which && this.$el.is(':visible') ) { 4355 this.escape(); 4356 event.stopImmediatePropagation(); 4357 } 4358 } 4359 }); 4360 4361 module.exports = Modal; 4362 4363 4364 /***/ }), 4365 /* 52 */ 4366 /***/ (function(module, exports) { 4367 4368 /** 4369 * wp.media.view.FocusManager 4370 * 4371 * @memberOf wp.media.view 4372 * 4373 * @class 4374 * @augments wp.media.View 4375 * @augments wp.Backbone.View 4376 * @augments Backbone.View 4377 */ 4378 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 4379 4380 events: { 4381 'keydown': 'constrainTabbing' 4382 }, 4383 4384 focus: function() { // Reset focus on first left menu item 4385 this.$('.media-menu-item').first().focus(); 4386 }, 4387 /** 4388 * @param {Object} event 4389 */ 4390 constrainTabbing: function( event ) { 4391 var tabbables; 4392 4393 // Look for the tab key. 4394 if ( 9 !== event.keyCode ) { 4395 return; 4396 } 4397 4398 // Skip the file input added by Plupload. 4399 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4400 4401 // Keep tab focus within media modal while it's open 4402 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4403 tabbables.first().focus(); 4404 return false; 4405 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4406 tabbables.last().focus(); 4407 return false; 4408 } 4409 } 4410 4411 }); 4412 4413 module.exports = FocusManager; 4414 4415 4416 /***/ }), 4417 /* 53 */ 4418 /***/ (function(module, exports) { 4419 4420 var $ = jQuery, 4421 UploaderWindow; 4422 4423 /** 4424 * wp.media.view.UploaderWindow 4425 * 4426 * An uploader window that allows for dragging and dropping media. 4427 * 4428 * @memberOf wp.media.view 4429 * 4430 * @class 4431 * @augments wp.media.View 4432 * @augments wp.Backbone.View 4433 * @augments Backbone.View 4434 * 4435 * @param {object} [options] Options hash passed to the view. 4436 * @param {object} [options.uploader] Uploader properties. 4437 * @param {jQuery} [options.uploader.browser] 4438 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 4439 * @param {object} [options.uploader.params] 4440 */ 4441 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{ 4442 tagName: 'div', 4443 className: 'uploader-window', 4444 template: wp.template('uploader-window'), 4445 4446 initialize: function() { 4447 var uploader; 4448 4449 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 4450 4451 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 4452 dropzone: this.$el, 4453 browser: this.$browser, 4454 params: {} 4455 }); 4456 4457 // Ensure the dropzone is a jQuery collection. 4458 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 4459 uploader.dropzone = $( uploader.dropzone ); 4460 } 4461 4462 this.controller.on( 'activate', this.refresh, this ); 4463 4464 this.controller.on( 'detach', function() { 4465 this.$browser.remove(); 4466 }, this ); 4467 }, 4468 4469 refresh: function() { 4470 if ( this.uploader ) { 4471 this.uploader.refresh(); 4472 } 4473 }, 4474 4475 ready: function() { 4476 var postId = wp.media.view.settings.post.id, 4477 dropzone; 4478 4479 // If the uploader already exists, bail. 4480 if ( this.uploader ) { 4481 return; 4482 } 4483 4484 if ( postId ) { 4485 this.options.uploader.params.post_id = postId; 4486 } 4487 this.uploader = new wp.Uploader( this.options.uploader ); 4488 4489 dropzone = this.uploader.dropzone; 4490 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 4491 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 4492 4493 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 4494 }, 4495 4496 _ready: function() { 4497 this.controller.trigger( 'uploader:ready' ); 4498 }, 4499 4500 show: function() { 4501 var $el = this.$el.show(); 4502 4503 // Ensure that the animation is triggered by waiting until 4504 // the transparent element is painted into the DOM. 4505 _.defer( function() { 4506 $el.css({ opacity: 1 }); 4507 }); 4508 }, 4509 4510 hide: function() { 4511 var $el = this.$el.css({ opacity: 0 }); 4512 4513 wp.media.transition( $el ).done( function() { 4514 // Transition end events are subject to race conditions. 4515 // Make sure that the value is set as intended. 4516 if ( '0' === $el.css('opacity') ) { 4517 $el.hide(); 4518 } 4519 }); 4520 4521 // https://core.trac.wordpress.org/ticket/27341 4522 _.delay( function() { 4523 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 4524 $el.hide(); 4525 } 4526 }, 500 ); 4527 } 4528 }); 4529 4530 module.exports = UploaderWindow; 4531 4532 4533 /***/ }), 4534 /* 54 */ 4535 /***/ (function(module, exports) { 4536 4537 var View = wp.media.View, 4538 l10n = wp.media.view.l10n, 4539 $ = jQuery, 4540 EditorUploader; 4541 4542 /** 4543 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 4544 * and relays drag'n'dropped files to a media workflow. 4545 * 4546 * wp.media.view.EditorUploader 4547 * 4548 * @memberOf wp.media.view 4549 * 4550 * @class 4551 * @augments wp.media.View 4552 * @augments wp.Backbone.View 4553 * @augments Backbone.View 4554 */ 4555 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{ 4556 tagName: 'div', 4557 className: 'uploader-editor', 4558 template: wp.template( 'uploader-editor' ), 4559 4560 localDrag: false, 4561 overContainer: false, 4562 overDropzone: false, 4563 draggingFile: null, 4564 4565 /** 4566 * Bind drag'n'drop events to callbacks. 4567 */ 4568 initialize: function() { 4569 this.initialized = false; 4570 4571 // Bail if not enabled or UA does not support drag'n'drop or File API. 4572 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 4573 return this; 4574 } 4575 4576 this.$document = $(document); 4577 this.dropzones = []; 4578 this.files = []; 4579 4580 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 4581 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 4582 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 4583 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 4584 4585 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 4586 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 4587 4588 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 4589 this.localDrag = event.type === 'dragstart'; 4590 4591 if ( event.type === 'drop' ) { 4592 this.containerDragleave(); 4593 } 4594 }, this ) ); 4595 4596 this.initialized = true; 4597 return this; 4598 }, 4599 4600 /** 4601 * Check browser support for drag'n'drop. 4602 * 4603 * @return Boolean 4604 */ 4605 browserSupport: function() { 4606 var supports = false, div = document.createElement('div'); 4607 4608 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 4609 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 4610 return supports; 4611 }, 4612 4613 isDraggingFile: function( event ) { 4614 if ( this.draggingFile !== null ) { 4615 return this.draggingFile; 4616 } 4617 4618 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 4619 return false; 4620 } 4621 4622 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 4623 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 4624 4625 return this.draggingFile; 4626 }, 4627 4628 refresh: function( e ) { 4629 var dropzone_id; 4630 for ( dropzone_id in this.dropzones ) { 4631 // Hide the dropzones only if dragging has left the screen. 4632 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 4633 } 4634 4635 if ( ! _.isUndefined( e ) ) { 4636 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 4637 } 4638 4639 if ( ! this.overContainer && ! this.overDropzone ) { 4640 this.draggingFile = null; 4641 } 4642 4643 return this; 4644 }, 4645 4646 render: function() { 4647 if ( ! this.initialized ) { 4648 return this; 4649 } 4650 4651 View.prototype.render.apply( this, arguments ); 4652 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 4653 return this; 4654 }, 4655 4656 attach: function( index, editor ) { 4657 // Attach a dropzone to an editor. 4658 var dropzone = this.$el.clone(); 4659 this.dropzones.push( dropzone ); 4660 $( editor ).append( dropzone ); 4661 return this; 4662 }, 4663 4664 /** 4665 * When a file is dropped on the editor uploader, open up an editor media workflow 4666 * and upload the file immediately. 4667 * 4668 * @param {jQuery.Event} event The 'drop' event. 4669 */ 4670 drop: function( event ) { 4671 var $wrap, uploadView; 4672 4673 this.containerDragleave( event ); 4674 this.dropzoneDragleave( event ); 4675 4676 this.files = event.originalEvent.dataTransfer.files; 4677 if ( this.files.length < 1 ) { 4678 return; 4679 } 4680 4681 // Set the active editor to the drop target. 4682 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 4683 if ( $wrap.length > 0 && $wrap[0].id ) { 4684 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 4685 } 4686 4687 if ( ! this.workflow ) { 4688 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 4689 frame: 'post', 4690 state: 'insert', 4691 title: l10n.addMedia, 4692 multiple: true 4693 }); 4694 4695 uploadView = this.workflow.uploader; 4696 4697 if ( uploadView.uploader && uploadView.uploader.ready ) { 4698 this.addFiles.apply( this ); 4699 } else { 4700 this.workflow.on( 'uploader:ready', this.addFiles, this ); 4701 } 4702 } else { 4703 this.workflow.state().reset(); 4704 this.addFiles.apply( this ); 4705 this.workflow.open(); 4706 } 4707 4708 return false; 4709 }, 4710 4711 /** 4712 * Add the files to the uploader. 4713 */ 4714 addFiles: function() { 4715 if ( this.files.length ) { 4716 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 4717 this.files = []; 4718 } 4719 return this; 4720 }, 4721 4722 containerDragover: function( event ) { 4723 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4724 return; 4725 } 4726 4727 this.overContainer = true; 4728 this.refresh(); 4729 }, 4730 4731 containerDragleave: function() { 4732 this.overContainer = false; 4733 4734 // Throttle dragleave because it's called when bouncing from some elements to others. 4735 _.delay( _.bind( this.refresh, this ), 50 ); 4736 }, 4737 4738 dropzoneDragover: function( event ) { 4739 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4740 return; 4741 } 4742 4743 this.overDropzone = true; 4744 this.refresh( event ); 4745 return false; 4746 }, 4747 4748 dropzoneDragleave: function( e ) { 4749 this.overDropzone = false; 4750 _.delay( _.bind( this.refresh, this, e ), 50 ); 4751 }, 4752 4753 click: function( e ) { 4754 // In the rare case where the dropzone gets stuck, hide it on click. 4755 this.containerDragleave( e ); 4756 this.dropzoneDragleave( e ); 4757 this.localDrag = false; 4758 } 4759 }); 4760 4761 module.exports = EditorUploader; 4762 4763 4764 /***/ }), 4765 /* 55 */ 4766 /***/ (function(module, exports) { 4767 4768 var View = wp.media.View, 4769 UploaderInline; 4770 4771 /** 4772 * wp.media.view.UploaderInline 4773 * 4774 * The inline uploader that shows up in the 'Upload Files' tab. 4775 * 4776 * @memberOf wp.media.view 4777 * 4778 * @class 4779 * @augments wp.media.View 4780 * @augments wp.Backbone.View 4781 * @augments Backbone.View 4782 */ 4783 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 4784 tagName: 'div', 4785 className: 'uploader-inline', 4786 template: wp.template('uploader-inline'), 4787 4788 events: { 4789 'click .close': 'hide' 4790 }, 4791 4792 initialize: function() { 4793 _.defaults( this.options, { 4794 message: '', 4795 status: true, 4796 canClose: false 4797 }); 4798 4799 if ( ! this.options.$browser && this.controller.uploader ) { 4800 this.options.$browser = this.controller.uploader.$browser; 4801 } 4802 4803 if ( _.isUndefined( this.options.postId ) ) { 4804 this.options.postId = wp.media.view.settings.post.id; 4805 } 4806 4807 if ( this.options.status ) { 4808 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 4809 controller: this.controller 4810 }) ); 4811 } 4812 }, 4813 4814 prepare: function() { 4815 var suggestedWidth = this.controller.state().get('suggestedWidth'), 4816 suggestedHeight = this.controller.state().get('suggestedHeight'), 4817 data = {}; 4818 4819 data.message = this.options.message; 4820 data.canClose = this.options.canClose; 4821 4822 if ( suggestedWidth && suggestedHeight ) { 4823 data.suggestedWidth = suggestedWidth; 4824 data.suggestedHeight = suggestedHeight; 4825 } 4826 4827 return data; 4828 }, 4829 /** 4830 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 2397 4831 */ 2398 4832 dispose: function() { 2399 if ( this.$(':focus').length ) { 2400 this.save(); 4833 if ( this.disposing ) { 4834 /** 4835 * call 'dispose' directly on the parent class 4836 */ 4837 return View.prototype.dispose.apply( this, arguments ); 4838 } 4839 4840 // Run remove on `dispose`, so we can be sure to refresh the 4841 // uploader with a view-less DOM. Track whether we're disposing 4842 // so we don't trigger an infinite loop. 4843 this.disposing = true; 4844 return this.remove(); 4845 }, 4846 /** 4847 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 4848 */ 4849 remove: function() { 4850 /** 4851 * call 'remove' directly on the parent class 4852 */ 4853 var result = View.prototype.remove.apply( this, arguments ); 4854 4855 _.defer( _.bind( this.refresh, this ) ); 4856 return result; 4857 }, 4858 4859 refresh: function() { 4860 var uploader = this.controller.uploader; 4861 4862 if ( uploader ) { 4863 uploader.refresh(); 4864 } 4865 }, 4866 /** 4867 * @returns {wp.media.view.UploaderInline} 4868 */ 4869 ready: function() { 4870 var $browser = this.options.$browser, 4871 $placeholder; 4872 4873 if ( this.controller.uploader ) { 4874 $placeholder = this.$('.browser'); 4875 4876 // Check if we've already replaced the placeholder. 4877 if ( $placeholder[0] === $browser[0] ) { 4878 return; 4879 } 4880 4881 $browser.detach().text( $placeholder.text() ); 4882 $browser[0].className = $placeholder[0].className; 4883 $placeholder.replaceWith( $browser.show() ); 4884 } 4885 4886 this.refresh(); 4887 return this; 4888 }, 4889 show: function() { 4890 this.$el.removeClass( 'hidden' ); 4891 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 4892 this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' ); 4893 } 4894 }, 4895 hide: function() { 4896 this.$el.addClass( 'hidden' ); 4897 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 4898 this.controller.$uploaderToggler 4899 .attr( 'aria-expanded', 'false' ) 4900 // Move focus back to the toggle button when closing the uploader. 4901 .focus(); 4902 } 4903 } 4904 4905 }); 4906 4907 module.exports = UploaderInline; 4908 4909 4910 /***/ }), 4911 /* 56 */ 4912 /***/ (function(module, exports) { 4913 4914 var View = wp.media.View, 4915 UploaderStatus; 4916 4917 /** 4918 * wp.media.view.UploaderStatus 4919 * 4920 * An uploader status for on-going uploads. 4921 * 4922 * @memberOf wp.media.view 4923 * 4924 * @class 4925 * @augments wp.media.View 4926 * @augments wp.Backbone.View 4927 * @augments Backbone.View 4928 */ 4929 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{ 4930 className: 'media-uploader-status', 4931 template: wp.template('uploader-status'), 4932 4933 events: { 4934 'click .upload-dismiss-errors': 'dismiss' 4935 }, 4936 4937 initialize: function() { 4938 this.queue = wp.Uploader.queue; 4939 this.queue.on( 'add remove reset', this.visibility, this ); 4940 this.queue.on( 'add remove reset change:percent', this.progress, this ); 4941 this.queue.on( 'add remove reset change:uploading', this.info, this ); 4942 4943 this.errors = wp.Uploader.errors; 4944 this.errors.reset(); 4945 this.errors.on( 'add remove reset', this.visibility, this ); 4946 this.errors.on( 'add', this.error, this ); 4947 }, 4948 /** 4949 * @returns {wp.media.view.UploaderStatus} 4950 */ 4951 dispose: function() { 4952 wp.Uploader.queue.off( null, null, this ); 4953 /** 4954 * call 'dispose' directly on the parent class 4955 */ 4956 View.prototype.dispose.apply( this, arguments ); 4957 return this; 4958 }, 4959 4960 visibility: function() { 4961 this.$el.toggleClass( 'uploading', !! this.queue.length ); 4962 this.$el.toggleClass( 'errors', !! this.errors.length ); 4963 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 4964 }, 4965 4966 ready: function() { 4967 _.each({ 4968 '$bar': '.media-progress-bar div', 4969 '$index': '.upload-index', 4970 '$total': '.upload-total', 4971 '$filename': '.upload-filename' 4972 }, function( selector, key ) { 4973 this[ key ] = this.$( selector ); 4974 }, this ); 4975 4976 this.visibility(); 4977 this.progress(); 4978 this.info(); 4979 }, 4980 4981 progress: function() { 4982 var queue = this.queue, 4983 $bar = this.$bar; 4984 4985 if ( ! $bar || ! queue.length ) { 4986 return; 4987 } 4988 4989 $bar.width( ( queue.reduce( function( memo, attachment ) { 4990 if ( ! attachment.get('uploading') ) { 4991 return memo + 100; 4992 } 4993 4994 var percent = attachment.get('percent'); 4995 return memo + ( _.isNumber( percent ) ? percent : 100 ); 4996 }, 0 ) / queue.length ) + '%' ); 4997 }, 4998 4999 info: function() { 5000 var queue = this.queue, 5001 index = 0, active; 5002 5003 if ( ! queue.length ) { 5004 return; 5005 } 5006 5007 active = this.queue.find( function( attachment, i ) { 5008 index = i; 5009 return attachment.get('uploading'); 5010 }); 5011 5012 this.$index.text( index + 1 ); 5013 this.$total.text( queue.length ); 5014 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 5015 }, 5016 /** 5017 * @param {string} filename 5018 * @returns {string} 5019 */ 5020 filename: function( filename ) { 5021 return _.escape( filename ); 5022 }, 5023 /** 5024 * @param {Backbone.Model} error 5025 */ 5026 error: function( error ) { 5027 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 5028 filename: this.filename( error.get('file').name ), 5029 message: error.get('message') 5030 }), { at: 0 }); 5031 }, 5032 5033 /** 5034 * @param {Object} event 5035 */ 5036 dismiss: function( event ) { 5037 var errors = this.views.get('.upload-errors'); 5038 5039 event.preventDefault(); 5040 5041 if ( errors ) { 5042 _.invoke( errors, 'remove' ); 5043 } 5044 wp.Uploader.errors.reset(); 5045 } 5046 }); 5047 5048 module.exports = UploaderStatus; 5049 5050 5051 /***/ }), 5052 /* 57 */ 5053 /***/ (function(module, exports) { 5054 5055 /** 5056 * wp.media.view.UploaderStatusError 5057 * 5058 * @memberOf wp.media.view 5059 * 5060 * @class 5061 * @augments wp.media.View 5062 * @augments wp.Backbone.View 5063 * @augments Backbone.View 5064 */ 5065 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{ 5066 className: 'upload-error', 5067 template: wp.template('uploader-status-error') 5068 }); 5069 5070 module.exports = UploaderStatusError; 5071 5072 5073 /***/ }), 5074 /* 58 */ 5075 /***/ (function(module, exports) { 5076 5077 var View = wp.media.View, 5078 Toolbar; 5079 5080 /** 5081 * wp.media.view.Toolbar 5082 * 5083 * A toolbar which consists of a primary and a secondary section. Each sections 5084 * can be filled with views. 5085 * 5086 * @memberOf wp.media.view 5087 * 5088 * @class 5089 * @augments wp.media.View 5090 * @augments wp.Backbone.View 5091 * @augments Backbone.View 5092 */ 5093 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ 5094 tagName: 'div', 5095 className: 'media-toolbar', 5096 5097 initialize: function() { 5098 var state = this.controller.state(), 5099 selection = this.selection = state.get('selection'), 5100 library = this.library = state.get('library'); 5101 5102 this._views = {}; 5103 5104 // The toolbar is composed of two `PriorityList` views. 5105 this.primary = new wp.media.view.PriorityList(); 5106 this.secondary = new wp.media.view.PriorityList(); 5107 this.primary.$el.addClass('media-toolbar-primary search-form'); 5108 this.secondary.$el.addClass('media-toolbar-secondary'); 5109 5110 this.views.set([ this.secondary, this.primary ]); 5111 5112 if ( this.options.items ) { 5113 this.set( this.options.items, { silent: true }); 5114 } 5115 5116 if ( ! this.options.silent ) { 5117 this.render(); 5118 } 5119 5120 if ( selection ) { 5121 selection.on( 'add remove reset', this.refresh, this ); 5122 } 5123 5124 if ( library ) { 5125 library.on( 'add remove reset', this.refresh, this ); 5126 } 5127 }, 5128 /** 5129 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 5130 */ 5131 dispose: function() { 5132 if ( this.selection ) { 5133 this.selection.off( null, null, this ); 5134 } 5135 5136 if ( this.library ) { 5137 this.library.off( null, null, this ); 2401 5138 } 2402 5139 /** … … 2405 5142 return View.prototype.dispose.apply( this, arguments ); 2406 5143 }, 2407 /** 2408 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 5144 5145 ready: function() { 5146 this.refresh(); 5147 }, 5148 5149 /** 5150 * @param {string} id 5151 * @param {Backbone.View|Object} view 5152 * @param {Object} [options={}] 5153 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5154 */ 5155 set: function( id, view, options ) { 5156 var list; 5157 options = options || {}; 5158 5159 // Accept an object with an `id` : `view` mapping. 5160 if ( _.isObject( id ) ) { 5161 _.each( id, function( view, id ) { 5162 this.set( id, view, { silent: true }); 5163 }, this ); 5164 5165 } else { 5166 if ( ! ( view instanceof Backbone.View ) ) { 5167 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 5168 view = new wp.media.view.Button( view ).render(); 5169 } 5170 5171 view.controller = view.controller || this.controller; 5172 5173 this._views[ id ] = view; 5174 5175 list = view.options.priority < 0 ? 'secondary' : 'primary'; 5176 this[ list ].set( id, view, options ); 5177 } 5178 5179 if ( ! options.silent ) { 5180 this.refresh(); 5181 } 5182 5183 return this; 5184 }, 5185 /** 5186 * @param {string} id 5187 * @returns {wp.media.view.Button} 5188 */ 5189 get: function( id ) { 5190 return this._views[ id ]; 5191 }, 5192 /** 5193 * @param {string} id 5194 * @param {Object} options 5195 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5196 */ 5197 unset: function( id, options ) { 5198 delete this._views[ id ]; 5199 this.primary.unset( id, options ); 5200 this.secondary.unset( id, options ); 5201 5202 if ( ! options || ! options.silent ) { 5203 this.refresh(); 5204 } 5205 return this; 5206 }, 5207 5208 refresh: function() { 5209 var state = this.controller.state(), 5210 library = state.get('library'), 5211 selection = state.get('selection'); 5212 5213 _.each( this._views, function( button ) { 5214 if ( ! button.model || ! button.options || ! button.options.requires ) { 5215 return; 5216 } 5217 5218 var requires = button.options.requires, 5219 disabled = false; 5220 5221 // Prevent insertion of attachments if any of them are still uploading 5222 if ( selection && selection.models ) { 5223 disabled = _.some( selection.models, function( attachment ) { 5224 return attachment.get('uploading') === true; 5225 }); 5226 } 5227 5228 if ( requires.selection && selection && ! selection.length ) { 5229 disabled = true; 5230 } else if ( requires.library && library && ! library.length ) { 5231 disabled = true; 5232 } 5233 button.model.set( 'disabled', disabled ); 5234 }); 5235 } 5236 }); 5237 5238 module.exports = Toolbar; 5239 5240 5241 /***/ }), 5242 /* 59 */ 5243 /***/ (function(module, exports) { 5244 5245 var Toolbar = wp.media.view.Toolbar, 5246 l10n = wp.media.view.l10n, 5247 Select; 5248 5249 /** 5250 * wp.media.view.Toolbar.Select 5251 * 5252 * @memberOf wp.media.view.Toolbar 5253 * 5254 * @class 5255 * @augments wp.media.view.Toolbar 5256 * @augments wp.media.View 5257 * @augments wp.Backbone.View 5258 * @augments Backbone.View 5259 */ 5260 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{ 5261 initialize: function() { 5262 var options = this.options; 5263 5264 _.bindAll( this, 'clickSelect' ); 5265 5266 _.defaults( options, { 5267 event: 'select', 5268 state: false, 5269 reset: true, 5270 close: true, 5271 text: l10n.select, 5272 5273 // Does the button rely on the selection? 5274 requires: { 5275 selection: true 5276 } 5277 }); 5278 5279 options.items = _.defaults( options.items || {}, { 5280 select: { 5281 style: 'primary', 5282 text: options.text, 5283 priority: 80, 5284 click: this.clickSelect, 5285 requires: options.requires 5286 } 5287 }); 5288 // Call 'initialize' directly on the parent class. 5289 Toolbar.prototype.initialize.apply( this, arguments ); 5290 }, 5291 5292 clickSelect: function() { 5293 var options = this.options, 5294 controller = this.controller; 5295 5296 if ( options.close ) { 5297 controller.close(); 5298 } 5299 5300 if ( options.event ) { 5301 controller.state().trigger( options.event ); 5302 } 5303 5304 if ( options.state ) { 5305 controller.setState( options.state ); 5306 } 5307 5308 if ( options.reset ) { 5309 controller.reset(); 5310 } 5311 } 5312 }); 5313 5314 module.exports = Select; 5315 5316 5317 /***/ }), 5318 /* 60 */ 5319 /***/ (function(module, exports) { 5320 5321 var Select = wp.media.view.Toolbar.Select, 5322 l10n = wp.media.view.l10n, 5323 Embed; 5324 5325 /** 5326 * wp.media.view.Toolbar.Embed 5327 * 5328 * @memberOf wp.media.view.Toolbar 5329 * 5330 * @class 5331 * @augments wp.media.view.Toolbar.Select 5332 * @augments wp.media.view.Toolbar 5333 * @augments wp.media.View 5334 * @augments wp.Backbone.View 5335 * @augments Backbone.View 5336 */ 5337 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{ 5338 initialize: function() { 5339 _.defaults( this.options, { 5340 text: l10n.insertIntoPost, 5341 requires: false 5342 }); 5343 // Call 'initialize' directly on the parent class. 5344 Select.prototype.initialize.apply( this, arguments ); 5345 }, 5346 5347 refresh: function() { 5348 var url = this.controller.state().props.get('url'); 5349 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 5350 /** 5351 * call 'refresh' directly on the parent class 5352 */ 5353 Select.prototype.refresh.apply( this, arguments ); 5354 } 5355 }); 5356 5357 module.exports = Embed; 5358 5359 5360 /***/ }), 5361 /* 61 */ 5362 /***/ (function(module, exports) { 5363 5364 /** 5365 * wp.media.view.Button 5366 * 5367 * @memberOf wp.media.view 5368 * 5369 * @class 5370 * @augments wp.media.View 5371 * @augments wp.Backbone.View 5372 * @augments Backbone.View 5373 */ 5374 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{ 5375 tagName: 'button', 5376 className: 'media-button', 5377 attributes: { type: 'button' }, 5378 5379 events: { 5380 'click': 'click' 5381 }, 5382 5383 defaults: { 5384 text: '', 5385 style: '', 5386 size: 'large', 5387 disabled: false 5388 }, 5389 5390 initialize: function() { 5391 /** 5392 * Create a model with the provided `defaults`. 5393 * 5394 * @member {Backbone.Model} 5395 */ 5396 this.model = new Backbone.Model( this.defaults ); 5397 5398 // If any of the `options` have a key from `defaults`, apply its 5399 // value to the `model` and remove it from the `options object. 5400 _.each( this.defaults, function( def, key ) { 5401 var value = this.options[ key ]; 5402 if ( _.isUndefined( value ) ) { 5403 return; 5404 } 5405 5406 this.model.set( key, value ); 5407 delete this.options[ key ]; 5408 }, this ); 5409 5410 this.listenTo( this.model, 'change', this.render ); 5411 }, 5412 /** 5413 * @returns {wp.media.view.Button} Returns itself to allow chaining 2409 5414 */ 2410 5415 render: function() { 2411 var compat = this.model.get('compat'); 2412 if ( ! compat || ! compat.item ) { 2413 return; 2414 } 2415 2416 this.views.detach(); 2417 this.$el.html( compat.item ); 2418 this.views.render(); 5416 var classes = [ 'button', this.className ], 5417 model = this.model.toJSON(); 5418 5419 if ( model.style ) { 5420 classes.push( 'button-' + model.style ); 5421 } 5422 5423 if ( model.size ) { 5424 classes.push( 'button-' + model.size ); 5425 } 5426 5427 classes = _.uniq( classes.concat( this.options.classes ) ); 5428 this.el.className = classes.join(' '); 5429 5430 this.$el.attr( 'disabled', model.disabled ); 5431 this.$el.text( this.model.get('text') ); 5432 2419 5433 return this; 2420 5434 }, … … 2422 5436 * @param {Object} event 2423 5437 */ 2424 preventDefault: function( event ) { 2425 event.preventDefault(); 5438 click: function( event ) { 5439 if ( '#' === this.attributes.href ) { 5440 event.preventDefault(); 5441 } 5442 5443 if ( this.options.click && ! this.model.get('disabled') ) { 5444 this.options.click.apply( this, arguments ); 5445 } 5446 } 5447 }); 5448 5449 module.exports = Button; 5450 5451 5452 /***/ }), 5453 /* 62 */ 5454 /***/ (function(module, exports) { 5455 5456 var $ = Backbone.$, 5457 ButtonGroup; 5458 5459 /** 5460 * wp.media.view.ButtonGroup 5461 * 5462 * @memberOf wp.media.view 5463 * 5464 * @class 5465 * @augments wp.media.View 5466 * @augments wp.Backbone.View 5467 * @augments Backbone.View 5468 */ 5469 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{ 5470 tagName: 'div', 5471 className: 'button-group button-large media-button-group', 5472 5473 initialize: function() { 5474 /** 5475 * @member {wp.media.view.Button[]} 5476 */ 5477 this.buttons = _.map( this.options.buttons || [], function( button ) { 5478 if ( button instanceof Backbone.View ) { 5479 return button; 5480 } else { 5481 return new wp.media.view.Button( button ).render(); 5482 } 5483 }); 5484 5485 delete this.options.buttons; 5486 5487 if ( this.options.classes ) { 5488 this.$el.addClass( this.options.classes ); 5489 } 5490 }, 5491 5492 /** 5493 * @returns {wp.media.view.ButtonGroup} 5494 */ 5495 render: function() { 5496 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 5497 return this; 5498 } 5499 }); 5500 5501 module.exports = ButtonGroup; 5502 5503 5504 /***/ }), 5505 /* 63 */ 5506 /***/ (function(module, exports) { 5507 5508 /** 5509 * wp.media.view.PriorityList 5510 * 5511 * @memberOf wp.media.view 5512 * 5513 * @class 5514 * @augments wp.media.View 5515 * @augments wp.Backbone.View 5516 * @augments Backbone.View 5517 */ 5518 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{ 5519 tagName: 'div', 5520 5521 initialize: function() { 5522 this._views = {}; 5523 5524 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 5525 delete this.options.views; 5526 5527 if ( ! this.options.silent ) { 5528 this.render(); 5529 } 5530 }, 5531 /** 5532 * @param {string} id 5533 * @param {wp.media.View|Object} view 5534 * @param {Object} options 5535 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 5536 */ 5537 set: function( id, view, options ) { 5538 var priority, views, index; 5539 5540 options = options || {}; 5541 5542 // Accept an object with an `id` : `view` mapping. 5543 if ( _.isObject( id ) ) { 5544 _.each( id, function( view, id ) { 5545 this.set( id, view ); 5546 }, this ); 5547 return this; 5548 } 5549 5550 if ( ! (view instanceof Backbone.View) ) { 5551 view = this.toView( view, id, options ); 5552 } 5553 view.controller = view.controller || this.controller; 5554 5555 this.unset( id ); 5556 5557 priority = view.options.priority || 10; 5558 views = this.views.get() || []; 5559 5560 _.find( views, function( existing, i ) { 5561 if ( existing.options.priority > priority ) { 5562 index = i; 5563 return true; 5564 } 5565 }); 5566 5567 this._views[ id ] = view; 5568 this.views.add( view, { 5569 at: _.isNumber( index ) ? index : views.length || 0 5570 }); 5571 5572 return this; 5573 }, 5574 /** 5575 * @param {string} id 5576 * @returns {wp.media.View} 5577 */ 5578 get: function( id ) { 5579 return this._views[ id ]; 5580 }, 5581 /** 5582 * @param {string} id 5583 * @returns {wp.media.view.PriorityList} 5584 */ 5585 unset: function( id ) { 5586 var view = this.get( id ); 5587 5588 if ( view ) { 5589 view.remove(); 5590 } 5591 5592 delete this._views[ id ]; 5593 return this; 5594 }, 5595 /** 5596 * @param {Object} options 5597 * @returns {wp.media.View} 5598 */ 5599 toView: function( options ) { 5600 return new wp.media.View( options ); 5601 } 5602 }); 5603 5604 module.exports = PriorityList; 5605 5606 5607 /***/ }), 5608 /* 64 */ 5609 /***/ (function(module, exports) { 5610 5611 var $ = jQuery, 5612 MenuItem; 5613 5614 /** 5615 * wp.media.view.MenuItem 5616 * 5617 * @memberOf wp.media.view 5618 * 5619 * @class 5620 * @augments wp.media.View 5621 * @augments wp.Backbone.View 5622 * @augments Backbone.View 5623 */ 5624 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{ 5625 tagName: 'a', 5626 className: 'media-menu-item', 5627 5628 attributes: { 5629 href: '#' 5630 }, 5631 5632 events: { 5633 'click': '_click' 2426 5634 }, 2427 5635 /** 2428 5636 * @param {Object} event 2429 5637 */ 2430 save: function( event ) {2431 var data = {};5638 _click: function( event ) { 5639 var clickOverride = this.options.click; 2432 5640 2433 5641 if ( event ) { … … 2435 5643 } 2436 5644 2437 _.each( this.$el.serializeArray(), function( pair ) { 2438 data[ pair.name ] = pair.value; 2439 }); 2440 2441 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 2442 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 2443 }, 2444 2445 postSave: function() { 2446 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 5645 if ( clickOverride ) { 5646 clickOverride.call( this ); 5647 } else { 5648 this.click(); 5649 } 5650 5651 // When selecting a tab along the left side, 5652 // focus should be transferred into the main panel 5653 if ( ! wp.media.isTouchDevice ) { 5654 $('.media-frame-content input').first().focus(); 5655 } 5656 }, 5657 5658 click: function() { 5659 var state = this.options.state; 5660 5661 if ( state ) { 5662 this.controller.setState( state ); 5663 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 5664 } 5665 }, 5666 /** 5667 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 5668 */ 5669 render: function() { 5670 var options = this.options; 5671 5672 if ( options.text ) { 5673 this.$el.text( options.text ); 5674 } else if ( options.html ) { 5675 this.$el.html( options.html ); 5676 } 5677 5678 return this; 2447 5679 } 2448 5680 }); 2449 5681 2450 module.exports = AttachmentCompat; 2451 2452 },{}],21:[function(require,module,exports){ 5682 module.exports = MenuItem; 5683 5684 5685 /***/ }), 5686 /* 65 */ 5687 /***/ (function(module, exports) { 5688 5689 var MenuItem = wp.media.view.MenuItem, 5690 PriorityList = wp.media.view.PriorityList, 5691 Menu; 5692 5693 /** 5694 * wp.media.view.Menu 5695 * 5696 * @memberOf wp.media.view 5697 * 5698 * @class 5699 * @augments wp.media.view.PriorityList 5700 * @augments wp.media.View 5701 * @augments wp.Backbone.View 5702 * @augments Backbone.View 5703 */ 5704 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{ 5705 tagName: 'div', 5706 className: 'media-menu', 5707 property: 'state', 5708 ItemView: MenuItem, 5709 region: 'menu', 5710 5711 /* TODO: alternatively hide on any click anywhere 5712 events: { 5713 'click': 'click' 5714 }, 5715 5716 click: function() { 5717 this.$el.removeClass( 'visible' ); 5718 }, 5719 */ 5720 5721 /** 5722 * @param {Object} options 5723 * @param {string} id 5724 * @returns {wp.media.View} 5725 */ 5726 toView: function( options, id ) { 5727 options = options || {}; 5728 options[ this.property ] = options[ this.property ] || id; 5729 return new this.ItemView( options ).render(); 5730 }, 5731 5732 ready: function() { 5733 /** 5734 * call 'ready' directly on the parent class 5735 */ 5736 PriorityList.prototype.ready.apply( this, arguments ); 5737 this.visibility(); 5738 }, 5739 5740 set: function() { 5741 /** 5742 * call 'set' directly on the parent class 5743 */ 5744 PriorityList.prototype.set.apply( this, arguments ); 5745 this.visibility(); 5746 }, 5747 5748 unset: function() { 5749 /** 5750 * call 'unset' directly on the parent class 5751 */ 5752 PriorityList.prototype.unset.apply( this, arguments ); 5753 this.visibility(); 5754 }, 5755 5756 visibility: function() { 5757 var region = this.region, 5758 view = this.controller[ region ].get(), 5759 views = this.views.get(), 5760 hide = ! views || views.length < 2; 5761 5762 if ( this === view ) { 5763 this.controller.$el.toggleClass( 'hide-' + region, hide ); 5764 } 5765 }, 5766 /** 5767 * @param {string} id 5768 */ 5769 select: function( id ) { 5770 var view = this.get( id ); 5771 5772 if ( ! view ) { 5773 return; 5774 } 5775 5776 this.deselect(); 5777 view.$el.addClass('active'); 5778 }, 5779 5780 deselect: function() { 5781 this.$el.children().removeClass('active'); 5782 }, 5783 5784 hide: function( id ) { 5785 var view = this.get( id ); 5786 5787 if ( ! view ) { 5788 return; 5789 } 5790 5791 view.$el.addClass('hidden'); 5792 }, 5793 5794 show: function( id ) { 5795 var view = this.get( id ); 5796 5797 if ( ! view ) { 5798 return; 5799 } 5800 5801 view.$el.removeClass('hidden'); 5802 } 5803 }); 5804 5805 module.exports = Menu; 5806 5807 5808 /***/ }), 5809 /* 66 */ 5810 /***/ (function(module, exports) { 5811 5812 /** 5813 * wp.media.view.RouterItem 5814 * 5815 * @memberOf wp.media.view 5816 * 5817 * @class 5818 * @augments wp.media.view.MenuItem 5819 * @augments wp.media.View 5820 * @augments wp.Backbone.View 5821 * @augments Backbone.View 5822 */ 5823 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{ 5824 /** 5825 * On click handler to activate the content region's corresponding mode. 5826 */ 5827 click: function() { 5828 var contentMode = this.options.contentMode; 5829 if ( contentMode ) { 5830 this.controller.content.mode( contentMode ); 5831 } 5832 } 5833 }); 5834 5835 module.exports = RouterItem; 5836 5837 5838 /***/ }), 5839 /* 67 */ 5840 /***/ (function(module, exports) { 5841 5842 var Menu = wp.media.view.Menu, 5843 Router; 5844 5845 /** 5846 * wp.media.view.Router 5847 * 5848 * @memberOf wp.media.view 5849 * 5850 * @class 5851 * @augments wp.media.view.Menu 5852 * @augments wp.media.view.PriorityList 5853 * @augments wp.media.View 5854 * @augments wp.Backbone.View 5855 * @augments Backbone.View 5856 */ 5857 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{ 5858 tagName: 'div', 5859 className: 'media-router', 5860 property: 'contentMode', 5861 ItemView: wp.media.view.RouterItem, 5862 region: 'router', 5863 5864 initialize: function() { 5865 this.controller.on( 'content:render', this.update, this ); 5866 // Call 'initialize' directly on the parent class. 5867 Menu.prototype.initialize.apply( this, arguments ); 5868 }, 5869 5870 update: function() { 5871 var mode = this.controller.content.mode(); 5872 if ( mode ) { 5873 this.select( mode ); 5874 } 5875 } 5876 }); 5877 5878 module.exports = Router; 5879 5880 5881 /***/ }), 5882 /* 68 */ 5883 /***/ (function(module, exports) { 5884 5885 /** 5886 * wp.media.view.Sidebar 5887 * 5888 * @memberOf wp.media.view 5889 * 5890 * @class 5891 * @augments wp.media.view.PriorityList 5892 * @augments wp.media.View 5893 * @augments wp.Backbone.View 5894 * @augments Backbone.View 5895 */ 5896 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{ 5897 className: 'media-sidebar' 5898 }); 5899 5900 module.exports = Sidebar; 5901 5902 5903 /***/ }), 5904 /* 69 */ 5905 /***/ (function(module, exports) { 5906 5907 var View = wp.media.View, 5908 $ = jQuery, 5909 Attachment; 5910 5911 /** 5912 * wp.media.view.Attachment 5913 * 5914 * @memberOf wp.media.view 5915 * 5916 * @class 5917 * @augments wp.media.View 5918 * @augments wp.Backbone.View 5919 * @augments Backbone.View 5920 */ 5921 Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{ 5922 tagName: 'li', 5923 className: 'attachment', 5924 template: wp.template('attachment'), 5925 5926 attributes: function() { 5927 return { 5928 'tabIndex': 0, 5929 'role': 'checkbox', 5930 'aria-label': this.model.get( 'title' ), 5931 'aria-checked': false, 5932 'data-id': this.model.get( 'id' ) 5933 }; 5934 }, 5935 5936 events: { 5937 'click': 'toggleSelectionHandler', 5938 'change [data-setting]': 'updateSetting', 5939 'change [data-setting] input': 'updateSetting', 5940 'change [data-setting] select': 'updateSetting', 5941 'change [data-setting] textarea': 'updateSetting', 5942 'click .attachment-close': 'removeFromLibrary', 5943 'click .check': 'checkClickHandler', 5944 'keydown': 'toggleSelectionHandler' 5945 }, 5946 5947 buttons: {}, 5948 5949 initialize: function() { 5950 var selection = this.options.selection, 5951 options = _.defaults( this.options, { 5952 rerenderOnModelChange: true 5953 } ); 5954 5955 if ( options.rerenderOnModelChange ) { 5956 this.listenTo( this.model, 'change', this.render ); 5957 } else { 5958 this.listenTo( this.model, 'change:percent', this.progress ); 5959 } 5960 this.listenTo( this.model, 'change:title', this._syncTitle ); 5961 this.listenTo( this.model, 'change:caption', this._syncCaption ); 5962 this.listenTo( this.model, 'change:artist', this._syncArtist ); 5963 this.listenTo( this.model, 'change:album', this._syncAlbum ); 5964 5965 // Update the selection. 5966 this.listenTo( this.model, 'add', this.select ); 5967 this.listenTo( this.model, 'remove', this.deselect ); 5968 if ( selection ) { 5969 selection.on( 'reset', this.updateSelect, this ); 5970 // Update the model's details view. 5971 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 5972 this.details( this.model, this.controller.state().get('selection') ); 5973 } 5974 5975 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 5976 }, 5977 /** 5978 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5979 */ 5980 dispose: function() { 5981 var selection = this.options.selection; 5982 5983 // Make sure all settings are saved before removing the view. 5984 this.updateAll(); 5985 5986 if ( selection ) { 5987 selection.off( null, null, this ); 5988 } 5989 /** 5990 * call 'dispose' directly on the parent class 5991 */ 5992 View.prototype.dispose.apply( this, arguments ); 5993 return this; 5994 }, 5995 /** 5996 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5997 */ 5998 render: function() { 5999 var options = _.defaults( this.model.toJSON(), { 6000 orientation: 'landscape', 6001 uploading: false, 6002 type: '', 6003 subtype: '', 6004 icon: '', 6005 filename: '', 6006 caption: '', 6007 title: '', 6008 dateFormatted: '', 6009 width: '', 6010 height: '', 6011 compat: false, 6012 alt: '', 6013 description: '' 6014 }, this.options ); 6015 6016 options.buttons = this.buttons; 6017 options.describe = this.controller.state().get('describe'); 6018 6019 if ( 'image' === options.type ) { 6020 options.size = this.imageSize(); 6021 } 6022 6023 options.can = {}; 6024 if ( options.nonces ) { 6025 options.can.remove = !! options.nonces['delete']; 6026 options.can.save = !! options.nonces.update; 6027 } 6028 6029 if ( this.controller.state().get('allowLocalEdits') ) { 6030 options.allowLocalEdits = true; 6031 } 6032 6033 if ( options.uploading && ! options.percent ) { 6034 options.percent = 0; 6035 } 6036 6037 this.views.detach(); 6038 this.$el.html( this.template( options ) ); 6039 6040 this.$el.toggleClass( 'uploading', options.uploading ); 6041 6042 if ( options.uploading ) { 6043 this.$bar = this.$('.media-progress-bar div'); 6044 } else { 6045 delete this.$bar; 6046 } 6047 6048 // Check if the model is selected. 6049 this.updateSelect(); 6050 6051 // Update the save status. 6052 this.updateSave(); 6053 6054 this.views.render(); 6055 6056 return this; 6057 }, 6058 6059 progress: function() { 6060 if ( this.$bar && this.$bar.length ) { 6061 this.$bar.width( this.model.get('percent') + '%' ); 6062 } 6063 }, 6064 6065 /** 6066 * @param {Object} event 6067 */ 6068 toggleSelectionHandler: function( event ) { 6069 var method; 6070 6071 // Don't do anything inside inputs and on the attachment check and remove buttons. 6072 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 6073 return; 6074 } 6075 6076 // Catch arrow events 6077 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 6078 this.controller.trigger( 'attachment:keydown:arrow', event ); 6079 return; 6080 } 6081 6082 // Catch enter and space events 6083 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6084 return; 6085 } 6086 6087 event.preventDefault(); 6088 6089 // In the grid view, bubble up an edit:attachment event to the controller. 6090 if ( this.controller.isModeActive( 'grid' ) ) { 6091 if ( this.controller.isModeActive( 'edit' ) ) { 6092 // Pass the current target to restore focus when closing 6093 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 6094 return; 6095 } 6096 6097 if ( this.controller.isModeActive( 'select' ) ) { 6098 method = 'toggle'; 6099 } 6100 } 6101 6102 if ( event.shiftKey ) { 6103 method = 'between'; 6104 } else if ( event.ctrlKey || event.metaKey ) { 6105 method = 'toggle'; 6106 } 6107 6108 this.toggleSelection({ 6109 method: method 6110 }); 6111 6112 this.controller.trigger( 'selection:toggle' ); 6113 }, 6114 /** 6115 * @param {Object} options 6116 */ 6117 toggleSelection: function( options ) { 6118 var collection = this.collection, 6119 selection = this.options.selection, 6120 model = this.model, 6121 method = options && options.method, 6122 single, models, singleIndex, modelIndex; 6123 6124 if ( ! selection ) { 6125 return; 6126 } 6127 6128 single = selection.single(); 6129 method = _.isUndefined( method ) ? selection.multiple : method; 6130 6131 // If the `method` is set to `between`, select all models that 6132 // exist between the current and the selected model. 6133 if ( 'between' === method && single && selection.multiple ) { 6134 // If the models are the same, short-circuit. 6135 if ( single === model ) { 6136 return; 6137 } 6138 6139 singleIndex = collection.indexOf( single ); 6140 modelIndex = collection.indexOf( this.model ); 6141 6142 if ( singleIndex < modelIndex ) { 6143 models = collection.models.slice( singleIndex, modelIndex + 1 ); 6144 } else { 6145 models = collection.models.slice( modelIndex, singleIndex + 1 ); 6146 } 6147 6148 selection.add( models ); 6149 selection.single( model ); 6150 return; 6151 6152 // If the `method` is set to `toggle`, just flip the selection 6153 // status, regardless of whether the model is the single model. 6154 } else if ( 'toggle' === method ) { 6155 selection[ this.selected() ? 'remove' : 'add' ]( model ); 6156 selection.single( model ); 6157 return; 6158 } else if ( 'add' === method ) { 6159 selection.add( model ); 6160 selection.single( model ); 6161 return; 6162 } 6163 6164 // Fixes bug that loses focus when selecting a featured image 6165 if ( ! method ) { 6166 method = 'add'; 6167 } 6168 6169 if ( method !== 'add' ) { 6170 method = 'reset'; 6171 } 6172 6173 if ( this.selected() ) { 6174 // If the model is the single model, remove it. 6175 // If it is not the same as the single model, 6176 // it now becomes the single model. 6177 selection[ single === model ? 'remove' : 'single' ]( model ); 6178 } else { 6179 // If the model is not selected, run the `method` on the 6180 // selection. By default, we `reset` the selection, but the 6181 // `method` can be set to `add` the model to the selection. 6182 selection[ method ]( model ); 6183 selection.single( model ); 6184 } 6185 }, 6186 6187 updateSelect: function() { 6188 this[ this.selected() ? 'select' : 'deselect' ](); 6189 }, 6190 /** 6191 * @returns {unresolved|Boolean} 6192 */ 6193 selected: function() { 6194 var selection = this.options.selection; 6195 if ( selection ) { 6196 return !! selection.get( this.model.cid ); 6197 } 6198 }, 6199 /** 6200 * @param {Backbone.Model} model 6201 * @param {Backbone.Collection} collection 6202 */ 6203 select: function( model, collection ) { 6204 var selection = this.options.selection, 6205 controller = this.controller; 6206 6207 // Check if a selection exists and if it's the collection provided. 6208 // If they're not the same collection, bail; we're in another 6209 // selection's event loop. 6210 if ( ! selection || ( collection && collection !== selection ) ) { 6211 return; 6212 } 6213 6214 // Bail if the model is already selected. 6215 if ( this.$el.hasClass( 'selected' ) ) { 6216 return; 6217 } 6218 6219 // Add 'selected' class to model, set aria-checked to true. 6220 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 6221 // Make the checkbox tabable, except in media grid (bulk select mode). 6222 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 6223 this.$( '.check' ).attr( 'tabindex', '0' ); 6224 } 6225 }, 6226 /** 6227 * @param {Backbone.Model} model 6228 * @param {Backbone.Collection} collection 6229 */ 6230 deselect: function( model, collection ) { 6231 var selection = this.options.selection; 6232 6233 // Check if a selection exists and if it's the collection provided. 6234 // If they're not the same collection, bail; we're in another 6235 // selection's event loop. 6236 if ( ! selection || ( collection && collection !== selection ) ) { 6237 return; 6238 } 6239 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 6240 .find( '.check' ).attr( 'tabindex', '-1' ); 6241 }, 6242 /** 6243 * @param {Backbone.Model} model 6244 * @param {Backbone.Collection} collection 6245 */ 6246 details: function( model, collection ) { 6247 var selection = this.options.selection, 6248 details; 6249 6250 if ( selection !== collection ) { 6251 return; 6252 } 6253 6254 details = selection.single(); 6255 this.$el.toggleClass( 'details', details === this.model ); 6256 }, 6257 /** 6258 * @param {string} size 6259 * @returns {Object} 6260 */ 6261 imageSize: function( size ) { 6262 var sizes = this.model.get('sizes'), matched = false; 6263 6264 size = size || 'medium'; 6265 6266 // Use the provided image size if possible. 6267 if ( sizes ) { 6268 if ( sizes[ size ] ) { 6269 matched = sizes[ size ]; 6270 } else if ( sizes.large ) { 6271 matched = sizes.large; 6272 } else if ( sizes.thumbnail ) { 6273 matched = sizes.thumbnail; 6274 } else if ( sizes.full ) { 6275 matched = sizes.full; 6276 } 6277 6278 if ( matched ) { 6279 return _.clone( matched ); 6280 } 6281 } 6282 6283 return { 6284 url: this.model.get('url'), 6285 width: this.model.get('width'), 6286 height: this.model.get('height'), 6287 orientation: this.model.get('orientation') 6288 }; 6289 }, 6290 /** 6291 * @param {Object} event 6292 */ 6293 updateSetting: function( event ) { 6294 var $setting = $( event.target ).closest('[data-setting]'), 6295 setting, value; 6296 6297 if ( ! $setting.length ) { 6298 return; 6299 } 6300 6301 setting = $setting.data('setting'); 6302 value = event.target.value; 6303 6304 if ( this.model.get( setting ) !== value ) { 6305 this.save( setting, value ); 6306 } 6307 }, 6308 6309 /** 6310 * Pass all the arguments to the model's save method. 6311 * 6312 * Records the aggregate status of all save requests and updates the 6313 * view's classes accordingly. 6314 */ 6315 save: function() { 6316 var view = this, 6317 save = this._save = this._save || { status: 'ready' }, 6318 request = this.model.save.apply( this.model, arguments ), 6319 requests = save.requests ? $.when( request, save.requests ) : request; 6320 6321 // If we're waiting to remove 'Saved.', stop. 6322 if ( save.savedTimer ) { 6323 clearTimeout( save.savedTimer ); 6324 } 6325 6326 this.updateSave('waiting'); 6327 save.requests = requests; 6328 requests.always( function() { 6329 // If we've performed another request since this one, bail. 6330 if ( save.requests !== requests ) { 6331 return; 6332 } 6333 6334 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 6335 save.savedTimer = setTimeout( function() { 6336 view.updateSave('ready'); 6337 delete save.savedTimer; 6338 }, 2000 ); 6339 }); 6340 }, 6341 /** 6342 * @param {string} status 6343 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6344 */ 6345 updateSave: function( status ) { 6346 var save = this._save = this._save || { status: 'ready' }; 6347 6348 if ( status && status !== save.status ) { 6349 this.$el.removeClass( 'save-' + save.status ); 6350 save.status = status; 6351 } 6352 6353 this.$el.addClass( 'save-' + save.status ); 6354 return this; 6355 }, 6356 6357 updateAll: function() { 6358 var $settings = this.$('[data-setting]'), 6359 model = this.model, 6360 changed; 6361 6362 changed = _.chain( $settings ).map( function( el ) { 6363 var $input = $('input, textarea, select, [value]', el ), 6364 setting, value; 6365 6366 if ( ! $input.length ) { 6367 return; 6368 } 6369 6370 setting = $(el).data('setting'); 6371 value = $input.val(); 6372 6373 // Record the value if it changed. 6374 if ( model.get( setting ) !== value ) { 6375 return [ setting, value ]; 6376 } 6377 }).compact().object().value(); 6378 6379 if ( ! _.isEmpty( changed ) ) { 6380 model.save( changed ); 6381 } 6382 }, 6383 /** 6384 * @param {Object} event 6385 */ 6386 removeFromLibrary: function( event ) { 6387 // Catch enter and space events 6388 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6389 return; 6390 } 6391 6392 // Stop propagation so the model isn't selected. 6393 event.stopPropagation(); 6394 6395 this.collection.remove( this.model ); 6396 }, 6397 6398 /** 6399 * Add the model if it isn't in the selection, if it is in the selection, 6400 * remove it. 6401 * 6402 * @param {[type]} event [description] 6403 * @return {[type]} [description] 6404 */ 6405 checkClickHandler: function ( event ) { 6406 var selection = this.options.selection; 6407 if ( ! selection ) { 6408 return; 6409 } 6410 event.stopPropagation(); 6411 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 6412 selection.remove( this.model ); 6413 // Move focus back to the attachment tile (from the check). 6414 this.$el.focus(); 6415 } else { 6416 selection.add( this.model ); 6417 } 6418 } 6419 }); 6420 6421 // Ensure settings remain in sync between attachment views. 6422 _.each({ 6423 caption: '_syncCaption', 6424 title: '_syncTitle', 6425 artist: '_syncArtist', 6426 album: '_syncAlbum' 6427 }, function( method, setting ) { 6428 /** 6429 * @function _syncCaption 6430 * @memberOf wp.media.view.Attachment 6431 * @instance 6432 * 6433 * @param {Backbone.Model} model 6434 * @param {string} value 6435 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6436 */ 6437 /** 6438 * @function _syncTitle 6439 * @memberOf wp.media.view.Attachment 6440 * @instance 6441 * 6442 * @param {Backbone.Model} model 6443 * @param {string} value 6444 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6445 */ 6446 /** 6447 * @function _syncArtist 6448 * @memberOf wp.media.view.Attachment 6449 * @instance 6450 * 6451 * @param {Backbone.Model} model 6452 * @param {string} value 6453 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6454 */ 6455 /** 6456 * @function _syncAlbum 6457 * @memberOf wp.media.view.Attachment 6458 * @instance 6459 * 6460 * @param {Backbone.Model} model 6461 * @param {string} value 6462 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6463 */ 6464 Attachment.prototype[ method ] = function( model, value ) { 6465 var $setting = this.$('[data-setting="' + setting + '"]'); 6466 6467 if ( ! $setting.length ) { 6468 return this; 6469 } 6470 6471 // If the updated value is in sync with the value in the DOM, there 6472 // is no need to re-render. If we're currently editing the value, 6473 // it will automatically be in sync, suppressing the re-render for 6474 // the view we're editing, while updating any others. 6475 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 6476 return this; 6477 } 6478 6479 return this.render(); 6480 }; 6481 }); 6482 6483 module.exports = Attachment; 6484 6485 6486 /***/ }), 6487 /* 70 */ 6488 /***/ (function(module, exports) { 6489 6490 /** 6491 * wp.media.view.Attachment.Library 6492 * 6493 * @memberOf wp.media.view.Attachment 6494 * 6495 * @class 6496 * @augments wp.media.view.Attachment 6497 * @augments wp.media.View 6498 * @augments wp.Backbone.View 6499 * @augments Backbone.View 6500 */ 6501 var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{ 6502 buttons: { 6503 check: true 6504 } 6505 }); 6506 6507 module.exports = Library; 6508 6509 6510 /***/ }), 6511 /* 71 */ 6512 /***/ (function(module, exports) { 6513 6514 /** 6515 * wp.media.view.Attachment.EditLibrary 6516 * 6517 * @memberOf wp.media.view.Attachment 6518 * 6519 * @class 6520 * @augments wp.media.view.Attachment 6521 * @augments wp.media.View 6522 * @augments wp.Backbone.View 6523 * @augments Backbone.View 6524 */ 6525 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{ 6526 buttons: { 6527 close: true 6528 } 6529 }); 6530 6531 module.exports = EditLibrary; 6532 6533 6534 /***/ }), 6535 /* 72 */ 6536 /***/ (function(module, exports) { 6537 6538 var View = wp.media.View, 6539 $ = jQuery, 6540 Attachments; 6541 6542 /** 6543 * wp.media.view.Attachments 6544 * 6545 * @memberOf wp.media.view 6546 * 6547 * @class 6548 * @augments wp.media.View 6549 * @augments wp.Backbone.View 6550 * @augments Backbone.View 6551 */ 6552 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 6553 tagName: 'ul', 6554 className: 'attachments', 6555 6556 attributes: { 6557 tabIndex: -1 6558 }, 6559 6560 initialize: function() { 6561 this.el.id = _.uniqueId('__attachments-view-'); 6562 6563 _.defaults( this.options, { 6564 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 6565 refreshThreshold: 3, 6566 AttachmentView: wp.media.view.Attachment, 6567 sortable: false, 6568 resize: true, 6569 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 6570 }); 6571 6572 this._viewsByCid = {}; 6573 this.$window = $( window ); 6574 this.resizeEvent = 'resize.media-modal-columns'; 6575 6576 this.collection.on( 'add', function( attachment ) { 6577 this.views.add( this.createAttachmentView( attachment ), { 6578 at: this.collection.indexOf( attachment ) 6579 }); 6580 }, this ); 6581 6582 this.collection.on( 'remove', function( attachment ) { 6583 var view = this._viewsByCid[ attachment.cid ]; 6584 delete this._viewsByCid[ attachment.cid ]; 6585 6586 if ( view ) { 6587 view.remove(); 6588 } 6589 }, this ); 6590 6591 this.collection.on( 'reset', this.render, this ); 6592 6593 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 6594 6595 // Throttle the scroll handler and bind this. 6596 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 6597 6598 this.options.scrollElement = this.options.scrollElement || this.el; 6599 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 6600 6601 this.initSortable(); 6602 6603 _.bindAll( this, 'setColumns' ); 6604 6605 if ( this.options.resize ) { 6606 this.on( 'ready', this.bindEvents ); 6607 this.controller.on( 'open', this.setColumns ); 6608 6609 // Call this.setColumns() after this view has been rendered in the DOM so 6610 // attachments get proper width applied. 6611 _.defer( this.setColumns, this ); 6612 } 6613 }, 6614 6615 bindEvents: function() { 6616 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 6617 }, 6618 6619 attachmentFocus: function() { 6620 this.$( 'li:first' ).focus(); 6621 }, 6622 6623 restoreFocus: function() { 6624 this.$( 'li.selected:first' ).focus(); 6625 }, 6626 6627 arrowEvent: function( event ) { 6628 var attachments = this.$el.children( 'li' ), 6629 perRow = this.columns, 6630 index = attachments.filter( ':focus' ).index(), 6631 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 6632 6633 if ( index === -1 ) { 6634 return; 6635 } 6636 6637 // Left arrow 6638 if ( 37 === event.keyCode ) { 6639 if ( 0 === index ) { 6640 return; 6641 } 6642 attachments.eq( index - 1 ).focus(); 6643 } 6644 6645 // Up arrow 6646 if ( 38 === event.keyCode ) { 6647 if ( 1 === row ) { 6648 return; 6649 } 6650 attachments.eq( index - perRow ).focus(); 6651 } 6652 6653 // Right arrow 6654 if ( 39 === event.keyCode ) { 6655 if ( attachments.length === index ) { 6656 return; 6657 } 6658 attachments.eq( index + 1 ).focus(); 6659 } 6660 6661 // Down arrow 6662 if ( 40 === event.keyCode ) { 6663 if ( Math.ceil( attachments.length / perRow ) === row ) { 6664 return; 6665 } 6666 attachments.eq( index + perRow ).focus(); 6667 } 6668 }, 6669 6670 dispose: function() { 6671 this.collection.props.off( null, null, this ); 6672 if ( this.options.resize ) { 6673 this.$window.off( this.resizeEvent ); 6674 } 6675 6676 /** 6677 * call 'dispose' directly on the parent class 6678 */ 6679 View.prototype.dispose.apply( this, arguments ); 6680 }, 6681 6682 setColumns: function() { 6683 var prev = this.columns, 6684 width = this.$el.width(); 6685 6686 if ( width ) { 6687 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 6688 6689 if ( ! prev || prev !== this.columns ) { 6690 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 6691 } 6692 } 6693 }, 6694 6695 initSortable: function() { 6696 var collection = this.collection; 6697 6698 if ( ! this.options.sortable || ! $.fn.sortable ) { 6699 return; 6700 } 6701 6702 this.$el.sortable( _.extend({ 6703 // If the `collection` has a `comparator`, disable sorting. 6704 disabled: !! collection.comparator, 6705 6706 // Change the position of the attachment as soon as the 6707 // mouse pointer overlaps a thumbnail. 6708 tolerance: 'pointer', 6709 6710 // Record the initial `index` of the dragged model. 6711 start: function( event, ui ) { 6712 ui.item.data('sortableIndexStart', ui.item.index()); 6713 }, 6714 6715 // Update the model's index in the collection. 6716 // Do so silently, as the view is already accurate. 6717 update: function( event, ui ) { 6718 var model = collection.at( ui.item.data('sortableIndexStart') ), 6719 comparator = collection.comparator; 6720 6721 // Temporarily disable the comparator to prevent `add` 6722 // from re-sorting. 6723 delete collection.comparator; 6724 6725 // Silently shift the model to its new index. 6726 collection.remove( model, { 6727 silent: true 6728 }); 6729 collection.add( model, { 6730 silent: true, 6731 at: ui.item.index() 6732 }); 6733 6734 // Restore the comparator. 6735 collection.comparator = comparator; 6736 6737 // Fire the `reset` event to ensure other collections sync. 6738 collection.trigger( 'reset', collection ); 6739 6740 // If the collection is sorted by menu order, 6741 // update the menu order. 6742 collection.saveMenuOrder(); 6743 } 6744 }, this.options.sortable ) ); 6745 6746 // If the `orderby` property is changed on the `collection`, 6747 // check to see if we have a `comparator`. If so, disable sorting. 6748 collection.props.on( 'change:orderby', function() { 6749 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 6750 }, this ); 6751 6752 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 6753 this.refreshSortable(); 6754 }, 6755 6756 refreshSortable: function() { 6757 if ( ! this.options.sortable || ! $.fn.sortable ) { 6758 return; 6759 } 6760 6761 // If the `collection` has a `comparator`, disable sorting. 6762 var collection = this.collection, 6763 orderby = collection.props.get('orderby'), 6764 enabled = 'menuOrder' === orderby || ! collection.comparator; 6765 6766 this.$el.sortable( 'option', 'disabled', ! enabled ); 6767 }, 6768 6769 /** 6770 * @param {wp.media.model.Attachment} attachment 6771 * @returns {wp.media.View} 6772 */ 6773 createAttachmentView: function( attachment ) { 6774 var view = new this.options.AttachmentView({ 6775 controller: this.controller, 6776 model: attachment, 6777 collection: this.collection, 6778 selection: this.options.selection 6779 }); 6780 6781 return this._viewsByCid[ attachment.cid ] = view; 6782 }, 6783 6784 prepare: function() { 6785 // Create all of the Attachment views, and replace 6786 // the list in a single DOM operation. 6787 if ( this.collection.length ) { 6788 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 6789 6790 // If there are no elements, clear the views and load some. 6791 } else { 6792 this.views.unset(); 6793 this.collection.more().done( this.scroll ); 6794 } 6795 }, 6796 6797 ready: function() { 6798 // Trigger the scroll event to check if we're within the 6799 // threshold to query for additional attachments. 6800 this.scroll(); 6801 }, 6802 6803 scroll: function() { 6804 var view = this, 6805 el = this.options.scrollElement, 6806 scrollTop = el.scrollTop, 6807 toolbar; 6808 6809 // The scroll event occurs on the document, but the element 6810 // that should be checked is the document body. 6811 if ( el === document ) { 6812 el = document.body; 6813 scrollTop = $(document).scrollTop(); 6814 } 6815 6816 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 6817 return; 6818 } 6819 6820 toolbar = this.views.parent.toolbar; 6821 6822 // Show the spinner only if we are close to the bottom. 6823 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 6824 toolbar.get('spinner').show(); 6825 } 6826 6827 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 6828 this.collection.more().done(function() { 6829 view.scroll(); 6830 toolbar.get('spinner').hide(); 6831 }); 6832 } 6833 } 6834 }); 6835 6836 module.exports = Attachments; 6837 6838 6839 /***/ }), 6840 /* 73 */ 6841 /***/ (function(module, exports) { 6842 6843 var l10n = wp.media.view.l10n, 6844 Search; 6845 6846 /** 6847 * wp.media.view.Search 6848 * 6849 * @memberOf wp.media.view 6850 * 6851 * @class 6852 * @augments wp.media.View 6853 * @augments wp.Backbone.View 6854 * @augments Backbone.View 6855 */ 6856 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{ 6857 tagName: 'input', 6858 className: 'search', 6859 id: 'media-search-input', 6860 6861 attributes: { 6862 type: 'search', 6863 placeholder: l10n.searchMediaPlaceholder 6864 }, 6865 6866 events: { 6867 'input': 'search', 6868 'keyup': 'search' 6869 }, 6870 6871 /** 6872 * @returns {wp.media.view.Search} Returns itself to allow chaining 6873 */ 6874 render: function() { 6875 this.el.value = this.model.escape('search'); 6876 return this; 6877 }, 6878 6879 search: _.debounce( function( event ) { 6880 if ( event.target.value ) { 6881 this.model.set( 'search', event.target.value ); 6882 } else { 6883 this.model.unset('search'); 6884 } 6885 }, 300 ) 6886 }); 6887 6888 module.exports = Search; 6889 6890 6891 /***/ }), 6892 /* 74 */ 6893 /***/ (function(module, exports) { 6894 2453 6895 var $ = jQuery, 2454 6896 AttachmentFilters; … … 2529 6971 module.exports = AttachmentFilters; 2530 6972 2531 },{}],22:[function(require,module,exports){ 2532 var l10n = wp.media.view.l10n, 2533 All; 2534 2535 /** 2536 * wp.media.view.AttachmentFilters.All 2537 * 2538 * @memberOf wp.media.view.AttachmentFilters 2539 * 2540 * @class 2541 * @augments wp.media.view.AttachmentFilters 2542 * @augments wp.media.View 2543 * @augments wp.Backbone.View 2544 * @augments Backbone.View 2545 */ 2546 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{ 2547 createFilters: function() { 2548 var filters = {}; 2549 2550 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 2551 filters[ key ] = { 2552 text: text, 2553 props: { 2554 status: null, 2555 type: key, 2556 uploadedTo: null, 2557 orderby: 'date', 2558 order: 'DESC' 2559 } 2560 }; 2561 }); 2562 2563 filters.all = { 2564 text: l10n.allMediaItems, 2565 props: { 2566 status: null, 2567 type: null, 2568 uploadedTo: null, 2569 orderby: 'date', 2570 order: 'DESC' 2571 }, 2572 priority: 10 2573 }; 2574 2575 if ( wp.media.view.settings.post.id ) { 2576 filters.uploaded = { 2577 text: l10n.uploadedToThisPost, 2578 props: { 2579 status: null, 2580 type: null, 2581 uploadedTo: wp.media.view.settings.post.id, 2582 orderby: 'menuOrder', 2583 order: 'ASC' 2584 }, 2585 priority: 20 2586 }; 2587 } 2588 2589 filters.unattached = { 2590 text: l10n.unattached, 2591 props: { 2592 status: null, 2593 uploadedTo: 0, 2594 type: null, 2595 orderby: 'menuOrder', 2596 order: 'ASC' 2597 }, 2598 priority: 50 2599 }; 2600 2601 if ( wp.media.view.settings.mediaTrash && 2602 this.controller.isModeActive( 'grid' ) ) { 2603 2604 filters.trash = { 2605 text: l10n.trash, 2606 props: { 2607 uploadedTo: null, 2608 status: 'trash', 2609 type: null, 2610 orderby: 'date', 2611 order: 'DESC' 2612 }, 2613 priority: 50 2614 }; 2615 } 2616 2617 this.filters = filters; 2618 } 2619 }); 2620 2621 module.exports = All; 2622 2623 },{}],23:[function(require,module,exports){ 6973 6974 /***/ }), 6975 /* 75 */ 6976 /***/ (function(module, exports) { 6977 2624 6978 var l10n = wp.media.view.l10n, 2625 6979 DateFilter; … … 2664 7018 module.exports = DateFilter; 2665 7019 2666 },{}],24:[function(require,module,exports){ 7020 7021 /***/ }), 7022 /* 76 */ 7023 /***/ (function(module, exports) { 7024 2667 7025 var l10n = wp.media.view.l10n, 2668 7026 Uploaded; … … 2725 7083 module.exports = Uploaded; 2726 7084 2727 },{}],25:[function(require,module,exports){ 2728 var View = wp.media.View, 2729 $ = jQuery, 2730 Attachment; 7085 7086 /***/ }), 7087 /* 77 */ 7088 /***/ (function(module, exports) { 7089 7090 var l10n = wp.media.view.l10n, 7091 All; 2731 7092 2732 7093 /** 2733 * wp.media.view.Attachment 2734 * 2735 * @memberOf wp.media.view 7094 * wp.media.view.AttachmentFilters.All 7095 * 7096 * @memberOf wp.media.view.AttachmentFilters 2736 7097 * 2737 7098 * @class 7099 * @augments wp.media.view.AttachmentFilters 2738 7100 * @augments wp.media.View 2739 7101 * @augments wp.Backbone.View 2740 7102 * @augments Backbone.View 2741 7103 */ 2742 Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{ 2743 tagName: 'li', 2744 className: 'attachment', 2745 template: wp.template('attachment'), 2746 2747 attributes: function() { 2748 return { 2749 'tabIndex': 0, 2750 'role': 'checkbox', 2751 'aria-label': this.model.get( 'title' ), 2752 'aria-checked': false, 2753 'data-id': this.model.get( 'id' ) 7104 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{ 7105 createFilters: function() { 7106 var filters = {}; 7107 7108 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 7109 filters[ key ] = { 7110 text: text, 7111 props: { 7112 status: null, 7113 type: key, 7114 uploadedTo: null, 7115 orderby: 'date', 7116 order: 'DESC' 7117 } 7118 }; 7119 }); 7120 7121 filters.all = { 7122 text: l10n.allMediaItems, 7123 props: { 7124 status: null, 7125 type: null, 7126 uploadedTo: null, 7127 orderby: 'date', 7128 order: 'DESC' 7129 }, 7130 priority: 10 2754 7131 }; 2755 }, 2756 2757 events: { 2758 'click': 'toggleSelectionHandler', 2759 'change [data-setting]': 'updateSetting', 2760 'change [data-setting] input': 'updateSetting', 2761 'change [data-setting] select': 'updateSetting', 2762 'change [data-setting] textarea': 'updateSetting', 2763 'click .attachment-close': 'removeFromLibrary', 2764 'click .check': 'checkClickHandler', 2765 'keydown': 'toggleSelectionHandler' 2766 }, 2767 2768 buttons: {}, 2769 2770 initialize: function() { 2771 var selection = this.options.selection, 2772 options = _.defaults( this.options, { 2773 rerenderOnModelChange: true 2774 } ); 2775 2776 if ( options.rerenderOnModelChange ) { 2777 this.listenTo( this.model, 'change', this.render ); 2778 } else { 2779 this.listenTo( this.model, 'change:percent', this.progress ); 2780 } 2781 this.listenTo( this.model, 'change:title', this._syncTitle ); 2782 this.listenTo( this.model, 'change:caption', this._syncCaption ); 2783 this.listenTo( this.model, 'change:artist', this._syncArtist ); 2784 this.listenTo( this.model, 'change:album', this._syncAlbum ); 2785 2786 // Update the selection. 2787 this.listenTo( this.model, 'add', this.select ); 2788 this.listenTo( this.model, 'remove', this.deselect ); 2789 if ( selection ) { 2790 selection.on( 'reset', this.updateSelect, this ); 2791 // Update the model's details view. 2792 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 2793 this.details( this.model, this.controller.state().get('selection') ); 2794 } 2795 2796 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 2797 }, 2798 /** 2799 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2800 */ 2801 dispose: function() { 2802 var selection = this.options.selection; 2803 2804 // Make sure all settings are saved before removing the view. 2805 this.updateAll(); 2806 2807 if ( selection ) { 2808 selection.off( null, null, this ); 2809 } 2810 /** 2811 * call 'dispose' directly on the parent class 2812 */ 2813 View.prototype.dispose.apply( this, arguments ); 2814 return this; 2815 }, 2816 /** 2817 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2818 */ 2819 render: function() { 2820 var options = _.defaults( this.model.toJSON(), { 2821 orientation: 'landscape', 2822 uploading: false, 2823 type: '', 2824 subtype: '', 2825 icon: '', 2826 filename: '', 2827 caption: '', 2828 title: '', 2829 dateFormatted: '', 2830 width: '', 2831 height: '', 2832 compat: false, 2833 alt: '', 2834 description: '' 2835 }, this.options ); 2836 2837 options.buttons = this.buttons; 2838 options.describe = this.controller.state().get('describe'); 2839 2840 if ( 'image' === options.type ) { 2841 options.size = this.imageSize(); 2842 } 2843 2844 options.can = {}; 2845 if ( options.nonces ) { 2846 options.can.remove = !! options.nonces['delete']; 2847 options.can.save = !! options.nonces.update; 2848 } 2849 2850 if ( this.controller.state().get('allowLocalEdits') ) { 2851 options.allowLocalEdits = true; 2852 } 2853 2854 if ( options.uploading && ! options.percent ) { 2855 options.percent = 0; 2856 } 2857 2858 this.views.detach(); 2859 this.$el.html( this.template( options ) ); 2860 2861 this.$el.toggleClass( 'uploading', options.uploading ); 2862 2863 if ( options.uploading ) { 2864 this.$bar = this.$('.media-progress-bar div'); 2865 } else { 2866 delete this.$bar; 2867 } 2868 2869 // Check if the model is selected. 2870 this.updateSelect(); 2871 2872 // Update the save status. 2873 this.updateSave(); 2874 2875 this.views.render(); 2876 2877 return this; 2878 }, 2879 2880 progress: function() { 2881 if ( this.$bar && this.$bar.length ) { 2882 this.$bar.width( this.model.get('percent') + '%' ); 2883 } 2884 }, 2885 2886 /** 2887 * @param {Object} event 2888 */ 2889 toggleSelectionHandler: function( event ) { 2890 var method; 2891 2892 // Don't do anything inside inputs and on the attachment check and remove buttons. 2893 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 2894 return; 2895 } 2896 2897 // Catch arrow events 2898 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 2899 this.controller.trigger( 'attachment:keydown:arrow', event ); 2900 return; 2901 } 2902 2903 // Catch enter and space events 2904 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 2905 return; 2906 } 2907 2908 event.preventDefault(); 2909 2910 // In the grid view, bubble up an edit:attachment event to the controller. 2911 if ( this.controller.isModeActive( 'grid' ) ) { 2912 if ( this.controller.isModeActive( 'edit' ) ) { 2913 // Pass the current target to restore focus when closing 2914 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 2915 return; 2916 } 2917 2918 if ( this.controller.isModeActive( 'select' ) ) { 2919 method = 'toggle'; 2920 } 2921 } 2922 2923 if ( event.shiftKey ) { 2924 method = 'between'; 2925 } else if ( event.ctrlKey || event.metaKey ) { 2926 method = 'toggle'; 2927 } 2928 2929 this.toggleSelection({ 2930 method: method 2931 }); 2932 2933 this.controller.trigger( 'selection:toggle' ); 2934 }, 2935 /** 2936 * @param {Object} options 2937 */ 2938 toggleSelection: function( options ) { 2939 var collection = this.collection, 2940 selection = this.options.selection, 2941 model = this.model, 2942 method = options && options.method, 2943 single, models, singleIndex, modelIndex; 2944 2945 if ( ! selection ) { 2946 return; 2947 } 2948 2949 single = selection.single(); 2950 method = _.isUndefined( method ) ? selection.multiple : method; 2951 2952 // If the `method` is set to `between`, select all models that 2953 // exist between the current and the selected model. 2954 if ( 'between' === method && single && selection.multiple ) { 2955 // If the models are the same, short-circuit. 2956 if ( single === model ) { 2957 return; 2958 } 2959 2960 singleIndex = collection.indexOf( single ); 2961 modelIndex = collection.indexOf( this.model ); 2962 2963 if ( singleIndex < modelIndex ) { 2964 models = collection.models.slice( singleIndex, modelIndex + 1 ); 2965 } else { 2966 models = collection.models.slice( modelIndex, singleIndex + 1 ); 2967 } 2968 2969 selection.add( models ); 2970 selection.single( model ); 2971 return; 2972 2973 // If the `method` is set to `toggle`, just flip the selection 2974 // status, regardless of whether the model is the single model. 2975 } else if ( 'toggle' === method ) { 2976 selection[ this.selected() ? 'remove' : 'add' ]( model ); 2977 selection.single( model ); 2978 return; 2979 } else if ( 'add' === method ) { 2980 selection.add( model ); 2981 selection.single( model ); 2982 return; 2983 } 2984 2985 // Fixes bug that loses focus when selecting a featured image 2986 if ( ! method ) { 2987 method = 'add'; 2988 } 2989 2990 if ( method !== 'add' ) { 2991 method = 'reset'; 2992 } 2993 2994 if ( this.selected() ) { 2995 // If the model is the single model, remove it. 2996 // If it is not the same as the single model, 2997 // it now becomes the single model. 2998 selection[ single === model ? 'remove' : 'single' ]( model ); 2999 } else { 3000 // If the model is not selected, run the `method` on the 3001 // selection. By default, we `reset` the selection, but the 3002 // `method` can be set to `add` the model to the selection. 3003 selection[ method ]( model ); 3004 selection.single( model ); 3005 } 3006 }, 3007 3008 updateSelect: function() { 3009 this[ this.selected() ? 'select' : 'deselect' ](); 3010 }, 3011 /** 3012 * @returns {unresolved|Boolean} 3013 */ 3014 selected: function() { 3015 var selection = this.options.selection; 3016 if ( selection ) { 3017 return !! selection.get( this.model.cid ); 3018 } 3019 }, 3020 /** 3021 * @param {Backbone.Model} model 3022 * @param {Backbone.Collection} collection 3023 */ 3024 select: function( model, collection ) { 3025 var selection = this.options.selection, 3026 controller = this.controller; 3027 3028 // Check if a selection exists and if it's the collection provided. 3029 // If they're not the same collection, bail; we're in another 3030 // selection's event loop. 3031 if ( ! selection || ( collection && collection !== selection ) ) { 3032 return; 3033 } 3034 3035 // Bail if the model is already selected. 3036 if ( this.$el.hasClass( 'selected' ) ) { 3037 return; 3038 } 3039 3040 // Add 'selected' class to model, set aria-checked to true. 3041 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 3042 // Make the checkbox tabable, except in media grid (bulk select mode). 3043 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 3044 this.$( '.check' ).attr( 'tabindex', '0' ); 3045 } 3046 }, 3047 /** 3048 * @param {Backbone.Model} model 3049 * @param {Backbone.Collection} collection 3050 */ 3051 deselect: function( model, collection ) { 3052 var selection = this.options.selection; 3053 3054 // Check if a selection exists and if it's the collection provided. 3055 // If they're not the same collection, bail; we're in another 3056 // selection's event loop. 3057 if ( ! selection || ( collection && collection !== selection ) ) { 3058 return; 3059 } 3060 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 3061 .find( '.check' ).attr( 'tabindex', '-1' ); 3062 }, 3063 /** 3064 * @param {Backbone.Model} model 3065 * @param {Backbone.Collection} collection 3066 */ 3067 details: function( model, collection ) { 3068 var selection = this.options.selection, 3069 details; 3070 3071 if ( selection !== collection ) { 3072 return; 3073 } 3074 3075 details = selection.single(); 3076 this.$el.toggleClass( 'details', details === this.model ); 3077 }, 3078 /** 3079 * @param {string} size 3080 * @returns {Object} 3081 */ 3082 imageSize: function( size ) { 3083 var sizes = this.model.get('sizes'), matched = false; 3084 3085 size = size || 'medium'; 3086 3087 // Use the provided image size if possible. 3088 if ( sizes ) { 3089 if ( sizes[ size ] ) { 3090 matched = sizes[ size ]; 3091 } else if ( sizes.large ) { 3092 matched = sizes.large; 3093 } else if ( sizes.thumbnail ) { 3094 matched = sizes.thumbnail; 3095 } else if ( sizes.full ) { 3096 matched = sizes.full; 3097 } 3098 3099 if ( matched ) { 3100 return _.clone( matched ); 3101 } 3102 } 3103 3104 return { 3105 url: this.model.get('url'), 3106 width: this.model.get('width'), 3107 height: this.model.get('height'), 3108 orientation: this.model.get('orientation') 7132 7133 if ( wp.media.view.settings.post.id ) { 7134 filters.uploaded = { 7135 text: l10n.uploadedToThisPost, 7136 props: { 7137 status: null, 7138 type: null, 7139 uploadedTo: wp.media.view.settings.post.id, 7140 orderby: 'menuOrder', 7141 order: 'ASC' 7142 }, 7143 priority: 20 7144 }; 7145 } 7146 7147 filters.unattached = { 7148 text: l10n.unattached, 7149 props: { 7150 status: null, 7151 uploadedTo: 0, 7152 type: null, 7153 orderby: 'menuOrder', 7154 order: 'ASC' 7155 }, 7156 priority: 50 3109 7157 }; 3110 }, 3111 /** 3112 * @param {Object} event 3113 */ 3114 updateSetting: function( event ) { 3115 var $setting = $( event.target ).closest('[data-setting]'), 3116 setting, value; 3117 3118 if ( ! $setting.length ) { 3119 return; 3120 } 3121 3122 setting = $setting.data('setting'); 3123 value = event.target.value; 3124 3125 if ( this.model.get( setting ) !== value ) { 3126 this.save( setting, value ); 3127 } 3128 }, 3129 3130 /** 3131 * Pass all the arguments to the model's save method. 3132 * 3133 * Records the aggregate status of all save requests and updates the 3134 * view's classes accordingly. 3135 */ 3136 save: function() { 3137 var view = this, 3138 save = this._save = this._save || { status: 'ready' }, 3139 request = this.model.save.apply( this.model, arguments ), 3140 requests = save.requests ? $.when( request, save.requests ) : request; 3141 3142 // If we're waiting to remove 'Saved.', stop. 3143 if ( save.savedTimer ) { 3144 clearTimeout( save.savedTimer ); 3145 } 3146 3147 this.updateSave('waiting'); 3148 save.requests = requests; 3149 requests.always( function() { 3150 // If we've performed another request since this one, bail. 3151 if ( save.requests !== requests ) { 3152 return; 3153 } 3154 3155 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 3156 save.savedTimer = setTimeout( function() { 3157 view.updateSave('ready'); 3158 delete save.savedTimer; 3159 }, 2000 ); 3160 }); 3161 }, 3162 /** 3163 * @param {string} status 3164 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3165 */ 3166 updateSave: function( status ) { 3167 var save = this._save = this._save || { status: 'ready' }; 3168 3169 if ( status && status !== save.status ) { 3170 this.$el.removeClass( 'save-' + save.status ); 3171 save.status = status; 3172 } 3173 3174 this.$el.addClass( 'save-' + save.status ); 3175 return this; 3176 }, 3177 3178 updateAll: function() { 3179 var $settings = this.$('[data-setting]'), 3180 model = this.model, 3181 changed; 3182 3183 changed = _.chain( $settings ).map( function( el ) { 3184 var $input = $('input, textarea, select, [value]', el ), 3185 setting, value; 3186 3187 if ( ! $input.length ) { 3188 return; 3189 } 3190 3191 setting = $(el).data('setting'); 3192 value = $input.val(); 3193 3194 // Record the value if it changed. 3195 if ( model.get( setting ) !== value ) { 3196 return [ setting, value ]; 3197 } 3198 }).compact().object().value(); 3199 3200 if ( ! _.isEmpty( changed ) ) { 3201 model.save( changed ); 3202 } 3203 }, 3204 /** 3205 * @param {Object} event 3206 */ 3207 removeFromLibrary: function( event ) { 3208 // Catch enter and space events 3209 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3210 return; 3211 } 3212 3213 // Stop propagation so the model isn't selected. 3214 event.stopPropagation(); 3215 3216 this.collection.remove( this.model ); 3217 }, 3218 3219 /** 3220 * Add the model if it isn't in the selection, if it is in the selection, 3221 * remove it. 3222 * 3223 * @param {[type]} event [description] 3224 * @return {[type]} [description] 3225 */ 3226 checkClickHandler: function ( event ) { 3227 var selection = this.options.selection; 3228 if ( ! selection ) { 3229 return; 3230 } 3231 event.stopPropagation(); 3232 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 3233 selection.remove( this.model ); 3234 // Move focus back to the attachment tile (from the check). 3235 this.$el.focus(); 3236 } else { 3237 selection.add( this.model ); 3238 } 7158 7159 if ( wp.media.view.settings.mediaTrash && 7160 this.controller.isModeActive( 'grid' ) ) { 7161 7162 filters.trash = { 7163 text: l10n.trash, 7164 props: { 7165 uploadedTo: null, 7166 status: 'trash', 7167 type: null, 7168 orderby: 'date', 7169 order: 'DESC' 7170 }, 7171 priority: 50 7172 }; 7173 } 7174 7175 this.filters = filters; 3239 7176 } 3240 7177 }); 3241 7178 3242 // Ensure settings remain in sync between attachment views. 3243 _.each({ 3244 caption: '_syncCaption', 3245 title: '_syncTitle', 3246 artist: '_syncArtist', 3247 album: '_syncAlbum' 3248 }, function( method, setting ) { 3249 /** 3250 * @function _syncCaption 3251 * @memberOf wp.media.view.Attachment 3252 * @instance 3253 * 3254 * @param {Backbone.Model} model 3255 * @param {string} value 3256 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3257 */ 3258 /** 3259 * @function _syncTitle 3260 * @memberOf wp.media.view.Attachment 3261 * @instance 3262 * 3263 * @param {Backbone.Model} model 3264 * @param {string} value 3265 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3266 */ 3267 /** 3268 * @function _syncArtist 3269 * @memberOf wp.media.view.Attachment 3270 * @instance 3271 * 3272 * @param {Backbone.Model} model 3273 * @param {string} value 3274 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3275 */ 3276 /** 3277 * @function _syncAlbum 3278 * @memberOf wp.media.view.Attachment 3279 * @instance 3280 * 3281 * @param {Backbone.Model} model 3282 * @param {string} value 3283 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3284 */ 3285 Attachment.prototype[ method ] = function( model, value ) { 3286 var $setting = this.$('[data-setting="' + setting + '"]'); 3287 3288 if ( ! $setting.length ) { 3289 return this; 3290 } 3291 3292 // If the updated value is in sync with the value in the DOM, there 3293 // is no need to re-render. If we're currently editing the value, 3294 // it will automatically be in sync, suppressing the re-render for 3295 // the view we're editing, while updating any others. 3296 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 3297 return this; 3298 } 3299 3300 return this.render(); 3301 }; 3302 }); 3303 3304 module.exports = Attachment; 3305 3306 },{}],26:[function(require,module,exports){ 3307 var Attachment = wp.media.view.Attachment, 3308 l10n = wp.media.view.l10n, 3309 Details; 3310 3311 /** 3312 * wp.media.view.Attachment.Details 3313 * 3314 * @memberOf wp.media.view.Attachment 3315 * 3316 * @class 3317 * @augments wp.media.view.Attachment 3318 * @augments wp.media.View 3319 * @augments wp.Backbone.View 3320 * @augments Backbone.View 3321 */ 3322 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{ 3323 tagName: 'div', 3324 className: 'attachment-details', 3325 template: wp.template('attachment-details'), 3326 3327 attributes: function() { 3328 return { 3329 'tabIndex': 0, 3330 'data-id': this.model.get( 'id' ) 3331 }; 3332 }, 3333 3334 events: { 3335 'change [data-setting]': 'updateSetting', 3336 'change [data-setting] input': 'updateSetting', 3337 'change [data-setting] select': 'updateSetting', 3338 'change [data-setting] textarea': 'updateSetting', 3339 'click .delete-attachment': 'deleteAttachment', 3340 'click .trash-attachment': 'trashAttachment', 3341 'click .untrash-attachment': 'untrashAttachment', 3342 'click .edit-attachment': 'editAttachment', 3343 'keydown': 'toggleSelectionHandler' 3344 }, 3345 3346 initialize: function() { 3347 this.options = _.defaults( this.options, { 3348 rerenderOnModelChange: false 3349 }); 3350 3351 this.on( 'ready', this.initialFocus ); 3352 // Call 'initialize' directly on the parent class. 3353 Attachment.prototype.initialize.apply( this, arguments ); 3354 }, 3355 3356 initialFocus: function() { 3357 if ( ! wp.media.isTouchDevice ) { 3358 /* 3359 Previously focused the first ':input' (the readonly URL text field). 3360 Since the first ':input' is now a button (delete/trash): when pressing 3361 spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment 3362 as soon as focus is moved. Explicitly target the first text field for now. 3363 @todo change initial focus logic, also for accessibility. 3364 */ 3365 this.$( 'input[type="text"]' ).eq( 0 ).focus(); 3366 } 3367 }, 3368 /** 3369 * @param {Object} event 3370 */ 3371 deleteAttachment: function( event ) { 3372 event.preventDefault(); 3373 3374 if ( window.confirm( l10n.warnDelete ) ) { 3375 this.model.destroy(); 3376 // Keep focus inside media modal 3377 // after image is deleted 3378 this.controller.modal.focusManager.focus(); 3379 } 3380 }, 3381 /** 3382 * @param {Object} event 3383 */ 3384 trashAttachment: function( event ) { 3385 var library = this.controller.library; 3386 event.preventDefault(); 3387 3388 if ( wp.media.view.settings.mediaTrash && 3389 'edit-metadata' === this.controller.content.mode() ) { 3390 3391 this.model.set( 'status', 'trash' ); 3392 this.model.save().done( function() { 3393 library._requery( true ); 3394 } ); 3395 } else { 3396 this.model.destroy(); 3397 } 3398 }, 3399 /** 3400 * @param {Object} event 3401 */ 3402 untrashAttachment: function( event ) { 3403 var library = this.controller.library; 3404 event.preventDefault(); 3405 3406 this.model.set( 'status', 'inherit' ); 3407 this.model.save().done( function() { 3408 library._requery( true ); 3409 } ); 3410 }, 3411 /** 3412 * @param {Object} event 3413 */ 3414 editAttachment: function( event ) { 3415 var editState = this.controller.states.get( 'edit-image' ); 3416 if ( window.imageEdit && editState ) { 3417 event.preventDefault(); 3418 3419 editState.set( 'image', this.model ); 3420 this.controller.setState( 'edit-image' ); 3421 } else { 3422 this.$el.addClass('needs-refresh'); 3423 } 3424 }, 3425 /** 3426 * When reverse tabbing(shift+tab) out of the right details panel, deliver 3427 * the focus to the item in the list that was being edited. 3428 * 3429 * @param {Object} event 3430 */ 3431 toggleSelectionHandler: function( event ) { 3432 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 3433 this.controller.trigger( 'attachment:details:shift-tab', event ); 3434 return false; 3435 } 3436 3437 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 3438 this.controller.trigger( 'attachment:keydown:arrow', event ); 3439 return; 3440 } 3441 } 3442 }); 3443 3444 module.exports = Details; 3445 3446 },{}],27:[function(require,module,exports){ 3447 /** 3448 * wp.media.view.Attachment.EditLibrary 3449 * 3450 * @memberOf wp.media.view.Attachment 3451 * 3452 * @class 3453 * @augments wp.media.view.Attachment 3454 * @augments wp.media.View 3455 * @augments wp.Backbone.View 3456 * @augments Backbone.View 3457 */ 3458 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{ 3459 buttons: { 3460 close: true 3461 } 3462 }); 3463 3464 module.exports = EditLibrary; 3465 3466 },{}],28:[function(require,module,exports){ 3467 /** 3468 * wp.media.view.Attachment.EditSelection 3469 * 3470 * @memberOf wp.media.view.Attachment 3471 * 3472 * @class 3473 * @augments wp.media.view.Attachment.Selection 3474 * @augments wp.media.view.Attachment 3475 * @augments wp.media.View 3476 * @augments wp.Backbone.View 3477 * @augments Backbone.View 3478 */ 3479 var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{ 3480 buttons: { 3481 close: true 3482 } 3483 }); 3484 3485 module.exports = EditSelection; 3486 3487 },{}],29:[function(require,module,exports){ 3488 /** 3489 * wp.media.view.Attachment.Library 3490 * 3491 * @memberOf wp.media.view.Attachment 3492 * 3493 * @class 3494 * @augments wp.media.view.Attachment 3495 * @augments wp.media.View 3496 * @augments wp.Backbone.View 3497 * @augments Backbone.View 3498 */ 3499 var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{ 3500 buttons: { 3501 check: true 3502 } 3503 }); 3504 3505 module.exports = Library; 3506 3507 },{}],30:[function(require,module,exports){ 3508 /** 3509 * wp.media.view.Attachment.Selection 3510 * 3511 * @memberOf wp.media.view.Attachment 3512 * 3513 * @class 3514 * @augments wp.media.view.Attachment 3515 * @augments wp.media.View 3516 * @augments wp.Backbone.View 3517 * @augments Backbone.View 3518 */ 3519 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{ 3520 className: 'attachment selection', 3521 3522 // On click, just select the model, instead of removing the model from 3523 // the selection. 3524 toggleSelection: function() { 3525 this.options.selection.single( this.model ); 3526 } 3527 }); 3528 3529 module.exports = Selection; 3530 3531 },{}],31:[function(require,module,exports){ 3532 var View = wp.media.View, 3533 $ = jQuery, 3534 Attachments; 3535 3536 /** 3537 * wp.media.view.Attachments 3538 * 3539 * @memberOf wp.media.view 3540 * 3541 * @class 3542 * @augments wp.media.View 3543 * @augments wp.Backbone.View 3544 * @augments Backbone.View 3545 */ 3546 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 3547 tagName: 'ul', 3548 className: 'attachments', 3549 3550 attributes: { 3551 tabIndex: -1 3552 }, 3553 3554 initialize: function() { 3555 this.el.id = _.uniqueId('__attachments-view-'); 3556 3557 _.defaults( this.options, { 3558 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 3559 refreshThreshold: 3, 3560 AttachmentView: wp.media.view.Attachment, 3561 sortable: false, 3562 resize: true, 3563 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 3564 }); 3565 3566 this._viewsByCid = {}; 3567 this.$window = $( window ); 3568 this.resizeEvent = 'resize.media-modal-columns'; 3569 3570 this.collection.on( 'add', function( attachment ) { 3571 this.views.add( this.createAttachmentView( attachment ), { 3572 at: this.collection.indexOf( attachment ) 3573 }); 3574 }, this ); 3575 3576 this.collection.on( 'remove', function( attachment ) { 3577 var view = this._viewsByCid[ attachment.cid ]; 3578 delete this._viewsByCid[ attachment.cid ]; 3579 3580 if ( view ) { 3581 view.remove(); 3582 } 3583 }, this ); 3584 3585 this.collection.on( 'reset', this.render, this ); 3586 3587 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 3588 3589 // Throttle the scroll handler and bind this. 3590 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 3591 3592 this.options.scrollElement = this.options.scrollElement || this.el; 3593 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 3594 3595 this.initSortable(); 3596 3597 _.bindAll( this, 'setColumns' ); 3598 3599 if ( this.options.resize ) { 3600 this.on( 'ready', this.bindEvents ); 3601 this.controller.on( 'open', this.setColumns ); 3602 3603 // Call this.setColumns() after this view has been rendered in the DOM so 3604 // attachments get proper width applied. 3605 _.defer( this.setColumns, this ); 3606 } 3607 }, 3608 3609 bindEvents: function() { 3610 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 3611 }, 3612 3613 attachmentFocus: function() { 3614 this.$( 'li:first' ).focus(); 3615 }, 3616 3617 restoreFocus: function() { 3618 this.$( 'li.selected:first' ).focus(); 3619 }, 3620 3621 arrowEvent: function( event ) { 3622 var attachments = this.$el.children( 'li' ), 3623 perRow = this.columns, 3624 index = attachments.filter( ':focus' ).index(), 3625 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 3626 3627 if ( index === -1 ) { 3628 return; 3629 } 3630 3631 // Left arrow 3632 if ( 37 === event.keyCode ) { 3633 if ( 0 === index ) { 3634 return; 3635 } 3636 attachments.eq( index - 1 ).focus(); 3637 } 3638 3639 // Up arrow 3640 if ( 38 === event.keyCode ) { 3641 if ( 1 === row ) { 3642 return; 3643 } 3644 attachments.eq( index - perRow ).focus(); 3645 } 3646 3647 // Right arrow 3648 if ( 39 === event.keyCode ) { 3649 if ( attachments.length === index ) { 3650 return; 3651 } 3652 attachments.eq( index + 1 ).focus(); 3653 } 3654 3655 // Down arrow 3656 if ( 40 === event.keyCode ) { 3657 if ( Math.ceil( attachments.length / perRow ) === row ) { 3658 return; 3659 } 3660 attachments.eq( index + perRow ).focus(); 3661 } 3662 }, 3663 3664 dispose: function() { 3665 this.collection.props.off( null, null, this ); 3666 if ( this.options.resize ) { 3667 this.$window.off( this.resizeEvent ); 3668 } 3669 3670 /** 3671 * call 'dispose' directly on the parent class 3672 */ 3673 View.prototype.dispose.apply( this, arguments ); 3674 }, 3675 3676 setColumns: function() { 3677 var prev = this.columns, 3678 width = this.$el.width(); 3679 3680 if ( width ) { 3681 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 3682 3683 if ( ! prev || prev !== this.columns ) { 3684 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 3685 } 3686 } 3687 }, 3688 3689 initSortable: function() { 3690 var collection = this.collection; 3691 3692 if ( ! this.options.sortable || ! $.fn.sortable ) { 3693 return; 3694 } 3695 3696 this.$el.sortable( _.extend({ 3697 // If the `collection` has a `comparator`, disable sorting. 3698 disabled: !! collection.comparator, 3699 3700 // Change the position of the attachment as soon as the 3701 // mouse pointer overlaps a thumbnail. 3702 tolerance: 'pointer', 3703 3704 // Record the initial `index` of the dragged model. 3705 start: function( event, ui ) { 3706 ui.item.data('sortableIndexStart', ui.item.index()); 3707 }, 3708 3709 // Update the model's index in the collection. 3710 // Do so silently, as the view is already accurate. 3711 update: function( event, ui ) { 3712 var model = collection.at( ui.item.data('sortableIndexStart') ), 3713 comparator = collection.comparator; 3714 3715 // Temporarily disable the comparator to prevent `add` 3716 // from re-sorting. 3717 delete collection.comparator; 3718 3719 // Silently shift the model to its new index. 3720 collection.remove( model, { 3721 silent: true 3722 }); 3723 collection.add( model, { 3724 silent: true, 3725 at: ui.item.index() 3726 }); 3727 3728 // Restore the comparator. 3729 collection.comparator = comparator; 3730 3731 // Fire the `reset` event to ensure other collections sync. 3732 collection.trigger( 'reset', collection ); 3733 3734 // If the collection is sorted by menu order, 3735 // update the menu order. 3736 collection.saveMenuOrder(); 3737 } 3738 }, this.options.sortable ) ); 3739 3740 // If the `orderby` property is changed on the `collection`, 3741 // check to see if we have a `comparator`. If so, disable sorting. 3742 collection.props.on( 'change:orderby', function() { 3743 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 3744 }, this ); 3745 3746 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 3747 this.refreshSortable(); 3748 }, 3749 3750 refreshSortable: function() { 3751 if ( ! this.options.sortable || ! $.fn.sortable ) { 3752 return; 3753 } 3754 3755 // If the `collection` has a `comparator`, disable sorting. 3756 var collection = this.collection, 3757 orderby = collection.props.get('orderby'), 3758 enabled = 'menuOrder' === orderby || ! collection.comparator; 3759 3760 this.$el.sortable( 'option', 'disabled', ! enabled ); 3761 }, 3762 3763 /** 3764 * @param {wp.media.model.Attachment} attachment 3765 * @returns {wp.media.View} 3766 */ 3767 createAttachmentView: function( attachment ) { 3768 var view = new this.options.AttachmentView({ 3769 controller: this.controller, 3770 model: attachment, 3771 collection: this.collection, 3772 selection: this.options.selection 3773 }); 3774 3775 return this._viewsByCid[ attachment.cid ] = view; 3776 }, 3777 3778 prepare: function() { 3779 // Create all of the Attachment views, and replace 3780 // the list in a single DOM operation. 3781 if ( this.collection.length ) { 3782 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 3783 3784 // If there are no elements, clear the views and load some. 3785 } else { 3786 this.views.unset(); 3787 this.collection.more().done( this.scroll ); 3788 } 3789 }, 3790 3791 ready: function() { 3792 // Trigger the scroll event to check if we're within the 3793 // threshold to query for additional attachments. 3794 this.scroll(); 3795 }, 3796 3797 scroll: function() { 3798 var view = this, 3799 el = this.options.scrollElement, 3800 scrollTop = el.scrollTop, 3801 toolbar; 3802 3803 // The scroll event occurs on the document, but the element 3804 // that should be checked is the document body. 3805 if ( el === document ) { 3806 el = document.body; 3807 scrollTop = $(document).scrollTop(); 3808 } 3809 3810 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 3811 return; 3812 } 3813 3814 toolbar = this.views.parent.toolbar; 3815 3816 // Show the spinner only if we are close to the bottom. 3817 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 3818 toolbar.get('spinner').show(); 3819 } 3820 3821 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 3822 this.collection.more().done(function() { 3823 view.scroll(); 3824 toolbar.get('spinner').hide(); 3825 }); 3826 } 3827 } 3828 }); 3829 3830 module.exports = Attachments; 3831 3832 },{}],32:[function(require,module,exports){ 7179 module.exports = All; 7180 7181 7182 /***/ }), 7183 /* 78 */ 7184 /***/ (function(module, exports) { 7185 3833 7186 var View = wp.media.View, 3834 7187 mediaTrash = wp.media.view.settings.mediaTrash, … … 4308 7661 module.exports = AttachmentsBrowser; 4309 7662 4310 },{}],33:[function(require,module,exports){ 7663 7664 /***/ }), 7665 /* 79 */ 7666 /***/ (function(module, exports) { 7667 7668 var l10n = wp.media.view.l10n, 7669 Selection; 7670 7671 /** 7672 * wp.media.view.Selection 7673 * 7674 * @memberOf wp.media.view 7675 * 7676 * @class 7677 * @augments wp.media.View 7678 * @augments wp.Backbone.View 7679 * @augments Backbone.View 7680 */ 7681 Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{ 7682 tagName: 'div', 7683 className: 'media-selection', 7684 template: wp.template('media-selection'), 7685 7686 events: { 7687 'click .edit-selection': 'edit', 7688 'click .clear-selection': 'clear' 7689 }, 7690 7691 initialize: function() { 7692 _.defaults( this.options, { 7693 editable: false, 7694 clearable: true 7695 }); 7696 7697 /** 7698 * @member {wp.media.view.Attachments.Selection} 7699 */ 7700 this.attachments = new wp.media.view.Attachments.Selection({ 7701 controller: this.controller, 7702 collection: this.collection, 7703 selection: this.collection, 7704 model: new Backbone.Model() 7705 }); 7706 7707 this.views.set( '.selection-view', this.attachments ); 7708 this.collection.on( 'add remove reset', this.refresh, this ); 7709 this.controller.on( 'content:activate', this.refresh, this ); 7710 }, 7711 7712 ready: function() { 7713 this.refresh(); 7714 }, 7715 7716 refresh: function() { 7717 // If the selection hasn't been rendered, bail. 7718 if ( ! this.$el.children().length ) { 7719 return; 7720 } 7721 7722 var collection = this.collection, 7723 editing = 'edit-selection' === this.controller.content.mode(); 7724 7725 // If nothing is selected, display nothing. 7726 this.$el.toggleClass( 'empty', ! collection.length ); 7727 this.$el.toggleClass( 'one', 1 === collection.length ); 7728 this.$el.toggleClass( 'editing', editing ); 7729 7730 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7731 }, 7732 7733 edit: function( event ) { 7734 event.preventDefault(); 7735 if ( this.options.editable ) { 7736 this.options.editable.call( this, this.collection ); 7737 } 7738 }, 7739 7740 clear: function( event ) { 7741 event.preventDefault(); 7742 this.collection.reset(); 7743 7744 // Keep focus inside media modal 7745 // after clear link is selected 7746 this.controller.modal.focusManager.focus(); 7747 } 7748 }); 7749 7750 module.exports = Selection; 7751 7752 7753 /***/ }), 7754 /* 80 */ 7755 /***/ (function(module, exports) { 7756 7757 /** 7758 * wp.media.view.Attachment.Selection 7759 * 7760 * @memberOf wp.media.view.Attachment 7761 * 7762 * @class 7763 * @augments wp.media.view.Attachment 7764 * @augments wp.media.View 7765 * @augments wp.Backbone.View 7766 * @augments Backbone.View 7767 */ 7768 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{ 7769 className: 'attachment selection', 7770 7771 // On click, just select the model, instead of removing the model from 7772 // the selection. 7773 toggleSelection: function() { 7774 this.options.selection.single( this.model ); 7775 } 7776 }); 7777 7778 module.exports = Selection; 7779 7780 7781 /***/ }), 7782 /* 81 */ 7783 /***/ (function(module, exports) { 7784 4311 7785 var Attachments = wp.media.view.Attachments, 4312 7786 Selection; … … 4340 7814 module.exports = Selection; 4341 7815 4342 },{}],34:[function(require,module,exports){ 4343 var $ = Backbone.$, 4344 ButtonGroup; 7816 7817 /***/ }), 7818 /* 82 */ 7819 /***/ (function(module, exports) { 4345 7820 4346 7821 /** 4347 * wp.media.view.ButtonGroup 7822 * wp.media.view.Attachment.EditSelection 7823 * 7824 * @memberOf wp.media.view.Attachment 7825 * 7826 * @class 7827 * @augments wp.media.view.Attachment.Selection 7828 * @augments wp.media.view.Attachment 7829 * @augments wp.media.View 7830 * @augments wp.Backbone.View 7831 * @augments Backbone.View 7832 */ 7833 var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{ 7834 buttons: { 7835 close: true 7836 } 7837 }); 7838 7839 module.exports = EditSelection; 7840 7841 7842 /***/ }), 7843 /* 83 */ 7844 /***/ (function(module, exports) { 7845 7846 var View = wp.media.View, 7847 $ = Backbone.$, 7848 Settings; 7849 7850 /** 7851 * wp.media.view.Settings 4348 7852 * 4349 7853 * @memberOf wp.media.view … … 4354 7858 * @augments Backbone.View 4355 7859 */ 4356 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{ 4357 tagName: 'div', 4358 className: 'button-group button-large media-button-group', 7860 Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 7861 events: { 7862 'click button': 'updateHandler', 7863 'change input': 'updateHandler', 7864 'change select': 'updateHandler', 7865 'change textarea': 'updateHandler' 7866 }, 4359 7867 4360 7868 initialize: function() { 4361 /** 4362 * @member {wp.media.view.Button[]} 4363 */ 4364 this.buttons = _.map( this.options.buttons || [], function( button ) { 4365 if ( button instanceof Backbone.View ) { 4366 return button; 7869 this.model = this.model || new Backbone.Model(); 7870 this.listenTo( this.model, 'change', this.updateChanges ); 7871 }, 7872 7873 prepare: function() { 7874 return _.defaults({ 7875 model: this.model.toJSON() 7876 }, this.options ); 7877 }, 7878 /** 7879 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7880 */ 7881 render: function() { 7882 View.prototype.render.apply( this, arguments ); 7883 // Select the correct values. 7884 _( this.model.attributes ).chain().keys().each( this.update, this ); 7885 return this; 7886 }, 7887 /** 7888 * @param {string} key 7889 */ 7890 update: function( key ) { 7891 var value = this.model.get( key ), 7892 $setting = this.$('[data-setting="' + key + '"]'), 7893 $buttons, $value; 7894 7895 // Bail if we didn't find a matching setting. 7896 if ( ! $setting.length ) { 7897 return; 7898 } 7899 7900 // Attempt to determine how the setting is rendered and update 7901 // the selected value. 7902 7903 // Handle dropdowns. 7904 if ( $setting.is('select') ) { 7905 $value = $setting.find('[value="' + value + '"]'); 7906 7907 if ( $value.length ) { 7908 $setting.find('option').prop( 'selected', false ); 7909 $value.prop( 'selected', true ); 4367 7910 } else { 4368 return new wp.media.view.Button( button ).render(); 7911 // If we can't find the desired value, record what *is* selected. 7912 this.model.set( key, $setting.find(':selected').val() ); 4369 7913 } 4370 }); 4371 4372 delete this.options.buttons; 4373 4374 if ( this.options.classes ) { 4375 this.$el.addClass( this.options.classes ); 4376 } 4377 }, 4378 4379 /** 4380 * @returns {wp.media.view.ButtonGroup} 4381 */ 4382 render: function() { 4383 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 4384 return this; 7914 7915 // Handle button groups. 7916 } else if ( $setting.hasClass('button-group') ) { 7917 $buttons = $setting.find('button').removeClass('active'); 7918 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7919 7920 // Handle text inputs and textareas. 7921 } else if ( $setting.is('input[type="text"], textarea') ) { 7922 if ( ! $setting.is(':focus') ) { 7923 $setting.val( value ); 7924 } 7925 // Handle checkboxes. 7926 } else if ( $setting.is('input[type="checkbox"]') ) { 7927 $setting.prop( 'checked', !! value && 'false' !== value ); 7928 } 7929 }, 7930 /** 7931 * @param {Object} event 7932 */ 7933 updateHandler: function( event ) { 7934 var $setting = $( event.target ).closest('[data-setting]'), 7935 value = event.target.value, 7936 userSetting; 7937 7938 event.preventDefault(); 7939 7940 if ( ! $setting.length ) { 7941 return; 7942 } 7943 7944 // Use the correct value for checkboxes. 7945 if ( $setting.is('input[type="checkbox"]') ) { 7946 value = $setting[0].checked; 7947 } 7948 7949 // Update the corresponding setting. 7950 this.model.set( $setting.data('setting'), value ); 7951 7952 // If the setting has a corresponding user setting, 7953 // update that as well. 7954 if ( userSetting = $setting.data('userSetting') ) { 7955 window.setUserSetting( userSetting, value ); 7956 } 7957 }, 7958 7959 updateChanges: function( model ) { 7960 if ( model.hasChanged() ) { 7961 _( model.changed ).chain().keys().each( this.update, this ); 7962 } 4385 7963 } 4386 7964 }); 4387 7965 4388 module.exports = ButtonGroup; 4389 4390 },{}],35:[function(require,module,exports){ 7966 module.exports = Settings; 7967 7968 7969 /***/ }), 7970 /* 84 */ 7971 /***/ (function(module, exports) { 7972 7973 var Settings = wp.media.view.Settings, 7974 AttachmentDisplay; 7975 4391 7976 /** 4392 * wp.media.view.Button 7977 * wp.media.view.Settings.AttachmentDisplay 7978 * 7979 * @memberOf wp.media.view.Settings 7980 * 7981 * @class 7982 * @augments wp.media.view.Settings 7983 * @augments wp.media.View 7984 * @augments wp.Backbone.View 7985 * @augments Backbone.View 7986 */ 7987 AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{ 7988 className: 'attachment-display-settings', 7989 template: wp.template('attachment-display-settings'), 7990 7991 initialize: function() { 7992 var attachment = this.options.attachment; 7993 7994 _.defaults( this.options, { 7995 userSettings: false 7996 }); 7997 // Call 'initialize' directly on the parent class. 7998 Settings.prototype.initialize.apply( this, arguments ); 7999 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 8000 8001 if ( attachment ) { 8002 attachment.on( 'change:uploading', this.render, this ); 8003 } 8004 }, 8005 8006 dispose: function() { 8007 var attachment = this.options.attachment; 8008 if ( attachment ) { 8009 attachment.off( null, null, this ); 8010 } 8011 /** 8012 * call 'dispose' directly on the parent class 8013 */ 8014 Settings.prototype.dispose.apply( this, arguments ); 8015 }, 8016 /** 8017 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 8018 */ 8019 render: function() { 8020 var attachment = this.options.attachment; 8021 if ( attachment ) { 8022 _.extend( this.options, { 8023 sizes: attachment.get('sizes'), 8024 type: attachment.get('type') 8025 }); 8026 } 8027 /** 8028 * call 'render' directly on the parent class 8029 */ 8030 Settings.prototype.render.call( this ); 8031 this.updateLinkTo(); 8032 return this; 8033 }, 8034 8035 updateLinkTo: function() { 8036 var linkTo = this.model.get('link'), 8037 $input = this.$('.link-to-custom'), 8038 attachment = this.options.attachment; 8039 8040 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 8041 $input.addClass( 'hidden' ); 8042 return; 8043 } 8044 8045 if ( attachment ) { 8046 if ( 'post' === linkTo ) { 8047 $input.val( attachment.get('link') ); 8048 } else if ( 'file' === linkTo ) { 8049 $input.val( attachment.get('url') ); 8050 } else if ( ! this.model.get('linkUrl') ) { 8051 $input.val('http://'); 8052 } 8053 8054 $input.prop( 'readonly', 'custom' !== linkTo ); 8055 } 8056 8057 $input.removeClass( 'hidden' ); 8058 8059 // If the input is visible, focus and select its contents. 8060 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 8061 $input.focus()[0].select(); 8062 } 8063 } 8064 }); 8065 8066 module.exports = AttachmentDisplay; 8067 8068 8069 /***/ }), 8070 /* 85 */ 8071 /***/ (function(module, exports) { 8072 8073 /** 8074 * wp.media.view.Settings.Gallery 8075 * 8076 * @memberOf wp.media.view.Settings 8077 * 8078 * @class 8079 * @augments wp.media.view.Settings 8080 * @augments wp.media.View 8081 * @augments wp.Backbone.View 8082 * @augments Backbone.View 8083 */ 8084 var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{ 8085 className: 'collection-settings gallery-settings', 8086 template: wp.template('gallery-settings') 8087 }); 8088 8089 module.exports = Gallery; 8090 8091 8092 /***/ }), 8093 /* 86 */ 8094 /***/ (function(module, exports) { 8095 8096 /** 8097 * wp.media.view.Settings.Playlist 8098 * 8099 * @memberOf wp.media.view.Settings 8100 * 8101 * @class 8102 * @augments wp.media.view.Settings 8103 * @augments wp.media.View 8104 * @augments wp.Backbone.View 8105 * @augments Backbone.View 8106 */ 8107 var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{ 8108 className: 'collection-settings playlist-settings', 8109 template: wp.template('playlist-settings') 8110 }); 8111 8112 module.exports = Playlist; 8113 8114 8115 /***/ }), 8116 /* 87 */ 8117 /***/ (function(module, exports) { 8118 8119 var Attachment = wp.media.view.Attachment, 8120 l10n = wp.media.view.l10n, 8121 Details; 8122 8123 /** 8124 * wp.media.view.Attachment.Details 8125 * 8126 * @memberOf wp.media.view.Attachment 8127 * 8128 * @class 8129 * @augments wp.media.view.Attachment 8130 * @augments wp.media.View 8131 * @augments wp.Backbone.View 8132 * @augments Backbone.View 8133 */ 8134 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{ 8135 tagName: 'div', 8136 className: 'attachment-details', 8137 template: wp.template('attachment-details'), 8138 8139 attributes: function() { 8140 return { 8141 'tabIndex': 0, 8142 'data-id': this.model.get( 'id' ) 8143 }; 8144 }, 8145 8146 events: { 8147 'change [data-setting]': 'updateSetting', 8148 'change [data-setting] input': 'updateSetting', 8149 'change [data-setting] select': 'updateSetting', 8150 'change [data-setting] textarea': 'updateSetting', 8151 'click .delete-attachment': 'deleteAttachment', 8152 'click .trash-attachment': 'trashAttachment', 8153 'click .untrash-attachment': 'untrashAttachment', 8154 'click .edit-attachment': 'editAttachment', 8155 'keydown': 'toggleSelectionHandler' 8156 }, 8157 8158 initialize: function() { 8159 this.options = _.defaults( this.options, { 8160 rerenderOnModelChange: false 8161 }); 8162 8163 this.on( 'ready', this.initialFocus ); 8164 // Call 'initialize' directly on the parent class. 8165 Attachment.prototype.initialize.apply( this, arguments ); 8166 }, 8167 8168 initialFocus: function() { 8169 if ( ! wp.media.isTouchDevice ) { 8170 /* 8171 Previously focused the first ':input' (the readonly URL text field). 8172 Since the first ':input' is now a button (delete/trash): when pressing 8173 spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment 8174 as soon as focus is moved. Explicitly target the first text field for now. 8175 @todo change initial focus logic, also for accessibility. 8176 */ 8177 this.$( 'input[type="text"]' ).eq( 0 ).focus(); 8178 } 8179 }, 8180 /** 8181 * @param {Object} event 8182 */ 8183 deleteAttachment: function( event ) { 8184 event.preventDefault(); 8185 8186 if ( window.confirm( l10n.warnDelete ) ) { 8187 this.model.destroy(); 8188 // Keep focus inside media modal 8189 // after image is deleted 8190 this.controller.modal.focusManager.focus(); 8191 } 8192 }, 8193 /** 8194 * @param {Object} event 8195 */ 8196 trashAttachment: function( event ) { 8197 var library = this.controller.library; 8198 event.preventDefault(); 8199 8200 if ( wp.media.view.settings.mediaTrash && 8201 'edit-metadata' === this.controller.content.mode() ) { 8202 8203 this.model.set( 'status', 'trash' ); 8204 this.model.save().done( function() { 8205 library._requery( true ); 8206 } ); 8207 } else { 8208 this.model.destroy(); 8209 } 8210 }, 8211 /** 8212 * @param {Object} event 8213 */ 8214 untrashAttachment: function( event ) { 8215 var library = this.controller.library; 8216 event.preventDefault(); 8217 8218 this.model.set( 'status', 'inherit' ); 8219 this.model.save().done( function() { 8220 library._requery( true ); 8221 } ); 8222 }, 8223 /** 8224 * @param {Object} event 8225 */ 8226 editAttachment: function( event ) { 8227 var editState = this.controller.states.get( 'edit-image' ); 8228 if ( window.imageEdit && editState ) { 8229 event.preventDefault(); 8230 8231 editState.set( 'image', this.model ); 8232 this.controller.setState( 'edit-image' ); 8233 } else { 8234 this.$el.addClass('needs-refresh'); 8235 } 8236 }, 8237 /** 8238 * When reverse tabbing(shift+tab) out of the right details panel, deliver 8239 * the focus to the item in the list that was being edited. 8240 * 8241 * @param {Object} event 8242 */ 8243 toggleSelectionHandler: function( event ) { 8244 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 8245 this.controller.trigger( 'attachment:details:shift-tab', event ); 8246 return false; 8247 } 8248 8249 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 8250 this.controller.trigger( 'attachment:keydown:arrow', event ); 8251 return; 8252 } 8253 } 8254 }); 8255 8256 module.exports = Details; 8257 8258 8259 /***/ }), 8260 /* 88 */ 8261 /***/ (function(module, exports) { 8262 8263 var View = wp.media.View, 8264 AttachmentCompat; 8265 8266 /** 8267 * wp.media.view.AttachmentCompat 8268 * 8269 * A view to display fields added via the `attachment_fields_to_edit` filter. 4393 8270 * 4394 8271 * @memberOf wp.media.view … … 4399 8276 * @augments Backbone.View 4400 8277 */ 4401 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{ 4402 tagName: 'button', 4403 className: 'media-button', 4404 attributes: { type: 'button' }, 8278 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{ 8279 tagName: 'form', 8280 className: 'compat-item', 4405 8281 4406 8282 events: { 4407 'click': 'click' 4408 }, 4409 4410 defaults: { 4411 text: '', 4412 style: '', 4413 size: 'large', 4414 disabled: false 8283 'submit': 'preventDefault', 8284 'change input': 'save', 8285 'change select': 'save', 8286 'change textarea': 'save' 4415 8287 }, 4416 8288 4417 8289 initialize: function() { 8290 this.listenTo( this.model, 'change:compat', this.render ); 8291 }, 8292 /** 8293 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 8294 */ 8295 dispose: function() { 8296 if ( this.$(':focus').length ) { 8297 this.save(); 8298 } 4418 8299 /** 4419 * Create a model with the provided `defaults`. 4420 * 4421 * @member {Backbone.Model} 8300 * call 'dispose' directly on the parent class 4422 8301 */ 4423 this.model = new Backbone.Model( this.defaults ); 4424 4425 // If any of the `options` have a key from `defaults`, apply its 4426 // value to the `model` and remove it from the `options object. 4427 _.each( this.defaults, function( def, key ) { 4428 var value = this.options[ key ]; 4429 if ( _.isUndefined( value ) ) { 4430 return; 4431 } 4432 4433 this.model.set( key, value ); 4434 delete this.options[ key ]; 4435 }, this ); 4436 4437 this.listenTo( this.model, 'change', this.render ); 4438 }, 4439 /** 4440 * @returns {wp.media.view.Button} Returns itself to allow chaining 8302 return View.prototype.dispose.apply( this, arguments ); 8303 }, 8304 /** 8305 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 4441 8306 */ 4442 8307 render: function() { 4443 var classes = [ 'button', this.className ], 4444 model = this.model.toJSON(); 4445 4446 if ( model.style ) { 4447 classes.push( 'button-' + model.style ); 4448 } 4449 4450 if ( model.size ) { 4451 classes.push( 'button-' + model.size ); 4452 } 4453 4454 classes = _.uniq( classes.concat( this.options.classes ) ); 4455 this.el.className = classes.join(' '); 4456 4457 this.$el.attr( 'disabled', model.disabled ); 4458 this.$el.text( this.model.get('text') ); 4459 8308 var compat = this.model.get('compat'); 8309 if ( ! compat || ! compat.item ) { 8310 return; 8311 } 8312 8313 this.views.detach(); 8314 this.$el.html( compat.item ); 8315 this.views.render(); 4460 8316 return this; 4461 8317 }, … … 4463 8319 * @param {Object} event 4464 8320 */ 4465 click: function( event ) { 4466 if ( '#' === this.attributes.href ) { 8321 preventDefault: function( event ) { 8322 event.preventDefault(); 8323 }, 8324 /** 8325 * @param {Object} event 8326 */ 8327 save: function( event ) { 8328 var data = {}; 8329 8330 if ( event ) { 4467 8331 event.preventDefault(); 4468 8332 } 4469 8333 4470 if ( this.options.click && ! this.model.get('disabled') ) { 4471 this.options.click.apply( this, arguments ); 4472 } 8334 _.each( this.$el.serializeArray(), function( pair ) { 8335 data[ pair.name ] = pair.value; 8336 }); 8337 8338 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 8339 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 8340 }, 8341 8342 postSave: function() { 8343 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 4473 8344 } 4474 8345 }); 4475 8346 4476 module.exports = Button; 4477 4478 },{}],36:[function(require,module,exports){ 4479 var View = wp.media.View, 4480 UploaderStatus = wp.media.view.UploaderStatus, 4481 l10n = wp.media.view.l10n, 4482 $ = jQuery, 4483 Cropper; 8347 module.exports = AttachmentCompat; 8348 8349 8350 /***/ }), 8351 /* 89 */ 8352 /***/ (function(module, exports) { 4484 8353 4485 8354 /** 4486 * wp.media.view.Cropper 4487 * 4488 * Uses the imgAreaSelect plugin to allow a user to crop an image. 4489 * 4490 * Takes imgAreaSelect options from 4491 * wp.customize.HeaderControl.calculateImageSelectOptions via 4492 * wp.customize.HeaderControl.openMM. 8355 * wp.media.view.Iframe 4493 8356 * 4494 8357 * @memberOf wp.media.view … … 4499 8362 * @augments Backbone.View 4500 8363 */ 4501 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{ 4502 className: 'crop-content', 4503 template: wp.template('crop-content'), 4504 initialize: function() { 4505 _.bindAll(this, 'onImageLoad'); 4506 }, 4507 ready: function() { 4508 this.controller.frame.on('content:error:crop', this.onError, this); 4509 this.$image = this.$el.find('.crop-image'); 4510 this.$image.on('load', this.onImageLoad); 4511 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 4512 }, 4513 remove: function() { 4514 $(window).off('resize.cropper'); 4515 this.$el.remove(); 4516 this.$el.off(); 4517 View.prototype.remove.apply(this, arguments); 4518 }, 4519 prepare: function() { 4520 return { 4521 title: l10n.cropYourImage, 4522 url: this.options.attachment.get('url') 4523 }; 4524 }, 4525 onImageLoad: function() { 4526 var imgOptions = this.controller.get('imgSelectOptions'), 4527 imgSelect; 4528 4529 if (typeof imgOptions === 'function') { 4530 imgOptions = imgOptions(this.options.attachment, this.controller); 4531 } 4532 4533 imgOptions = _.extend(imgOptions, { 4534 parent: this.$el, 4535 onInit: function() { 4536 this.parent.children().on( 'mousedown touchstart', function( e ){ 4537 4538 if ( e.shiftKey ) { 4539 imgSelect.setOptions( { 4540 aspectRatio: '1:1' 4541 } ); 4542 } else { 4543 imgSelect.setOptions( { 4544 aspectRatio: false 4545 } ); 4546 } 4547 } ); 4548 } 4549 } ); 4550 this.trigger('image-loaded'); 4551 imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 4552 }, 4553 onError: function() { 4554 var filename = this.options.attachment.get('filename'); 4555 4556 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4557 filename: UploaderStatus.prototype.filename(filename), 4558 message: window._wpMediaViewsL10n.cropError 4559 }), { at: 0 }); 8364 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{ 8365 className: 'media-iframe', 8366 /** 8367 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 8368 */ 8369 render: function() { 8370 this.views.detach(); 8371 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 8372 this.views.render(); 8373 return this; 4560 8374 } 4561 8375 }); 4562 8376 4563 module.exports = Cropper; 4564 4565 },{}],37:[function(require,module,exports){ 4566 var View = wp.media.View, 4567 EditImage; 4568 4569 /** 4570 * wp.media.view.EditImage 4571 * 4572 * @memberOf wp.media.view 4573 * 4574 * @class 4575 * @augments wp.media.View 4576 * @augments wp.Backbone.View 4577 * @augments Backbone.View 4578 */ 4579 EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{ 4580 className: 'image-editor', 4581 template: wp.template('image-editor'), 4582 4583 initialize: function( options ) { 4584 this.editor = window.imageEdit; 4585 this.controller = options.controller; 4586 View.prototype.initialize.apply( this, arguments ); 4587 }, 4588 4589 prepare: function() { 4590 return this.model.toJSON(); 4591 }, 4592 4593 loadEditor: function() { 4594 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 4595 dfd.done( _.bind( this.focus, this ) ); 4596 }, 4597 4598 focus: function() { 4599 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 4600 }, 4601 4602 back: function() { 4603 var lastState = this.controller.lastState(); 4604 this.controller.setState( lastState ); 4605 }, 4606 4607 refresh: function() { 4608 this.model.fetch(); 4609 }, 4610 4611 save: function() { 4612 var lastState = this.controller.lastState(); 4613 4614 this.model.fetch().done( _.bind( function() { 4615 this.controller.setState( lastState ); 4616 }, this ) ); 4617 } 4618 4619 }); 4620 4621 module.exports = EditImage; 4622 4623 },{}],38:[function(require,module,exports){ 8377 module.exports = Iframe; 8378 8379 8380 /***/ }), 8381 /* 90 */ 8382 /***/ (function(module, exports) { 8383 4624 8384 /** 4625 8385 * wp.media.view.Embed … … 4687 8447 module.exports = Embed; 4688 8448 4689 },{}],39:[function(require,module,exports){ 4690 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 4691 EmbedImage; 8449 8450 /***/ }), 8451 /* 91 */ 8452 /***/ (function(module, exports) { 4692 8453 4693 8454 /** 4694 * wp.media.view. EmbedImage8455 * wp.media.view.Label 4695 8456 * 4696 8457 * @memberOf wp.media.view 4697 8458 * 4698 8459 * @class 4699 * @augments wp.media.view.Settings.AttachmentDisplay4700 * @augments wp.media.view.Settings4701 8460 * @augments wp.media.View 4702 8461 * @augments wp.Backbone.View 4703 8462 * @augments Backbone.View 4704 8463 */ 4705 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{4706 className: 'embed-media-settings',4707 template: wp.template('embed-image-settings'),8464 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{ 8465 tagName: 'label', 8466 className: 'screen-reader-text', 4708 8467 4709 8468 initialize: function() { 4710 /** 4711 * Call `initialize` directly on parent class with passed arguments 4712 */ 4713 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 4714 this.listenTo( this.model, 'change:url', this.updateImage ); 4715 }, 4716 4717 updateImage: function() { 4718 this.$('img').attr( 'src', this.model.get('url') ); 8469 this.value = this.options.value; 8470 }, 8471 8472 render: function() { 8473 this.$el.html( this.value ); 8474 8475 return this; 4719 8476 } 4720 8477 }); 4721 8478 4722 module.exports = EmbedImage; 4723 4724 },{}],40:[function(require,module,exports){ 8479 module.exports = Label; 8480 8481 8482 /***/ }), 8483 /* 92 */ 8484 /***/ (function(module, exports) { 8485 8486 var View = wp.media.View, 8487 $ = jQuery, 8488 EmbedUrl; 8489 8490 /** 8491 * wp.media.view.EmbedUrl 8492 * 8493 * @memberOf wp.media.view 8494 * 8495 * @class 8496 * @augments wp.media.View 8497 * @augments wp.Backbone.View 8498 * @augments Backbone.View 8499 */ 8500 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{ 8501 tagName: 'label', 8502 className: 'embed-url', 8503 8504 events: { 8505 'input': 'url', 8506 'keyup': 'url', 8507 'change': 'url' 8508 }, 8509 8510 initialize: function() { 8511 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 8512 this.input = this.$input[0]; 8513 8514 this.spinner = $('<span class="spinner" />')[0]; 8515 this.$el.append([ this.input, this.spinner ]); 8516 8517 this.listenTo( this.model, 'change:url', this.render ); 8518 8519 if ( this.model.get( 'url' ) ) { 8520 _.delay( _.bind( function () { 8521 this.model.trigger( 'change:url' ); 8522 }, this ), 500 ); 8523 } 8524 }, 8525 /** 8526 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 8527 */ 8528 render: function() { 8529 var $input = this.$input; 8530 8531 if ( $input.is(':focus') ) { 8532 return; 8533 } 8534 8535 this.input.value = this.model.get('url') || 'http://'; 8536 /** 8537 * Call `render` directly on parent class with passed arguments 8538 */ 8539 View.prototype.render.apply( this, arguments ); 8540 return this; 8541 }, 8542 8543 ready: function() { 8544 if ( ! wp.media.isTouchDevice ) { 8545 this.focus(); 8546 } 8547 }, 8548 8549 url: function( event ) { 8550 this.model.set( 'url', $.trim( event.target.value ) ); 8551 }, 8552 8553 /** 8554 * If the input is visible, focus and select its contents. 8555 */ 8556 focus: function() { 8557 var $input = this.$input; 8558 if ( $input.is(':visible') ) { 8559 $input.focus()[0].select(); 8560 } 8561 } 8562 }); 8563 8564 module.exports = EmbedUrl; 8565 8566 8567 /***/ }), 8568 /* 93 */ 8569 /***/ (function(module, exports) { 8570 4725 8571 var $ = jQuery, 4726 8572 EmbedLink; … … 4821 8667 module.exports = EmbedLink; 4822 8668 4823 },{}],41:[function(require,module,exports){ 4824 var View = wp.media.View, 4825 $ = jQuery, 4826 EmbedUrl; 8669 8670 /***/ }), 8671 /* 94 */ 8672 /***/ (function(module, exports) { 8673 8674 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 8675 EmbedImage; 4827 8676 4828 8677 /** 4829 * wp.media.view.Embed Url8678 * wp.media.view.EmbedImage 4830 8679 * 4831 8680 * @memberOf wp.media.view 4832 8681 * 4833 8682 * @class 8683 * @augments wp.media.view.Settings.AttachmentDisplay 8684 * @augments wp.media.view.Settings 4834 8685 * @augments wp.media.View 4835 8686 * @augments wp.Backbone.View 4836 8687 * @augments Backbone.View 4837 8688 */ 4838 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{ 4839 tagName: 'label', 4840 className: 'embed-url', 4841 4842 events: { 4843 'input': 'url', 4844 'keyup': 'url', 4845 'change': 'url' 4846 }, 8689 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{ 8690 className: 'embed-media-settings', 8691 template: wp.template('embed-image-settings'), 4847 8692 4848 8693 initialize: function() { 4849 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );4850 this.input = this.$input[0];4851 4852 this.spinner = $('<span class="spinner" />')[0];4853 this.$el.append([ this.input, this.spinner ]);4854 4855 this.listenTo( this.model, 'change:url', this.render );4856 4857 if ( this.model.get( 'url' ) ) {4858 _.delay( _.bind( function () {4859 this.model.trigger( 'change:url' );4860 }, this ), 500 );4861 }4862 },4863 /**4864 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining4865 */4866 render: function() {4867 var $input = this.$input;4868 4869 if ( $input.is(':focus') ) {4870 return;4871 }4872 4873 this.input.value = this.model.get('url') || 'http://';4874 8694 /** 4875 * Call ` render` directly on parent class with passed arguments8695 * Call `initialize` directly on parent class with passed arguments 4876 8696 */ 4877 View.prototype.render.apply( this, arguments ); 4878 return this; 4879 }, 4880 4881 ready: function() { 4882 if ( ! wp.media.isTouchDevice ) { 4883 this.focus(); 4884 } 4885 }, 4886 4887 url: function( event ) { 4888 this.model.set( 'url', $.trim( event.target.value ) ); 4889 }, 4890 4891 /** 4892 * If the input is visible, focus and select its contents. 4893 */ 4894 focus: function() { 4895 var $input = this.$input; 4896 if ( $input.is(':visible') ) { 4897 $input.focus()[0].select(); 4898 } 8697 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 8698 this.listenTo( this.model, 'change:url', this.updateImage ); 8699 }, 8700 8701 updateImage: function() { 8702 this.$('img').attr( 'src', this.model.get('url') ); 4899 8703 } 4900 8704 }); 4901 8705 4902 module.exports = EmbedUrl; 4903 4904 },{}],42:[function(require,module,exports){ 4905 /** 4906 * wp.media.view.FocusManager 4907 * 4908 * @memberOf wp.media.view 4909 * 4910 * @class 4911 * @augments wp.media.View 4912 * @augments wp.Backbone.View 4913 * @augments Backbone.View 4914 */ 4915 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 4916 4917 events: { 4918 'keydown': 'constrainTabbing' 4919 }, 4920 4921 focus: function() { // Reset focus on first left menu item 4922 this.$('.media-menu-item').first().focus(); 4923 }, 4924 /** 4925 * @param {Object} event 4926 */ 4927 constrainTabbing: function( event ) { 4928 var tabbables; 4929 4930 // Look for the tab key. 4931 if ( 9 !== event.keyCode ) { 4932 return; 4933 } 4934 4935 // Skip the file input added by Plupload. 4936 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4937 4938 // Keep tab focus within media modal while it's open 4939 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4940 tabbables.first().focus(); 4941 return false; 4942 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4943 tabbables.last().focus(); 4944 return false; 4945 } 4946 } 4947 4948 }); 4949 4950 module.exports = FocusManager; 4951 4952 },{}],43:[function(require,module,exports){ 4953 /** 4954 * wp.media.view.Frame 4955 * 4956 * A frame is a composite view consisting of one or more regions and one or more 4957 * states. 4958 * 4959 * @memberOf wp.media.view 4960 * 4961 * @see wp.media.controller.State 4962 * @see wp.media.controller.Region 4963 * 4964 * @class 4965 * @augments wp.media.View 4966 * @augments wp.Backbone.View 4967 * @augments Backbone.View 4968 * @mixes wp.media.controller.StateMachine 4969 */ 4970 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{ 4971 initialize: function() { 4972 _.defaults( this.options, { 4973 mode: [ 'select' ] 4974 }); 4975 this._createRegions(); 4976 this._createStates(); 4977 this._createModes(); 4978 }, 4979 4980 _createRegions: function() { 4981 // Clone the regions array. 4982 this.regions = this.regions ? this.regions.slice() : []; 4983 4984 // Initialize regions. 4985 _.each( this.regions, function( region ) { 4986 this[ region ] = new wp.media.controller.Region({ 4987 view: this, 4988 id: region, 4989 selector: '.media-frame-' + region 4990 }); 4991 }, this ); 4992 }, 4993 /** 4994 * Create the frame's states. 4995 * 4996 * @see wp.media.controller.State 4997 * @see wp.media.controller.StateMachine 4998 * 4999 * @fires wp.media.controller.State#ready 5000 */ 5001 _createStates: function() { 5002 // Create the default `states` collection. 5003 this.states = new Backbone.Collection( null, { 5004 model: wp.media.controller.State 5005 }); 5006 5007 // Ensure states have a reference to the frame. 5008 this.states.on( 'add', function( model ) { 5009 model.frame = this; 5010 model.trigger('ready'); 5011 }, this ); 5012 5013 if ( this.options.states ) { 5014 this.states.add( this.options.states ); 5015 } 5016 }, 5017 5018 /** 5019 * A frame can be in a mode or multiple modes at one time. 5020 * 5021 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 5022 */ 5023 _createModes: function() { 5024 // Store active "modes" that the frame is in. Unrelated to region modes. 5025 this.activeModes = new Backbone.Collection(); 5026 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 5027 5028 _.each( this.options.mode, function( mode ) { 5029 this.activateMode( mode ); 5030 }, this ); 5031 }, 5032 /** 5033 * Reset all states on the frame to their defaults. 5034 * 5035 * @returns {wp.media.view.Frame} Returns itself to allow chaining 5036 */ 5037 reset: function() { 5038 this.states.invoke( 'trigger', 'reset' ); 5039 return this; 5040 }, 5041 /** 5042 * Map activeMode collection events to the frame. 5043 */ 5044 triggerModeEvents: function( model, collection, options ) { 5045 var collectionEvent, 5046 modeEventMap = { 5047 add: 'activate', 5048 remove: 'deactivate' 5049 }, 5050 eventToTrigger; 5051 // Probably a better way to do this. 5052 _.each( options, function( value, key ) { 5053 if ( value ) { 5054 collectionEvent = key; 5055 } 5056 } ); 5057 5058 if ( ! _.has( modeEventMap, collectionEvent ) ) { 5059 return; 5060 } 5061 5062 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 5063 this.trigger( eventToTrigger ); 5064 }, 5065 /** 5066 * Activate a mode on the frame. 5067 * 5068 * @param string mode Mode ID. 5069 * @returns {this} Returns itself to allow chaining. 5070 */ 5071 activateMode: function( mode ) { 5072 // Bail if the mode is already active. 5073 if ( this.isModeActive( mode ) ) { 5074 return; 5075 } 5076 this.activeModes.add( [ { id: mode } ] ); 5077 // Add a CSS class to the frame so elements can be styled for the mode. 5078 this.$el.addClass( 'mode-' + mode ); 5079 5080 return this; 5081 }, 5082 /** 5083 * Deactivate a mode on the frame. 5084 * 5085 * @param string mode Mode ID. 5086 * @returns {this} Returns itself to allow chaining. 5087 */ 5088 deactivateMode: function( mode ) { 5089 // Bail if the mode isn't active. 5090 if ( ! this.isModeActive( mode ) ) { 5091 return this; 5092 } 5093 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 5094 this.$el.removeClass( 'mode-' + mode ); 5095 /** 5096 * Frame mode deactivation event. 5097 * 5098 * @event wp.media.view.Frame#{mode}:deactivate 5099 */ 5100 this.trigger( mode + ':deactivate' ); 5101 5102 return this; 5103 }, 5104 /** 5105 * Check if a mode is enabled on the frame. 5106 * 5107 * @param string mode Mode ID. 5108 * @return bool 5109 */ 5110 isModeActive: function( mode ) { 5111 return Boolean( this.activeModes.where( { id: mode } ).length ); 5112 } 5113 }); 5114 5115 // Make the `Frame` a `StateMachine`. 5116 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 5117 5118 module.exports = Frame; 5119 5120 },{}],44:[function(require,module,exports){ 5121 var Select = wp.media.view.MediaFrame.Select, 5122 l10n = wp.media.view.l10n, 5123 ImageDetails; 5124 5125 /** 5126 * wp.media.view.MediaFrame.ImageDetails 5127 * 5128 * A media frame for manipulating an image that's already been inserted 5129 * into a post. 5130 * 5131 * @memberOf wp.media.view.MediaFrame 5132 * 5133 * @class 5134 * @augments wp.media.view.MediaFrame.Select 5135 * @augments wp.media.view.MediaFrame 5136 * @augments wp.media.view.Frame 5137 * @augments wp.media.View 5138 * @augments wp.Backbone.View 5139 * @augments Backbone.View 5140 * @mixes wp.media.controller.StateMachine 5141 */ 5142 ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{ 5143 defaults: { 5144 id: 'image', 5145 url: '', 5146 menu: 'image-details', 5147 content: 'image-details', 5148 toolbar: 'image-details', 5149 type: 'link', 5150 title: l10n.imageDetailsTitle, 5151 priority: 120 5152 }, 5153 5154 initialize: function( options ) { 5155 this.image = new wp.media.model.PostImage( options.metadata ); 5156 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 5157 Select.prototype.initialize.apply( this, arguments ); 5158 }, 5159 5160 bindHandlers: function() { 5161 Select.prototype.bindHandlers.apply( this, arguments ); 5162 this.on( 'menu:create:image-details', this.createMenu, this ); 5163 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 5164 this.on( 'content:render:edit-image', this.editImageContent, this ); 5165 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 5166 // override the select toolbar 5167 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 5168 }, 5169 5170 createStates: function() { 5171 this.states.add([ 5172 new wp.media.controller.ImageDetails({ 5173 image: this.image, 5174 editable: false 5175 }), 5176 new wp.media.controller.ReplaceImage({ 5177 id: 'replace-image', 5178 library: wp.media.query( { type: 'image' } ), 5179 image: this.image, 5180 multiple: false, 5181 title: l10n.imageReplaceTitle, 5182 toolbar: 'replace', 5183 priority: 80, 5184 displaySettings: true 5185 }), 5186 new wp.media.controller.EditImage( { 5187 image: this.image, 5188 selection: this.options.selection 5189 } ) 5190 ]); 5191 }, 5192 5193 imageDetailsContent: function( options ) { 5194 options.view = new wp.media.view.ImageDetails({ 5195 controller: this, 5196 model: this.state().image, 5197 attachment: this.state().image.attachment 5198 }); 5199 }, 5200 5201 editImageContent: function() { 5202 var state = this.state(), 5203 model = state.get('image'), 5204 view; 5205 5206 if ( ! model ) { 5207 return; 5208 } 5209 5210 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 5211 5212 this.content.set( view ); 5213 5214 // after bringing in the frame, load the actual editor via an ajax call 5215 view.loadEditor(); 5216 5217 }, 5218 5219 renderImageDetailsToolbar: function() { 5220 this.toolbar.set( new wp.media.view.Toolbar({ 5221 controller: this, 5222 items: { 5223 select: { 5224 style: 'primary', 5225 text: l10n.update, 5226 priority: 80, 5227 5228 click: function() { 5229 var controller = this.controller, 5230 state = controller.state(); 5231 5232 controller.close(); 5233 5234 // not sure if we want to use wp.media.string.image which will create a shortcode or 5235 // perhaps wp.html.string to at least to build the <img /> 5236 state.trigger( 'update', controller.image.toJSON() ); 5237 5238 // Restore and reset the default state. 5239 controller.setState( controller.options.state ); 5240 controller.reset(); 5241 } 5242 } 5243 } 5244 }) ); 5245 }, 5246 5247 renderReplaceImageToolbar: function() { 5248 var frame = this, 5249 lastState = frame.lastState(), 5250 previous = lastState && lastState.id; 5251 5252 this.toolbar.set( new wp.media.view.Toolbar({ 5253 controller: this, 5254 items: { 5255 back: { 5256 text: l10n.back, 5257 priority: 20, 5258 click: function() { 5259 if ( previous ) { 5260 frame.setState( previous ); 5261 } else { 5262 frame.close(); 5263 } 5264 } 5265 }, 5266 5267 replace: { 5268 style: 'primary', 5269 text: l10n.replace, 5270 priority: 80, 5271 requires: { selection: true }, 5272 5273 click: function() { 5274 var controller = this.controller, 5275 state = controller.state(), 5276 selection = state.get( 'selection' ), 5277 attachment = selection.single(); 5278 5279 controller.close(); 5280 5281 controller.image.changeAttachment( attachment, state.display( attachment ) ); 5282 5283 // not sure if we want to use wp.media.string.image which will create a shortcode or 5284 // perhaps wp.html.string to at least to build the <img /> 5285 state.trigger( 'replace', controller.image.toJSON() ); 5286 5287 // Restore and reset the default state. 5288 controller.setState( controller.options.state ); 5289 controller.reset(); 5290 } 5291 } 5292 } 5293 }) ); 5294 } 5295 5296 }); 5297 5298 module.exports = ImageDetails; 5299 5300 },{}],45:[function(require,module,exports){ 5301 var Select = wp.media.view.MediaFrame.Select, 5302 Library = wp.media.controller.Library, 5303 l10n = wp.media.view.l10n, 5304 Post; 5305 5306 /** 5307 * wp.media.view.MediaFrame.Post 5308 * 5309 * The frame for manipulating media on the Edit Post page. 5310 * 5311 * @memberOf wp.media.view.MediaFrame 5312 * 5313 * @class 5314 * @augments wp.media.view.MediaFrame.Select 5315 * @augments wp.media.view.MediaFrame 5316 * @augments wp.media.view.Frame 5317 * @augments wp.media.View 5318 * @augments wp.Backbone.View 5319 * @augments Backbone.View 5320 * @mixes wp.media.controller.StateMachine 5321 */ 5322 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{ 5323 initialize: function() { 5324 this.counts = { 5325 audio: { 5326 count: wp.media.view.settings.attachmentCounts.audio, 5327 state: 'playlist' 5328 }, 5329 video: { 5330 count: wp.media.view.settings.attachmentCounts.video, 5331 state: 'video-playlist' 5332 } 5333 }; 5334 5335 _.defaults( this.options, { 5336 multiple: true, 5337 editing: false, 5338 state: 'insert', 5339 metadata: {} 5340 }); 5341 5342 // Call 'initialize' directly on the parent class. 5343 Select.prototype.initialize.apply( this, arguments ); 5344 this.createIframeStates(); 5345 5346 }, 5347 5348 /** 5349 * Create the default states. 5350 */ 5351 createStates: function() { 5352 var options = this.options; 5353 5354 this.states.add([ 5355 // Main states. 5356 new Library({ 5357 id: 'insert', 5358 title: l10n.insertMediaTitle, 5359 priority: 20, 5360 toolbar: 'main-insert', 5361 filterable: 'all', 5362 library: wp.media.query( options.library ), 5363 multiple: options.multiple ? 'reset' : false, 5364 editable: true, 5365 5366 // If the user isn't allowed to edit fields, 5367 // can they still edit it locally? 5368 allowLocalEdits: true, 5369 5370 // Show the attachment display settings. 5371 displaySettings: true, 5372 // Update user settings when users adjust the 5373 // attachment display settings. 5374 displayUserSettings: true 5375 }), 5376 5377 new Library({ 5378 id: 'gallery', 5379 title: l10n.createGalleryTitle, 5380 priority: 40, 5381 toolbar: 'main-gallery', 5382 filterable: 'uploaded', 5383 multiple: 'add', 5384 editable: false, 5385 5386 library: wp.media.query( _.defaults({ 5387 type: 'image' 5388 }, options.library ) ) 5389 }), 5390 5391 // Embed states. 5392 new wp.media.controller.Embed( { metadata: options.metadata } ), 5393 5394 new wp.media.controller.EditImage( { model: options.editImage } ), 5395 5396 // Gallery states. 5397 new wp.media.controller.GalleryEdit({ 5398 library: options.selection, 5399 editing: options.editing, 5400 menu: 'gallery' 5401 }), 5402 5403 new wp.media.controller.GalleryAdd(), 5404 5405 new Library({ 5406 id: 'playlist', 5407 title: l10n.createPlaylistTitle, 5408 priority: 60, 5409 toolbar: 'main-playlist', 5410 filterable: 'uploaded', 5411 multiple: 'add', 5412 editable: false, 5413 5414 library: wp.media.query( _.defaults({ 5415 type: 'audio' 5416 }, options.library ) ) 5417 }), 5418 5419 // Playlist states. 5420 new wp.media.controller.CollectionEdit({ 5421 type: 'audio', 5422 collectionType: 'playlist', 5423 title: l10n.editPlaylistTitle, 5424 SettingsView: wp.media.view.Settings.Playlist, 5425 library: options.selection, 5426 editing: options.editing, 5427 menu: 'playlist', 5428 dragInfoText: l10n.playlistDragInfo, 5429 dragInfo: false 5430 }), 5431 5432 new wp.media.controller.CollectionAdd({ 5433 type: 'audio', 5434 collectionType: 'playlist', 5435 title: l10n.addToPlaylistTitle 5436 }), 5437 5438 new Library({ 5439 id: 'video-playlist', 5440 title: l10n.createVideoPlaylistTitle, 5441 priority: 60, 5442 toolbar: 'main-video-playlist', 5443 filterable: 'uploaded', 5444 multiple: 'add', 5445 editable: false, 5446 5447 library: wp.media.query( _.defaults({ 5448 type: 'video' 5449 }, options.library ) ) 5450 }), 5451 5452 new wp.media.controller.CollectionEdit({ 5453 type: 'video', 5454 collectionType: 'playlist', 5455 title: l10n.editVideoPlaylistTitle, 5456 SettingsView: wp.media.view.Settings.Playlist, 5457 library: options.selection, 5458 editing: options.editing, 5459 menu: 'video-playlist', 5460 dragInfoText: l10n.videoPlaylistDragInfo, 5461 dragInfo: false 5462 }), 5463 5464 new wp.media.controller.CollectionAdd({ 5465 type: 'video', 5466 collectionType: 'playlist', 5467 title: l10n.addToVideoPlaylistTitle 5468 }) 5469 ]); 5470 5471 if ( wp.media.view.settings.post.featuredImageId ) { 5472 this.states.add( new wp.media.controller.FeaturedImage() ); 5473 } 5474 }, 5475 5476 bindHandlers: function() { 5477 var handlers, checkCounts; 5478 5479 Select.prototype.bindHandlers.apply( this, arguments ); 5480 5481 this.on( 'activate', this.activate, this ); 5482 5483 // Only bother checking media type counts if one of the counts is zero 5484 checkCounts = _.find( this.counts, function( type ) { 5485 return type.count === 0; 5486 } ); 5487 5488 if ( typeof checkCounts !== 'undefined' ) { 5489 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 5490 } 5491 5492 this.on( 'menu:create:gallery', this.createMenu, this ); 5493 this.on( 'menu:create:playlist', this.createMenu, this ); 5494 this.on( 'menu:create:video-playlist', this.createMenu, this ); 5495 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 5496 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 5497 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 5498 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 5499 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 5500 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 5501 5502 handlers = { 5503 menu: { 5504 'default': 'mainMenu', 5505 'gallery': 'galleryMenu', 5506 'playlist': 'playlistMenu', 5507 'video-playlist': 'videoPlaylistMenu' 5508 }, 5509 5510 content: { 5511 'embed': 'embedContent', 5512 'edit-image': 'editImageContent', 5513 'edit-selection': 'editSelectionContent' 5514 }, 5515 5516 toolbar: { 5517 'main-insert': 'mainInsertToolbar', 5518 'main-gallery': 'mainGalleryToolbar', 5519 'gallery-edit': 'galleryEditToolbar', 5520 'gallery-add': 'galleryAddToolbar', 5521 'main-playlist': 'mainPlaylistToolbar', 5522 'playlist-edit': 'playlistEditToolbar', 5523 'playlist-add': 'playlistAddToolbar', 5524 'main-video-playlist': 'mainVideoPlaylistToolbar', 5525 'video-playlist-edit': 'videoPlaylistEditToolbar', 5526 'video-playlist-add': 'videoPlaylistAddToolbar' 5527 } 5528 }; 5529 5530 _.each( handlers, function( regionHandlers, region ) { 5531 _.each( regionHandlers, function( callback, handler ) { 5532 this.on( region + ':render:' + handler, this[ callback ], this ); 5533 }, this ); 5534 }, this ); 5535 }, 5536 5537 activate: function() { 5538 // Hide menu items for states tied to particular media types if there are no items 5539 _.each( this.counts, function( type ) { 5540 if ( type.count < 1 ) { 5541 this.menuItemVisibility( type.state, 'hide' ); 5542 } 5543 }, this ); 5544 }, 5545 5546 mediaTypeCounts: function( model, attr ) { 5547 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 5548 this.counts[ attr ].count++; 5549 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 5550 } 5551 }, 5552 5553 // Menus 5554 /** 5555 * @param {wp.Backbone.View} view 5556 */ 5557 mainMenu: function( view ) { 5558 view.set({ 5559 'library-separator': new wp.media.View({ 5560 className: 'separator', 5561 priority: 100 5562 }) 5563 }); 5564 }, 5565 5566 menuItemVisibility: function( state, visibility ) { 5567 var menu = this.menu.get(); 5568 if ( visibility === 'hide' ) { 5569 menu.hide( state ); 5570 } else if ( visibility === 'show' ) { 5571 menu.show( state ); 5572 } 5573 }, 5574 /** 5575 * @param {wp.Backbone.View} view 5576 */ 5577 galleryMenu: function( view ) { 5578 var lastState = this.lastState(), 5579 previous = lastState && lastState.id, 5580 frame = this; 5581 5582 view.set({ 5583 cancel: { 5584 text: l10n.cancelGalleryTitle, 5585 priority: 20, 5586 click: function() { 5587 if ( previous ) { 5588 frame.setState( previous ); 5589 } else { 5590 frame.close(); 5591 } 5592 5593 // Keep focus inside media modal 5594 // after canceling a gallery 5595 this.controller.modal.focusManager.focus(); 5596 } 5597 }, 5598 separateCancel: new wp.media.View({ 5599 className: 'separator', 5600 priority: 40 5601 }) 5602 }); 5603 }, 5604 5605 playlistMenu: function( view ) { 5606 var lastState = this.lastState(), 5607 previous = lastState && lastState.id, 5608 frame = this; 5609 5610 view.set({ 5611 cancel: { 5612 text: l10n.cancelPlaylistTitle, 5613 priority: 20, 5614 click: function() { 5615 if ( previous ) { 5616 frame.setState( previous ); 5617 } else { 5618 frame.close(); 5619 } 5620 } 5621 }, 5622 separateCancel: new wp.media.View({ 5623 className: 'separator', 5624 priority: 40 5625 }) 5626 }); 5627 }, 5628 5629 videoPlaylistMenu: function( view ) { 5630 var lastState = this.lastState(), 5631 previous = lastState && lastState.id, 5632 frame = this; 5633 5634 view.set({ 5635 cancel: { 5636 text: l10n.cancelVideoPlaylistTitle, 5637 priority: 20, 5638 click: function() { 5639 if ( previous ) { 5640 frame.setState( previous ); 5641 } else { 5642 frame.close(); 5643 } 5644 } 5645 }, 5646 separateCancel: new wp.media.View({ 5647 className: 'separator', 5648 priority: 40 5649 }) 5650 }); 5651 }, 5652 5653 // Content 5654 embedContent: function() { 5655 var view = new wp.media.view.Embed({ 5656 controller: this, 5657 model: this.state() 5658 }).render(); 5659 5660 this.content.set( view ); 5661 5662 if ( ! wp.media.isTouchDevice ) { 5663 view.url.focus(); 5664 } 5665 }, 5666 5667 editSelectionContent: function() { 5668 var state = this.state(), 5669 selection = state.get('selection'), 5670 view; 5671 5672 view = new wp.media.view.AttachmentsBrowser({ 5673 controller: this, 5674 collection: selection, 5675 selection: selection, 5676 model: state, 5677 sortable: true, 5678 search: false, 5679 date: false, 5680 dragInfo: true, 5681 5682 AttachmentView: wp.media.view.Attachments.EditSelection 5683 }).render(); 5684 5685 view.toolbar.set( 'backToLibrary', { 5686 text: l10n.returnToLibrary, 5687 priority: -100, 5688 5689 click: function() { 5690 this.controller.content.mode('browse'); 5691 } 5692 }); 5693 5694 // Browse our library of attachments. 5695 this.content.set( view ); 5696 5697 // Trigger the controller to set focus 5698 this.trigger( 'edit:selection', this ); 5699 }, 5700 5701 editImageContent: function() { 5702 var image = this.state().get('image'), 5703 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 5704 5705 this.content.set( view ); 5706 5707 // after creating the wrapper view, load the actual editor via an ajax call 5708 view.loadEditor(); 5709 5710 }, 5711 5712 // Toolbars 5713 5714 /** 5715 * @param {wp.Backbone.View} view 5716 */ 5717 selectionStatusToolbar: function( view ) { 5718 var editable = this.state().get('editable'); 5719 5720 view.set( 'selection', new wp.media.view.Selection({ 5721 controller: this, 5722 collection: this.state().get('selection'), 5723 priority: -40, 5724 5725 // If the selection is editable, pass the callback to 5726 // switch the content mode. 5727 editable: editable && function() { 5728 this.controller.content.mode('edit-selection'); 5729 } 5730 }).render() ); 5731 }, 5732 5733 /** 5734 * @param {wp.Backbone.View} view 5735 */ 5736 mainInsertToolbar: function( view ) { 5737 var controller = this; 5738 5739 this.selectionStatusToolbar( view ); 5740 5741 view.set( 'insert', { 5742 style: 'primary', 5743 priority: 80, 5744 text: l10n.insertIntoPost, 5745 requires: { selection: true }, 5746 5747 /** 5748 * @callback 5749 * @fires wp.media.controller.State#insert 5750 */ 5751 click: function() { 5752 var state = controller.state(), 5753 selection = state.get('selection'); 5754 5755 controller.close(); 5756 state.trigger( 'insert', selection ).reset(); 5757 } 5758 }); 5759 }, 5760 5761 /** 5762 * @param {wp.Backbone.View} view 5763 */ 5764 mainGalleryToolbar: function( view ) { 5765 var controller = this; 5766 5767 this.selectionStatusToolbar( view ); 5768 5769 view.set( 'gallery', { 5770 style: 'primary', 5771 text: l10n.createNewGallery, 5772 priority: 60, 5773 requires: { selection: true }, 5774 5775 click: function() { 5776 var selection = controller.state().get('selection'), 5777 edit = controller.state('gallery-edit'), 5778 models = selection.where({ type: 'image' }); 5779 5780 edit.set( 'library', new wp.media.model.Selection( models, { 5781 props: selection.props.toJSON(), 5782 multiple: true 5783 }) ); 5784 5785 this.controller.setState('gallery-edit'); 5786 5787 // Keep focus inside media modal 5788 // after jumping to gallery view 5789 this.controller.modal.focusManager.focus(); 5790 } 5791 }); 5792 }, 5793 5794 mainPlaylistToolbar: function( view ) { 5795 var controller = this; 5796 5797 this.selectionStatusToolbar( view ); 5798 5799 view.set( 'playlist', { 5800 style: 'primary', 5801 text: l10n.createNewPlaylist, 5802 priority: 100, 5803 requires: { selection: true }, 5804 5805 click: function() { 5806 var selection = controller.state().get('selection'), 5807 edit = controller.state('playlist-edit'), 5808 models = selection.where({ type: 'audio' }); 5809 5810 edit.set( 'library', new wp.media.model.Selection( models, { 5811 props: selection.props.toJSON(), 5812 multiple: true 5813 }) ); 5814 5815 this.controller.setState('playlist-edit'); 5816 5817 // Keep focus inside media modal 5818 // after jumping to playlist view 5819 this.controller.modal.focusManager.focus(); 5820 } 5821 }); 5822 }, 5823 5824 mainVideoPlaylistToolbar: function( view ) { 5825 var controller = this; 5826 5827 this.selectionStatusToolbar( view ); 5828 5829 view.set( 'video-playlist', { 5830 style: 'primary', 5831 text: l10n.createNewVideoPlaylist, 5832 priority: 100, 5833 requires: { selection: true }, 5834 5835 click: function() { 5836 var selection = controller.state().get('selection'), 5837 edit = controller.state('video-playlist-edit'), 5838 models = selection.where({ type: 'video' }); 5839 5840 edit.set( 'library', new wp.media.model.Selection( models, { 5841 props: selection.props.toJSON(), 5842 multiple: true 5843 }) ); 5844 5845 this.controller.setState('video-playlist-edit'); 5846 5847 // Keep focus inside media modal 5848 // after jumping to video playlist view 5849 this.controller.modal.focusManager.focus(); 5850 } 5851 }); 5852 }, 5853 5854 featuredImageToolbar: function( toolbar ) { 5855 this.createSelectToolbar( toolbar, { 5856 text: l10n.setFeaturedImage, 5857 state: this.options.state 5858 }); 5859 }, 5860 5861 mainEmbedToolbar: function( toolbar ) { 5862 toolbar.view = new wp.media.view.Toolbar.Embed({ 5863 controller: this 5864 }); 5865 }, 5866 5867 galleryEditToolbar: function() { 5868 var editing = this.state().get('editing'); 5869 this.toolbar.set( new wp.media.view.Toolbar({ 5870 controller: this, 5871 items: { 5872 insert: { 5873 style: 'primary', 5874 text: editing ? l10n.updateGallery : l10n.insertGallery, 5875 priority: 80, 5876 requires: { library: true }, 5877 5878 /** 5879 * @fires wp.media.controller.State#update 5880 */ 5881 click: function() { 5882 var controller = this.controller, 5883 state = controller.state(); 5884 5885 controller.close(); 5886 state.trigger( 'update', state.get('library') ); 5887 5888 // Restore and reset the default state. 5889 controller.setState( controller.options.state ); 5890 controller.reset(); 5891 } 5892 } 5893 } 5894 }) ); 5895 }, 5896 5897 galleryAddToolbar: function() { 5898 this.toolbar.set( new wp.media.view.Toolbar({ 5899 controller: this, 5900 items: { 5901 insert: { 5902 style: 'primary', 5903 text: l10n.addToGallery, 5904 priority: 80, 5905 requires: { selection: true }, 5906 5907 /** 5908 * @fires wp.media.controller.State#reset 5909 */ 5910 click: function() { 5911 var controller = this.controller, 5912 state = controller.state(), 5913 edit = controller.state('gallery-edit'); 5914 5915 edit.get('library').add( state.get('selection').models ); 5916 state.trigger('reset'); 5917 controller.setState('gallery-edit'); 5918 } 5919 } 5920 } 5921 }) ); 5922 }, 5923 5924 playlistEditToolbar: function() { 5925 var editing = this.state().get('editing'); 5926 this.toolbar.set( new wp.media.view.Toolbar({ 5927 controller: this, 5928 items: { 5929 insert: { 5930 style: 'primary', 5931 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 5932 priority: 80, 5933 requires: { library: true }, 5934 5935 /** 5936 * @fires wp.media.controller.State#update 5937 */ 5938 click: function() { 5939 var controller = this.controller, 5940 state = controller.state(); 5941 5942 controller.close(); 5943 state.trigger( 'update', state.get('library') ); 5944 5945 // Restore and reset the default state. 5946 controller.setState( controller.options.state ); 5947 controller.reset(); 5948 } 5949 } 5950 } 5951 }) ); 5952 }, 5953 5954 playlistAddToolbar: function() { 5955 this.toolbar.set( new wp.media.view.Toolbar({ 5956 controller: this, 5957 items: { 5958 insert: { 5959 style: 'primary', 5960 text: l10n.addToPlaylist, 5961 priority: 80, 5962 requires: { selection: true }, 5963 5964 /** 5965 * @fires wp.media.controller.State#reset 5966 */ 5967 click: function() { 5968 var controller = this.controller, 5969 state = controller.state(), 5970 edit = controller.state('playlist-edit'); 5971 5972 edit.get('library').add( state.get('selection').models ); 5973 state.trigger('reset'); 5974 controller.setState('playlist-edit'); 5975 } 5976 } 5977 } 5978 }) ); 5979 }, 5980 5981 videoPlaylistEditToolbar: function() { 5982 var editing = this.state().get('editing'); 5983 this.toolbar.set( new wp.media.view.Toolbar({ 5984 controller: this, 5985 items: { 5986 insert: { 5987 style: 'primary', 5988 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 5989 priority: 140, 5990 requires: { library: true }, 5991 5992 click: function() { 5993 var controller = this.controller, 5994 state = controller.state(), 5995 library = state.get('library'); 5996 5997 library.type = 'video'; 5998 5999 controller.close(); 6000 state.trigger( 'update', library ); 6001 6002 // Restore and reset the default state. 6003 controller.setState( controller.options.state ); 6004 controller.reset(); 6005 } 6006 } 6007 } 6008 }) ); 6009 }, 6010 6011 videoPlaylistAddToolbar: function() { 6012 this.toolbar.set( new wp.media.view.Toolbar({ 6013 controller: this, 6014 items: { 6015 insert: { 6016 style: 'primary', 6017 text: l10n.addToVideoPlaylist, 6018 priority: 140, 6019 requires: { selection: true }, 6020 6021 click: function() { 6022 var controller = this.controller, 6023 state = controller.state(), 6024 edit = controller.state('video-playlist-edit'); 6025 6026 edit.get('library').add( state.get('selection').models ); 6027 state.trigger('reset'); 6028 controller.setState('video-playlist-edit'); 6029 } 6030 } 6031 } 6032 }) ); 6033 } 6034 }); 6035 6036 module.exports = Post; 6037 6038 },{}],46:[function(require,module,exports){ 6039 var MediaFrame = wp.media.view.MediaFrame, 6040 l10n = wp.media.view.l10n, 6041 Select; 6042 6043 /** 6044 * wp.media.view.MediaFrame.Select 6045 * 6046 * A frame for selecting an item or items from the media library. 6047 * 6048 * @memberOf wp.media.view.MediaFrame 6049 * 6050 * @class 6051 * @augments wp.media.view.MediaFrame 6052 * @augments wp.media.view.Frame 6053 * @augments wp.media.View 6054 * @augments wp.Backbone.View 6055 * @augments Backbone.View 6056 * @mixes wp.media.controller.StateMachine 6057 */ 6058 Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{ 6059 initialize: function() { 6060 // Call 'initialize' directly on the parent class. 6061 MediaFrame.prototype.initialize.apply( this, arguments ); 6062 6063 _.defaults( this.options, { 6064 selection: [], 6065 library: {}, 6066 multiple: false, 6067 state: 'library' 6068 }); 6069 6070 this.createSelection(); 6071 this.createStates(); 6072 this.bindHandlers(); 6073 }, 6074 6075 /** 6076 * Attach a selection collection to the frame. 6077 * 6078 * A selection is a collection of attachments used for a specific purpose 6079 * by a media frame. e.g. Selecting an attachment (or many) to insert into 6080 * post content. 6081 * 6082 * @see media.model.Selection 6083 */ 6084 createSelection: function() { 6085 var selection = this.options.selection; 6086 6087 if ( ! (selection instanceof wp.media.model.Selection) ) { 6088 this.options.selection = new wp.media.model.Selection( selection, { 6089 multiple: this.options.multiple 6090 }); 6091 } 6092 6093 this._selection = { 6094 attachments: new wp.media.model.Attachments(), 6095 difference: [] 6096 }; 6097 }, 6098 6099 /** 6100 * Create the default states on the frame. 6101 */ 6102 createStates: function() { 6103 var options = this.options; 6104 6105 if ( this.options.states ) { 6106 return; 6107 } 6108 6109 // Add the default states. 6110 this.states.add([ 6111 // Main states. 6112 new wp.media.controller.Library({ 6113 library: wp.media.query( options.library ), 6114 multiple: options.multiple, 6115 title: options.title, 6116 priority: 20 6117 }) 6118 ]); 6119 }, 6120 6121 /** 6122 * Bind region mode event callbacks. 6123 * 6124 * @see media.controller.Region.render 6125 */ 6126 bindHandlers: function() { 6127 this.on( 'router:create:browse', this.createRouter, this ); 6128 this.on( 'router:render:browse', this.browseRouter, this ); 6129 this.on( 'content:create:browse', this.browseContent, this ); 6130 this.on( 'content:render:upload', this.uploadContent, this ); 6131 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 6132 }, 6133 6134 /** 6135 * Render callback for the router region in the `browse` mode. 6136 * 6137 * @param {wp.media.view.Router} routerView 6138 */ 6139 browseRouter: function( routerView ) { 6140 routerView.set({ 6141 upload: { 6142 text: l10n.uploadFilesTitle, 6143 priority: 20 6144 }, 6145 browse: { 6146 text: l10n.mediaLibraryTitle, 6147 priority: 40 6148 } 6149 }); 6150 }, 6151 6152 /** 6153 * Render callback for the content region in the `browse` mode. 6154 * 6155 * @param {wp.media.controller.Region} contentRegion 6156 */ 6157 browseContent: function( contentRegion ) { 6158 var state = this.state(); 6159 6160 this.$el.removeClass('hide-toolbar'); 6161 6162 // Browse our library of attachments. 6163 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 6164 controller: this, 6165 collection: state.get('library'), 6166 selection: state.get('selection'), 6167 model: state, 6168 sortable: state.get('sortable'), 6169 search: state.get('searchable'), 6170 filters: state.get('filterable'), 6171 date: state.get('date'), 6172 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 6173 dragInfo: state.get('dragInfo'), 6174 6175 idealColumnWidth: state.get('idealColumnWidth'), 6176 suggestedWidth: state.get('suggestedWidth'), 6177 suggestedHeight: state.get('suggestedHeight'), 6178 6179 AttachmentView: state.get('AttachmentView') 6180 }); 6181 }, 6182 6183 /** 6184 * Render callback for the content region in the `upload` mode. 6185 */ 6186 uploadContent: function() { 6187 this.$el.removeClass( 'hide-toolbar' ); 6188 this.content.set( new wp.media.view.UploaderInline({ 6189 controller: this 6190 }) ); 6191 }, 6192 6193 /** 6194 * Toolbars 6195 * 6196 * @param {Object} toolbar 6197 * @param {Object} [options={}] 6198 * @this wp.media.controller.Region 6199 */ 6200 createSelectToolbar: function( toolbar, options ) { 6201 options = options || this.options.button || {}; 6202 options.controller = this; 6203 6204 toolbar.view = new wp.media.view.Toolbar.Select( options ); 6205 } 6206 }); 6207 6208 module.exports = Select; 6209 6210 },{}],47:[function(require,module,exports){ 6211 /** 6212 * wp.media.view.Iframe 6213 * 6214 * @memberOf wp.media.view 6215 * 6216 * @class 6217 * @augments wp.media.View 6218 * @augments wp.Backbone.View 6219 * @augments Backbone.View 6220 */ 6221 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{ 6222 className: 'media-iframe', 6223 /** 6224 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 6225 */ 6226 render: function() { 6227 this.views.detach(); 6228 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 6229 this.views.render(); 6230 return this; 6231 } 6232 }); 6233 6234 module.exports = Iframe; 6235 6236 },{}],48:[function(require,module,exports){ 8706 module.exports = EmbedImage; 8707 8708 8709 /***/ }), 8710 /* 95 */ 8711 /***/ (function(module, exports) { 8712 6237 8713 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 6238 8714 $ = jQuery, … … 6404 8880 module.exports = ImageDetails; 6405 8881 6406 },{}],49:[function(require,module,exports){ 8882 8883 /***/ }), 8884 /* 96 */ 8885 /***/ (function(module, exports) { 8886 8887 var View = wp.media.View, 8888 UploaderStatus = wp.media.view.UploaderStatus, 8889 l10n = wp.media.view.l10n, 8890 $ = jQuery, 8891 Cropper; 8892 6407 8893 /** 6408 * wp.media.view.Label 8894 * wp.media.view.Cropper 8895 * 8896 * Uses the imgAreaSelect plugin to allow a user to crop an image. 8897 * 8898 * Takes imgAreaSelect options from 8899 * wp.customize.HeaderControl.calculateImageSelectOptions via 8900 * wp.customize.HeaderControl.openMM. 6409 8901 * 6410 8902 * @memberOf wp.media.view … … 6415 8907 * @augments Backbone.View 6416 8908 */ 6417 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{ 6418 tagName: 'label', 6419 className: 'screen-reader-text', 6420 8909 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{ 8910 className: 'crop-content', 8911 template: wp.template('crop-content'), 6421 8912 initialize: function() { 6422 this.value = this.options.value; 6423 }, 6424 6425 render: function() { 6426 this.$el.html( this.value ); 6427 6428 return this; 8913 _.bindAll(this, 'onImageLoad'); 8914 }, 8915 ready: function() { 8916 this.controller.frame.on('content:error:crop', this.onError, this); 8917 this.$image = this.$el.find('.crop-image'); 8918 this.$image.on('load', this.onImageLoad); 8919 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 8920 }, 8921 remove: function() { 8922 $(window).off('resize.cropper'); 8923 this.$el.remove(); 8924 this.$el.off(); 8925 View.prototype.remove.apply(this, arguments); 8926 }, 8927 prepare: function() { 8928 return { 8929 title: l10n.cropYourImage, 8930 url: this.options.attachment.get('url') 8931 }; 8932 }, 8933 onImageLoad: function() { 8934 var imgOptions = this.controller.get('imgSelectOptions'), 8935 imgSelect; 8936 8937 if (typeof imgOptions === 'function') { 8938 imgOptions = imgOptions(this.options.attachment, this.controller); 8939 } 8940 8941 imgOptions = _.extend(imgOptions, { 8942 parent: this.$el, 8943 onInit: function() { 8944 this.parent.children().on( 'mousedown touchstart', function( e ){ 8945 8946 if ( e.shiftKey ) { 8947 imgSelect.setOptions( { 8948 aspectRatio: '1:1' 8949 } ); 8950 } else { 8951 imgSelect.setOptions( { 8952 aspectRatio: false 8953 } ); 8954 } 8955 } ); 8956 } 8957 } ); 8958 this.trigger('image-loaded'); 8959 imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 8960 }, 8961 onError: function() { 8962 var filename = this.options.attachment.get('filename'); 8963 8964 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8965 filename: UploaderStatus.prototype.filename(filename), 8966 message: window._wpMediaViewsL10n.cropError 8967 }), { at: 0 }); 6429 8968 } 6430 8969 }); 6431 8970 6432 module.exports = Label; 6433 6434 },{}],50:[function(require,module,exports){ 6435 var Frame = wp.media.view.Frame, 6436 $ = jQuery, 6437 MediaFrame; 6438 6439 /** 6440 * wp.media.view.MediaFrame 6441 * 6442 * The frame used to create the media modal. 6443 * 6444 * @memberOf wp.media.view 6445 * 6446 * @class 6447 * @augments wp.media.view.Frame 6448 * @augments wp.media.View 6449 * @augments wp.Backbone.View 6450 * @augments Backbone.View 6451 * @mixes wp.media.controller.StateMachine 6452 */ 6453 MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{ 6454 className: 'media-frame', 6455 template: wp.template('media-frame'), 6456 regions: ['menu','title','content','toolbar','router'], 6457 6458 events: { 6459 'click div.media-frame-title h1': 'toggleMenu' 6460 }, 6461 6462 /** 6463 * @constructs 6464 */ 6465 initialize: function() { 6466 Frame.prototype.initialize.apply( this, arguments ); 6467 6468 _.defaults( this.options, { 6469 title: '', 6470 modal: true, 6471 uploader: true 6472 }); 6473 6474 // Ensure core UI is enabled. 6475 this.$el.addClass('wp-core-ui'); 6476 6477 // Initialize modal container view. 6478 if ( this.options.modal ) { 6479 this.modal = new wp.media.view.Modal({ 6480 controller: this, 6481 title: this.options.title 6482 }); 6483 6484 this.modal.content( this ); 6485 } 6486 6487 // Force the uploader off if the upload limit has been exceeded or 6488 // if the browser isn't supported. 6489 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 6490 this.options.uploader = false; 6491 } 6492 6493 // Initialize window-wide uploader. 6494 if ( this.options.uploader ) { 6495 this.uploader = new wp.media.view.UploaderWindow({ 6496 controller: this, 6497 uploader: { 6498 dropzone: this.modal ? this.modal.$el : this.$el, 6499 container: this.$el 6500 } 6501 }); 6502 this.views.set( '.media-frame-uploader', this.uploader ); 6503 } 6504 6505 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 6506 6507 // Bind default title creation. 6508 this.on( 'title:create:default', this.createTitle, this ); 6509 this.title.mode('default'); 6510 6511 this.on( 'title:render', function( view ) { 6512 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 6513 }); 6514 6515 // Bind default menu. 6516 this.on( 'menu:create:default', this.createMenu, this ); 6517 }, 6518 /** 6519 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6520 */ 6521 render: function() { 6522 // Activate the default state if no active state exists. 6523 if ( ! this.state() && this.options.state ) { 6524 this.setState( this.options.state ); 6525 } 6526 /** 6527 * call 'render' directly on the parent class 6528 */ 6529 return Frame.prototype.render.apply( this, arguments ); 6530 }, 6531 /** 6532 * @param {Object} title 6533 * @this wp.media.controller.Region 6534 */ 6535 createTitle: function( title ) { 6536 title.view = new wp.media.View({ 6537 controller: this, 6538 tagName: 'h1' 6539 }); 6540 }, 6541 /** 6542 * @param {Object} menu 6543 * @this wp.media.controller.Region 6544 */ 6545 createMenu: function( menu ) { 6546 menu.view = new wp.media.view.Menu({ 6547 controller: this 6548 }); 6549 }, 6550 6551 toggleMenu: function() { 6552 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 6553 }, 6554 6555 /** 6556 * @param {Object} toolbar 6557 * @this wp.media.controller.Region 6558 */ 6559 createToolbar: function( toolbar ) { 6560 toolbar.view = new wp.media.view.Toolbar({ 6561 controller: this 6562 }); 6563 }, 6564 /** 6565 * @param {Object} router 6566 * @this wp.media.controller.Region 6567 */ 6568 createRouter: function( router ) { 6569 router.view = new wp.media.view.Router({ 6570 controller: this 6571 }); 6572 }, 6573 /** 6574 * @param {Object} options 6575 */ 6576 createIframeStates: function( options ) { 6577 var settings = wp.media.view.settings, 6578 tabs = settings.tabs, 6579 tabUrl = settings.tabUrl, 6580 $postId; 6581 6582 if ( ! tabs || ! tabUrl ) { 6583 return; 6584 } 6585 6586 // Add the post ID to the tab URL if it exists. 6587 $postId = $('#post_ID'); 6588 if ( $postId.length ) { 6589 tabUrl += '&post_id=' + $postId.val(); 6590 } 6591 6592 // Generate the tab states. 6593 _.each( tabs, function( title, id ) { 6594 this.state( 'iframe:' + id ).set( _.defaults({ 6595 tab: id, 6596 src: tabUrl + '&tab=' + id, 6597 title: title, 6598 content: 'iframe', 6599 menu: 'default' 6600 }, options ) ); 6601 }, this ); 6602 6603 this.on( 'content:create:iframe', this.iframeContent, this ); 6604 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 6605 this.on( 'menu:render:default', this.iframeMenu, this ); 6606 this.on( 'open', this.hijackThickbox, this ); 6607 this.on( 'close', this.restoreThickbox, this ); 6608 }, 6609 6610 /** 6611 * @param {Object} content 6612 * @this wp.media.controller.Region 6613 */ 6614 iframeContent: function( content ) { 6615 this.$el.addClass('hide-toolbar'); 6616 content.view = new wp.media.view.Iframe({ 6617 controller: this 6618 }); 6619 }, 6620 6621 iframeContentCleanup: function() { 6622 this.$el.removeClass('hide-toolbar'); 6623 }, 6624 6625 iframeMenu: function( view ) { 6626 var views = {}; 6627 6628 if ( ! view ) { 6629 return; 6630 } 6631 6632 _.each( wp.media.view.settings.tabs, function( title, id ) { 6633 views[ 'iframe:' + id ] = { 6634 text: this.state( 'iframe:' + id ).get('title'), 6635 priority: 200 6636 }; 6637 }, this ); 6638 6639 view.set( views ); 6640 }, 6641 6642 hijackThickbox: function() { 6643 var frame = this; 6644 6645 if ( ! window.tb_remove || this._tb_remove ) { 6646 return; 6647 } 6648 6649 this._tb_remove = window.tb_remove; 6650 window.tb_remove = function() { 6651 frame.close(); 6652 frame.reset(); 6653 frame.setState( frame.options.state ); 6654 frame._tb_remove.call( window ); 6655 }; 6656 }, 6657 6658 restoreThickbox: function() { 6659 if ( ! this._tb_remove ) { 6660 return; 6661 } 6662 6663 window.tb_remove = this._tb_remove; 6664 delete this._tb_remove; 6665 } 6666 }); 6667 6668 // Map some of the modal's methods to the frame. 6669 _.each(['open','close','attach','detach','escape'], function( method ) { 6670 /** 6671 * @function open 6672 * @memberOf wp.media.view.MediaFrame 6673 * @instance 6674 * 6675 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6676 */ 6677 /** 6678 * @function close 6679 * @memberOf wp.media.view.MediaFrame 6680 * @instance 6681 * 6682 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6683 */ 6684 /** 6685 * @function attach 6686 * @memberOf wp.media.view.MediaFrame 6687 * @instance 6688 * 6689 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6690 */ 6691 /** 6692 * @function detach 6693 * @memberOf wp.media.view.MediaFrame 6694 * @instance 6695 * 6696 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6697 */ 6698 /** 6699 * @function escape 6700 * @memberOf wp.media.view.MediaFrame 6701 * @instance 6702 * 6703 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6704 */ 6705 MediaFrame.prototype[ method ] = function() { 6706 if ( this.modal ) { 6707 this.modal[ method ].apply( this.modal, arguments ); 6708 } 6709 return this; 6710 }; 6711 }); 6712 6713 module.exports = MediaFrame; 6714 6715 },{}],51:[function(require,module,exports){ 6716 var $ = jQuery, 6717 MenuItem; 6718 6719 /** 6720 * wp.media.view.MenuItem 6721 * 6722 * @memberOf wp.media.view 6723 * 6724 * @class 6725 * @augments wp.media.View 6726 * @augments wp.Backbone.View 6727 * @augments Backbone.View 6728 */ 6729 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{ 6730 tagName: 'a', 6731 className: 'media-menu-item', 6732 6733 attributes: { 6734 href: '#' 6735 }, 6736 6737 events: { 6738 'click': '_click' 6739 }, 6740 /** 6741 * @param {Object} event 6742 */ 6743 _click: function( event ) { 6744 var clickOverride = this.options.click; 6745 6746 if ( event ) { 6747 event.preventDefault(); 6748 } 6749 6750 if ( clickOverride ) { 6751 clickOverride.call( this ); 6752 } else { 6753 this.click(); 6754 } 6755 6756 // When selecting a tab along the left side, 6757 // focus should be transferred into the main panel 6758 if ( ! wp.media.isTouchDevice ) { 6759 $('.media-frame-content input').first().focus(); 6760 } 6761 }, 6762 6763 click: function() { 6764 var state = this.options.state; 6765 6766 if ( state ) { 6767 this.controller.setState( state ); 6768 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 6769 } 6770 }, 6771 /** 6772 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 6773 */ 6774 render: function() { 6775 var options = this.options; 6776 6777 if ( options.text ) { 6778 this.$el.text( options.text ); 6779 } else if ( options.html ) { 6780 this.$el.html( options.html ); 6781 } 6782 6783 return this; 6784 } 6785 }); 6786 6787 module.exports = MenuItem; 6788 6789 },{}],52:[function(require,module,exports){ 6790 var MenuItem = wp.media.view.MenuItem, 6791 PriorityList = wp.media.view.PriorityList, 6792 Menu; 6793 6794 /** 6795 * wp.media.view.Menu 6796 * 6797 * @memberOf wp.media.view 6798 * 6799 * @class 6800 * @augments wp.media.view.PriorityList 6801 * @augments wp.media.View 6802 * @augments wp.Backbone.View 6803 * @augments Backbone.View 6804 */ 6805 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{ 6806 tagName: 'div', 6807 className: 'media-menu', 6808 property: 'state', 6809 ItemView: MenuItem, 6810 region: 'menu', 6811 6812 /* TODO: alternatively hide on any click anywhere 6813 events: { 6814 'click': 'click' 6815 }, 6816 6817 click: function() { 6818 this.$el.removeClass( 'visible' ); 6819 }, 6820 */ 6821 6822 /** 6823 * @param {Object} options 6824 * @param {string} id 6825 * @returns {wp.media.View} 6826 */ 6827 toView: function( options, id ) { 6828 options = options || {}; 6829 options[ this.property ] = options[ this.property ] || id; 6830 return new this.ItemView( options ).render(); 6831 }, 6832 6833 ready: function() { 6834 /** 6835 * call 'ready' directly on the parent class 6836 */ 6837 PriorityList.prototype.ready.apply( this, arguments ); 6838 this.visibility(); 6839 }, 6840 6841 set: function() { 6842 /** 6843 * call 'set' directly on the parent class 6844 */ 6845 PriorityList.prototype.set.apply( this, arguments ); 6846 this.visibility(); 6847 }, 6848 6849 unset: function() { 6850 /** 6851 * call 'unset' directly on the parent class 6852 */ 6853 PriorityList.prototype.unset.apply( this, arguments ); 6854 this.visibility(); 6855 }, 6856 6857 visibility: function() { 6858 var region = this.region, 6859 view = this.controller[ region ].get(), 6860 views = this.views.get(), 6861 hide = ! views || views.length < 2; 6862 6863 if ( this === view ) { 6864 this.controller.$el.toggleClass( 'hide-' + region, hide ); 6865 } 6866 }, 6867 /** 6868 * @param {string} id 6869 */ 6870 select: function( id ) { 6871 var view = this.get( id ); 6872 6873 if ( ! view ) { 6874 return; 6875 } 6876 6877 this.deselect(); 6878 view.$el.addClass('active'); 6879 }, 6880 6881 deselect: function() { 6882 this.$el.children().removeClass('active'); 6883 }, 6884 6885 hide: function( id ) { 6886 var view = this.get( id ); 6887 6888 if ( ! view ) { 6889 return; 6890 } 6891 6892 view.$el.addClass('hidden'); 6893 }, 6894 6895 show: function( id ) { 6896 var view = this.get( id ); 6897 6898 if ( ! view ) { 6899 return; 6900 } 6901 6902 view.$el.removeClass('hidden'); 6903 } 6904 }); 6905 6906 module.exports = Menu; 6907 6908 },{}],53:[function(require,module,exports){ 6909 var $ = jQuery, 6910 Modal; 6911 6912 /** 6913 * wp.media.view.Modal 6914 * 6915 * A modal view, which the media modal uses as its default container. 6916 * 6917 * @memberOf wp.media.view 6918 * 6919 * @class 6920 * @augments wp.media.View 6921 * @augments wp.Backbone.View 6922 * @augments Backbone.View 6923 */ 6924 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ 6925 tagName: 'div', 6926 template: wp.template('media-modal'), 6927 6928 attributes: { 6929 tabindex: 0 6930 }, 6931 6932 events: { 6933 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 6934 'keydown': 'keydown' 6935 }, 6936 6937 clickedOpenerEl: null, 6938 6939 initialize: function() { 6940 _.defaults( this.options, { 6941 container: document.body, 6942 title: '', 6943 propagate: true, 6944 freeze: true 6945 }); 6946 6947 this.focusManager = new wp.media.view.FocusManager({ 6948 el: this.el 6949 }); 6950 }, 6951 /** 6952 * @returns {Object} 6953 */ 6954 prepare: function() { 6955 return { 6956 title: this.options.title 6957 }; 6958 }, 6959 6960 /** 6961 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6962 */ 6963 attach: function() { 6964 if ( this.views.attached ) { 6965 return this; 6966 } 6967 6968 if ( ! this.views.rendered ) { 6969 this.render(); 6970 } 6971 6972 this.$el.appendTo( this.options.container ); 6973 6974 // Manually mark the view as attached and trigger ready. 6975 this.views.attached = true; 6976 this.views.ready(); 6977 6978 return this.propagate('attach'); 6979 }, 6980 6981 /** 6982 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6983 */ 6984 detach: function() { 6985 if ( this.$el.is(':visible') ) { 6986 this.close(); 6987 } 6988 6989 this.$el.detach(); 6990 this.views.attached = false; 6991 return this.propagate('detach'); 6992 }, 6993 6994 /** 6995 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6996 */ 6997 open: function() { 6998 var $el = this.$el, 6999 options = this.options, 7000 mceEditor; 7001 7002 if ( $el.is(':visible') ) { 7003 return this; 7004 } 7005 7006 this.clickedOpenerEl = document.activeElement; 7007 7008 if ( ! this.views.attached ) { 7009 this.attach(); 7010 } 7011 7012 // If the `freeze` option is set, record the window's scroll position. 7013 if ( options.freeze ) { 7014 this._freeze = { 7015 scrollTop: $( window ).scrollTop() 7016 }; 7017 } 7018 7019 // Disable page scrolling. 7020 $( 'body' ).addClass( 'modal-open' ); 7021 7022 $el.show(); 7023 7024 // Try to close the onscreen keyboard 7025 if ( 'ontouchend' in document ) { 7026 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 7027 mceEditor.iframeElement.focus(); 7028 mceEditor.iframeElement.blur(); 7029 7030 setTimeout( function() { 7031 mceEditor.iframeElement.blur(); 7032 }, 100 ); 7033 } 7034 } 7035 7036 this.$el.focus(); 7037 7038 return this.propagate('open'); 7039 }, 7040 7041 /** 7042 * @param {Object} options 7043 * @returns {wp.media.view.Modal} Returns itself to allow chaining 7044 */ 7045 close: function( options ) { 7046 var freeze = this._freeze; 7047 7048 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 7049 return this; 7050 } 7051 7052 // Enable page scrolling. 7053 $( 'body' ).removeClass( 'modal-open' ); 7054 7055 // Hide modal and remove restricted media modal tab focus once it's closed 7056 this.$el.hide().undelegate( 'keydown' ); 7057 7058 // Put focus back in useful location once modal is closed. 7059 if ( null !== this.clickedOpenerEl ) { 7060 this.clickedOpenerEl.focus(); 7061 } else { 7062 $( '#wpbody-content' ).focus(); 7063 } 7064 7065 this.propagate('close'); 7066 7067 // If the `freeze` option is set, restore the container's scroll position. 7068 if ( freeze ) { 7069 $( window ).scrollTop( freeze.scrollTop ); 7070 } 7071 7072 if ( options && options.escape ) { 7073 this.propagate('escape'); 7074 } 7075 7076 return this; 7077 }, 7078 /** 7079 * @returns {wp.media.view.Modal} Returns itself to allow chaining 7080 */ 7081 escape: function() { 7082 return this.close({ escape: true }); 7083 }, 7084 /** 7085 * @param {Object} event 7086 */ 7087 escapeHandler: function( event ) { 7088 event.preventDefault(); 7089 this.escape(); 7090 }, 7091 7092 /** 7093 * @param {Array|Object} content Views to register to '.media-modal-content' 7094 * @returns {wp.media.view.Modal} Returns itself to allow chaining 7095 */ 7096 content: function( content ) { 7097 this.views.set( '.media-modal-content', content ); 7098 return this; 7099 }, 7100 7101 /** 7102 * Triggers a modal event and if the `propagate` option is set, 7103 * forwards events to the modal's controller. 7104 * 7105 * @param {string} id 7106 * @returns {wp.media.view.Modal} Returns itself to allow chaining 7107 */ 7108 propagate: function( id ) { 7109 this.trigger( id ); 7110 7111 if ( this.options.propagate ) { 7112 this.controller.trigger( id ); 7113 } 7114 7115 return this; 7116 }, 7117 /** 7118 * @param {Object} event 7119 */ 7120 keydown: function( event ) { 7121 // Close the modal when escape is pressed. 7122 if ( 27 === event.which && this.$el.is(':visible') ) { 7123 this.escape(); 7124 event.stopImmediatePropagation(); 7125 } 7126 } 7127 }); 7128 7129 module.exports = Modal; 7130 7131 },{}],54:[function(require,module,exports){ 7132 /** 7133 * wp.media.view.PriorityList 7134 * 7135 * @memberOf wp.media.view 7136 * 7137 * @class 7138 * @augments wp.media.View 7139 * @augments wp.Backbone.View 7140 * @augments Backbone.View 7141 */ 7142 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{ 7143 tagName: 'div', 7144 7145 initialize: function() { 7146 this._views = {}; 7147 7148 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 7149 delete this.options.views; 7150 7151 if ( ! this.options.silent ) { 7152 this.render(); 7153 } 7154 }, 7155 /** 7156 * @param {string} id 7157 * @param {wp.media.View|Object} view 7158 * @param {Object} options 7159 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 7160 */ 7161 set: function( id, view, options ) { 7162 var priority, views, index; 7163 7164 options = options || {}; 7165 7166 // Accept an object with an `id` : `view` mapping. 7167 if ( _.isObject( id ) ) { 7168 _.each( id, function( view, id ) { 7169 this.set( id, view ); 7170 }, this ); 7171 return this; 7172 } 7173 7174 if ( ! (view instanceof Backbone.View) ) { 7175 view = this.toView( view, id, options ); 7176 } 7177 view.controller = view.controller || this.controller; 7178 7179 this.unset( id ); 7180 7181 priority = view.options.priority || 10; 7182 views = this.views.get() || []; 7183 7184 _.find( views, function( existing, i ) { 7185 if ( existing.options.priority > priority ) { 7186 index = i; 7187 return true; 7188 } 7189 }); 7190 7191 this._views[ id ] = view; 7192 this.views.add( view, { 7193 at: _.isNumber( index ) ? index : views.length || 0 7194 }); 7195 7196 return this; 7197 }, 7198 /** 7199 * @param {string} id 7200 * @returns {wp.media.View} 7201 */ 7202 get: function( id ) { 7203 return this._views[ id ]; 7204 }, 7205 /** 7206 * @param {string} id 7207 * @returns {wp.media.view.PriorityList} 7208 */ 7209 unset: function( id ) { 7210 var view = this.get( id ); 7211 7212 if ( view ) { 7213 view.remove(); 7214 } 7215 7216 delete this._views[ id ]; 7217 return this; 7218 }, 7219 /** 7220 * @param {Object} options 7221 * @returns {wp.media.View} 7222 */ 7223 toView: function( options ) { 7224 return new wp.media.View( options ); 7225 } 7226 }); 7227 7228 module.exports = PriorityList; 7229 7230 },{}],55:[function(require,module,exports){ 7231 /** 7232 * wp.media.view.RouterItem 7233 * 7234 * @memberOf wp.media.view 7235 * 7236 * @class 7237 * @augments wp.media.view.MenuItem 7238 * @augments wp.media.View 7239 * @augments wp.Backbone.View 7240 * @augments Backbone.View 7241 */ 7242 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{ 7243 /** 7244 * On click handler to activate the content region's corresponding mode. 7245 */ 7246 click: function() { 7247 var contentMode = this.options.contentMode; 7248 if ( contentMode ) { 7249 this.controller.content.mode( contentMode ); 7250 } 7251 } 7252 }); 7253 7254 module.exports = RouterItem; 7255 7256 },{}],56:[function(require,module,exports){ 7257 var Menu = wp.media.view.Menu, 7258 Router; 7259 7260 /** 7261 * wp.media.view.Router 7262 * 7263 * @memberOf wp.media.view 7264 * 7265 * @class 7266 * @augments wp.media.view.Menu 7267 * @augments wp.media.view.PriorityList 7268 * @augments wp.media.View 7269 * @augments wp.Backbone.View 7270 * @augments Backbone.View 7271 */ 7272 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{ 7273 tagName: 'div', 7274 className: 'media-router', 7275 property: 'contentMode', 7276 ItemView: wp.media.view.RouterItem, 7277 region: 'router', 7278 7279 initialize: function() { 7280 this.controller.on( 'content:render', this.update, this ); 7281 // Call 'initialize' directly on the parent class. 7282 Menu.prototype.initialize.apply( this, arguments ); 7283 }, 7284 7285 update: function() { 7286 var mode = this.controller.content.mode(); 7287 if ( mode ) { 7288 this.select( mode ); 7289 } 7290 } 7291 }); 7292 7293 module.exports = Router; 7294 7295 },{}],57:[function(require,module,exports){ 7296 var l10n = wp.media.view.l10n, 7297 Search; 7298 7299 /** 7300 * wp.media.view.Search 7301 * 7302 * @memberOf wp.media.view 7303 * 7304 * @class 7305 * @augments wp.media.View 7306 * @augments wp.Backbone.View 7307 * @augments Backbone.View 7308 */ 7309 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{ 7310 tagName: 'input', 7311 className: 'search', 7312 id: 'media-search-input', 7313 7314 attributes: { 7315 type: 'search', 7316 placeholder: l10n.searchMediaPlaceholder 7317 }, 7318 7319 events: { 7320 'input': 'search', 7321 'keyup': 'search' 7322 }, 7323 7324 /** 7325 * @returns {wp.media.view.Search} Returns itself to allow chaining 7326 */ 7327 render: function() { 7328 this.el.value = this.model.escape('search'); 7329 return this; 7330 }, 7331 7332 search: _.debounce( function( event ) { 7333 if ( event.target.value ) { 7334 this.model.set( 'search', event.target.value ); 7335 } else { 7336 this.model.unset('search'); 7337 } 7338 }, 300 ) 7339 }); 7340 7341 module.exports = Search; 7342 7343 },{}],58:[function(require,module,exports){ 7344 var l10n = wp.media.view.l10n, 7345 Selection; 7346 7347 /** 7348 * wp.media.view.Selection 7349 * 7350 * @memberOf wp.media.view 7351 * 7352 * @class 7353 * @augments wp.media.View 7354 * @augments wp.Backbone.View 7355 * @augments Backbone.View 7356 */ 7357 Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{ 7358 tagName: 'div', 7359 className: 'media-selection', 7360 template: wp.template('media-selection'), 7361 7362 events: { 7363 'click .edit-selection': 'edit', 7364 'click .clear-selection': 'clear' 7365 }, 7366 7367 initialize: function() { 7368 _.defaults( this.options, { 7369 editable: false, 7370 clearable: true 7371 }); 7372 7373 /** 7374 * @member {wp.media.view.Attachments.Selection} 7375 */ 7376 this.attachments = new wp.media.view.Attachments.Selection({ 7377 controller: this.controller, 7378 collection: this.collection, 7379 selection: this.collection, 7380 model: new Backbone.Model() 7381 }); 7382 7383 this.views.set( '.selection-view', this.attachments ); 7384 this.collection.on( 'add remove reset', this.refresh, this ); 7385 this.controller.on( 'content:activate', this.refresh, this ); 7386 }, 7387 7388 ready: function() { 7389 this.refresh(); 7390 }, 7391 7392 refresh: function() { 7393 // If the selection hasn't been rendered, bail. 7394 if ( ! this.$el.children().length ) { 7395 return; 7396 } 7397 7398 var collection = this.collection, 7399 editing = 'edit-selection' === this.controller.content.mode(); 7400 7401 // If nothing is selected, display nothing. 7402 this.$el.toggleClass( 'empty', ! collection.length ); 7403 this.$el.toggleClass( 'one', 1 === collection.length ); 7404 this.$el.toggleClass( 'editing', editing ); 7405 7406 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7407 }, 7408 7409 edit: function( event ) { 7410 event.preventDefault(); 7411 if ( this.options.editable ) { 7412 this.options.editable.call( this, this.collection ); 7413 } 7414 }, 7415 7416 clear: function( event ) { 7417 event.preventDefault(); 7418 this.collection.reset(); 7419 7420 // Keep focus inside media modal 7421 // after clear link is selected 7422 this.controller.modal.focusManager.focus(); 7423 } 7424 }); 7425 7426 module.exports = Selection; 7427 7428 },{}],59:[function(require,module,exports){ 7429 var View = wp.media.View, 7430 $ = Backbone.$, 7431 Settings; 7432 7433 /** 7434 * wp.media.view.Settings 7435 * 7436 * @memberOf wp.media.view 7437 * 7438 * @class 7439 * @augments wp.media.View 7440 * @augments wp.Backbone.View 7441 * @augments Backbone.View 7442 */ 7443 Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 7444 events: { 7445 'click button': 'updateHandler', 7446 'change input': 'updateHandler', 7447 'change select': 'updateHandler', 7448 'change textarea': 'updateHandler' 7449 }, 7450 7451 initialize: function() { 7452 this.model = this.model || new Backbone.Model(); 7453 this.listenTo( this.model, 'change', this.updateChanges ); 7454 }, 7455 7456 prepare: function() { 7457 return _.defaults({ 7458 model: this.model.toJSON() 7459 }, this.options ); 7460 }, 7461 /** 7462 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7463 */ 7464 render: function() { 7465 View.prototype.render.apply( this, arguments ); 7466 // Select the correct values. 7467 _( this.model.attributes ).chain().keys().each( this.update, this ); 7468 return this; 7469 }, 7470 /** 7471 * @param {string} key 7472 */ 7473 update: function( key ) { 7474 var value = this.model.get( key ), 7475 $setting = this.$('[data-setting="' + key + '"]'), 7476 $buttons, $value; 7477 7478 // Bail if we didn't find a matching setting. 7479 if ( ! $setting.length ) { 7480 return; 7481 } 7482 7483 // Attempt to determine how the setting is rendered and update 7484 // the selected value. 7485 7486 // Handle dropdowns. 7487 if ( $setting.is('select') ) { 7488 $value = $setting.find('[value="' + value + '"]'); 7489 7490 if ( $value.length ) { 7491 $setting.find('option').prop( 'selected', false ); 7492 $value.prop( 'selected', true ); 7493 } else { 7494 // If we can't find the desired value, record what *is* selected. 7495 this.model.set( key, $setting.find(':selected').val() ); 7496 } 7497 7498 // Handle button groups. 7499 } else if ( $setting.hasClass('button-group') ) { 7500 $buttons = $setting.find('button').removeClass('active'); 7501 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7502 7503 // Handle text inputs and textareas. 7504 } else if ( $setting.is('input[type="text"], textarea') ) { 7505 if ( ! $setting.is(':focus') ) { 7506 $setting.val( value ); 7507 } 7508 // Handle checkboxes. 7509 } else if ( $setting.is('input[type="checkbox"]') ) { 7510 $setting.prop( 'checked', !! value && 'false' !== value ); 7511 } 7512 }, 7513 /** 7514 * @param {Object} event 7515 */ 7516 updateHandler: function( event ) { 7517 var $setting = $( event.target ).closest('[data-setting]'), 7518 value = event.target.value, 7519 userSetting; 7520 7521 event.preventDefault(); 7522 7523 if ( ! $setting.length ) { 7524 return; 7525 } 7526 7527 // Use the correct value for checkboxes. 7528 if ( $setting.is('input[type="checkbox"]') ) { 7529 value = $setting[0].checked; 7530 } 7531 7532 // Update the corresponding setting. 7533 this.model.set( $setting.data('setting'), value ); 7534 7535 // If the setting has a corresponding user setting, 7536 // update that as well. 7537 if ( userSetting = $setting.data('userSetting') ) { 7538 window.setUserSetting( userSetting, value ); 7539 } 7540 }, 7541 7542 updateChanges: function( model ) { 7543 if ( model.hasChanged() ) { 7544 _( model.changed ).chain().keys().each( this.update, this ); 7545 } 7546 } 7547 }); 7548 7549 module.exports = Settings; 7550 7551 },{}],60:[function(require,module,exports){ 7552 var Settings = wp.media.view.Settings, 7553 AttachmentDisplay; 7554 7555 /** 7556 * wp.media.view.Settings.AttachmentDisplay 7557 * 7558 * @memberOf wp.media.view.Settings 7559 * 7560 * @class 7561 * @augments wp.media.view.Settings 7562 * @augments wp.media.View 7563 * @augments wp.Backbone.View 7564 * @augments Backbone.View 7565 */ 7566 AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{ 7567 className: 'attachment-display-settings', 7568 template: wp.template('attachment-display-settings'), 7569 7570 initialize: function() { 7571 var attachment = this.options.attachment; 7572 7573 _.defaults( this.options, { 7574 userSettings: false 7575 }); 7576 // Call 'initialize' directly on the parent class. 7577 Settings.prototype.initialize.apply( this, arguments ); 7578 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7579 7580 if ( attachment ) { 7581 attachment.on( 'change:uploading', this.render, this ); 7582 } 7583 }, 7584 7585 dispose: function() { 7586 var attachment = this.options.attachment; 7587 if ( attachment ) { 7588 attachment.off( null, null, this ); 7589 } 7590 /** 7591 * call 'dispose' directly on the parent class 7592 */ 7593 Settings.prototype.dispose.apply( this, arguments ); 7594 }, 7595 /** 7596 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7597 */ 7598 render: function() { 7599 var attachment = this.options.attachment; 7600 if ( attachment ) { 7601 _.extend( this.options, { 7602 sizes: attachment.get('sizes'), 7603 type: attachment.get('type') 7604 }); 7605 } 7606 /** 7607 * call 'render' directly on the parent class 7608 */ 7609 Settings.prototype.render.call( this ); 7610 this.updateLinkTo(); 7611 return this; 7612 }, 7613 7614 updateLinkTo: function() { 7615 var linkTo = this.model.get('link'), 7616 $input = this.$('.link-to-custom'), 7617 attachment = this.options.attachment; 7618 7619 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7620 $input.addClass( 'hidden' ); 7621 return; 7622 } 7623 7624 if ( attachment ) { 7625 if ( 'post' === linkTo ) { 7626 $input.val( attachment.get('link') ); 7627 } else if ( 'file' === linkTo ) { 7628 $input.val( attachment.get('url') ); 7629 } else if ( ! this.model.get('linkUrl') ) { 7630 $input.val('http://'); 7631 } 7632 7633 $input.prop( 'readonly', 'custom' !== linkTo ); 7634 } 7635 7636 $input.removeClass( 'hidden' ); 7637 7638 // If the input is visible, focus and select its contents. 7639 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7640 $input.focus()[0].select(); 7641 } 7642 } 7643 }); 7644 7645 module.exports = AttachmentDisplay; 7646 7647 },{}],61:[function(require,module,exports){ 7648 /** 7649 * wp.media.view.Settings.Gallery 7650 * 7651 * @memberOf wp.media.view.Settings 7652 * 7653 * @class 7654 * @augments wp.media.view.Settings 7655 * @augments wp.media.View 7656 * @augments wp.Backbone.View 7657 * @augments Backbone.View 7658 */ 7659 var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{ 7660 className: 'collection-settings gallery-settings', 7661 template: wp.template('gallery-settings') 7662 }); 7663 7664 module.exports = Gallery; 7665 7666 },{}],62:[function(require,module,exports){ 7667 /** 7668 * wp.media.view.Settings.Playlist 7669 * 7670 * @memberOf wp.media.view.Settings 7671 * 7672 * @class 7673 * @augments wp.media.view.Settings 7674 * @augments wp.media.View 7675 * @augments wp.Backbone.View 7676 * @augments Backbone.View 7677 */ 7678 var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{ 7679 className: 'collection-settings playlist-settings', 7680 template: wp.template('playlist-settings') 7681 }); 7682 7683 module.exports = Playlist; 7684 7685 },{}],63:[function(require,module,exports){ 7686 /** 7687 * wp.media.view.Sidebar 7688 * 7689 * @memberOf wp.media.view 7690 * 7691 * @class 7692 * @augments wp.media.view.PriorityList 7693 * @augments wp.media.View 7694 * @augments wp.Backbone.View 7695 * @augments Backbone.View 7696 */ 7697 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{ 7698 className: 'media-sidebar' 7699 }); 7700 7701 module.exports = Sidebar; 7702 7703 },{}],64:[function(require,module,exports){ 8971 module.exports = Cropper; 8972 8973 8974 /***/ }), 8975 /* 97 */ 8976 /***/ (function(module, exports) { 8977 7704 8978 var View = wp.media.view, 7705 8979 SiteIconCropper; … … 7746 9020 module.exports = SiteIconCropper; 7747 9021 7748 },{}],65:[function(require,module,exports){ 9022 9023 /***/ }), 9024 /* 98 */ 9025 /***/ (function(module, exports) { 9026 7749 9027 var View = wp.media.View, 7750 9028 $ = jQuery, … … 7804 9082 module.exports = SiteIconPreview; 7805 9083 7806 },{}],66:[function(require,module,exports){ 9084 9085 /***/ }), 9086 /* 99 */ 9087 /***/ (function(module, exports) { 9088 9089 var View = wp.media.View, 9090 EditImage; 9091 9092 /** 9093 * wp.media.view.EditImage 9094 * 9095 * @memberOf wp.media.view 9096 * 9097 * @class 9098 * @augments wp.media.View 9099 * @augments wp.Backbone.View 9100 * @augments Backbone.View 9101 */ 9102 EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{ 9103 className: 'image-editor', 9104 template: wp.template('image-editor'), 9105 9106 initialize: function( options ) { 9107 this.editor = window.imageEdit; 9108 this.controller = options.controller; 9109 View.prototype.initialize.apply( this, arguments ); 9110 }, 9111 9112 prepare: function() { 9113 return this.model.toJSON(); 9114 }, 9115 9116 loadEditor: function() { 9117 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 9118 dfd.done( _.bind( this.focus, this ) ); 9119 }, 9120 9121 focus: function() { 9122 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 9123 }, 9124 9125 back: function() { 9126 var lastState = this.controller.lastState(); 9127 this.controller.setState( lastState ); 9128 }, 9129 9130 refresh: function() { 9131 this.model.fetch(); 9132 }, 9133 9134 save: function() { 9135 var lastState = this.controller.lastState(); 9136 9137 this.model.fetch().done( _.bind( function() { 9138 this.controller.setState( lastState ); 9139 }, this ) ); 9140 } 9141 9142 }); 9143 9144 module.exports = EditImage; 9145 9146 9147 /***/ }), 9148 /* 100 */ 9149 /***/ (function(module, exports) { 9150 7807 9151 /** 7808 9152 * wp.media.view.Spinner … … 7841 9185 module.exports = Spinner; 7842 9186 7843 },{}],67:[function(require,module,exports){ 7844 var View = wp.media.View, 7845 Toolbar; 7846 7847 /** 7848 * wp.media.view.Toolbar 7849 * 7850 * A toolbar which consists of a primary and a secondary section. Each sections 7851 * can be filled with views. 7852 * 7853 * @memberOf wp.media.view 7854 * 7855 * @class 7856 * @augments wp.media.View 7857 * @augments wp.Backbone.View 7858 * @augments Backbone.View 7859 */ 7860 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ 7861 tagName: 'div', 7862 className: 'media-toolbar', 7863 7864 initialize: function() { 7865 var state = this.controller.state(), 7866 selection = this.selection = state.get('selection'), 7867 library = this.library = state.get('library'); 7868 7869 this._views = {}; 7870 7871 // The toolbar is composed of two `PriorityList` views. 7872 this.primary = new wp.media.view.PriorityList(); 7873 this.secondary = new wp.media.view.PriorityList(); 7874 this.primary.$el.addClass('media-toolbar-primary search-form'); 7875 this.secondary.$el.addClass('media-toolbar-secondary'); 7876 7877 this.views.set([ this.secondary, this.primary ]); 7878 7879 if ( this.options.items ) { 7880 this.set( this.options.items, { silent: true }); 7881 } 7882 7883 if ( ! this.options.silent ) { 7884 this.render(); 7885 } 7886 7887 if ( selection ) { 7888 selection.on( 'add remove reset', this.refresh, this ); 7889 } 7890 7891 if ( library ) { 7892 library.on( 'add remove reset', this.refresh, this ); 7893 } 7894 }, 7895 /** 7896 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 7897 */ 7898 dispose: function() { 7899 if ( this.selection ) { 7900 this.selection.off( null, null, this ); 7901 } 7902 7903 if ( this.library ) { 7904 this.library.off( null, null, this ); 7905 } 7906 /** 7907 * call 'dispose' directly on the parent class 7908 */ 7909 return View.prototype.dispose.apply( this, arguments ); 7910 }, 7911 7912 ready: function() { 7913 this.refresh(); 7914 }, 7915 7916 /** 7917 * @param {string} id 7918 * @param {Backbone.View|Object} view 7919 * @param {Object} [options={}] 7920 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7921 */ 7922 set: function( id, view, options ) { 7923 var list; 7924 options = options || {}; 7925 7926 // Accept an object with an `id` : `view` mapping. 7927 if ( _.isObject( id ) ) { 7928 _.each( id, function( view, id ) { 7929 this.set( id, view, { silent: true }); 7930 }, this ); 7931 7932 } else { 7933 if ( ! ( view instanceof Backbone.View ) ) { 7934 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 7935 view = new wp.media.view.Button( view ).render(); 7936 } 7937 7938 view.controller = view.controller || this.controller; 7939 7940 this._views[ id ] = view; 7941 7942 list = view.options.priority < 0 ? 'secondary' : 'primary'; 7943 this[ list ].set( id, view, options ); 7944 } 7945 7946 if ( ! options.silent ) { 7947 this.refresh(); 7948 } 7949 7950 return this; 7951 }, 7952 /** 7953 * @param {string} id 7954 * @returns {wp.media.view.Button} 7955 */ 7956 get: function( id ) { 7957 return this._views[ id ]; 7958 }, 7959 /** 7960 * @param {string} id 7961 * @param {Object} options 7962 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7963 */ 7964 unset: function( id, options ) { 7965 delete this._views[ id ]; 7966 this.primary.unset( id, options ); 7967 this.secondary.unset( id, options ); 7968 7969 if ( ! options || ! options.silent ) { 7970 this.refresh(); 7971 } 7972 return this; 7973 }, 7974 7975 refresh: function() { 7976 var state = this.controller.state(), 7977 library = state.get('library'), 7978 selection = state.get('selection'); 7979 7980 _.each( this._views, function( button ) { 7981 if ( ! button.model || ! button.options || ! button.options.requires ) { 7982 return; 7983 } 7984 7985 var requires = button.options.requires, 7986 disabled = false; 7987 7988 // Prevent insertion of attachments if any of them are still uploading 7989 if ( selection && selection.models ) { 7990 disabled = _.some( selection.models, function( attachment ) { 7991 return attachment.get('uploading') === true; 7992 }); 7993 } 7994 7995 if ( requires.selection && selection && ! selection.length ) { 7996 disabled = true; 7997 } else if ( requires.library && library && ! library.length ) { 7998 disabled = true; 7999 } 8000 button.model.set( 'disabled', disabled ); 8001 }); 8002 } 8003 }); 8004 8005 module.exports = Toolbar; 8006 8007 },{}],68:[function(require,module,exports){ 8008 var Select = wp.media.view.Toolbar.Select, 8009 l10n = wp.media.view.l10n, 8010 Embed; 8011 8012 /** 8013 * wp.media.view.Toolbar.Embed 8014 * 8015 * @memberOf wp.media.view.Toolbar 8016 * 8017 * @class 8018 * @augments wp.media.view.Toolbar.Select 8019 * @augments wp.media.view.Toolbar 8020 * @augments wp.media.View 8021 * @augments wp.Backbone.View 8022 * @augments Backbone.View 8023 */ 8024 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{ 8025 initialize: function() { 8026 _.defaults( this.options, { 8027 text: l10n.insertIntoPost, 8028 requires: false 8029 }); 8030 // Call 'initialize' directly on the parent class. 8031 Select.prototype.initialize.apply( this, arguments ); 8032 }, 8033 8034 refresh: function() { 8035 var url = this.controller.state().props.get('url'); 8036 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 8037 /** 8038 * call 'refresh' directly on the parent class 8039 */ 8040 Select.prototype.refresh.apply( this, arguments ); 8041 } 8042 }); 8043 8044 module.exports = Embed; 8045 8046 },{}],69:[function(require,module,exports){ 8047 var Toolbar = wp.media.view.Toolbar, 8048 l10n = wp.media.view.l10n, 8049 Select; 8050 8051 /** 8052 * wp.media.view.Toolbar.Select 8053 * 8054 * @memberOf wp.media.view.Toolbar 8055 * 8056 * @class 8057 * @augments wp.media.view.Toolbar 8058 * @augments wp.media.View 8059 * @augments wp.Backbone.View 8060 * @augments Backbone.View 8061 */ 8062 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{ 8063 initialize: function() { 8064 var options = this.options; 8065 8066 _.bindAll( this, 'clickSelect' ); 8067 8068 _.defaults( options, { 8069 event: 'select', 8070 state: false, 8071 reset: true, 8072 close: true, 8073 text: l10n.select, 8074 8075 // Does the button rely on the selection? 8076 requires: { 8077 selection: true 8078 } 8079 }); 8080 8081 options.items = _.defaults( options.items || {}, { 8082 select: { 8083 style: 'primary', 8084 text: options.text, 8085 priority: 80, 8086 click: this.clickSelect, 8087 requires: options.requires 8088 } 8089 }); 8090 // Call 'initialize' directly on the parent class. 8091 Toolbar.prototype.initialize.apply( this, arguments ); 8092 }, 8093 8094 clickSelect: function() { 8095 var options = this.options, 8096 controller = this.controller; 8097 8098 if ( options.close ) { 8099 controller.close(); 8100 } 8101 8102 if ( options.event ) { 8103 controller.state().trigger( options.event ); 8104 } 8105 8106 if ( options.state ) { 8107 controller.setState( options.state ); 8108 } 8109 8110 if ( options.reset ) { 8111 controller.reset(); 8112 } 8113 } 8114 }); 8115 8116 module.exports = Select; 8117 8118 },{}],70:[function(require,module,exports){ 8119 var View = wp.media.View, 8120 l10n = wp.media.view.l10n, 8121 $ = jQuery, 8122 EditorUploader; 8123 8124 /** 8125 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 8126 * and relays drag'n'dropped files to a media workflow. 8127 * 8128 * wp.media.view.EditorUploader 8129 * 8130 * @memberOf wp.media.view 8131 * 8132 * @class 8133 * @augments wp.media.View 8134 * @augments wp.Backbone.View 8135 * @augments Backbone.View 8136 */ 8137 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{ 8138 tagName: 'div', 8139 className: 'uploader-editor', 8140 template: wp.template( 'uploader-editor' ), 8141 8142 localDrag: false, 8143 overContainer: false, 8144 overDropzone: false, 8145 draggingFile: null, 8146 8147 /** 8148 * Bind drag'n'drop events to callbacks. 8149 */ 8150 initialize: function() { 8151 this.initialized = false; 8152 8153 // Bail if not enabled or UA does not support drag'n'drop or File API. 8154 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 8155 return this; 8156 } 8157 8158 this.$document = $(document); 8159 this.dropzones = []; 8160 this.files = []; 8161 8162 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 8163 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 8164 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 8165 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 8166 8167 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 8168 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 8169 8170 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 8171 this.localDrag = event.type === 'dragstart'; 8172 8173 if ( event.type === 'drop' ) { 8174 this.containerDragleave(); 8175 } 8176 }, this ) ); 8177 8178 this.initialized = true; 8179 return this; 8180 }, 8181 8182 /** 8183 * Check browser support for drag'n'drop. 8184 * 8185 * @return Boolean 8186 */ 8187 browserSupport: function() { 8188 var supports = false, div = document.createElement('div'); 8189 8190 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 8191 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 8192 return supports; 8193 }, 8194 8195 isDraggingFile: function( event ) { 8196 if ( this.draggingFile !== null ) { 8197 return this.draggingFile; 8198 } 8199 8200 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 8201 return false; 8202 } 8203 8204 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 8205 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 8206 8207 return this.draggingFile; 8208 }, 8209 8210 refresh: function( e ) { 8211 var dropzone_id; 8212 for ( dropzone_id in this.dropzones ) { 8213 // Hide the dropzones only if dragging has left the screen. 8214 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 8215 } 8216 8217 if ( ! _.isUndefined( e ) ) { 8218 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 8219 } 8220 8221 if ( ! this.overContainer && ! this.overDropzone ) { 8222 this.draggingFile = null; 8223 } 8224 8225 return this; 8226 }, 8227 8228 render: function() { 8229 if ( ! this.initialized ) { 8230 return this; 8231 } 8232 8233 View.prototype.render.apply( this, arguments ); 8234 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 8235 return this; 8236 }, 8237 8238 attach: function( index, editor ) { 8239 // Attach a dropzone to an editor. 8240 var dropzone = this.$el.clone(); 8241 this.dropzones.push( dropzone ); 8242 $( editor ).append( dropzone ); 8243 return this; 8244 }, 8245 8246 /** 8247 * When a file is dropped on the editor uploader, open up an editor media workflow 8248 * and upload the file immediately. 8249 * 8250 * @param {jQuery.Event} event The 'drop' event. 8251 */ 8252 drop: function( event ) { 8253 var $wrap, uploadView; 8254 8255 this.containerDragleave( event ); 8256 this.dropzoneDragleave( event ); 8257 8258 this.files = event.originalEvent.dataTransfer.files; 8259 if ( this.files.length < 1 ) { 8260 return; 8261 } 8262 8263 // Set the active editor to the drop target. 8264 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 8265 if ( $wrap.length > 0 && $wrap[0].id ) { 8266 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 8267 } 8268 8269 if ( ! this.workflow ) { 8270 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 8271 frame: 'post', 8272 state: 'insert', 8273 title: l10n.addMedia, 8274 multiple: true 8275 }); 8276 8277 uploadView = this.workflow.uploader; 8278 8279 if ( uploadView.uploader && uploadView.uploader.ready ) { 8280 this.addFiles.apply( this ); 8281 } else { 8282 this.workflow.on( 'uploader:ready', this.addFiles, this ); 8283 } 8284 } else { 8285 this.workflow.state().reset(); 8286 this.addFiles.apply( this ); 8287 this.workflow.open(); 8288 } 8289 8290 return false; 8291 }, 8292 8293 /** 8294 * Add the files to the uploader. 8295 */ 8296 addFiles: function() { 8297 if ( this.files.length ) { 8298 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 8299 this.files = []; 8300 } 8301 return this; 8302 }, 8303 8304 containerDragover: function( event ) { 8305 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 8306 return; 8307 } 8308 8309 this.overContainer = true; 8310 this.refresh(); 8311 }, 8312 8313 containerDragleave: function() { 8314 this.overContainer = false; 8315 8316 // Throttle dragleave because it's called when bouncing from some elements to others. 8317 _.delay( _.bind( this.refresh, this ), 50 ); 8318 }, 8319 8320 dropzoneDragover: function( event ) { 8321 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 8322 return; 8323 } 8324 8325 this.overDropzone = true; 8326 this.refresh( event ); 8327 return false; 8328 }, 8329 8330 dropzoneDragleave: function( e ) { 8331 this.overDropzone = false; 8332 _.delay( _.bind( this.refresh, this, e ), 50 ); 8333 }, 8334 8335 click: function( e ) { 8336 // In the rare case where the dropzone gets stuck, hide it on click. 8337 this.containerDragleave( e ); 8338 this.dropzoneDragleave( e ); 8339 this.localDrag = false; 8340 } 8341 }); 8342 8343 module.exports = EditorUploader; 8344 8345 },{}],71:[function(require,module,exports){ 8346 var View = wp.media.View, 8347 UploaderInline; 8348 8349 /** 8350 * wp.media.view.UploaderInline 8351 * 8352 * The inline uploader that shows up in the 'Upload Files' tab. 8353 * 8354 * @memberOf wp.media.view 8355 * 8356 * @class 8357 * @augments wp.media.View 8358 * @augments wp.Backbone.View 8359 * @augments Backbone.View 8360 */ 8361 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 8362 tagName: 'div', 8363 className: 'uploader-inline', 8364 template: wp.template('uploader-inline'), 8365 8366 events: { 8367 'click .close': 'hide' 8368 }, 8369 8370 initialize: function() { 8371 _.defaults( this.options, { 8372 message: '', 8373 status: true, 8374 canClose: false 8375 }); 8376 8377 if ( ! this.options.$browser && this.controller.uploader ) { 8378 this.options.$browser = this.controller.uploader.$browser; 8379 } 8380 8381 if ( _.isUndefined( this.options.postId ) ) { 8382 this.options.postId = wp.media.view.settings.post.id; 8383 } 8384 8385 if ( this.options.status ) { 8386 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 8387 controller: this.controller 8388 }) ); 8389 } 8390 }, 8391 8392 prepare: function() { 8393 var suggestedWidth = this.controller.state().get('suggestedWidth'), 8394 suggestedHeight = this.controller.state().get('suggestedHeight'), 8395 data = {}; 8396 8397 data.message = this.options.message; 8398 data.canClose = this.options.canClose; 8399 8400 if ( suggestedWidth && suggestedHeight ) { 8401 data.suggestedWidth = suggestedWidth; 8402 data.suggestedHeight = suggestedHeight; 8403 } 8404 8405 return data; 8406 }, 8407 /** 8408 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8409 */ 8410 dispose: function() { 8411 if ( this.disposing ) { 8412 /** 8413 * call 'dispose' directly on the parent class 8414 */ 8415 return View.prototype.dispose.apply( this, arguments ); 8416 } 8417 8418 // Run remove on `dispose`, so we can be sure to refresh the 8419 // uploader with a view-less DOM. Track whether we're disposing 8420 // so we don't trigger an infinite loop. 8421 this.disposing = true; 8422 return this.remove(); 8423 }, 8424 /** 8425 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8426 */ 8427 remove: function() { 8428 /** 8429 * call 'remove' directly on the parent class 8430 */ 8431 var result = View.prototype.remove.apply( this, arguments ); 8432 8433 _.defer( _.bind( this.refresh, this ) ); 8434 return result; 8435 }, 8436 8437 refresh: function() { 8438 var uploader = this.controller.uploader; 8439 8440 if ( uploader ) { 8441 uploader.refresh(); 8442 } 8443 }, 8444 /** 8445 * @returns {wp.media.view.UploaderInline} 8446 */ 8447 ready: function() { 8448 var $browser = this.options.$browser, 8449 $placeholder; 8450 8451 if ( this.controller.uploader ) { 8452 $placeholder = this.$('.browser'); 8453 8454 // Check if we've already replaced the placeholder. 8455 if ( $placeholder[0] === $browser[0] ) { 8456 return; 8457 } 8458 8459 $browser.detach().text( $placeholder.text() ); 8460 $browser[0].className = $placeholder[0].className; 8461 $placeholder.replaceWith( $browser.show() ); 8462 } 8463 8464 this.refresh(); 8465 return this; 8466 }, 8467 show: function() { 8468 this.$el.removeClass( 'hidden' ); 8469 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 8470 this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' ); 8471 } 8472 }, 8473 hide: function() { 8474 this.$el.addClass( 'hidden' ); 8475 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 8476 this.controller.$uploaderToggler 8477 .attr( 'aria-expanded', 'false' ) 8478 // Move focus back to the toggle button when closing the uploader. 8479 .focus(); 8480 } 8481 } 8482 8483 }); 8484 8485 module.exports = UploaderInline; 8486 8487 },{}],72:[function(require,module,exports){ 8488 /** 8489 * wp.media.view.UploaderStatusError 8490 * 8491 * @memberOf wp.media.view 8492 * 8493 * @class 8494 * @augments wp.media.View 8495 * @augments wp.Backbone.View 8496 * @augments Backbone.View 8497 */ 8498 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{ 8499 className: 'upload-error', 8500 template: wp.template('uploader-status-error') 8501 }); 8502 8503 module.exports = UploaderStatusError; 8504 8505 },{}],73:[function(require,module,exports){ 8506 var View = wp.media.View, 8507 UploaderStatus; 8508 8509 /** 8510 * wp.media.view.UploaderStatus 8511 * 8512 * An uploader status for on-going uploads. 8513 * 8514 * @memberOf wp.media.view 8515 * 8516 * @class 8517 * @augments wp.media.View 8518 * @augments wp.Backbone.View 8519 * @augments Backbone.View 8520 */ 8521 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{ 8522 className: 'media-uploader-status', 8523 template: wp.template('uploader-status'), 8524 8525 events: { 8526 'click .upload-dismiss-errors': 'dismiss' 8527 }, 8528 8529 initialize: function() { 8530 this.queue = wp.Uploader.queue; 8531 this.queue.on( 'add remove reset', this.visibility, this ); 8532 this.queue.on( 'add remove reset change:percent', this.progress, this ); 8533 this.queue.on( 'add remove reset change:uploading', this.info, this ); 8534 8535 this.errors = wp.Uploader.errors; 8536 this.errors.reset(); 8537 this.errors.on( 'add remove reset', this.visibility, this ); 8538 this.errors.on( 'add', this.error, this ); 8539 }, 8540 /** 8541 * @returns {wp.media.view.UploaderStatus} 8542 */ 8543 dispose: function() { 8544 wp.Uploader.queue.off( null, null, this ); 8545 /** 8546 * call 'dispose' directly on the parent class 8547 */ 8548 View.prototype.dispose.apply( this, arguments ); 8549 return this; 8550 }, 8551 8552 visibility: function() { 8553 this.$el.toggleClass( 'uploading', !! this.queue.length ); 8554 this.$el.toggleClass( 'errors', !! this.errors.length ); 8555 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 8556 }, 8557 8558 ready: function() { 8559 _.each({ 8560 '$bar': '.media-progress-bar div', 8561 '$index': '.upload-index', 8562 '$total': '.upload-total', 8563 '$filename': '.upload-filename' 8564 }, function( selector, key ) { 8565 this[ key ] = this.$( selector ); 8566 }, this ); 8567 8568 this.visibility(); 8569 this.progress(); 8570 this.info(); 8571 }, 8572 8573 progress: function() { 8574 var queue = this.queue, 8575 $bar = this.$bar; 8576 8577 if ( ! $bar || ! queue.length ) { 8578 return; 8579 } 8580 8581 $bar.width( ( queue.reduce( function( memo, attachment ) { 8582 if ( ! attachment.get('uploading') ) { 8583 return memo + 100; 8584 } 8585 8586 var percent = attachment.get('percent'); 8587 return memo + ( _.isNumber( percent ) ? percent : 100 ); 8588 }, 0 ) / queue.length ) + '%' ); 8589 }, 8590 8591 info: function() { 8592 var queue = this.queue, 8593 index = 0, active; 8594 8595 if ( ! queue.length ) { 8596 return; 8597 } 8598 8599 active = this.queue.find( function( attachment, i ) { 8600 index = i; 8601 return attachment.get('uploading'); 8602 }); 8603 8604 this.$index.text( index + 1 ); 8605 this.$total.text( queue.length ); 8606 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 8607 }, 8608 /** 8609 * @param {string} filename 8610 * @returns {string} 8611 */ 8612 filename: function( filename ) { 8613 return _.escape( filename ); 8614 }, 8615 /** 8616 * @param {Backbone.Model} error 8617 */ 8618 error: function( error ) { 8619 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8620 filename: this.filename( error.get('file').name ), 8621 message: error.get('message') 8622 }), { at: 0 }); 8623 }, 8624 8625 /** 8626 * @param {Object} event 8627 */ 8628 dismiss: function( event ) { 8629 var errors = this.views.get('.upload-errors'); 8630 8631 event.preventDefault(); 8632 8633 if ( errors ) { 8634 _.invoke( errors, 'remove' ); 8635 } 8636 wp.Uploader.errors.reset(); 8637 } 8638 }); 8639 8640 module.exports = UploaderStatus; 8641 8642 },{}],74:[function(require,module,exports){ 8643 var $ = jQuery, 8644 UploaderWindow; 8645 8646 /** 8647 * wp.media.view.UploaderWindow 8648 * 8649 * An uploader window that allows for dragging and dropping media. 8650 * 8651 * @memberOf wp.media.view 8652 * 8653 * @class 8654 * @augments wp.media.View 8655 * @augments wp.Backbone.View 8656 * @augments Backbone.View 8657 * 8658 * @param {object} [options] Options hash passed to the view. 8659 * @param {object} [options.uploader] Uploader properties. 8660 * @param {jQuery} [options.uploader.browser] 8661 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 8662 * @param {object} [options.uploader.params] 8663 */ 8664 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{ 8665 tagName: 'div', 8666 className: 'uploader-window', 8667 template: wp.template('uploader-window'), 8668 8669 initialize: function() { 8670 var uploader; 8671 8672 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 8673 8674 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 8675 dropzone: this.$el, 8676 browser: this.$browser, 8677 params: {} 8678 }); 8679 8680 // Ensure the dropzone is a jQuery collection. 8681 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 8682 uploader.dropzone = $( uploader.dropzone ); 8683 } 8684 8685 this.controller.on( 'activate', this.refresh, this ); 8686 8687 this.controller.on( 'detach', function() { 8688 this.$browser.remove(); 8689 }, this ); 8690 }, 8691 8692 refresh: function() { 8693 if ( this.uploader ) { 8694 this.uploader.refresh(); 8695 } 8696 }, 8697 8698 ready: function() { 8699 var postId = wp.media.view.settings.post.id, 8700 dropzone; 8701 8702 // If the uploader already exists, bail. 8703 if ( this.uploader ) { 8704 return; 8705 } 8706 8707 if ( postId ) { 8708 this.options.uploader.params.post_id = postId; 8709 } 8710 this.uploader = new wp.Uploader( this.options.uploader ); 8711 8712 dropzone = this.uploader.dropzone; 8713 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 8714 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 8715 8716 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 8717 }, 8718 8719 _ready: function() { 8720 this.controller.trigger( 'uploader:ready' ); 8721 }, 8722 8723 show: function() { 8724 var $el = this.$el.show(); 8725 8726 // Ensure that the animation is triggered by waiting until 8727 // the transparent element is painted into the DOM. 8728 _.defer( function() { 8729 $el.css({ opacity: 1 }); 8730 }); 8731 }, 8732 8733 hide: function() { 8734 var $el = this.$el.css({ opacity: 0 }); 8735 8736 wp.media.transition( $el ).done( function() { 8737 // Transition end events are subject to race conditions. 8738 // Make sure that the value is set as intended. 8739 if ( '0' === $el.css('opacity') ) { 8740 $el.hide(); 8741 } 8742 }); 8743 8744 // https://core.trac.wordpress.org/ticket/27341 8745 _.delay( function() { 8746 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 8747 $el.hide(); 8748 } 8749 }, 500 ); 8750 } 8751 }); 8752 8753 module.exports = UploaderWindow; 8754 8755 },{}],75:[function(require,module,exports){ 8756 /** 8757 * wp.media.View 8758 * 8759 * The base view class for media. 8760 * 8761 * Undelegating events, removing events from the model, and 8762 * removing events from the controller mirror the code for 8763 * `Backbone.View.dispose` in Backbone 0.9.8 development. 8764 * 8765 * This behavior has since been removed, and should not be used 8766 * outside of the media manager. 8767 * 8768 * @memberOf wp.media 8769 * 8770 * @class 8771 * @augments wp.Backbone.View 8772 * @augments Backbone.View 8773 */ 8774 var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{ 8775 constructor: function( options ) { 8776 if ( options && options.controller ) { 8777 this.controller = options.controller; 8778 } 8779 wp.Backbone.View.apply( this, arguments ); 8780 }, 8781 /** 8782 * @todo The internal comment mentions this might have been a stop-gap 8783 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 8784 * care of this in Backbone.View now. 8785 * 8786 * @returns {wp.media.View} Returns itself to allow chaining 8787 */ 8788 dispose: function() { 8789 // Undelegating events, removing events from the model, and 8790 // removing events from the controller mirror the code for 8791 // `Backbone.View.dispose` in Backbone 0.9.8 development. 8792 this.undelegateEvents(); 8793 8794 if ( this.model && this.model.off ) { 8795 this.model.off( null, null, this ); 8796 } 8797 8798 if ( this.collection && this.collection.off ) { 8799 this.collection.off( null, null, this ); 8800 } 8801 8802 // Unbind controller events. 8803 if ( this.controller && this.controller.off ) { 8804 this.controller.off( null, null, this ); 8805 } 8806 8807 return this; 8808 }, 8809 /** 8810 * @returns {wp.media.View} Returns itself to allow chaining 8811 */ 8812 remove: function() { 8813 this.dispose(); 8814 /** 8815 * call 'remove' directly on the parent class 8816 */ 8817 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 8818 } 8819 }); 8820 8821 module.exports = View; 8822 8823 },{}]},{},[19]); 9187 9188 /***/ }) 9189 /******/ ]));
Note: See TracChangeset
for help on using the changeset viewer.