Ticket #40894: 40894.10.diff
File 40894.10.diff, 527.0 KB (added by , 7 years ago) |
---|
-
.jshintrc
diff --git .jshintrc .jshintrc index 0a082dded5..278eac22c3 100644
21 21 "Backbone": false, 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 } -
Gruntfile.js
diff --git Gruntfile.js Gruntfile.js index d13be10800..1be0a88159 100644
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'), 5 8 fs = require( 'fs' ), … … module.exports = function(grunt) { 7 10 SOURCE_DIR = 'src/', 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. 15 16 require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks ); 16 17 // Load legacy utils 17 18 grunt.util = require('grunt-legacy-util'); 18 19 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 25 20 // Project configuration. 26 21 grunt.initConfig({ 27 22 postcss: { … … module.exports = function(grunt) { 177 172 } 178 173 } 179 174 }, 180 browserify: mediaConfig,181 175 sass: { 182 176 colors: { 183 177 expand: true, … … module.exports = function(grunt) { 338 332 ] 339 333 }, 340 334 media: { 341 options: {342 browserify: true343 },344 335 src: [ 345 336 SOURCE_DIR + 'wp-includes/js/media/**/*.js' 346 337 ] … … module.exports = function(grunt) { 553 544 dest: SOURCE_DIR + 'wp-includes/js/jquery/jquery.masonry.min.js' 554 545 } 555 546 }, 556 547 webpack: { 548 prod: webpackConfig, 549 dev: webpackDevConfig 550 }, 557 551 concat: { 558 552 tinymce: { 559 553 options: { … … module.exports = function(grunt) { 719 713 } 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: { 725 723 files: [SOURCE_DIR + 'wp-admin/css/colors/**'], … … module.exports = function(grunt) { 757 755 758 756 // Register tasks. 759 757 758 // Webpack task. 759 grunt.loadNpmTasks( 'grunt-webpack' ); 760 760 761 // RTL task. 761 762 grunt.registerTask('rtl', ['rtlcss:core', 'rtlcss:colors']); 762 763 … … module.exports = function(grunt) { 780 781 grunt.renameTask( 'watch', '_watch' ); 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 } ); 784 if ( ! this.args.length || this.args.indexOf( 'webpack' ) > -1 ) { 790 785 791 grunt.task.run( ' browserify' );786 grunt.task.run( 'webpack:dev' ); 792 787 } 793 788 794 789 grunt.task.run( '_' + this.nameArgs ); … … module.exports = function(grunt) { 799 794 ] ); 800 795 801 796 grunt.registerTask( 'precommit:js', [ 802 ' browserify',797 'webpack:prod', 803 798 'jshint:corejs', 804 799 'uglify:masonry', 805 800 'qunit:compiled' … … module.exports = function(grunt) { 978 973 grunt.event.on('watch', function( action, filepath, target ) { 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 } 984 979 -
package.json
diff --git package.json package.json index 71695dfb77..26a787bd45 100644
15 15 "autoprefixer": "^6.5.1", 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", 21 20 "grunt-contrib-concat": "~1.0.0", … … 36 35 "grunt-rtlcss": "~2.0.1", 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 } -
src/wp-includes/js/media-audiovideo.js
diff --git src/wp-includes/js/media-audiovideo.js src/wp-includes/js/media-audiovideo.js index 0a819747d2..cb2abd57ae 100644
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 || {}, 4 72 l10n = window._wpMediaViewsL10n || {}; … … wp.media.video = { 268 336 } 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){ 282 var State = wp.media.controller.State, 283 l10n = wp.media.view.l10n, 284 AudioDetails; 285 286 /** 287 * wp.media.controller.AudioDetails 288 * 289 * The controller for the Audio Details state 290 * 291 * @memberOf wp.media.controller 292 * 293 * @class 294 * @augments wp.media.controller.State 295 * @augments Backbone.Model 296 */ 297 AudioDetails = State.extend(/** @lends wp.media.controller.AudioDetails.prototype */{ 298 defaults: { 299 id: 'audio-details', 300 toolbar: 'audio-details', 301 title: l10n.audioDetailsTitle, 302 content: 'audio-details', 303 menu: 'audio-details', 304 router: false, 305 priority: 60 306 }, 307 308 initialize: function( options ) { 309 this.media = options.media; 310 State.prototype.initialize.apply( this, arguments ); 311 } 312 }); 313 314 module.exports = AudioDetails; 315 316 },{}],3:[function(require,module,exports){ 317 /** 318 * wp.media.controller.VideoDetails 319 * 320 * The controller for the Video Details state 321 * 322 * @memberOf wp.media.controller 323 * 324 * @class 325 * @augments wp.media.controller.State 326 * @augments Backbone.Model 327 */ 328 var State = wp.media.controller.State, 329 l10n = wp.media.view.l10n, 330 VideoDetails; 331 332 VideoDetails = State.extend(/** @lends wp.media.controller.VideoDetails.prototype */{ 333 defaults: { 334 id: 'video-details', 335 toolbar: 'video-details', 336 title: l10n.videoDetailsTitle, 337 content: 'video-details', 338 menu: 'video-details', 339 router: false, 340 priority: 60 341 }, 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 ); 342 348 343 initialize: function( options ) {344 this.media = options.media;345 State.prototype.initialize.apply( this, arguments );346 }347 });348 349 349 module.exports = VideoDetails; 350 /***/ }), 351 /* 1 */ 352 /***/ (function(module, exports) { 350 353 351 },{}],4:[function(require,module,exports){352 354 /** 353 355 * wp.media.model.PostMedia 354 356 * … … var PostMedia = Backbone.Model.extend(/** @lends wp.media.model.PostMedia.protot 392 394 393 395 module.exports = PostMedia; 394 396 395 },{}],5:[function(require,module,exports){ 396 var MediaDetails = wp.media.view.MediaDetails, 397 398 /***/ }), 399 /* 2 */ 400 /***/ (function(module, exports) { 401 402 var State = wp.media.controller.State, 403 l10n = wp.media.view.l10n, 397 404 AudioDetails; 398 405 399 406 /** 400 * wp.media. view.AudioDetails407 * wp.media.controller.AudioDetails 401 408 * 402 * @memberOf wp.media.view 409 * The controller for the Audio Details state 410 * 411 * @memberOf wp.media.controller 403 412 * 404 413 * @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 414 * @augments wp.media.controller.State 415 * @augments Backbone.Model 411 416 */ 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 } 417 AudioDetails = State.extend(/** @lends wp.media.controller.AudioDetails.prototype */{ 418 defaults: { 419 id: 'audio-details', 420 toolbar: 'audio-details', 421 title: l10n.audioDetailsTitle, 422 content: 'audio-details', 423 menu: 'audio-details', 424 router: false, 425 priority: 60 426 }, 428 427 429 return this; 428 initialize: function( options ) { 429 this.media = options.media; 430 State.prototype.initialize.apply( this, arguments ); 430 431 } 431 432 }); 432 433 433 434 module.exports = AudioDetails; 434 435 435 },{}],6:[function(require,module,exports){436 var MediaDetails = wp.media.view.MediaFrame.MediaDetails,437 MediaLibrary = wp.media.controller.MediaLibrary,438 436 439 l10n = wp.media.view.l10n, 440 AudioDetails; 437 /***/ }), 438 /* 3 */ 439 /***/ (function(module, exports) { 441 440 442 441 /** 443 * wp.media. view.MediaFrame.AudioDetails442 * wp.media.controller.VideoDetails 444 443 * 445 * @memberOf wp.media.view.MediaFrame 444 * The controller for the Video Details state 445 * 446 * @memberOf wp.media.controller 446 447 * 447 448 * @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 449 * @augments wp.media.controller.State 450 * @augments Backbone.Model 456 451 */ 457 AudioDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.AudioDetails.prototype */{ 452 var State = wp.media.controller.State, 453 l10n = wp.media.view.l10n, 454 VideoDetails; 455 456 VideoDetails = State.extend(/** @lends wp.media.controller.VideoDetails.prototype */{ 458 457 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 458 id: 'video-details', 459 toolbar: 'video-details', 460 title: l10n.videoDetailsTitle, 461 content: 'video-details', 462 menu: 'video-details', 463 router: false, 464 priority: 60 467 465 }, 468 466 469 467 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 ]); 468 this.media = options.media; 469 State.prototype.initialize.apply( this, arguments ); 508 470 } 509 471 }); 510 472 511 module.exports = AudioDetails; 473 module.exports = VideoDetails; 474 475 476 /***/ }), 477 /* 4 */ 478 /***/ (function(module, exports) { 512 479 513 },{}],7:[function(require,module,exports){514 480 var Select = wp.media.view.MediaFrame.Select, 515 481 l10n = wp.media.view.l10n, 516 482 MediaDetails; … … MediaDetails = Select.extend(/** @lends wp.media.view.MediaFrame.MediaDetails.pr 642 608 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, 648 700 l10n = wp.media.view.l10n, … … VideoDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.VideoDeta 779 831 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, 785 841 $ = jQuery, … … MediaDetails = AttachmentDisplay.extend(/** @lends wp.media.view.MediaDetails.pr 949 1005 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; 955 1059 … … VideoDetails = MediaDetails.extend(/** @lends wp.media.view.VideoDetails.prototy 994 1098 995 1099 module.exports = VideoDetails; 996 1100 997 },{}]},{},[1]); 1101 1102 /***/ }) 1103 /******/ ]); 1104 No newline at end of file -
src/wp-includes/js/media-grid.js
diff --git src/wp-includes/js/media-grid.js src/wp-includes/js/media-grid.js index 6e35fb52a6..c9babd6d50 100644
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; 4 99 … … EditAttachmentMetadata = wp.media.controller.State.extend(/** @lends wp.media.co 28 123 29 124 module.exports = EditAttachmentMetadata; 30 125 31 },{}],2:[function(require,module,exports){32 var media = wp.media;33 126 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){ 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 45 137 /** 46 * wp.media.view.MediaFrame.Manage .Router138 * wp.media.view.MediaFrame.Manage 47 139 * 48 * A router for handling the browser history and application state.140 * A generic management frame workflow. 49 141 * 50 * @memberOf wp.media.view.MediaFrame.Manage 142 * Used in the media grid view. 143 * 144 * @memberOf wp.media.view.MediaFrame 51 145 * 52 146 * @class 53 * @augments Backbone.Router 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 54 153 */ 55 var Router = Backbone.Router.extend(/** @lends wp.media.view.MediaFrame.Manage.Router.prototype */{ 56 routes: { 57 'upload.php?item=:slug&mode=edit': 'editItem', 58 'upload.php?item=:slug': 'showItem', 59 'upload.php?search=:query': 'search', 60 'upload.php': 'reset' 61 }, 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 }); 62 169 63 // Map routes against the page URL 64 baseUrl: function( url ) { 65 return 'upload.php' + url; 66 }, 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 ) ); 67 177 68 reset: function() { 69 var frame = wp.media.frames.edit; 178 this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) ); 70 179 71 if ( frame ) { 72 frame.close(); 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; 73 187 } 74 },75 188 76 // Respond to the search route by filling the search field and trigggering the input event 77 search: function( query ) { 78 jQuery( '#media-search-input' ).val( query ).trigger( 'input' ); 79 }, 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 ); 80 200 81 // Show the modal with a specific item 82 showItem: function( query ) { 83 var media = wp.media, 84 frame = media.frames.browse, 85 library = frame.state().get('library'), 86 item; 201 this.options.uploader = false; 202 } 87 203 88 // Trigger the media frame to open the correct item 89 item = library.findWhere( { id: parseInt( query, 10 ) } ); 90 item.set( 'skipHistory', true ); 204 this.gridRouter = new wp.media.view.MediaFrame.Manage.Router(); 91 205 92 if ( item ) { 93 frame.trigger( 'edit:attachment', item ); 94 } else { 95 item = media.attachment( query ); 96 frame.listenTo( item, 'change', function( model ) { 97 frame.stopListening( item ); 98 frame.trigger( 'edit:attachment', model ); 99 } ); 100 item.fetch(); 101 } 102 }, 206 // Call 'initialize' directly on the parent class. 207 MediaFrame.prototype.initialize.apply( this, arguments ); 103 208 104 // Show the modal in edit mode with a specific item. 105 editItem: function( query ) { 106 this.showItem( query ); 107 wp.media.frames.edit.content.mode( 'edit-details' ); 108 } 109 }); 209 // Append the frame view directly the supplied container. 210 this.$el.appendTo( this.options.container ); 110 211 111 module.exports = Router; 212 this.createStates(); 213 this.bindRegionModeHandlers(); 214 this.render(); 215 this.bindSearchHandler(); 112 216 113 },{}],4:[function(require,module,exports){ 114 var Details = wp.media.view.Attachment.Details, 115 TwoColumn; 217 wp.media.frames.browse = this; 218 }, 116 219 117 /** 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 124 * 125 * @class 126 * @augments wp.media.view.Attachment.Details 127 * @augments wp.media.view.Attachment 128 * @augments wp.media.View 129 * @augments wp.Backbone.View 130 * @augments Backbone.View 131 */ 132 TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{ 133 template: wp.template( 'attachment-details-two-column' ), 220 bindSearchHandler: function() { 221 var search = this.$( '#media-search-input' ), 222 searchView = this.browserView.toolbar.get( 'search' ).$el, 223 listMode = this.$( '.view-list' ), 134 224 135 initialize: function() { 136 this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) ); 225 input = _.throttle( function (e) { 226 var val = $( e.currentTarget ).val(), 227 url = ''; 137 228 138 Details.prototype.initialize.apply( this, arguments ); 139 }, 229 if ( val ) { 230 url += '?search=' + val; 231 this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } ); 232 } 233 }, 1000 ); 140 234 141 editAttachment: function( event ) { 142 if ( event ) { 143 event.preventDefault(); 144 } 145 this.controller.content.mode( 'edit-image' ); 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 }); 146 252 }, 147 253 148 254 /** 149 * Noop this from parent class, doesn't apply here.255 * Create the default states for the frame. 150 256 */ 151 toggleSelectionHandler: function() {}, 152 153 render: function() { 154 Details.prototype.render.apply( this, arguments ); 257 createStates: function() { 258 var options = this.options; 155 259 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 } ); 161 } 162 }); 260 if ( this.options.states ) { 261 return; 262 } 163 263 164 module.exports = TwoColumn; 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 }, 165 278 166 },{}],5:[function(require,module,exports){ 167 var Button = wp.media.view.Button, 168 DeleteSelected = wp.media.view.DeleteSelectedButton, 169 DeleteSelectedPermanently; 279 /** 280 * Bind region mode activation events to proper handlers. 281 */ 282 bindRegionModeHandlers: function() { 283 this.on( 'content:create:browse', this.browseContent, this ); 170 284 171 /** 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 177 * 178 * @class 179 * @augments wp.media.view.DeleteSelectedButton 180 * @augments wp.media.view.Button 181 * @augments wp.media.View 182 * @augments wp.Backbone.View 183 * @augments Backbone.View 184 */ 185 DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{ 186 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 }, 285 // Handle a frame-level event for editing an attachment. 286 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 191 287 192 filterChange: function( model ) {193 this. canShow = ( 'trash' === model.get( 'status' ));288 this.on( 'select:activate', this.bindKeydown, this ); 289 this.on( 'select:deactivate', this.unbindKeydown, this ); 194 290 }, 195 291 196 selectActivate: function() { 197 this.toggleDisabled(); 198 this.$el.toggleClass( 'hidden', ! this.canShow ); 292 handleKeydown: function( e ) { 293 if ( 27 === e.which ) { 294 e.preventDefault(); 295 this.deactivateMode( 'select' ).activateMode( 'edit' ); 296 } 199 297 }, 200 298 201 selectDeactivate: function() { 202 this.toggleDisabled(); 203 this.$el.addClass( 'hidden' ); 299 bindKeydown: function() { 300 this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) ); 204 301 }, 205 302 206 render: function() { 207 Button.prototype.render.apply( this, arguments ); 208 this.selectActivate(); 209 return this; 210 } 211 }); 303 unbindKeydown: function() { 304 this.$body.off( 'keydown.select' ); 305 }, 212 306 213 module.exports = DeleteSelectedPermanently; 307 fixPosition: function() { 308 var $browser, $toolbar; 309 if ( ! this.isModeActive( 'select' ) ) { 310 return; 311 } 214 312 215 },{}],6:[function(require,module,exports){ 216 var Button = wp.media.view.Button, 217 l10n = wp.media.view.l10n, 218 DeleteSelected; 313 $browser = this.$('.attachments-browser'); 314 $toolbar = $browser.find('.media-toolbar'); 219 315 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 ); 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', ''); 238 323 } 239 this.controller.on( 'selection:toggle', this.toggleDisabled, this );240 324 }, 241 325 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 ); 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 ); 247 345 } else { 248 this.model.set( 'text', l10n.deleteSelected ); 346 wp.media.frames.edit = wp.media( { 347 frame: 'edit-attachments', 348 controller: this, 349 library: this.state().get('library'), 350 model: model 351 } ); 249 352 } 250 353 }, 251 354 252 toggleDisabled: function() { 253 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 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 ); 254 390 }, 255 391 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' ); 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 } ); 262 413 } 263 this.toggleDisabled();264 return this;265 414 } 266 415 }); 267 416 268 module.exports = DeleteSelected;417 module.exports = Manage; 269 418 270 },{}],7:[function(require,module,exports){271 419 272 var Button = wp.media.view.Button, 273 l10n = wp.media.view.l10n, 274 SelectModeToggle; 420 /***/ }), 421 /* 13 */ 422 /***/ (function(module, exports) { 423 424 var Details = wp.media.view.Attachment.Details, 425 TwoColumn; 275 426 276 427 /** 277 * wp.media.view. SelectModeToggleButton428 * wp.media.view.Attachment.Details.TwoColumn 278 429 * 279 * @memberOf wp.media.view 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 280 434 * 281 435 * @class 282 * @augments wp.media.view.Button 436 * @augments wp.media.view.Attachment.Details 437 * @augments wp.media.view.Attachment 283 438 * @augments wp.media.View 284 439 * @augments wp.Backbone.View 285 440 * @augments Backbone.View 286 441 */ 287 SelectModeToggle = Button.extend(/** @lends wp.media.view.SelectModeToggle.prototype */{ 288 initialize: function() { 289 _.defaults( this.options, { 290 size : '' 291 } ); 442 TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{ 443 template: wp.template( 'attachment-details-two-column' ), 292 444 293 Button.prototype.initialize.apply( this, arguments ); 294 this.controller.on( 'select:activate select:deactivate', this.toggleBulkEditHandler, this ); 295 this.controller.on( 'selection:action:done', this.back, this ); 296 }, 445 initialize: function() { 446 this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) ); 297 447 298 back: function () { 299 this.controller.deactivateMode( 'select' ).activateMode( 'edit' ); 448 Details.prototype.initialize.apply( this, arguments ); 300 449 }, 301 450 302 click: function() { 303 Button.prototype.click.apply( this, arguments ); 304 if ( this.controller.isModeActive( 'select' ) ) { 305 this.back(); 306 } else { 307 this.controller.deactivateMode( 'edit' ).activateMode( 'select' ); 451 editAttachment: function( event ) { 452 if ( event ) { 453 event.preventDefault(); 308 454 } 455 this.controller.content.mode( 'edit-image' ); 309 456 }, 310 457 311 render: function() { 312 Button.prototype.render.apply( this, arguments ); 313 this.$el.addClass( 'select-mode-toggle-button' ); 314 return this; 315 }, 316 317 toggleBulkEditHandler: function() { 318 var toolbar = this.controller.content.get().toolbar, children; 458 /** 459 * Noop this from parent class, doesn't apply here. 460 */ 461 toggleSelectionHandler: function() {}, 319 462 320 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' ); 463 render: function() { 464 Details.prototype.render.apply( this, arguments ); 321 465 322 // TODO: the Frame should be doing all of this. 323 if ( this.controller.isModeActive( 'select' ) ) { 324 this.model.set( { 325 size: 'large', 326 text: l10n.cancelSelection 327 } ); 328 children.not( '.spinner, .media-button' ).hide(); 329 this.$el.show(); 330 toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' ); 331 } else { 332 this.model.set( { 333 size: '', 334 text: l10n.bulkSelect 335 } ); 336 this.controller.content.get().$el.removeClass( 'fixed' ); 337 toolbar.$el.css( 'width', '' ); 338 toolbar.$( '.delete-selected-button' ).addClass( 'hidden' ); 339 children.not( '.media-button' ).show(); 340 this.controller.state().get( 'selection' ).reset(); 341 } 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 } ); 342 471 } 343 472 }); 344 473 345 module.exports = SelectModeToggle;474 module.exports = TwoColumn; 346 475 347 },{}],8:[function(require,module,exports){ 348 var View = wp.media.View,349 EditImage = wp.media.view.EditImage, 350 Details; 476 477 /***/ }), 478 /* 14 */ 479 /***/ (function(module, exports) { 351 480 352 481 /** 353 * wp.media.view. EditImage.Details482 * wp.media.view.MediaFrame.Manage.Router 354 483 * 355 * @memberOf wp.media.view.EditImage 484 * A router for handling the browser history and application state. 485 * 486 * @memberOf wp.media.view.MediaFrame.Manage 356 487 * 357 488 * @class 358 * @augments wp.media.view.EditImage 359 * @augments wp.media.View 360 * @augments wp.Backbone.View 361 * @augments Backbone.View 489 * @augments Backbone.Router 362 490 */ 363 Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{ 491 var Router = Backbone.Router.extend(/** @lends wp.media.view.MediaFrame.Manage.Router.prototype */{ 492 routes: { 493 'upload.php?item=:slug&mode=edit': 'editItem', 494 'upload.php?item=:slug': 'showItem', 495 'upload.php?search=:query': 'search', 496 'upload.php': 'reset' 497 }, 498 499 // Map routes against the page URL 500 baseUrl: function( url ) { 501 return 'upload.php' + url; 502 }, 503 504 reset: function() { 505 var frame = wp.media.frames.edit; 506 507 if ( frame ) { 508 frame.close(); 509 } 510 }, 511 512 // Respond to the search route by filling the search field and trigggering the input event 513 search: function( query ) { 514 jQuery( '#media-search-input' ).val( query ).trigger( 'input' ); 515 }, 516 517 // Show the modal with a specific item 518 showItem: function( query ) { 519 var media = wp.media, 520 frame = media.frames.browse, 521 library = frame.state().get('library'), 522 item; 523 524 // Trigger the media frame to open the correct item 525 item = library.findWhere( { id: parseInt( query, 10 ) } ); 526 item.set( 'skipHistory', true ); 527 528 if ( item ) { 529 frame.trigger( 'edit:attachment', item ); 530 } else { 531 item = media.attachment( query ); 532 frame.listenTo( item, 'change', function( model ) { 533 frame.stopListening( item ); 534 frame.trigger( 'edit:attachment', model ); 535 } ); 536 item.fetch(); 537 } 538 }, 539 540 // Show the modal in edit mode with a specific item. 541 editItem: function( query ) { 542 this.showItem( query ); 543 wp.media.frames.edit.content.mode( 'edit-details' ); 544 } 545 }); 546 547 module.exports = Router; 548 549 550 /***/ }), 551 /* 15 */ 552 /***/ (function(module, exports) { 553 554 var View = wp.media.View, 555 EditImage = wp.media.view.EditImage, 556 Details; 557 558 /** 559 * wp.media.view.EditImage.Details 560 * 561 * @memberOf wp.media.view.EditImage 562 * 563 * @class 564 * @augments wp.media.view.EditImage 565 * @augments wp.media.View 566 * @augments wp.Backbone.View 567 * @augments Backbone.View 568 */ 569 Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{ 364 570 initialize: function( options ) { 365 571 this.editor = window.imageEdit; 366 572 this.frame = options.frame; … … Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype 381 587 382 588 module.exports = Details; 383 589 384 },{}],9:[function(require,module,exports){ 590 591 /***/ }), 592 /* 16 */ 593 /***/ (function(module, exports) { 594 385 595 var Frame = wp.media.view.Frame, 386 596 MediaFrame = wp.media.view.MediaFrame, 387 597 … … EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAtta 643 853 644 854 module.exports = EditAttachments; 645 855 646 },{}],10:[function(require,module,exports){647 var MediaFrame = wp.media.view.MediaFrame,648 Library = wp.media.controller.Library,649 856 650 $ = Backbone.$, 651 Manage; 857 /***/ }), 858 /* 17 */ 859 /***/ (function(module, exports) { 860 861 862 var Button = wp.media.view.Button, 863 l10n = wp.media.view.l10n, 864 SelectModeToggle; 652 865 653 866 /** 654 * wp.media.view.MediaFrame.Manage 655 * 656 * A generic management frame workflow. 657 * 658 * Used in the media grid view. 867 * wp.media.view.SelectModeToggleButton 659 868 * 660 * @memberOf wp.media.view .MediaFrame869 * @memberOf wp.media.view 661 870 * 662 871 * @class 663 * @augments wp.media.view.MediaFrame 664 * @augments wp.media.view.Frame 872 * @augments wp.media.view.Button 665 873 * @augments wp.media.View 666 874 * @augments wp.Backbone.View 667 875 * @augments Backbone.View 668 * @mixes wp.media.controller.StateMachine669 876 */ 670 Manage = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Manage.prototype */{ 671 /** 672 * @constructs 673 */ 877 SelectModeToggle = Button.extend(/** @lends wp.media.view.SelectModeToggle.prototype */{ 674 878 initialize: function() { 675 879 _.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'); 880 size : '' 881 } ); 698 882 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 } 883 Button.prototype.initialize.apply( this, arguments ); 884 this.controller.on( 'select:activate select:deactivate', this.toggleBulkEditHandler, this ); 885 this.controller.on( 'selection:action:done', this.back, this ); 886 }, 704 887 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 ); 888 back: function () { 889 this.controller.deactivateMode( 'select' ).activateMode( 'edit' ); 890 }, 716 891 717 this.options.uploader = false; 892 click: function() { 893 Button.prototype.click.apply( this, arguments ); 894 if ( this.controller.isModeActive( 'select' ) ) { 895 this.back(); 896 } else { 897 this.controller.deactivateMode( 'edit' ).activateMode( 'select' ); 718 898 } 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 899 }, 735 900 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 }); 901 render: function() { 902 Button.prototype.render.apply( this, arguments ); 903 this.$el.addClass( 'select-mode-toggle-button' ); 904 return this; 768 905 }, 769 906 770 /** 771 * Create the default states for the frame. 772 */ 773 createStates: function() { 774 var options = this.options; 907 toggleBulkEditHandler: function() { 908 var toolbar = this.controller.content.get().toolbar, children; 775 909 776 if ( this.options.states ) { 777 return; 910 children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' ); 911 912 // TODO: the Frame should be doing all of this. 913 if ( this.controller.isModeActive( 'select' ) ) { 914 this.model.set( { 915 size: 'large', 916 text: l10n.cancelSelection 917 } ); 918 children.not( '.spinner, .media-button' ).hide(); 919 this.$el.show(); 920 toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' ); 921 } else { 922 this.model.set( { 923 size: '', 924 text: l10n.bulkSelect 925 } ); 926 this.controller.content.get().$el.removeClass( 'fixed' ); 927 toolbar.$el.css( 'width', '' ); 928 toolbar.$( '.delete-selected-button' ).addClass( 'hidden' ); 929 children.not( '.media-button' ).show(); 930 this.controller.state().get( 'selection' ).reset(); 778 931 } 932 } 933 }); 779 934 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 }, 935 module.exports = SelectModeToggle; 794 936 795 /**796 * Bind region mode activation events to proper handlers.797 */798 bindRegionModeHandlers: function() {799 this.on( 'content:create:browse', this.browseContent, this );800 937 801 // Handle a frame-level event for editing an attachment. 802 this.on( 'edit:attachment', this.openEditAttachmentModal, this ); 938 /***/ }), 939 /* 18 */ 940 /***/ (function(module, exports) { 803 941 804 this.on( 'select:activate', this.bindKeydown, this ); 805 this.on( 'select:deactivate', this.unbindKeydown, this );806 },942 var Button = wp.media.view.Button, 943 l10n = wp.media.view.l10n, 944 DeleteSelected; 807 945 808 handleKeydown: function( e ) { 809 if ( 27 === e.which ) { 810 e.preventDefault(); 811 this.deactivateMode( 'select' ).activateMode( 'edit' ); 946 /** 947 * wp.media.view.DeleteSelectedButton 948 * 949 * A button that handles bulk Delete/Trash logic 950 * 951 * @memberOf wp.media.view 952 * 953 * @class 954 * @augments wp.media.view.Button 955 * @augments wp.media.View 956 * @augments wp.Backbone.View 957 * @augments Backbone.View 958 */ 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 ); 812 964 } 965 this.controller.on( 'selection:toggle', this.toggleDisabled, this ); 813 966 }, 814 967 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'); 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 ); 836 973 } else { 837 $browser.removeClass( 'fixed' ); 838 $toolbar.css('width', ''); 974 this.model.set( 'text', l10n.deleteSelected ); 839 975 } 840 976 }, 841 977 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 } 978 toggleDisabled: function() { 979 this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length ); 852 980 }, 853 981 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 ); 982 render: function() { 983 Button.prototype.render.apply( this, arguments ); 984 if ( this.controller.isModeActive( 'select' ) ) { 985 this.$el.addClass( 'delete-selected-button' ); 861 986 } 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 } ); 987 this.$el.addClass( 'delete-selected-button hidden' ); 868 988 } 869 }, 989 this.toggleDisabled(); 990 return this; 991 } 992 }); 870 993 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(); 994 module.exports = DeleteSelected; 880 995 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 996 895 suggestedWidth: state.get('suggestedWidth'), 896 suggestedHeight: state.get('suggestedHeight'), 997 /***/ }), 998 /* 19 */ 999 /***/ (function(module, exports) { 897 1000 898 AttachmentView: state.get('AttachmentView'), 1001 var Button = wp.media.view.Button, 1002 DeleteSelected = wp.media.view.DeleteSelectedButton, 1003 DeleteSelectedPermanently; 899 1004 900 scrollElement: document 901 }); 902 this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) ); 1005 /** 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 1011 * 1012 * @class 1013 * @augments wp.media.view.DeleteSelectedButton 1014 * @augments wp.media.view.Button 1015 * @augments wp.media.View 1016 * @augments wp.Backbone.View 1017 * @augments Backbone.View 1018 */ 1019 DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{ 1020 initialize: function() { 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 }, 903 1025 904 this.errors = wp.Uploader.errors;905 this. errors.on( 'add remove reset', this.sidebarVisibility, this);1026 filterChange: function( model ) { 1027 this.canShow = ( 'trash' === model.get( 'status' ) ); 906 1028 }, 907 1029 908 sidebarVisibility: function() { 909 this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length ); 1030 selectActivate: function() { 1031 this.toggleDisabled(); 1032 this.$el.toggleClass( 'hidden', ! this.canShow ); 910 1033 }, 911 1034 912 bindDeferred: function() { 913 if ( ! this.browserView.dfd ) { 914 return; 915 } 916 this.browserView.dfd.done( _.bind( this.startHistory, this ) ); 1035 selectDeactivate: function() { 1036 this.toggleDisabled(); 1037 this.$el.addClass( 'hidden' ); 917 1038 }, 918 1039 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 } 1040 render: function() { 1041 Button.prototype.render.apply( this, arguments ); 1042 this.selectActivate(); 1043 return this; 930 1044 } 931 1045 }); 932 1046 933 module.exports = Manage; 1047 module.exports = DeleteSelectedPermanently; 1048 934 1049 935 },{}]},{},[2]); 1050 /***/ }) 1051 /******/ ]); 1052 No newline at end of file -
src/wp-includes/js/media-models.js
diff --git src/wp-includes/js/media-models.js src/wp-includes/js/media-models.js index caa7fcb590..af64387257 100644
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; 4 73 … … l10n = media.model.l10n = window._wpMediaModelsL10n || {}; 65 134 media.model.settings = l10n.settings || {}; 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');137 Attachment = media.model.Attachment = __webpack_require__( 21 ); 138 Attachments = media.model.Attachments = __webpack_require__( 22 ); 70 139 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');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 /** 76 145 * ======================================================================== … … $(window).on('unload', function(){ 238 307 window.wp = null; 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; 244 318 … … Attachment = Backbone.Model.extend(/** @lends wp.media.model.Attachment.prototyp 409 483 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 415 494 * … … var Attachments = Backbone.Collection.extend(/** @lends wp.media.model.Attachmen 954 1033 955 1034 module.exports = Attachments; 956 1035 957 },{}],4:[function(require,module,exports){958 /**959 * wp.media.model.PostImage960 *961 * An instance of an image that's been embedded into a post.962 *963 * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails.964 *965 * @memberOf wp.media.model966 *967 * @class968 * @augments Backbone.Model969 *970 * @param {int} [attributes] Initial model attributes.971 * @param {int} [attributes.attachment_id] ID of the attachment.972 **/973 var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{974 975 initialize: function( attributes ) {976 var Attachment = wp.media.model.Attachment;977 this.attachment = false;978 1036 979 if ( attributes.attachment_id ) { 980 this.attachment = Attachment.get( attributes.attachment_id ); 981 if ( this.attachment.get( 'url' ) ) { 982 this.dfd = jQuery.Deferred(); 983 this.dfd.resolve(); 984 } else { 985 this.dfd = this.attachment.fetch(); 986 } 987 this.bindAttachmentListeners(); 988 } 1037 /***/ }), 989 1038 990 // keep url in sync with changes to the type of link 991 this.on( 'change:link', this.updateLinkUrl, this ); 992 this.on( 'change:size', this.updateSize, this ); 1039 /***/ 23: 1040 /***/ (function(module, exports) { 993 1041 994 this.setLinkTypeFromUrl();995 this.setAspectRatio();996 997 this.set( 'originalUrl', attributes.url );998 },999 1000 bindAttachmentListeners: function() {1001 this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );1002 this.listenTo( this.attachment, 'sync', this.setAspectRatio );1003 this.listenTo( this.attachment, 'change', this.updateSize );1004 },1005 1006 changeAttachment: function( attachment, props ) {1007 this.stopListening( this.attachment );1008 this.attachment = attachment;1009 this.bindAttachmentListeners();1010 1011 this.set( 'attachment_id', this.attachment.get( 'id' ) );1012 this.set( 'caption', this.attachment.get( 'caption' ) );1013 this.set( 'alt', this.attachment.get( 'alt' ) );1014 this.set( 'size', props.get( 'size' ) );1015 this.set( 'align', props.get( 'align' ) );1016 this.set( 'link', props.get( 'link' ) );1017 this.updateLinkUrl();1018 this.updateSize();1019 },1020 1021 setLinkTypeFromUrl: function() {1022 var linkUrl = this.get( 'linkUrl' ),1023 type;1024 1025 if ( ! linkUrl ) {1026 this.set( 'link', 'none' );1027 return;1028 }1029 1030 // default to custom if there is a linkUrl1031 type = 'custom';1032 1033 if ( this.attachment ) {1034 if ( this.attachment.get( 'url' ) === linkUrl ) {1035 type = 'file';1036 } else if ( this.attachment.get( 'link' ) === linkUrl ) {1037 type = 'post';1038 }1039 } else {1040 if ( this.get( 'url' ) === linkUrl ) {1041 type = 'file';1042 }1043 }1044 1045 this.set( 'link', type );1046 },1047 1048 updateLinkUrl: function() {1049 var link = this.get( 'link' ),1050 url;1051 1052 switch( link ) {1053 case 'file':1054 if ( this.attachment ) {1055 url = this.attachment.get( 'url' );1056 } else {1057 url = this.get( 'url' );1058 }1059 this.set( 'linkUrl', url );1060 break;1061 case 'post':1062 this.set( 'linkUrl', this.attachment.get( 'link' ) );1063 break;1064 case 'none':1065 this.set( 'linkUrl', '' );1066 break;1067 }1068 },1069 1070 updateSize: function() {1071 var size;1072 1073 if ( ! this.attachment ) {1074 return;1075 }1076 1077 if ( this.get( 'size' ) === 'custom' ) {1078 this.set( 'width', this.get( 'customWidth' ) );1079 this.set( 'height', this.get( 'customHeight' ) );1080 this.set( 'url', this.get( 'originalUrl' ) );1081 return;1082 }1083 1084 size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];1085 1086 if ( ! size ) {1087 return;1088 }1089 1090 this.set( 'url', size.url );1091 this.set( 'width', size.width );1092 this.set( 'height', size.height );1093 },1094 1095 setAspectRatio: function() {1096 var full;1097 1098 if ( this.attachment && this.attachment.get( 'sizes' ) ) {1099 full = this.attachment.get( 'sizes' ).full;1100 1101 if ( full ) {1102 this.set( 'aspectRatio', full.width / full.height );1103 return;1104 }1105 }1106 1107 this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) );1108 }1109 });1110 1111 module.exports = PostImage;1112 1113 },{}],5:[function(require,module,exports){1114 1042 var Attachments = wp.media.model.Attachments, 1115 1043 Query; 1116 1044 … … Query = Attachments.extend(/** @lends wp.media.model.Query.prototype */{ 1418 1346 1419 1347 module.exports = Query; 1420 1348 1421 },{}],6:[function(require,module,exports){ 1349 1350 /***/ }), 1351 1352 /***/ 24: 1353 /***/ (function(module, exports) { 1354 1355 /** 1356 * wp.media.model.PostImage 1357 * 1358 * An instance of an image that's been embedded into a post. 1359 * 1360 * Used in the embedded image attachment display settings modal - @see wp.media.view.MediaFrame.ImageDetails. 1361 * 1362 * @memberOf wp.media.model 1363 * 1364 * @class 1365 * @augments Backbone.Model 1366 * 1367 * @param {int} [attributes] Initial model attributes. 1368 * @param {int} [attributes.attachment_id] ID of the attachment. 1369 **/ 1370 var PostImage = Backbone.Model.extend(/** @lends wp.media.model.PostImage.prototype */{ 1371 1372 initialize: function( attributes ) { 1373 var Attachment = wp.media.model.Attachment; 1374 this.attachment = false; 1375 1376 if ( attributes.attachment_id ) { 1377 this.attachment = Attachment.get( attributes.attachment_id ); 1378 if ( this.attachment.get( 'url' ) ) { 1379 this.dfd = jQuery.Deferred(); 1380 this.dfd.resolve(); 1381 } else { 1382 this.dfd = this.attachment.fetch(); 1383 } 1384 this.bindAttachmentListeners(); 1385 } 1386 1387 // keep url in sync with changes to the type of link 1388 this.on( 'change:link', this.updateLinkUrl, this ); 1389 this.on( 'change:size', this.updateSize, this ); 1390 1391 this.setLinkTypeFromUrl(); 1392 this.setAspectRatio(); 1393 1394 this.set( 'originalUrl', attributes.url ); 1395 }, 1396 1397 bindAttachmentListeners: function() { 1398 this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl ); 1399 this.listenTo( this.attachment, 'sync', this.setAspectRatio ); 1400 this.listenTo( this.attachment, 'change', this.updateSize ); 1401 }, 1402 1403 changeAttachment: function( attachment, props ) { 1404 this.stopListening( this.attachment ); 1405 this.attachment = attachment; 1406 this.bindAttachmentListeners(); 1407 1408 this.set( 'attachment_id', this.attachment.get( 'id' ) ); 1409 this.set( 'caption', this.attachment.get( 'caption' ) ); 1410 this.set( 'alt', this.attachment.get( 'alt' ) ); 1411 this.set( 'size', props.get( 'size' ) ); 1412 this.set( 'align', props.get( 'align' ) ); 1413 this.set( 'link', props.get( 'link' ) ); 1414 this.updateLinkUrl(); 1415 this.updateSize(); 1416 }, 1417 1418 setLinkTypeFromUrl: function() { 1419 var linkUrl = this.get( 'linkUrl' ), 1420 type; 1421 1422 if ( ! linkUrl ) { 1423 this.set( 'link', 'none' ); 1424 return; 1425 } 1426 1427 // default to custom if there is a linkUrl 1428 type = 'custom'; 1429 1430 if ( this.attachment ) { 1431 if ( this.attachment.get( 'url' ) === linkUrl ) { 1432 type = 'file'; 1433 } else if ( this.attachment.get( 'link' ) === linkUrl ) { 1434 type = 'post'; 1435 } 1436 } else { 1437 if ( this.get( 'url' ) === linkUrl ) { 1438 type = 'file'; 1439 } 1440 } 1441 1442 this.set( 'link', type ); 1443 }, 1444 1445 updateLinkUrl: function() { 1446 var link = this.get( 'link' ), 1447 url; 1448 1449 switch( link ) { 1450 case 'file': 1451 if ( this.attachment ) { 1452 url = this.attachment.get( 'url' ); 1453 } else { 1454 url = this.get( 'url' ); 1455 } 1456 this.set( 'linkUrl', url ); 1457 break; 1458 case 'post': 1459 this.set( 'linkUrl', this.attachment.get( 'link' ) ); 1460 break; 1461 case 'none': 1462 this.set( 'linkUrl', '' ); 1463 break; 1464 } 1465 }, 1466 1467 updateSize: function() { 1468 var size; 1469 1470 if ( ! this.attachment ) { 1471 return; 1472 } 1473 1474 if ( this.get( 'size' ) === 'custom' ) { 1475 this.set( 'width', this.get( 'customWidth' ) ); 1476 this.set( 'height', this.get( 'customHeight' ) ); 1477 this.set( 'url', this.get( 'originalUrl' ) ); 1478 return; 1479 } 1480 1481 size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ]; 1482 1483 if ( ! size ) { 1484 return; 1485 } 1486 1487 this.set( 'url', size.url ); 1488 this.set( 'width', size.width ); 1489 this.set( 'height', size.height ); 1490 }, 1491 1492 setAspectRatio: function() { 1493 var full; 1494 1495 if ( this.attachment && this.attachment.get( 'sizes' ) ) { 1496 full = this.attachment.get( 'sizes' ).full; 1497 1498 if ( full ) { 1499 this.set( 'aspectRatio', full.width / full.height ); 1500 return; 1501 } 1502 } 1503 1504 this.set( 'aspectRatio', this.get( 'customWidth' ) / this.get( 'customHeight' ) ); 1505 } 1506 }); 1507 1508 module.exports = PostImage; 1509 1510 1511 /***/ }), 1512 1513 /***/ 25: 1514 /***/ (function(module, exports) { 1515 1422 1516 var Attachments = wp.media.model.Attachments, 1423 1517 Selection; 1424 1518 … … Selection = Attachments.extend(/** @lends wp.media.model.Selection.prototype */{ 1517 1611 1518 1612 module.exports = Selection; 1519 1613 1520 },{}]},{},[1]); 1614 1615 /***/ }) 1616 1617 /******/ }); 1618 No newline at end of file -
src/wp-includes/js/media-views.js
diff --git src/wp-includes/js/media-views.js src/wp-includes/js/media-views.js index 18e6444691..2e462971e2 100644
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__) { 5 69 6 /** 7 * wp.media.controller.CollectionAdd 8 * 9 * A state for adding attachments to a collection (e.g. video playlist). 10 * 11 * @memberOf wp.media.controller 12 * 13 * @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'). 40 */ 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', 70 var media = wp.media, 71 $ = jQuery, 72 l10n; 47 73 48 priority: 100, 49 syncSelection: false 50 }, Library.prototype.defaults ), 74 media.isTouchDevice = ( 'ontouchend' in document ); 51 75 52 /** 53 * @since 3.9.0 54 */ 55 initialize: function() { 56 var collectionType = this.get('collectionType'); 76 // Link any localized strings. 77 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 57 78 58 if ( 'video' === this.get( 'type' ) ) { 59 collectionType = 'video-' + collectionType;60 } 79 // Link any settings. 80 media.view.settings = l10n.settings || {}; 81 delete l10n.settings; 61 82 62 this.set( 'id', collectionType + '-library' ); 63 this.set( 'toolbar', collectionType + '-add' ); 64 this.set( 'menu', collectionType ); 83 // Copy the `post` setting over to the model settings. 84 media.model.settings.post = media.view.settings.post; 65 85 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 }, 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; 72 95 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'); 96 transition = _.find( _.keys( transitions ), function( transition ) { 97 return ! _.isUndefined( style[ transition ] ); 98 }); 80 99 81 if ( editLibrary && editLibrary !== edit ) { 82 library.unobserve( editLibrary ); 100 return transition && { 101 end: transitions[ transition ] 102 }; 103 }()); 104 105 /** 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 ); 83 127 } 84 128 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 }; 129 // Resolve the deferred when the first element finishes animating. 130 selector.first().one( $.support.transition.end, deferred.resolve ); 90 131 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); 132 // Just in case the event doesn't trigger, fire a callback. 133 _.delay( deferred.resolve, sensitivity ); 97 134 98 Library.prototype.activate.apply( this, arguments ); 135 // Otherwise, execute on the spot. 136 } else { 137 deferred.resolve(); 99 138 } 100 });101 139 102 module.exports = CollectionAdd; 140 return deferred.promise(); 141 }; 103 142 104 },{}],2:[function(require,module,exports){ 105 var Library = wp.media.controller.Library, 106 l10n = wp.media.view.l10n, 107 $ = jQuery, 108 CollectionEdit; 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) { 109 224 110 225 /** 111 * wp.media.controller. CollectionEdit226 * wp.media.controller.Region 112 227 * 113 * A state for editing a collection, which is used by audio and video playlists, 114 * and can be used for other collections. 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. 115 237 * 116 238 * @memberOf wp.media.controller 117 239 * 118 240 * @class 119 * @augments wp.media.controller.Library120 * @augments wp.media.controller.State121 * @augments Backbone.Model122 241 * 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'). 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. 146 246 */ 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 }, 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; 162 253 254 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{ 163 255 /** 164 * @since 3.9.0 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. 165 266 */ 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() ); 267 mode: function( mode ) { 268 if ( ! mode ) { 269 return this._mode; 179 270 } 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 );271 // Bail if we're trying to change to the current mode. 272 if ( mode === this._mode ) { 273 return this; 183 274 } 184 Library.prototype.initialize.apply( this, arguments );185 },186 187 /**188 * @since 3.9.0189 */190 activate: function() {191 var library = this.get('library');192 275 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 ); 276 /** 277 * Region mode deactivation event. 278 * 279 * @event wp.media.controller.Region#deactivate 280 */ 281 this.trigger('deactivate'); 198 282 199 this.frame.on( 'content:render:browse', this.renderSettings, this ); 283 this._mode = mode; 284 this.render( mode ); 200 285 201 Library.prototype.activate.apply( this, arguments ); 286 /** 287 * Region mode activation event. 288 * 289 * @event wp.media.controller.Region#activate 290 */ 291 this.trigger('activate'); 292 return this; 202 293 }, 203 204 294 /** 205 * @since 3.9.0 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 206 305 */ 207 deactivate: function() { 208 // Stop watching for uploaded attachments. 209 this.get('library').unobserve( wp.Uploader.queue ); 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 } 210 311 211 this.frame.off( 'content:render:browse', this.renderSettings, this ); 312 var set = { view: null }, 313 view; 212 314 213 Library.prototype.deactivate.apply( this, arguments ); 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; 214 340 }, 215 341 216 342 /** 217 * Render the collection embed settings view in the browser sidebar. 218 * 219 * @todo This is against the pattern elsewhere in media. Typically the frame 220 * is responsible for adding region mode callbacks. Explain. 343 * Get the region's view. 221 344 * 222 * @since 3. 9.0345 * @since 3.5.0 223 346 * 224 * @ param {wp.media.view.attachmentsBrowser} The attachments browser view.347 * @returns {wp.media.View} 225 348 */ 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 = {}; 349 get: function() { 350 return this.view.views.first( this.selector ); 351 }, 232 352 233 if ( ! library || ! attachmentsBrowserView ) { 234 return; 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; 235 365 } 366 return this.view.views.set( this.selector, views, options ); 367 }, 236 368 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 ); 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; 246 379 247 if ( dragInfoText ) { 248 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 249 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 250 priority: -40 251 }) ); 380 if ( ! this._mode ) { 381 return; 252 382 } 253 383 254 // Add the 'Reverse order' button to the toolbar. 255 attachmentsBrowserView.toolbar.set( 'reverse', { 256 text: l10n.reverseOrder, 257 priority: 80, 384 args = _.toArray( arguments ); 385 base = this.id + ':' + event; 258 386 259 click: function() { 260 library.reset( library.toArray().reverse() ); 261 } 262 }); 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; 263 395 } 264 396 }); 265 397 266 module.exports = CollectionEdit;398 module.exports = Region; 267 399 268 },{}],3:[function(require,module,exports){ 269 var l10n = wp.media.view.l10n, 270 Cropper; 400 401 /***/ }), 402 /* 28 */ 403 /***/ (function(module, exports) { 271 404 272 405 /** 273 * wp.media.controller. Cropper406 * wp.media.controller.StateMachine 274 407 * 275 * A state for cropping an image. 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. 276 412 * 277 413 * @memberOf wp.media.controller 278 414 * 415 * @since 3.5.0 416 * 279 417 * @class 280 * @augments wp.media.controller.State281 418 * @augments Backbone.Model 419 * @mixin 420 * @mixes Backbone.Events 421 * 422 * @param {Array} states 282 423 */ 283 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{ 284 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 }, 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 }; 296 428 297 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 }, 429 // Use Backbone's self-propagating `extend` inheritance method. 430 StateMachine.extend = Backbone.Model.extend; 302 431 303 deactivate: function() { 304 this.frame.toolbar.mode('browse'); 305 }, 432 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{ 433 /** 434 * Fetch a state. 435 * 436 * If no `id` is provided, returns the active state. 437 * 438 * Implicitly creates states. 439 * 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(); 306 451 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); 452 // Default to the active state. 453 id = id || this._state; 314 454 455 if ( id && ! this.states.get( id ) ) { 456 this.states.add({ id: id }); 457 } 458 return this.states.get( id ); 315 459 }, 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 460 325 canSkipCrop = this.get('canSkipCrop') || false; 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(); 326 479 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 } 354 } 355 }; 480 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 481 return this; 482 } 356 483 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 }); 484 if ( previous ) { 485 previous.trigger('deactivate'); 486 this._lastState = previous.id; 372 487 } 373 488 374 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 489 this._state = id; 490 this.state().trigger('activate'); 491 492 return this; 375 493 }, 376 494 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 ) ); 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 } 387 510 } 388 511 }); 389 512 390 module.exports = Cropper; 391 392 },{}],4:[function(require,module,exports){ 393 var Controller = wp.media.controller, 394 CustomizeImageCropper; 395 396 /** 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 } 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 }; 433 541 }); 434 542 435 module.exports = CustomizeImageCropper;543 module.exports = StateMachine; 436 544 437 },{}],5:[function(require,module,exports){ 438 var l10n = wp.media.view.l10n, 439 EditImage; 545 546 /***/ }), 547 /* 29 */ 548 /***/ (function(module, exports) { 440 549 441 550 /** 442 * wp.media.controller. EditImage551 * wp.media.controller.State 443 552 * 444 * A state for editing (cropping, etc.) an image. 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. 445 563 * 446 564 * @memberOf wp.media.controller 447 565 * 448 566 * @class 449 * @augments wp.media.controller.State450 567 * @augments Backbone.Model 451 *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.460 568 */ 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: '' 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 ); 469 589 }, 590 /** 591 * Ready event callback. 592 * 593 * @abstract 594 * @since 3.5.0 595 */ 596 ready: function() {}, 470 597 471 598 /** 472 * @since 3.9.0 599 * Activate event callback. 600 * 601 * @abstract 602 * @since 3.5.0 473 603 */ 474 activate: function() { 475 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) ); 476 }, 604 activate: function() {}, 477 605 478 606 /** 479 * @since 3.9.0 607 * Deactivate event callback. 608 * 609 * @abstract 610 * @since 3.5.0 480 611 */ 481 deactivate: function() { 482 this.frame.off( 'toolbar:render:edit-image' ); 483 }, 612 deactivate: function() {}, 484 613 485 614 /** 486 * @since 3.9.0 615 * Reset event callback. 616 * 617 * @abstract 618 * @since 3.5.0 487 619 */ 488 toolbar: function() { 489 var frame = this.frame, 490 lastState = frame.lastState(), 491 previous = lastState && lastState.id; 620 reset: function() {}, 492 621 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 }) ); 510 } 511 }); 622 /** 623 * @access private 624 * @since 3.5.0 625 */ 626 _ready: function() { 627 this._updateMenu(); 628 }, 512 629 513 module.exports = EditImage; 630 /** 631 * @access private 632 * @since 3.5.0 633 */ 634 _preActivate: function() { 635 this.active = true; 636 }, 514 637 515 },{}],6:[function(require,module,exports){ 516 var l10n = wp.media.view.l10n, 517 $ = Backbone.$, 518 Embed; 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 ); 519 647 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: {} 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(); 554 655 }, 555 656 556 // The amount of time used when debouncing the scan. 557 sensitivity: 400, 657 /** 658 * @access private 659 * @since 3.5.0 660 */ 661 _deactivate: function() { 662 this.active = false; 558 663 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 ); 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 ); 566 670 }, 567 671 568 672 /** 569 * Trigger a scan of the embedded URL's content for metadata required to embed. 570 * 571 * @fires wp.media.controller.Embed#scan 673 * @access private 674 * @since 3.5.0 572 675 */ 573 scan: function() { 574 var scanners, 575 embed = this, 576 attributes = { 577 type: 'link', 578 scanners: [] 579 }; 676 _title: function() { 677 this.frame.title.render( this.get('titleMode') || 'default' ); 678 }, 580 679 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 } 680 /** 681 * @access private 682 * @since 3.5.0 683 */ 684 _renderTitle: function( view ) { 685 view.$el.text( this.get('title') || '' ); 686 }, 587 687 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; 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; 597 700 } 598 701 599 attributes.loading = !! attributes.scanners; 600 this.set( attributes ); 702 this.frame.router.render( mode ); 703 704 view = router.get(); 705 if ( view && view.select ) { 706 view.select( this.frame.content.mode() ); 707 } 601 708 }, 709 602 710 /** 603 * Try scanning the embed as an image to discover its dimensions. 604 * 605 * @param {Object} attributes 711 * @access private 712 * @since 3.5.0 606 713 */ 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(); 714 _menu: function() { 715 var menu = this.frame.menu, 716 mode = this.get('menu'), 717 view; 613 718 614 attributes.scanners.push( deferred.promise() ); 719 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 720 if ( ! mode ) { 721 return; 722 } 615 723 616 // Try to load the image and find its width/height. 617 image.onload = function() { 618 deferred.resolve(); 724 menu.mode( mode ); 619 725 620 if ( state !== frame.state() || url !== state.props.get('url') ) { 621 return; 622 } 726 view = menu.get(); 727 if ( view && view.select ) { 728 view.select( this.id ); 729 } 730 }, 623 731 624 state.set({ 625 type: 'image' 626 }); 732 /** 733 * @access private 734 * @since 3.5.0 735 */ 736 _updateMenu: function() { 737 var previous = this.previous('menu'), 738 menu = this.get('menu'); 627 739 628 state.props.set({ 629 width: image.width, 630 height: image.height 631 }); 632 }; 740 if ( previous ) { 741 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 742 } 633 743 634 image.onerror = deferred.reject; 635 image.src = url; 744 if ( menu ) { 745 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 746 } 636 747 }, 637 748 638 refresh: function() { 639 this.frame.toolbar.get().refresh(); 640 }, 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'); 641 761 642 reset: function() {643 this.props.clear().set({ url: '' });762 if ( ! menuItem && title ) { 763 menuItem = { text: title }; 644 764 645 if ( this.active ) { 646 this.refresh(); 765 if ( priority ) { 766 menuItem.priority = priority; 767 } 768 } 769 770 if ( ! menuItem ) { 771 return; 647 772 } 773 774 view.set( this.id, menuItem ); 648 775 } 649 776 }); 650 777 651 module.exports = Embed; 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; 652 791 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; 792 793 /***/ }), 794 /* 30 */ 795 /***/ (function(module, exports) { 658 796 659 797 /** 660 * wp.media. controller.FeaturedImage798 * wp.media.selectionSync 661 799 * 662 * A state for selecting a featured image for a post.800 * Sync an attachments selection in a state with another state. 663 801 * 664 * @memberOf wp.media.controller 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. 665 804 * 666 * @class 667 * @augments wp.media.controller.Library 668 * @augments wp.media.controller.State 669 * @augments Backbone.Model 805 * @memberOf wp.media 670 806 * 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. 807 * @mixin 691 808 */ 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 809 var selectionSync = { 703 810 /** 704 811 * @since 3.5.0 705 812 */ 706 initialize: function() { 707 var library, comparator; 813 syncSelection: function() { 814 var selection = this.get('selection'), 815 manager = this.frame._selection; 708 816 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' }) ); 817 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 818 return; 712 819 } 713 820 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 ); 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 } 754 830 755 Library.prototype.deactivate.apply( this, arguments ); 831 // Sync the selection's single item with the master. 832 selection.single( manager.single ); 756 833 }, 757 834 758 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 * 759 841 * @since 3.5.0 760 842 */ 761 updateSelection: function() {843 recordSelection: function() { 762 844 var selection = this.get('selection'), 763 id = wp.media.view.settings.post.featuredImageId, 764 attachment; 845 manager = this.frame._selection; 765 846 766 if ( '' !== id && -1 !== id ) { 767 attachment = Attachment.get( id ); 768 attachment.fetch(); 847 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 848 return; 769 849 } 770 850 771 selection.reset( attachment ? [ attachment ] : [] ); 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; 772 859 } 773 } );860 }; 774 861 775 module.exports = FeaturedImage;862 module.exports = selectionSync; 776 863 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; 864 865 /***/ }), 866 /* 31 */ 867 /***/ (function(module, exports) { 868 869 var l10n = wp.media.view.l10n, 870 getUserSetting = window.getUserSetting, 871 setUserSetting = window.setUserSetting, 872 Library; 782 873 783 874 /** 784 * wp.media.controller. GalleryAdd875 * wp.media.controller.Library 785 876 * 786 * A state for selecting more images to add to a gallery.877 * A state for choosing an attachment or group of attachments from the media library. 787 878 * 788 879 * @memberOf wp.media.controller 789 880 * 790 881 * @class 791 * @augments wp.media.controller.Library792 882 * @augments wp.media.controller.State 793 883 * @augments Backbone.Model 884 * @mixes media.selectionSync 794 885 * 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. 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. 815 909 */ 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 ), 910 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{ 911 defaults: { 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 }, 827 927 828 928 /** 929 * If a library isn't provided, query all media items. 930 * If a selection instance isn't provided, create one. 931 * 829 932 * @since 3.5.0 830 933 */ 831 934 initialize: function() { 832 // If a library wasn't supplied, create a library of images. 935 var selection = this.get('selection'), 936 props; 937 833 938 if ( ! this.get('library') ) { 834 this.set( 'library', wp.media.query( { type: 'image' }) );939 this.set( 'library', wp.media.query() ); 835 940 } 836 941 837 Library.prototype.initialize.apply( this, arguments ); 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(); 838 957 }, 839 958 840 959 /** 841 960 * @since 3.5.0 842 961 */ 843 962 activate: function() { 844 var library = this.get('library'), 845 edit = this.frame.state('gallery-edit').get('library'); 963 this.syncSelection(); 846 964 847 if ( this.editLibrary && this.editLibrary !== edit ) { 848 library.unobserve( this.editLibrary ); 849 } 965 wp.Uploader.queue.on( 'add', this.uploading, this ); 850 966 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 }; 967 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 856 968 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; 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 }, 863 974 864 Library.prototype.activate.apply( this, arguments ); 865 } 866 }); 975 /** 976 * @since 3.5.0 977 */ 978 deactivate: function() { 979 this.recordSelection(); 867 980 868 module.exports = GalleryAdd;981 this.frame.off( 'content:activate', this.saveContentMode, this ); 869 982 870 },{}],9:[function(require,module,exports){ 871 var Library = wp.media.controller.Library, 872 l10n = wp.media.view.l10n, 873 GalleryEdit; 983 // Unbind all event handlers that use this state as the context 984 // from the selection. 985 this.get('selection').off( null, null, this ); 874 986 875 /** 876 * wp.media.controller.GalleryEdit 877 * 878 * A state for editing a gallery's images and settings. 879 * 880 * @memberOf wp.media.controller 881 * 882 * @class 883 * @augments wp.media.controller.Library 884 * @augments wp.media.controller.State 885 * @augments Backbone.Model 886 * 887 * @param {object} [attributes] The attributes hash passed to the state. 888 * @param {string} [attributes.id=gallery-edit] Unique identifier. 889 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 890 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 891 * If one is not supplied, an empty media.model.Selection collection is created. 892 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 893 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 894 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 895 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 896 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 897 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 898 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 899 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 900 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 901 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 902 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 903 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 904 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 905 * Defaults to false for this state, because the library passed in *is* the selection. 906 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 907 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 908 */ 909 GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{ 910 defaults: { 911 id: 'gallery-edit', 912 title: l10n.editGalleryTitle, 913 multiple: false, 914 searchable: false, 915 sortable: true, 916 date: false, 917 display: false, 918 content: 'browse', 919 toolbar: 'gallery-edit', 920 describe: true, 921 displaySettings: true, 922 dragInfo: true, 923 idealColumnWidth: 170, 924 editing: false, 925 priority: 60, 926 syncSelection: false 987 wp.Uploader.queue.off( null, null, this ); 927 988 }, 928 989 929 990 /** 991 * Reset the library to its initial state. 992 * 930 993 * @since 3.5.0 931 994 */ 932 initialize: function() { 933 // If we haven't been provided a `library`, create a `Selection`. 934 if ( ! this.get('library') ) { 935 this.set( 'library', new wp.media.model.Selection() ); 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 ) ); 936 1031 } 1032 return displays[ attachment.cid ]; 1033 }, 937 1034 938 // The single `Attachment` view to be used in the `Attachments` view. 939 if ( ! this.get('AttachmentView') ) { 940 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 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'; 941 1050 } 942 1051 943 Library.prototype.initialize.apply( this, arguments );1052 return settings; 944 1053 }, 945 1054 946 1055 /** 947 * @since 3.5.0 1056 * Whether an attachment is image. 1057 * 1058 * @since 4.4.1 1059 * 1060 * @param {wp.media.model.Attachment} attachment 1061 * @returns {Boolean} 948 1062 */ 949 activate: function() { 950 var library = this.get('library'); 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 } 951 1068 952 // Limit the library to images only.953 library.props.set( 'type', 'image' );1069 return attachment.get('type') === 'image'; 1070 }, 954 1071 955 // Watch for uploaded attachments. 956 this.get('library').observe( wp.Uploader.queue ); 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; 1086 } 1087 } 957 1088 958 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 1089 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1090 }, 959 1091 960 Library.prototype.activate.apply( this, arguments ); 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 } 961 1109 }, 962 1110 963 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 * 964 1122 * @since 3.5.0 1123 * 1124 * @param {wp.media.model.Attachment} attachment 965 1125 */ 966 deactivate: function() { 967 // Stop watching for uploaded attachments. 968 this.get('library').unobserve( wp.Uploader.queue ); 1126 uploading: function( attachment ) { 1127 var content = this.frame.content; 969 1128 970 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 1129 if ( 'upload' === content.mode() ) { 1130 this.frame.content.mode('browse'); 1131 } 971 1132 972 Library.prototype.deactivate.apply( this, arguments ); 1133 if ( this.get( 'autoSelect' ) ) { 1134 this.get('selection').add( attachment ); 1135 this.frame.trigger( 'library:selection:add' ); 1136 } 973 1137 }, 974 1138 975 1139 /** 976 * @since 3.5.01140 * Persist the mode of the content region as a user setting. 977 1141 * 978 * @ param browser1142 * @since 3.5.0 979 1143 */ 980 gallerySettings: function( browser) {981 if ( ! this.get('displaySettings') ) {1144 saveContentMode: function() { 1145 if ( 'browse' !== this.get('router') ) { 982 1146 return; 983 1147 } 984 1148 985 var library = this.get('library'); 1149 var mode = this.frame.content.mode(), 1150 view = this.frame.router.get(); 986 1151 987 if ( ! library || ! browser) {988 return;1152 if ( view && view.get( mode ) ) { 1153 setUserSetting( 'libraryContent', mode ); 989 1154 } 1155 } 1156 }); 990 1157 991 library.gallery = library.gallery || new Backbone.Model(); 1158 // Make selectionSync available on any Media Library state. 1159 _.extend( Library.prototype, wp.media.selectionSync ); 992 1160 993 browser.sidebar.set({ 994 gallery: new wp.media.view.Settings.Gallery({ 995 controller: this, 996 model: library.gallery, 997 priority: 40 998 }) 999 }); 1161 module.exports = Library; 1000 1162 1001 browser.toolbar.set( 'reverse', {1002 text: l10n.reverseOrder,1003 priority: 80,1004 1005 click: function() {1006 library.reset( library.toArray().reverse() );1007 }1008 });1009 }1010 });1011 1163 1012 module.exports = GalleryEdit; 1164 /***/ }), 1165 /* 32 */ 1166 /***/ (function(module, exports) { 1013 1167 1014 },{}],10:[function(require,module,exports){1015 1168 var State = wp.media.controller.State, 1016 1169 Library = wp.media.controller.Library, 1017 1170 l10n = wp.media.view.l10n, … … ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototyp 1075 1228 1076 1229 module.exports = ImageDetails; 1077 1230 1078 },{}],11:[function(require,module,exports){ 1079 var l10n = wp.media.view.l10n, 1080 getUserSetting = window.getUserSetting, 1081 setUserSetting = window.setUserSetting, 1082 Library; 1231 1232 /***/ }), 1233 /* 33 */ 1234 /***/ (function(module, exports) { 1235 1236 var Library = wp.media.controller.Library, 1237 l10n = wp.media.view.l10n, 1238 GalleryEdit; 1083 1239 1084 1240 /** 1085 * wp.media.controller. Library1241 * wp.media.controller.GalleryEdit 1086 1242 * 1087 * A state for choosing an attachment or group of attachments from the media library.1243 * A state for editing a gallery's images and settings. 1088 1244 * 1089 1245 * @memberOf wp.media.controller 1090 1246 * 1091 1247 * @class 1248 * @augments wp.media.controller.Library 1092 1249 * @augments wp.media.controller.State 1093 1250 * @augments Backbone.Model 1094 * @mixes media.selectionSync1095 1251 * 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. 1252 * @param {object} [attributes] The attributes hash passed to the state. 1253 * @param {string} [attributes.id=gallery-edit] Unique identifier. 1254 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 1255 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 1256 * If one is not supplied, an empty media.model.Selection collection is created. 1257 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1258 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1259 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1260 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1261 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 1262 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1263 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1264 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 1265 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1266 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1267 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1268 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1269 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1270 * Defaults to false for this state, because the library passed in *is* the selection. 1271 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1272 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1119 1273 */ 1120 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{1274 GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{ 1121 1275 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 1276 id: 'gallery-edit', 1277 title: l10n.editGalleryTitle, 1278 multiple: false, 1279 searchable: false, 1280 sortable: true, 1281 date: false, 1282 display: false, 1283 content: 'browse', 1284 toolbar: 'gallery-edit', 1285 describe: true, 1286 displaySettings: true, 1287 dragInfo: true, 1288 idealColumnWidth: 170, 1289 editing: false, 1290 priority: 60, 1291 syncSelection: false 1136 1292 }, 1137 1293 1138 1294 /** 1139 * If a library isn't provided, query all media items.1140 * If a selection instance isn't provided, create one.1141 *1142 1295 * @since 3.5.0 1143 1296 */ 1144 1297 initialize: function() { 1145 var selection = this.get('selection'), 1146 props; 1147 1298 // If we haven't been provided a `library`, create a `Selection`. 1148 1299 if ( ! this.get('library') ) { 1149 this.set( 'library', wp.media.query() );1300 this.set( 'library', new wp.media.model.Selection() ); 1150 1301 } 1151 1302 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 }) ); 1303 // The single `Attachment` view to be used in the `Attachments` view. 1304 if ( ! this.get('AttachmentView') ) { 1305 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1164 1306 } 1165 1307 1166 this.resetDisplays();1308 Library.prototype.initialize.apply( this, arguments ); 1167 1309 }, 1168 1310 1169 1311 /** 1170 1312 * @since 3.5.0 1171 1313 */ 1172 1314 activate: function() { 1173 this.syncSelection();1315 var library = this.get('library'); 1174 1316 1175 wp.Uploader.queue.on( 'add', this.uploading, this ); 1317 // Limit the library to images only. 1318 library.props.set( 'type', 'image' ); 1176 1319 1177 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 1320 // Watch for uploaded attachments. 1321 this.get('library').observe( wp.Uploader.queue ); 1178 1322 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 } 1323 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 1324 1325 Library.prototype.activate.apply( this, arguments ); 1183 1326 }, 1184 1327 1185 1328 /** 1186 1329 * @since 3.5.0 1187 1330 */ 1188 1331 deactivate: function() { 1189 this.recordSelection(); 1190 1191 this.frame.off( 'content:activate', this.saveContentMode, this ); 1332 // Stop watching for uploaded attachments. 1333 this.get('library').unobserve( wp.Uploader.queue ); 1192 1334 1193 // Unbind all event handlers that use this state as the context 1194 // from the selection. 1195 this.get('selection').off( null, null, this ); 1335 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 1196 1336 1197 wp.Uploader.queue.off( null, null, this );1337 Library.prototype.deactivate.apply( this, arguments ); 1198 1338 }, 1199 1339 1200 1340 /** 1201 * Reset the library to its initial state.1202 *1203 1341 * @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 1342 * 1216 * @ since 3.5.01343 * @param browser 1217 1344 */ 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 }, 1345 gallerySettings: function( browser ) { 1346 if ( ! this.get('displaySettings') ) { 1347 return; 1348 } 1227 1349 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; 1350 var library = this.get('library'); 1238 1351 1239 if ( ! displays[ attachment.cid ]) {1240 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );1352 if ( ! library || ! browser ) { 1353 return; 1241 1354 } 1242 return displays[ attachment.cid ];1243 },1244 1355 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 ); 1356 library.gallery = library.gallery || new Backbone.Model(); 1255 1357 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 } 1358 browser.sidebar.set({ 1359 gallery: new wp.media.view.Settings.Gallery({ 1360 controller: this, 1361 model: library.gallery, 1362 priority: 40 1363 }) 1364 }); 1261 1365 1262 return settings; 1263 }, 1366 browser.toolbar.set( 'reverse', { 1367 text: l10n.reverseOrder, 1368 priority: 80, 1264 1369 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 } 1370 click: function() { 1371 library.reset( library.toArray().reverse() ); 1372 } 1373 }); 1374 } 1375 }); 1278 1376 1279 return attachment.get('type') === 'image'; 1280 }, 1377 module.exports = GalleryEdit; 1281 1378 1282 /**1283 * Whether an attachment can be embedded (audio or video).1284 *1285 * @since 3.6.01286 *1287 * @param {wp.media.model.Attachment} attachment1288 * @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 1379 1380 /***/ }), 1381 /* 34 */ 1382 /***/ (function(module, exports) { 1302 1383 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(); 1384 var Selection = wp.media.model.Selection, 1385 Library = wp.media.controller.Library, 1386 l10n = wp.media.view.l10n, 1387 GalleryAdd; 1315 1388 1316 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1317 this.frame.content.render( this.get('content') ); 1318 } 1319 }, 1389 /** 1390 * wp.media.controller.GalleryAdd 1391 * 1392 * A state for selecting more images to add to a gallery. 1393 * 1394 * @memberOf wp.media.controller 1395 * 1396 * @class 1397 * @augments wp.media.controller.Library 1398 * @augments wp.media.controller.State 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. 1421 */ 1422 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{ 1423 defaults: _.defaults({ 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 1432 }, Library.prototype.defaults ), 1320 1433 1321 1434 /** 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 multiple1329 * attachments are uploaded, the last attachment in the upload queue will1330 * be selected.1331 *1332 1435 * @since 3.5.0 1333 *1334 * @param {wp.media.model.Attachment} attachment1335 1436 */ 1336 uploading: function( attachment ) { 1337 var content = this.frame.content; 1338 1339 if ( 'upload' === content.mode() ) { 1340 this.frame.content.mode('browse'); 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' }) ); 1341 1441 } 1342 1442 1343 if ( this.get( 'autoSelect' ) ) { 1344 this.get('selection').add( attachment ); 1345 this.frame.trigger( 'library:selection:add' ); 1346 } 1443 Library.prototype.initialize.apply( this, arguments ); 1347 1444 }, 1348 1445 1349 1446 /** 1350 * Persist the mode of the content region as a user setting.1351 *1352 1447 * @since 3.5.0 1353 1448 */ 1354 saveContentMode: function() { 1355 if ( 'browse' !== this.get('router') ) { 1356 return; 1449 activate: function() { 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 ); 1357 1455 } 1358 1456 1359 var mode = this.frame.content.mode(), 1360 view = this.frame.router.get(); 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 }; 1361 1462 1362 if ( view && view.get( mode ) ) { 1363 setUserSetting( 'libraryContent', mode ); 1364 } 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 1470 Library.prototype.activate.apply( this, arguments ); 1365 1471 } 1366 1472 }); 1367 1473 1368 // Make selectionSync available on any Media Library state. 1369 _.extend( Library.prototype, wp.media.selectionSync ); 1474 module.exports = GalleryAdd; 1370 1475 1371 module.exports = Library;1372 1476 1373 },{}],12:[function(require,module,exports){ 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 1374 1486 /** 1375 * wp.media.controller.MediaLibrary 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. 1376 1491 * 1377 1492 * @memberOf wp.media.controller 1378 1493 * … … module.exports = Library; 1380 1495 * @augments wp.media.controller.Library 1381 1496 * @augments wp.media.controller.State 1382 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'). 1383 1522 */ 1384 var Library = wp.media.controller.Library, 1385 MediaLibrary; 1386 1387 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{ 1388 defaults: _.defaults({ 1389 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1390 filterable: 'uploaded', 1391 1392 displaySettings: false, 1393 priority: 80, 1394 syncSelection: false 1395 }, Library.prototype.defaults ), 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 }, 1396 1538 1397 1539 /** 1398 1540 * @since 3.9.0 1399 *1400 * @param options1401 1541 */ 1402 initialize: function( options ) { 1403 this.media = options.media; 1404 this.type = options.type; 1405 this.set( 'library', wp.media.query({ type: this.type }) ); 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' ); 1406 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 } 1407 1560 Library.prototype.initialize.apply( this, arguments ); 1408 1561 }, 1409 1562 … … MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.protot 1411 1564 * @since 3.9.0 1412 1565 */ 1413 1566 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 } 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 1419 1577 Library.prototype.activate.apply( this, arguments ); 1420 } 1421 }); 1578 }, 1422 1579 1423 module.exports = MediaLibrary; 1580 /** 1581 * @since 3.9.0 1582 */ 1583 deactivate: function() { 1584 // Stop watching for uploaded attachments. 1585 this.get('library').unobserve( wp.Uploader.queue ); 1424 1586 1425 },{}],13:[function(require,module,exports){ 1426 /** 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. 1438 * 1439 * @memberOf wp.media.controller 1440 * 1441 * @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. 1447 */ 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. 1458 * 1459 * @since 3.5.0 1460 * 1461 * @param {string} mode 1462 * 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 ); 1587 this.frame.off( 'content:render:browse', this.renderSettings, this ); 1486 1588 1487 /** 1488 * Region mode activation event. 1489 * 1490 * @event wp.media.controller.Region#activate 1491 */ 1492 this.trigger('activate'); 1493 return this; 1589 Library.prototype.deactivate.apply( this, arguments ); 1494 1590 }, 1591 1495 1592 /** 1496 * Render a mode. 1497 * 1498 * @since 3.5.0 1593 * Render the collection embed settings view in the browser sidebar. 1499 1594 * 1500 * @param {string} mode 1595 * @todo This is against the pattern elsewhere in media. Typically the frame 1596 * is responsible for adding region mode callbacks. Explain. 1501 1597 * 1502 * @fires Region#create 1503 * @fires Region#render 1598 * @since 3.9.0 1504 1599 * 1505 * @ returns {wp.media.controller.Region} Returns itself to allow chaining1600 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 1506 1601 */ 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; 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 = {}; 1527 1608 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 ); 1609 if ( ! library || ! attachmentsBrowserView ) { 1610 return; 1539 1611 } 1540 return this;1541 },1542 1612 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 }, 1613 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 1553 1614 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 }, 1615 obj[ collectionType ] = new SettingsView({ 1616 controller: this, 1617 model: library[ collectionType ], 1618 priority: 40 1619 }); 1569 1620 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; 1621 attachmentsBrowserView.sidebar.set( obj ); 1580 1622 1581 if ( ! this._mode ) { 1582 return; 1623 if ( dragInfoText ) { 1624 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 1625 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 1626 priority: -40 1627 }) ); 1583 1628 } 1584 1629 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 ); 1630 // Add the 'Reverse order' button to the toolbar. 1631 attachmentsBrowserView.toolbar.set( 'reverse', { 1632 text: l10n.reverseOrder, 1633 priority: 80, 1591 1634 1592 // Trigger `{this.id}:{event}` event on the frame.1593 args[0] = base;1594 this.view.trigger.apply( this.view, args );1595 return this;1635 click: function() { 1636 library.reset( library.toArray().reverse() ); 1637 } 1638 }); 1596 1639 } 1597 1640 }); 1598 1641 1599 module.exports = Region;1642 module.exports = CollectionEdit; 1600 1643 1601 },{}],14:[function(require,module,exports){ 1602 var Library = wp.media.controller.Library, 1603 l10n = wp.media.view.l10n, 1604 ReplaceImage; 1644 1645 /***/ }), 1646 /* 36 */ 1647 /***/ (function(module, exports) { 1648 1649 var Selection = wp.media.model.Selection, 1650 Library = wp.media.controller.Library, 1651 CollectionAdd; 1605 1652 1606 1653 /** 1607 * wp.media.controller. ReplaceImage1654 * wp.media.controller.CollectionAdd 1608 1655 * 1609 * A state for replacing an image.1656 * A state for adding attachments to a collection (e.g. video playlist). 1610 1657 * 1611 1658 * @memberOf wp.media.controller 1612 1659 * … … var Library = wp.media.controller.Library, 1616 1663 * @augments Backbone.Model 1617 1664 * 1618 1665 * @param {object} [attributes] The attributes hash passed to the state. 1619 * @param {string} [attributes.id=replace-image] Unique identifier. 1620 * @param {string} [attributes.title=Replace Image] Title for the state. Displays in the media menu and the frame's title region. 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. 1621 1669 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1622 * If one is not supplied, a collection of all images will be created. 1623 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 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. 1624 1674 * @param {string} [attributes.content=upload] Initial mode for the content region. 1625 1675 * Overridden by persistent user setting if 'contentUserSetting' is true. 1626 * @param {string} [attributes.menu=default] Initial mode for the menu region.1627 1676 * @param {string} [attributes.router=browse] Initial mode for the router region. 1628 * @param {string} [attributes.toolbar=replace] Initial mode for the toolbar region. 1629 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1677 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1630 1678 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1631 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown.1632 * Accepts 'all', 'uploaded', or 'unattached'.1633 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. 1634 1680 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1635 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery.1636 1681 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1637 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 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'). 1638 1687 */ 1639 ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{1640 defaults: _.defaults( {1641 id: 'replace-image',1642 title: l10n.replaceImageTitle,1643 multiple: false,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 1644 1693 filterable: 'uploaded', 1645 toolbar: 'replace', 1646 menu: false, 1647 priority: 60, 1648 syncSelection: true 1694 1695 priority: 100, 1696 syncSelection: false 1649 1697 }, Library.prototype.defaults ), 1650 1698 1651 1699 /** 1652 1700 * @since 3.9.0 1653 *1654 * @param options1655 1701 */ 1656 initialize: function( options ) { 1657 var library, comparator; 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 ); 1658 1712 1659 this.image = options.image;1660 1713 // If we haven't been provided a `library`, create a `Selection`. 1661 1714 if ( ! this.get('library') ) { 1662 this.set( 'library', wp.media.query({ type: 'image'}) );1715 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 1663 1716 } 1664 1665 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; 1666 1820 1667 library = this.get('library');1668 comparator = library.comparator;1669 1670 1821 // Overload the library's comparator to push items that are not in 1671 1822 // the mirrored query to the front of the aggregate collection. 1672 1823 library.comparator = function( a, b ) { … … ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.protot 1688 1839 }, 1689 1840 1690 1841 /** 1691 * @since 3. 9.01842 * @since 3.5.0 1692 1843 */ 1693 1844 activate: function() { 1694 1845 this.updateSelection(); 1846 this.frame.on( 'open', this.updateSelection, this ); 1847 1695 1848 Library.prototype.activate.apply( this, arguments ); 1696 1849 }, 1697 1850 1698 1851 /** 1699 * @since 3.9.0 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 1700 1862 */ 1701 1863 updateSelection: function() { 1702 1864 var selection = this.get('selection'), 1703 attachment = this.image.attachment; 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 } 1704 1872 1705 1873 selection.reset( attachment ? [ attachment ] : [] ); 1706 1874 } 1707 1875 }); 1708 1876 1709 module.exports = ReplaceImage;1877 module.exports = FeaturedImage; 1710 1878 1711 },{}],15:[function(require,module,exports){ 1712 var Controller = wp.media.controller, 1713 SiteIconCropper; 1879 1880 /***/ }), 1881 /* 38 */ 1882 /***/ (function(module, exports) { 1883 1884 var Library = wp.media.controller.Library, 1885 l10n = wp.media.view.l10n, 1886 ReplaceImage; 1714 1887 1715 1888 /** 1716 * wp.media.controller. SiteIconCropper1889 * wp.media.controller.ReplaceImage 1717 1890 * 1718 * A state for cropping a Site Icon.1891 * A state for replacing an image. 1719 1892 * 1720 1893 * @memberOf wp.media.controller 1721 1894 * 1722 1895 * @class 1723 * @augments wp.media.controller. Cropper1896 * @augments wp.media.controller.Library 1724 1897 * @augments wp.media.controller.State 1725 1898 * @augments Backbone.Model 1899 * 1900 * @param {object} [attributes] The attributes hash passed to the state. 1901 * @param {string} [attributes.id=replace-image] Unique identifier. 1902 * @param {string} [attributes.title=Replace Image] Title for the state. Displays in the media menu and the frame's title region. 1903 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1904 * If one is not supplied, a collection of all images will be created. 1905 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1906 * @param {string} [attributes.content=upload] Initial mode for the content region. 1907 * Overridden by persistent user setting if 'contentUserSetting' is true. 1908 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1909 * @param {string} [attributes.router=browse] Initial mode for the router region. 1910 * @param {string} [attributes.toolbar=replace] Initial mode for the toolbar region. 1911 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1912 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1913 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1914 * Accepts 'all', 'uploaded', or 'unattached'. 1915 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1916 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1917 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1918 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1919 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1726 1920 */ 1727 SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{ 1728 activate: function() { 1729 this.frame.on( 'content:create:crop', this.createCropContent, this ); 1730 this.frame.on( 'close', this.removeCropper, this ); 1731 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 1732 }, 1921 ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{ 1922 defaults: _.defaults({ 1923 id: 'replace-image', 1924 title: l10n.replaceImageTitle, 1925 multiple: false, 1926 filterable: 'uploaded', 1927 toolbar: 'replace', 1928 menu: false, 1929 priority: 60, 1930 syncSelection: true 1931 }, Library.prototype.defaults ), 1733 1932 1734 createCropContent: function() { 1735 this.cropperView = new wp.media.view.SiteIconCropper({ 1736 controller: this, 1737 attachment: this.get('selection').first() 1738 }); 1739 this.cropperView.on('image-loaded', this.createCropToolbar, this); 1740 this.frame.content.set(this.cropperView); 1933 /** 1934 * @since 3.9.0 1935 * 1936 * @param options 1937 */ 1938 initialize: function( options ) { 1939 var library, comparator; 1940 1941 this.image = options.image; 1942 // If we haven't been provided a `library`, create a `Selection`. 1943 if ( ! this.get('library') ) { 1944 this.set( 'library', wp.media.query({ type: 'image' }) ); 1945 } 1946 1947 Library.prototype.initialize.apply( this, arguments ); 1948 1949 library = this.get('library'); 1950 comparator = library.comparator; 1951 1952 // Overload the library's comparator to push items that are not in 1953 // the mirrored query to the front of the aggregate collection. 1954 library.comparator = function( a, b ) { 1955 var aInQuery = !! this.mirroring.get( a.cid ), 1956 bInQuery = !! this.mirroring.get( b.cid ); 1957 1958 if ( ! aInQuery && bInQuery ) { 1959 return -1; 1960 } else if ( aInQuery && ! bInQuery ) { 1961 return 1; 1962 } else { 1963 return comparator.apply( this, arguments ); 1964 } 1965 }; 1741 1966 1967 // Add all items in the selection to the library, so any featured 1968 // images that are not initially loaded still appear. 1969 library.observe( this.get('selection') ); 1742 1970 }, 1743 1971 1744 doCrop: function( attachment ) { 1745 var cropDetails = attachment.get( 'cropDetails' ), 1746 control = this.get( 'control' ); 1972 /** 1973 * @since 3.9.0 1974 */ 1975 activate: function() { 1976 this.updateSelection(); 1977 Library.prototype.activate.apply( this, arguments ); 1978 }, 1747 1979 1748 cropDetails.dst_width = control.params.width; 1749 cropDetails.dst_height = control.params.height; 1980 /** 1981 * @since 3.9.0 1982 */ 1983 updateSelection: function() { 1984 var selection = this.get('selection'), 1985 attachment = this.image.attachment; 1750 1986 1751 return wp.ajax.post( 'crop-image', { 1752 nonce: attachment.get( 'nonces' ).edit, 1753 id: attachment.get( 'id' ), 1754 context: 'site-icon', 1755 cropDetails: cropDetails 1756 } ); 1987 selection.reset( attachment ? [ attachment ] : [] ); 1757 1988 } 1758 1989 }); 1759 1990 1760 module.exports = SiteIconCropper; 1991 module.exports = ReplaceImage; 1992 1993 1994 /***/ }), 1995 /* 39 */ 1996 /***/ (function(module, exports) { 1997 1998 var l10n = wp.media.view.l10n, 1999 EditImage; 1761 2000 1762 },{}],16:[function(require,module,exports){1763 2001 /** 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. 2002 * wp.media.controller.EditImage 1768 2003 * 1769 * States are stored as models in a Backbone collection.2004 * A state for editing (cropping, etc.) an image. 1770 2005 * 1771 2006 * @memberOf wp.media.controller 1772 2007 * 1773 * @since 3.5.01774 *1775 2008 * @class 2009 * @augments wp.media.controller.State 1776 2010 * @augments Backbone.Model 1777 * @mixin1778 * @mixes Backbone.Events1779 2011 * 1780 * @param {Array} states 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. 1781 2020 */ 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; 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 }, 1789 2030 1790 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{1791 2031 /** 1792 * Fetch a state. 1793 * 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 2032 * @since 3.9.0 1806 2033 */ 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 ); 2034 activate: function() { 2035 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) ); 1817 2036 }, 1818 2037 1819 2038 /** 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 2039 * @since 3.9.0 1834 2040 */ 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'); 1849 1850 return this; 2041 deactivate: function() { 2042 this.frame.off( 'toolbar:render:edit-image' ); 1851 2043 }, 1852 2044 1853 2045 /** 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 2046 * @since 3.9.0 1863 2047 */ 1864 lastState: function() { 1865 if ( this._lastState ) { 1866 return this.state( this._lastState ); 1867 } 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 }) ); 1868 2070 } 1869 2071 }); 1870 2072 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 1876 * @instance 1877 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1878 */ 1879 /** 1880 * @function off 1881 * @memberOf wp.media.controller.StateMachine 1882 * @instance 1883 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1884 */ 1885 /** 1886 * @function trigger 1887 * @memberOf wp.media.controller.StateMachine 1888 * @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 ); 1897 return this; 1898 }; 1899 }); 2073 module.exports = EditImage; 1900 2074 1901 module.exports = StateMachine;1902 2075 1903 },{}],17:[function(require,module,exports){ 2076 /***/ }), 2077 /* 40 */ 2078 /***/ (function(module, exports) { 2079 1904 2080 /** 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. 2081 * wp.media.controller.MediaLibrary 1917 2082 * 1918 2083 * @memberOf wp.media.controller 1919 2084 * 1920 2085 * @class 2086 * @augments wp.media.controller.Library 2087 * @augments wp.media.controller.State 1921 2088 * @augments Backbone.Model 1922 2089 */ 1923 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{ 1924 /** 1925 * Constructor. 1926 * 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. 1946 * 1947 * @abstract 1948 * @since 3.5.0 1949 */ 1950 ready: function() {}, 2090 var Library = wp.media.controller.Library, 2091 MediaLibrary; 1951 2092 1952 /** 1953 * Activate event callback. 1954 * 1955 * @abstract 1956 * @since 3.5.0 1957 */ 1958 activate: function() {}, 2093 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{ 2094 defaults: _.defaults({ 2095 // Attachments browser defaults. @see media.view.AttachmentsBrowser 2096 filterable: 'uploaded', 1959 2097 1960 /** 1961 * Deactivate event callback. 1962 * 1963 * @abstract 1964 * @since 3.5.0 1965 */ 1966 deactivate: function() {}, 2098 displaySettings: false, 2099 priority: 80, 2100 syncSelection: false 2101 }, Library.prototype.defaults ), 1967 2102 1968 2103 /** 1969 * Reset event callback.2104 * @since 3.9.0 1970 2105 * 1971 * @abstract 1972 * @since 3.5.0 1973 */ 1974 reset: function() {}, 1975 1976 /** 1977 * @access private 1978 * @since 3.5.0 2106 * @param options 1979 2107 */ 1980 _ready: function() { 1981 this._updateMenu(); 1982 }, 2108 initialize: function( options ) { 2109 this.media = options.media; 2110 this.type = options.type; 2111 this.set( 'library', wp.media.query({ type: this.type }) ); 1983 2112 1984 /** 1985 * @access private 1986 * @since 3.5.0 1987 */ 1988 _preActivate: function() { 1989 this.active = true; 2113 Library.prototype.initialize.apply( this, arguments ); 1990 2114 }, 1991 2115 1992 2116 /** 1993 * @access private 1994 * @since 3.5.0 2117 * @since 3.9.0 1995 2118 */ 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 ); 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 }); 2001 2128 2002 this.frame.on( 'title:render:default', this._renderTitle, this );2129 module.exports = MediaLibrary; 2003 2130 2004 this._title();2005 this._menu();2006 this._toolbar();2007 this._content();2008 this._router();2009 },2010 2131 2011 /** 2012 * @access private 2013 * @since 3.5.0 2014 */ 2015 _deactivate: function() { 2016 this.active = false; 2132 /***/ }), 2133 /* 41 */ 2134 /***/ (function(module, exports) { 2017 2135 2018 this.frame.off( 'title:render:default', this._renderTitle, this ); 2136 var l10n = wp.media.view.l10n, 2137 $ = Backbone.$, 2138 Embed; 2019 2139 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 ); 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: {} 2024 2174 }, 2025 2175 2026 /** 2027 * @access private 2028 * @since 3.5.0 2029 */ 2030 _title: function() { 2031 this.frame.title.render( this.get('titleMode') || 'default' ); 2032 }, 2176 // The amount of time used when debouncing the scan. 2177 sensitivity: 400, 2033 2178 2034 /** 2035 * @access private 2036 * @since 3.5.0 2037 */ 2038 _renderTitle: function( view ) { 2039 view.$el.text( this.get('title') || '' ); 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 ); 2040 2186 }, 2041 2187 2042 2188 /** 2043 * @access private 2044 * @since 3.5.0 2189 * Trigger a scan of the embedded URL's content for metadata required to embed. 2190 * 2191 * @fires wp.media.controller.Embed#scan 2045 2192 */ 2046 _router: function() { 2047 var router = this.frame.router, 2048 mode = this.get('router'), 2049 view; 2193 scan: function() { 2194 var scanners, 2195 embed = this, 2196 attributes = { 2197 type: 'link', 2198 scanners: [] 2199 }; 2050 2200 2051 this.frame.$el.toggleClass( 'hide-router', ! mode ); 2052 if ( ! mode ) { 2053 return; 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 ); 2054 2206 } 2055 2207 2056 this.frame.router.render( mode ); 2057 2058 view = router.get(); 2059 if ( view && view.select ) { 2060 view.select( this.frame.content.mode() ); 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; 2061 2217 } 2062 },2063 2218 2219 attributes.loading = !! attributes.scanners; 2220 this.set( attributes ); 2221 }, 2064 2222 /** 2065 * @access private 2066 * @since 3.5.0 2223 * Try scanning the embed as an image to discover its dimensions. 2224 * 2225 * @param {Object} attributes 2067 2226 */ 2068 _menu: function() { 2069 var menu = this.frame.menu, 2070 mode = this.get('menu'), 2071 view; 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(); 2072 2233 2073 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 2074 if ( ! mode ) { 2075 return; 2076 } 2234 attributes.scanners.push( deferred.promise() ); 2077 2235 2078 menu.mode( mode ); 2236 // Try to load the image and find its width/height. 2237 image.onload = function() { 2238 deferred.resolve(); 2079 2239 2080 view = menu.get(); 2081 if ( view && view.select ) { 2082 view.select( this.id ); 2083 } 2084 }, 2240 if ( state !== frame.state() || url !== state.props.get('url') ) { 2241 return; 2242 } 2085 2243 2086 /** 2087 * @access private 2088 * @since 3.5.0 2089 */ 2090 _updateMenu: function() { 2091 var previous = this.previous('menu'), 2092 menu = this.get('menu'); 2244 state.set({ 2245 type: 'image' 2246 }); 2093 2247 2094 if ( previous ) { 2095 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 2096 } 2248 state.props.set({ 2249 width: image.width, 2250 height: image.height 2251 }); 2252 }; 2097 2253 2098 if ( menu ) { 2099 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 2100 } 2254 image.onerror = deferred.reject; 2255 image.src = url; 2101 2256 }, 2102 2257 2103 /** 2104 * Create a view in the media menu for the state. 2105 * 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 }; 2258 refresh: function() { 2259 this.frame.toolbar.get().refresh(); 2260 }, 2118 2261 2119 if ( priority ) { 2120 menuItem.priority = priority; 2121 } 2122 } 2262 reset: function() { 2263 this.props.clear().set({ url: '' }); 2123 2264 2124 if ( ! menuItem) {2125 return;2265 if ( this.active ) { 2266 this.refresh(); 2126 2267 } 2127 2128 view.set( this.id, menuItem );2129 2268 } 2130 2269 }); 2131 2270 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 }; 2142 }); 2271 module.exports = Embed; 2143 2272 2144 module.exports = State;2145 2273 2146 },{}],18:[function(require,module,exports){ 2274 /***/ }), 2275 /* 42 */ 2276 /***/ (function(module, exports) { 2277 2278 var l10n = wp.media.view.l10n, 2279 Cropper; 2280 2147 2281 /** 2148 * wp.media.selectionSync 2149 * 2150 * Sync an attachments selection in a state with another state. 2282 * wp.media.controller.Cropper 2151 2283 * 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. 2284 * A state for cropping an image. 2154 2285 * 2155 * @memberOf wp.media 2286 * @memberOf wp.media.controller 2156 2287 * 2157 * @mixin 2288 * @class 2289 * @augments wp.media.controller.State 2290 * @augments Backbone.Model 2158 2291 */ 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 ) { 2168 return; 2169 } 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, 2170 2301 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 } 2302 // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state. 2303 doCropArgs: {} 2304 }, 2180 2305 2181 // Sync the selection's single item with the master. 2182 selection.single( manager.single ); 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)); 2183 2310 }, 2184 2311 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; 2312 deactivate: function() { 2313 this.frame.toolbar.mode('browse'); 2314 }, 2196 2315 2197 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2198 return; 2199 } 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); 2200 2323 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 } 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; 2207 2333 2208 manager.single = selection._single; 2209 } 2210 }; 2334 canSkipCrop = this.get('canSkipCrop') || false; 2211 2335 2212 module.exports = selectionSync; 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 }, 2213 2344 2214 },{}],19:[function(require,module,exports){ 2215 var media = wp.media, 2216 $ = jQuery, 2217 l10n; 2345 click: function() { 2346 var controller = this.controller, 2347 selection; 2218 2348 2219 media.isTouchDevice = ( 'ontouchend' in document ); 2349 selection = controller.state().get('selection').first(); 2350 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 2220 2351 2221 // Link any localized strings. 2222 l10n = media.view.l10n = window._wpMediaViewsL10n || {};2352 this.$el.text(l10n.cropping); 2353 this.$el.attr('disabled', true); 2223 2354 2224 // Link any settings. 2225 media.view.settings = l10n.settings || {}; 2226 delete l10n.settings; 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 }; 2227 2365 2228 // Copy the `post` setting over to the model settings. 2229 media.model.settings.post = media.view.settings.post; 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 } 2230 2382 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; 2383 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 2384 }, 2240 2385 2241 transition = _.find( _.keys( transitions ), function( transition ) { 2242 return ! _.isUndefined( style[ transition ] ); 2243 }); 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 }); 2244 2398 2245 return transition && { 2246 end: transitions[ transition ] 2247 }; 2248 }()); 2399 module.exports = Cropper; 2249 2400 2250 /** 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 ); 2401 2402 /***/ }), 2403 /* 43 */ 2404 /***/ (function(module, exports) { 2405 2406 var Controller = wp.media.controller, 2407 CustomizeImageCropper; 2256 2408 2257 2409 /** 2258 * Makes it easier to bind events using transitions.2410 * wp.media.controller.CustomizeImageCropper 2259 2411 * 2260 * @param {string} selector 2261 * @param {Number} sensitivity 2262 * @returns {Promise} 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 2263 2420 */ 2264 media.transition = function( selector, sensitivity ) { 2265 var deferred = $.Deferred(); 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; 2266 2426 2267 sensitivity = sensitivity || 2000; 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; 2268 2431 2269 if ( $.support.transition ) { 2270 if ( ! (selector instanceof $) ) { 2271 selector = $( selector ); 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; 2272 2436 } 2273 2437 2274 // Resolve the deferred when the first element finishes animating. 2275 selector.first().one( $.support.transition.end, deferred.resolve ); 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 }); 2276 2447 2277 // Just in case the event doesn't trigger, fire a callback. 2278 _.delay( deferred.resolve, sensitivity ); 2448 module.exports = CustomizeImageCropper; 2279 2449 2280 // Otherwise, execute on the spot.2281 } else {2282 deferred.resolve();2283 }2284 2450 2285 return deferred.promise(); 2286 }; 2451 /***/ }), 2452 /* 44 */ 2453 /***/ (function(module, exports) { 2287 2454 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; 2455 var Controller = wp.media.controller, 2456 SiteIconCropper; 2368 2457 2369 2458 /** 2370 * wp.media. view.AttachmentCompat2459 * wp.media.controller.SiteIconCropper 2371 2460 * 2372 * A view to display fields added via the `attachment_fields_to_edit` filter.2461 * A state for cropping a Site Icon. 2373 2462 * 2374 * @memberOf wp.media. view2463 * @memberOf wp.media.controller 2375 2464 * 2376 2465 * @class 2377 * @augments wp.media. View2378 * @augments wp. Backbone.View2379 * @augments Backbone. View2466 * @augments wp.media.controller.Cropper 2467 * @augments wp.media.controller.State 2468 * @augments Backbone.Model 2380 2469 */ 2381 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{ 2382 tagName: 'form', 2383 className: 'compat-item', 2470 SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{ 2471 activate: function() { 2472 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2473 this.frame.on( 'close', this.removeCropper, this ); 2474 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2475 }, 2476 2477 createCropContent: function() { 2478 this.cropperView = new wp.media.view.SiteIconCropper({ 2479 controller: this, 2480 attachment: this.get('selection').first() 2481 }); 2482 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2483 this.frame.content.set(this.cropperView); 2384 2484 2385 events: {2386 'submit': 'preventDefault',2387 'change input': 'save',2388 'change select': 'save',2389 'change textarea': 'save'2390 2485 }, 2391 2486 2392 initialize: function() { 2393 this.listenTo( this.model, 'change:compat', this.render ); 2487 doCrop: function( attachment ) { 2488 var cropDetails = attachment.get( 'cropDetails' ), 2489 control = this.get( 'control' ); 2490 2491 cropDetails.dst_width = control.params.width; 2492 cropDetails.dst_height = control.params.height; 2493 2494 return wp.ajax.post( 'crop-image', { 2495 nonce: attachment.get( 'nonces' ).edit, 2496 id: attachment.get( 'id' ), 2497 context: 'site-icon', 2498 cropDetails: cropDetails 2499 } ); 2500 } 2501 }); 2502 2503 module.exports = SiteIconCropper; 2504 2505 2506 /***/ }), 2507 /* 45 */ 2508 /***/ (function(module, exports) { 2509 2510 /** 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 2523 * 2524 * @class 2525 * @augments wp.Backbone.View 2526 * @augments Backbone.View 2527 */ 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 ); 2394 2534 }, 2395 2535 /** 2396 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 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. 2539 * 2540 * @returns {wp.media.View} Returns itself to allow chaining 2397 2541 */ 2398 2542 dispose: function() { 2399 if ( this.$(':focus').length ) { 2400 this.save(); 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 ); 2401 2550 } 2402 /** 2403 * call 'dispose' directly on the parent class 2404 */ 2405 return View.prototype.dispose.apply( this, arguments ); 2406 }, 2407 /** 2408 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 2409 */ 2410 render: function() { 2411 var compat = this.model.get('compat'); 2412 if ( ! compat || ! compat.item ) { 2413 return; 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 ); 2414 2559 } 2415 2560 2416 this.views.detach();2417 this.$el.html( compat.item );2418 this.views.render();2419 2561 return this; 2420 2562 }, 2421 2563 /** 2422 * @param {Object} event 2423 */ 2424 preventDefault: function( event ) { 2425 event.preventDefault(); 2426 }, 2427 /** 2428 * @param {Object} event 2564 * @returns {wp.media.View} Returns itself to allow chaining 2429 2565 */ 2430 save: function( event ) { 2431 var data = {}; 2432 2433 if ( event ) { 2434 event.preventDefault(); 2435 } 2436 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'] ); 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 ); 2447 2572 } 2448 2573 }); 2449 2574 2450 module.exports = AttachmentCompat;2575 module.exports = View; 2451 2576 2452 },{}],21:[function(require,module,exports){ 2453 var $ = jQuery, 2454 AttachmentFilters; 2577 2578 /***/ }), 2579 /* 46 */ 2580 /***/ (function(module, exports) { 2455 2581 2456 2582 /** 2457 * wp.media.view.AttachmentFilters 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. 2458 2587 * 2459 2588 * @memberOf wp.media.view 2460 2589 * 2590 * @see wp.media.controller.State 2591 * @see wp.media.controller.Region 2592 * 2461 2593 * @class 2462 2594 * @augments wp.media.View 2463 2595 * @augments wp.Backbone.View 2464 2596 * @augments Backbone.View 2597 * @mixes wp.media.controller.StateMachine 2465 2598 */ 2466 AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{ 2467 tagName: 'select', 2468 className: 'attachment-filters', 2469 id: 'media-attachment-filters', 2470 2471 events: { 2472 change: 'change' 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(); 2473 2607 }, 2474 2608 2475 keys: [], 2609 _createRegions: function() { 2610 // Clone the regions array. 2611 this.regions = this.regions ? this.regions.slice() : []; 2476 2612 2477 initialize: function() { 2478 this.createFilters(); 2479 _.extend( this.filters, this.options.filters ); 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 }); 2480 2635 2481 // Build `<option>` elements. 2482 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 2483 return { 2484 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 2485 priority: filter.priority || 50 2486 }; 2487 }, this ).sortBy('priority').pluck('el').value() ); 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 ); 2488 2641 2489 this.listenTo( this.model, 'change', this.select ); 2490 this.select(); 2642 if ( this.options.states ) { 2643 this.states.add( this.options.states ); 2644 } 2491 2645 }, 2492 2646 2493 2647 /** 2494 * @abstract 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. 2495 2651 */ 2496 createFilters: function() { 2497 this.filters = {}; 2498 }, 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 ) ); 2499 2656 2657 _.each( this.options.mode, function( mode ) { 2658 this.activateMode( mode ); 2659 }, this ); 2660 }, 2500 2661 /** 2501 * When the selected filter changes, update the Attachment Query properties to match. 2662 * Reset all states on the frame to their defaults. 2663 * 2664 * @returns {wp.media.view.Frame} Returns itself to allow chaining 2502 2665 */ 2503 change: function() { 2504 var filter = this.filters[ this.el.value ]; 2505 if ( filter ) { 2506 this.model.set( filter.props ); 2507 } 2666 reset: function() { 2667 this.states.invoke( 'trigger', 'reset' ); 2668 return this; 2508 2669 }, 2509 2510 select: function() { 2511 var model = this.model, 2512 value = 'all', 2513 props = model.toJSON(); 2514 2515 _.find( this.filters, function( filter, id ) { 2516 var equal = _.all( filter.props, function( prop, key ) { 2517 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); 2518 }); 2519 2520 if ( equal ) { 2521 return value = id; 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; 2522 2684 } 2523 }); 2524 2525 this.$el.val( value ); 2526 } 2527 }); 2685 } ); 2528 2686 2529 module.exports = AttachmentFilters; 2687 if ( ! _.has( modeEventMap, collectionEvent ) ) { 2688 return; 2689 } 2530 2690 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 }; 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; 2587 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 ); 2588 2708 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 }; 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; 2615 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' ); 2616 2730 2617 this.filters = filters; 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 ); 2618 2741 } 2619 2742 }); 2620 2743 2621 module.exports = All; 2622 2623 },{}],23:[function(require,module,exports){ 2624 var l10n = wp.media.view.l10n, 2625 DateFilter; 2744 // Make the `Frame` a `StateMachine`. 2745 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 2626 2746 2627 /** 2628 * A filter dropdown for month/dates. 2629 * 2630 * @memberOf wp.media.view.AttachmentFilters 2631 * 2632 * @class 2633 * @augments wp.media.view.AttachmentFilters 2634 * @augments wp.media.View 2635 * @augments wp.Backbone.View 2636 * @augments Backbone.View 2637 */ 2638 DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{ 2639 id: 'media-attachment-date-filters', 2747 module.exports = Frame; 2640 2748 2641 createFilters: function() {2642 var filters = {};2643 _.each( wp.media.view.settings.months || {}, function( value, index ) {2644 filters[ index ] = {2645 text: value.text,2646 props: {2647 year: value.year,2648 monthnum: value.month2649 }2650 };2651 });2652 filters.all = {2653 text: l10n.allDates,2654 props: {2655 monthnum: false,2656 year: false2657 },2658 priority: 102659 };2660 this.filters = filters;2661 }2662 });2663 2749 2664 module.exports = DateFilter; 2750 /***/ }), 2751 /* 47 */ 2752 /***/ (function(module, exports) { 2665 2753 2666 },{}],24:[function(require,module,exports){ 2667 var l10n = wp.media.view.l10n,2668 Uploaded;2754 var Frame = wp.media.view.Frame, 2755 $ = jQuery, 2756 MediaFrame; 2669 2757 2670 2758 /** 2671 * wp.media.view. AttachmentFilters.Uploaded2759 * wp.media.view.MediaFrame 2672 2760 * 2673 * @memberOf wp.media.view.AttachmentFilters 2761 * The frame used to create the media modal. 2762 * 2763 * @memberOf wp.media.view 2674 2764 * 2675 2765 * @class 2676 * @augments wp.media.view. AttachmentFilters2766 * @augments wp.media.view.Frame 2677 2767 * @augments wp.media.View 2678 2768 * @augments wp.Backbone.View 2679 2769 * @augments Backbone.View 2770 * @mixes wp.media.controller.StateMachine 2680 2771 */ 2681 Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{ 2682 createFilters: function() { 2683 var type = this.model.get('type'), 2684 types = wp.media.view.settings.mimeTypes, 2685 text; 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'], 2686 2776 2687 if ( types && type ){2688 text = types[ type ];2689 }2777 events: { 2778 'click div.media-frame-title h1': 'toggleMenu' 2779 }, 2690 2780 2691 this.filters = { 2692 all: { 2693 text: text || l10n.allMediaItems, 2694 props: { 2695 uploadedTo: null, 2696 orderby: 'date', 2697 order: 'DESC' 2698 }, 2699 priority: 10 2700 }, 2781 /** 2782 * @constructs 2783 */ 2784 initialize: function() { 2785 Frame.prototype.initialize.apply( this, arguments ); 2701 2786 2702 uploaded: { 2703 text: l10n.uploadedToThisPost, 2704 props: { 2705 uploadedTo: wp.media.view.settings.post.id, 2706 orderby: 'menuOrder', 2707 order: 'ASC' 2708 }, 2709 priority: 20 2710 }, 2787 _.defaults( this.options, { 2788 title: '', 2789 modal: true, 2790 uploader: true 2791 }); 2711 2792 2712 unattached: { 2713 text: l10n.unattached, 2714 props: { 2715 uploadedTo: 0, 2716 orderby: 'menuOrder', 2717 order: 'ASC' 2718 }, 2719 priority: 50 2720 } 2721 }; 2722 } 2723 }); 2793 // Ensure core UI is enabled. 2794 this.$el.addClass('wp-core-ui'); 2724 2795 2725 module.exports = Uploaded; 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 }); 2726 2802 2727 },{}],25:[function(require,module,exports){ 2728 var View = wp.media.View, 2729 $ = jQuery, 2730 Attachment; 2803 this.modal.content( this ); 2804 } 2731 2805 2732 /** 2733 * wp.media.view.Attachment 2734 * 2735 * @memberOf wp.media.view 2736 * 2737 * @class 2738 * @augments wp.media.View 2739 * @augments wp.Backbone.View 2740 * @augments Backbone.View 2741 */ 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' ) 2754 }; 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 }, 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 } 2767 2811 2768 buttons: {}, 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 } 2769 2823 2770 initialize: function() { 2771 var selection = this.options.selection, 2772 options = _.defaults( this.options, { 2773 rerenderOnModelChange: true 2774 } ); 2824 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 2775 2825 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 ); 2826 // Bind default title creation. 2827 this.on( 'title:create:default', this.createTitle, this ); 2828 this.title.mode('default'); 2785 2829 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 } 2830 this.on( 'title:render', function( view ) { 2831 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 2832 }); 2795 2833 2796 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 2834 // Bind default menu. 2835 this.on( 'menu:create:default', this.createMenu, this ); 2797 2836 }, 2798 2837 /** 2799 * @returns {wp.media.view. Attachment} Returns itself to allow chaining2838 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2800 2839 */ 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 ); 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 ); 2809 2844 } 2810 2845 /** 2811 * call ' dispose' directly on the parent class2846 * call 'render' directly on the parent class 2812 2847 */ 2813 View.prototype.dispose.apply( this, arguments ); 2814 return this; 2848 return Frame.prototype.render.apply( this, arguments ); 2815 2849 }, 2816 2850 /** 2817 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2851 * @param {Object} title 2852 * @this wp.media.controller.Region 2818 2853 */ 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'); 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 }, 2839 2869 2840 if ( 'image' === options.type) {2841 options.size = this.imageSize();2842 }2870 toggleMenu: function() { 2871 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 2872 }, 2843 2873 2844 options.can = {}; 2845 if ( options.nonces ) { 2846 options.can.remove = !! options.nonces['delete']; 2847 options.can.save = !! options.nonces.update; 2848 } 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; 2849 2900 2850 if ( this.controller.state().get('allowLocalEdits')) {2851 options.allowLocalEdits = true;2901 if ( ! tabs || ! tabUrl ) { 2902 return; 2852 2903 } 2853 2904 2854 if ( options.uploading && ! options.percent ) { 2855 options.percent = 0; 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(); 2856 2909 } 2857 2910 2858 this.views.detach(); 2859 this.$el.html( this.template( options ) ); 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 ); 2860 2921 2861 this.$el.toggleClass( 'uploading', options.uploading ); 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 }, 2862 2928 2863 if ( options.uploading ) { 2864 this.$bar = this.$('.media-progress-bar div'); 2865 } else { 2866 delete this.$bar; 2867 } 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 }, 2868 2939 2869 // Check if the model is selected. 2870 this.updateSelect(); 2940 iframeContentCleanup: function() { 2941 this.$el.removeClass('hide-toolbar'); 2942 }, 2871 2943 2872 // Update the save status.2873 this.updateSave();2944 iframeMenu: function( view ) { 2945 var views = {}; 2874 2946 2875 this.views.render(); 2947 if ( ! view ) { 2948 return; 2949 } 2876 2950 2877 return this; 2878 }, 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 ); 2879 2957 2880 progress: function() { 2881 if ( this.$bar && this.$bar.length ) { 2882 this.$bar.width( this.model.get('percent') + '%' ); 2883 } 2958 view.set( views ); 2884 2959 }, 2885 2960 2886 /** 2887 * @param {Object} event 2888 */ 2889 toggleSelectionHandler: function( event ) { 2890 var method; 2961 hijackThickbox: function() { 2962 var frame = this; 2891 2963 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 ) { 2964 if ( ! window.tb_remove || this._tb_remove ) { 2894 2965 return; 2895 2966 } 2896 2967 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 } 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 }, 2902 2976 2903 // Catch enter and space events2904 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {2977 restoreThickbox: function() { 2978 if ( ! this._tb_remove ) { 2905 2979 return; 2906 2980 } 2907 2981 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 }); 2982 window.tb_remove = this._tb_remove; 2983 delete this._tb_remove; 2984 } 2985 }); 2932 2986 2933 this.controller.trigger( 'selection:toggle' ); 2934 }, 2987 // Map some of the modal's methods to the frame. 2988 _.each(['open','close','attach','detach','escape'], function( method ) { 2935 2989 /** 2936 * @param {Object} options 2990 * @function open 2991 * @memberOf wp.media.view.MediaFrame 2992 * @instance 2993 * 2994 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2937 2995 */ 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; 2996 /** 2997 * @function close 2998 * @memberOf wp.media.view.MediaFrame 2999 * @instance 3000 * 3001 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 3002 */ 3003 /** 3004 * @function attach 3005 * @memberOf wp.media.view.MediaFrame 3006 * @instance 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 ); 2947 3027 } 3028 return this; 3029 }; 3030 }); 2948 3031 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 ); 3032 module.exports = MediaFrame; 2962 3033 2963 if ( singleIndex < modelIndex ) {2964 models = collection.models.slice( singleIndex, modelIndex + 1 );2965 } else {2966 models = collection.models.slice( modelIndex, singleIndex + 1 );2967 }2968 3034 2969 selection.add( models ); 2970 selection.single( model ); 2971 return; 3035 /***/ }), 3036 /* 48 */ 3037 /***/ (function(module, exports) { 2972 3038 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 } 3039 var MediaFrame = wp.media.view.MediaFrame, 3040 l10n = wp.media.view.l10n, 3041 Select; 2984 3042 2985 // Fixes bug that loses focus when selecting a featured image 2986 if ( ! method ) { 2987 method = 'add'; 2988 } 3043 /** 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 3049 * 3050 * @class 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 3057 */ 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 ); 2989 3062 2990 if ( method !== 'add' ) { 2991 method = 'reset'; 2992 } 3063 _.defaults( this.options, { 3064 selection: [], 3065 library: {}, 3066 multiple: false, 3067 state: 'library' 3068 }); 2993 3069 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 } 3070 this.createSelection(); 3071 this.createStates(); 3072 this.bindHandlers(); 3006 3073 }, 3007 3074 3008 updateSelect: function() {3009 this[ this.selected() ? 'select' : 'deselect' ]();3010 },3011 3075 /** 3012 * @returns {unresolved|Boolean} 3076 * Attach a selection collection to the frame. 3077 * 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. 3081 * 3082 * @see media.model.Selection 3013 3083 */ 3014 selected: function() {3084 createSelection: function() { 3015 3085 var selection = this.options.selection; 3016 if ( selection ) { 3017 return !! selection.get( this.model.cid ); 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 }); 3018 3091 } 3092 3093 this._selection = { 3094 attachments: new wp.media.model.Attachments(), 3095 difference: [] 3096 }; 3019 3097 }, 3098 3020 3099 /** 3021 * @param {Backbone.Model} model 3022 * @param {Backbone.Collection} collection 3100 * Create the default states on the frame. 3023 3101 */ 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 } 3102 createStates: function() { 3103 var options = this.options; 3034 3104 3035 // Bail if the model is already selected. 3036 if ( this.$el.hasClass( 'selected' ) ) { 3105 if ( this.options.states ) { 3037 3106 return; 3038 3107 } 3039 3108 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 } 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 ]); 3046 3119 }, 3120 3047 3121 /** 3048 * @param {Backbone.Model} model 3049 * @param {Backbone.Collection} collection 3122 * Bind region mode event callbacks. 3123 * 3124 * @see media.controller.Region.render 3050 3125 */ 3051 deselect: function( model, collection ) { 3052 var selection = this.options.selection; 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 }, 3053 3133 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 another3056 // 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 3134 /** 3064 * @param {Backbone.Model} model 3065 * @param {Backbone.Collection} collection 3135 * Render callback for the router region in the `browse` mode. 3136 * 3137 * @param {wp.media.view.Router} routerView 3066 3138 */ 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 ); 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 }); 3077 3150 }, 3151 3078 3152 /** 3079 * @param {string} size 3080 * @returns {Object} 3153 * Render callback for the content region in the `browse` mode. 3154 * 3155 * @param {wp.media.controller.Region} contentRegion 3081 3156 */ 3082 imageSize: function( size) {3083 var s izes = this.model.get('sizes'), matched = false;3157 browseContent: function( contentRegion ) { 3158 var state = this.state(); 3084 3159 3085 size = size || 'medium';3160 this.$el.removeClass('hide-toolbar'); 3086 3161 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 } 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'), 3098 3174 3099 if ( matched ) { 3100 return _.clone( matched ); 3101 } 3102 } 3175 idealColumnWidth: state.get('idealColumnWidth'), 3176 suggestedWidth: state.get('suggestedWidth'), 3177 suggestedHeight: state.get('suggestedHeight'), 3103 3178 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') 3109 }; 3179 AttachmentView: state.get('AttachmentView') 3180 }); 3110 3181 }, 3182 3111 3183 /** 3112 * @param {Object} event3184 * Render callback for the content region in the `upload` mode. 3113 3185 */ 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 } 3186 uploadContent: function() { 3187 this.$el.removeClass( 'hide-toolbar' ); 3188 this.content.set( new wp.media.view.UploaderInline({ 3189 controller: this 3190 }) ); 3128 3191 }, 3129 3192 3130 3193 /** 3131 * Pass all the arguments to the model's save method.3194 * Toolbars 3132 3195 * 3133 * Records the aggregate status of all save requests and updates the 3134 * view's classes accordingly. 3196 * @param {Object} toolbar 3197 * @param {Object} [options={}] 3198 * @this wp.media.controller.Region 3135 3199 */ 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 } 3200 createSelectToolbar: function( toolbar, options ) { 3201 options = options || this.options.button || {}; 3202 options.controller = this; 3154 3203 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' }; 3204 toolbar.view = new wp.media.view.Toolbar.Select( options ); 3205 } 3206 }); 3168 3207 3169 if ( status && status !== save.status ) { 3170 this.$el.removeClass( 'save-' + save.status ); 3171 save.status = status; 3172 } 3208 module.exports = Select; 3173 3209 3174 this.$el.addClass( 'save-' + save.status );3175 return this;3176 },3177 3210 3178 updateAll: function() { 3179 var $settings = this.$('[data-setting]'), 3180 model = this.model, 3181 changed; 3211 /***/ }), 3212 /* 49 */ 3213 /***/ (function(module, exports) { 3182 3214 3183 changed = _.chain( $settings ).map( function( el ) { 3184 var $input = $('input, textarea, select, [value]', el ), 3185 setting, value; 3215 var Select = wp.media.view.MediaFrame.Select, 3216 Library = wp.media.controller.Library, 3217 l10n = wp.media.view.l10n, 3218 Post; 3186 3219 3187 if ( ! $input.length ) { 3188 return; 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' 3189 3246 } 3247 }; 3190 3248 3191 setting = $(el).data('setting'); 3192 value = $input.val(); 3249 _.defaults( this.options, { 3250 multiple: true, 3251 editing: false, 3252 state: 'insert', 3253 metadata: {} 3254 }); 3193 3255 3194 // Record the value if it changed. 3195 if ( model.get( setting ) !== value ) { 3196 return [ setting, value ]; 3197 } 3198 }).compact().object().value(); 3256 // Call 'initialize' directly on the parent class. 3257 Select.prototype.initialize.apply( this, arguments ); 3258 this.createIframeStates(); 3199 3259 3200 if ( ! _.isEmpty( changed ) ) {3201 model.save( changed );3202 }3203 3260 }, 3261 3204 3262 /** 3205 * @param {Object} event3263 * Create the default states. 3206 3264 */ 3207 removeFromLibrary: function( event ) { 3208 // Catch enter and space events 3209 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3210 return; 3211 } 3265 createStates: function() { 3266 var options = this.options; 3212 3267 3213 // Stop propagation so the model isn't selected. 3214 event.stopPropagation(); 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, 3215 3279 3216 this.collection.remove( this.model ); 3217 }, 3280 // If the user isn't allowed to edit fields, 3281 // can they still edit it locally? 3282 allowLocalEdits: true, 3218 3283 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 } 3239 } 3240 }); 3241 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 } 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 }), 3291 3290 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 } 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 3299 3300 return this.render(); 3301 }; 3302 }); 3300 library: wp.media.query( _.defaults({ 3301 type: 'image' 3302 }, options.library ) ) 3303 }), 3303 3304 3304 module.exports = Attachment; 3305 // Embed states. 3306 new wp.media.controller.Embed( { metadata: options.metadata } ), 3305 3307 3306 },{}],26:[function(require,module,exports){ 3307 var Attachment = wp.media.view.Attachment, 3308 l10n = wp.media.view.l10n, 3309 Details; 3308 new wp.media.controller.EditImage( { model: options.editImage } ), 3310 3309 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'), 3310 // Gallery states. 3311 new wp.media.controller.GalleryEdit({ 3312 library: options.selection, 3313 editing: options.editing, 3314 menu: 'gallery' 3315 }), 3326 3316 3327 attributes: function() { 3328 return { 3329 'tabIndex': 0, 3330 'data-id': this.model.get( 'id' ) 3331 }; 3332 }, 3317 new wp.media.controller.GalleryAdd(), 3333 3318 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 }, 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, 3345 3327 3346 initialize: function(){3347 this.options = _.defaults( this.options, {3348 rerenderOnModelChange: false3349 });3328 library: wp.media.query( _.defaults({ 3329 type: 'audio' 3330 }, options.library ) ) 3331 }), 3350 3332 3351 this.on( 'ready', this.initialFocus ); 3352 // Call 'initialize' directly on the parent class. 3353 Attachment.prototype.initialize.apply( this, arguments ); 3354 }, 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 }), 3355 3345 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(); 3346 new wp.media.controller.CollectionAdd({ 3347 type: 'audio', 3348 collectionType: 'playlist', 3349 title: l10n.addToPlaylistTitle 3350 }), 3373 3351 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(); 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, 3387 3360 3388 if ( wp.media.view.settings.mediaTrash && 3389 'edit-metadata' === this.controller.content.mode() ) { 3361 library: wp.media.query( _.defaults({ 3362 type: 'video' 3363 }, options.library ) ) 3364 }), 3390 3365 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(); 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 }), 3405 3377 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(); 3378 new wp.media.controller.CollectionAdd({ 3379 type: 'video', 3380 collectionType: 'playlist', 3381 title: l10n.addToVideoPlaylistTitle 3382 }) 3383 ]); 3418 3384 3419 editState.set( 'image', this.model ); 3420 this.controller.setState( 'edit-image' ); 3421 } else { 3422 this.$el.addClass('needs-refresh'); 3385 if ( wp.media.view.settings.post.featuredImageId ) { 3386 this.states.add( new wp.media.controller.FeaturedImage() ); 3423 3387 } 3424 3388 }, 3425 /**3426 * When reverse tabbing(shift+tab) out of the right details panel, deliver3427 * the focus to the item in the list that was being edited.3428 *3429 * @param {Object} event3430 */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 3389 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 }); 3390 bindHandlers: function() { 3391 var handlers, checkCounts; 3443 3392 3444 module.exports = Details;3393 Select.prototype.bindHandlers.apply( this, arguments ); 3445 3394 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 }); 3395 this.on( 'activate', this.activate, this ); 3463 3396 3464 module.exports = EditLibrary; 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 } ); 3465 3401 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 }); 3402 if ( typeof checkCounts !== 'undefined' ) { 3403 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 3404 } 3484 3405 3485 module.exports = EditSelection; 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 ); 3486 3415 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 }); 3416 handlers = { 3417 menu: { 3418 'default': 'mainMenu', 3419 'gallery': 'galleryMenu', 3420 'playlist': 'playlistMenu', 3421 'video-playlist': 'videoPlaylistMenu' 3422 }, 3504 3423 3505 module.exports = Library; 3424 content: { 3425 'embed': 'embedContent', 3426 'edit-image': 'editImageContent', 3427 'edit-selection': 'editSelectionContent' 3428 }, 3506 3429 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', 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 }; 3521 3443 3522 // On click, just select the model, instead of removing the model from3523 // the selection.3524 toggleSelection: function() {3525 this.options.selection.single( this.model);3526 }3527 }); 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 }, 3528 3450 3529 module.exports = Selection; 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 }, 3530 3459 3531 },{}],31:[function(require,module,exports){ 3532 var View = wp.media.View, 3533 $ = jQuery, 3534 Attachments; 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 }, 3535 3466 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', 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 }, 3549 3479 3550 attributes: { 3551 tabIndex: -1 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 } 3552 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; 3553 3495 3554 initialize: function() { 3555 this.el.id = _.uniqueId('__attachments-view-'); 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 } 3556 3506 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 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 }) 3564 3516 }); 3517 }, 3565 3518 3566 this._viewsByCid = {}; 3567 this.$window = $( window ); 3568 this.resizeEvent = 'resize.media-modal-columns'; 3519 playlistMenu: function( view ) { 3520 var lastState = this.lastState(), 3521 previous = lastState && lastState.id, 3522 frame = this; 3569 3523 3570 this.collection.on( 'add', function( attachment ) { 3571 this.views.add( this.createAttachmentView( attachment ), { 3572 at: this.collection.indexOf( attachment ) 3573 }); 3574 }, this ); 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 }, 3575 3542 3576 this.collection.on( 'remove', function( attachment ) { 3577 var view = this._viewsByCid[ attachment.cid ]; 3578 delete this._viewsByCid[ attachment.cid ]; 3543 videoPlaylistMenu: function( view ) { 3544 var lastState = this.lastState(), 3545 previous = lastState && lastState.id, 3546 frame = this; 3579 3547 3580 if ( view ) { 3581 view.remove(); 3582 } 3583 }, this ); 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 }, 3584 3566 3585 this.collection.on( 'reset', this.render, this ); 3567 // Content 3568 embedContent: function() { 3569 var view = new wp.media.view.Embed({ 3570 controller: this, 3571 model: this.state() 3572 }).render(); 3586 3573 3587 this. listenTo( this.controller, 'library:selection:add', this.attachmentFocus);3574 this.content.set( view ); 3588 3575 3589 // Throttle the scroll handler and bind this. 3590 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 3576 if ( ! wp.media.isTouchDevice ) { 3577 view.url.focus(); 3578 } 3579 }, 3591 3580 3592 this.options.scrollElement = this.options.scrollElement || this.el; 3593 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 3581 editSelectionContent: function() { 3582 var state = this.state(), 3583 selection = state.get('selection'), 3584 view; 3594 3585 3595 this.initSortable(); 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(); 3596 3598 3597 _.bindAll( this, 'setColumns' ); 3599 view.toolbar.set( 'backToLibrary', { 3600 text: l10n.returnToLibrary, 3601 priority: -100, 3598 3602 3599 if ( this.options.resize ) { 3600 this.on( 'ready', this.bindEvents ); 3601 this.controller.on( 'open', this.setColumns ); 3603 click: function() { 3604 this.controller.content.mode('browse'); 3605 } 3606 }); 3602 3607 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 // Browse our library of attachments. 3609 this.content.set( view ); 3608 3610 3609 bindEvents: function() {3610 this. $window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ));3611 // Trigger the controller to set focus 3612 this.trigger( 'edit:selection', this ); 3611 3613 }, 3612 3614 3613 attachmentFocus: function() {3614 this.$( 'li:first' ).focus();3615 },3615 editImageContent: function() { 3616 var image = this.state().get('image'), 3617 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 3616 3618 3617 restoreFocus: function() { 3618 this.$( 'li.selected:first' ).focus(); 3619 }, 3619 this.content.set( view ); 3620 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 ); 3621 // after creating the wrapper view, load the actual editor via an ajax call 3622 view.loadEditor(); 3626 3623 3627 if ( index === -1 ) { 3628 return; 3629 } 3624 }, 3630 3625 3631 // Left arrow 3632 if ( 37 === event.keyCode ) { 3633 if ( 0 === index ) { 3634 return; 3635 } 3636 attachments.eq( index - 1 ).focus(); 3637 } 3626 // Toolbars 3638 3627 3639 // Up arrow 3640 if ( 38 === event.keyCode ) { 3641 if ( 1 === row ) { 3642 return; 3643 } 3644 attachments.eq( index - perRow ).focus(); 3645 } 3628 /** 3629 * @param {wp.Backbone.View} view 3630 */ 3631 selectionStatusToolbar: function( view ) { 3632 var editable = this.state().get('editable'); 3646 3633 3647 // Right arrow 3648 if ( 39 === event.keyCode ) { 3649 if ( attachments.length === index ) { 3650 return; 3651 } 3652 attachments.eq( index + 1 ).focus(); 3653 } 3634 view.set( 'selection', new wp.media.view.Selection({ 3635 controller: this, 3636 collection: this.state().get('selection'), 3637 priority: -40, 3654 3638 3655 // Down arrow3656 if ( 40 === event.keyCode ) {3657 if ( Math.ceil( attachments.length / perRow ) === row) {3658 return;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'); 3659 3643 } 3660 attachments.eq( index + perRow ).focus(); 3661 } 3644 }).render() ); 3662 3645 }, 3663 3646 3664 dispose: function() {3665 this.collection.props.off( null, null, this );3666 if ( this.options.resize ) {3667 this.$window.off( this.resizeEvent );3668 }3647 /** 3648 * @param {wp.Backbone.View} view 3649 */ 3650 mainInsertToolbar: function( view ) { 3651 var controller = this; 3669 3652 3670 /** 3671 * call 'dispose' directly on the parent class 3672 */ 3673 View.prototype.dispose.apply( this, arguments ); 3674 }, 3653 this.selectionStatusToolbar( view ); 3675 3654 3676 setColumns: function() { 3677 var prev = this.columns, 3678 width = this.$el.width(); 3655 view.set( 'insert', { 3656 style: 'primary', 3657 priority: 80, 3658 text: l10n.insertIntoPost, 3659 requires: { selection: true }, 3679 3660 3680 if ( width ) { 3681 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 3661 /** 3662 * @callback 3663 * @fires wp.media.controller.State#insert 3664 */ 3665 click: function() { 3666 var state = controller.state(), 3667 selection = state.get('selection'); 3682 3668 3683 if ( ! prev || prev !== this.columns ) {3684 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns);3669 controller.close(); 3670 state.trigger( 'insert', selection ).reset(); 3685 3671 } 3686 } 3672 }); 3687 3673 }, 3688 3674 3689 initSortable: function() { 3690 var collection = this.collection; 3675 /** 3676 * @param {wp.Backbone.View} view 3677 */ 3678 mainGalleryToolbar: function( view ) { 3679 var controller = this; 3691 3680 3692 if ( ! this.options.sortable || ! $.fn.sortable ) { 3693 return; 3694 } 3681 this.selectionStatusToolbar( view ); 3695 3682 3696 this.$el.sortable( _.extend({ 3697 // If the `collection` has a `comparator`, disable sorting. 3698 disabled: !! collection.comparator, 3683 view.set( 'gallery', { 3684 style: 'primary', 3685 text: l10n.createNewGallery, 3686 priority: 60, 3687 requires: { selection: true }, 3699 3688 3700 // Change the position of the attachment as soon as the 3701 // mouse pointer overlaps a thumbnail. 3702 tolerance: 'pointer', 3689 click: function() { 3690 var selection = controller.state().get('selection'), 3691 edit = controller.state('gallery-edit'), 3692 models = selection.where({ type: 'image' }); 3703 3693 3704 // Record the initial `index` of the dragged model.3705 start: function( event, ui ) {3706 ui.item.data('sortableIndexStart', ui.item.index());3707 },3694 edit.set( 'library', new wp.media.model.Selection( models, { 3695 props: selection.props.toJSON(), 3696 multiple: true 3697 }) ); 3708 3698 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; 3699 this.controller.setState('gallery-edit'); 3714 3700 3715 // Temporarily disable the comparator to prevent `add` 3716 // from re-sorting. 3717 delete collection.comparator; 3701 // Keep focus inside media modal 3702 // after jumping to gallery view 3703 this.controller.modal.focusManager.focus(); 3704 } 3705 }); 3706 }, 3718 3707 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 }); 3708 mainPlaylistToolbar: function( view ) { 3709 var controller = this; 3727 3710 3728 // Restore the comparator. 3729 collection.comparator = comparator; 3711 this.selectionStatusToolbar( view ); 3730 3712 3731 // Fire the `reset` event to ensure other collections sync. 3732 collection.trigger( 'reset', collection ); 3713 view.set( 'playlist', { 3714 style: 'primary', 3715 text: l10n.createNewPlaylist, 3716 priority: 100, 3717 requires: { selection: true }, 3733 3718 3734 // If the collection is sorted by menu order, 3735 // update the menu order. 3736 collection.saveMenuOrder(); 3737 } 3738 }, this.options.sortable ) ); 3719 click: function() { 3720 var selection = controller.state().get('selection'), 3721 edit = controller.state('playlist-edit'), 3722 models = selection.where({ type: 'audio' }); 3739 3723 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 ); 3724 edit.set( 'library', new wp.media.model.Selection( models, { 3725 props: selection.props.toJSON(), 3726 multiple: true 3727 }) ); 3745 3728 3746 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 3747 this.refreshSortable(); 3748 }, 3729 this.controller.setState('playlist-edit'); 3749 3730 3750 refreshSortable: function() { 3751 if ( ! this.options.sortable || ! $.fn.sortable ) { 3752 return; 3753 } 3731 // Keep focus inside media modal 3732 // after jumping to playlist view 3733 this.controller.modal.focusManager.focus(); 3734 } 3735 }); 3736 }, 3754 3737 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; 3738 mainVideoPlaylistToolbar: function( view ) { 3739 var controller = this; 3759 3740 3760 this.$el.sortable( 'option', 'disabled', ! enabled ); 3761 }, 3741 this.selectionStatusToolbar( view ); 3762 3742 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 }); 3743 view.set( 'video-playlist', { 3744 style: 'primary', 3745 text: l10n.createNewVideoPlaylist, 3746 priority: 100, 3747 requires: { selection: true }, 3774 3748 3775 return this._viewsByCid[ attachment.cid ] = view; 3776 }, 3749 click: function() { 3750 var selection = controller.state().get('selection'), 3751 edit = controller.state('video-playlist-edit'), 3752 models = selection.where({ type: 'video' }); 3777 3753 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 ) ); 3754 edit.set( 'library', new wp.media.model.Selection( models, { 3755 props: selection.props.toJSON(), 3756 multiple: true 3757 }) ); 3783 3758 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 }, 3759 this.controller.setState('video-playlist-edit'); 3790 3760 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(); 3761 // Keep focus inside media modal 3762 // after jumping to video playlist view 3763 this.controller.modal.focusManager.focus(); 3764 } 3765 }); 3795 3766 }, 3796 3767 3797 scroll: function() { 3798 var view = this, 3799 el = this.options.scrollElement, 3800 scrollTop = el.scrollTop, 3801 toolbar; 3768 featuredImageToolbar: function( toolbar ) { 3769 this.createSelectToolbar( toolbar, { 3770 text: l10n.setFeaturedImage, 3771 state: this.options.state 3772 }); 3773 }, 3802 3774 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 } 3775 mainEmbedToolbar: function( toolbar ) { 3776 toolbar.view = new wp.media.view.Toolbar.Embed({ 3777 controller: this 3778 }); 3779 }, 3809 3780 3810 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 3811 return; 3812 } 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 }, 3813 3791 3814 toolbar = this.views.parent.toolbar; 3792 /** 3793 * @fires wp.media.controller.State#update 3794 */ 3795 click: function() { 3796 var controller = this.controller, 3797 state = controller.state(); 3815 3798 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 } 3799 controller.close(); 3800 state.trigger( 'update', state.get('library') ); 3820 3801 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 }); 3802 // Restore and reset the default state. 3803 controller.setState( controller.options.state ); 3804 controller.reset(); 3805 } 3806 } 3807 } 3808 }) ); 3809 }, 3829 3810 3830 module.exports = Attachments; 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 }, 3831 3820 3832 },{}],32:[function(require,module,exports){ 3833 var View = wp.media.View, 3834 mediaTrash = wp.media.view.settings.mediaTrash, 3835 l10n = wp.media.view.l10n, 3836 $ = jQuery, 3837 AttachmentsBrowser; 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'); 3838 3828 3839 /** 3840 * wp.media.view.AttachmentsBrowser 3841 * 3842 * @memberOf wp.media.view 3843 * 3844 * @class 3845 * @augments wp.media.View 3846 * @augments wp.Backbone.View 3847 * @augments Backbone.View 3848 * 3849 * @param {object} [options] The options hash passed to the view. 3850 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar. 3851 * Accepts 'uploaded' and 'all'. 3852 * @param {boolean} [options.search=true] Whether to show the search interface in the 3853 * browser's toolbar. 3854 * @param {boolean} [options.date=true] Whether to show the date filter in the 3855 * browser's toolbar. 3856 * @param {boolean} [options.display=false] Whether to show the attachments display settings 3857 * view in the sidebar. 3858 * @param {boolean|string} [options.sidebar=true] Whether to create a sidebar for the browser. 3859 * Accepts true, false, and 'errors'. 3860 */ 3861 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{ 3862 tagName: 'div', 3863 className: 'attachments-browser', 3829 edit.get('library').add( state.get('selection').models ); 3830 state.trigger('reset'); 3831 controller.setState('gallery-edit'); 3832 } 3833 } 3834 } 3835 }) ); 3836 }, 3864 3837 3865 initialize: function() { 3866 _.defaults( this.options, { 3867 filters: false, 3868 search: true, 3869 date: true, 3870 display: false, 3871 sidebar: true, 3872 AttachmentView: wp.media.view.Attachment.Library 3873 }); 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 }, 3874 3848 3875 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this ); 3876 this.controller.on( 'edit:selection', this.editSelection ); 3849 /** 3850 * @fires wp.media.controller.State#update 3851 */ 3852 click: function() { 3853 var controller = this.controller, 3854 state = controller.state(); 3877 3855 3878 // In the Media Library, the sidebar is used to display errors before the attachments grid. 3879 if ( this.options.sidebar && 'errors' === this.options.sidebar ) { 3880 this.createSidebar(); 3881 } 3856 controller.close(); 3857 state.trigger( 'update', state.get('library') ); 3882 3858 3883 /* 3884 * For accessibility reasons, place the Inline Uploader before other sections. 3885 * This way, in the Media Library, it's right after the Add New button, see ticket #37188. 3886 */ 3887 this.createUploader(); 3859 // Restore and reset the default state. 3860 controller.setState( controller.options.state ); 3861 controller.reset(); 3862 } 3863 } 3864 } 3865 }) ); 3866 }, 3888 3867 3889 /* 3890 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library 3891 * and also for other things, for example the "Drag and drop to reorder" and 3892 * "Suggested dimensions" info in the media modal. 3893 */ 3894 this.createToolbar(); 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 }, 3895 3877 3896 // Create the list of attachments. 3897 this.createAttachments(); 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'); 3898 3885 3899 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. 3900 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { 3901 this.createSidebar(); 3902 } 3886 edit.get('library').add( state.get('selection').models ); 3887 state.trigger('reset'); 3888 controller.setState('playlist-edit'); 3889 } 3890 } 3891 } 3892 }) ); 3893 }, 3903 3894 3904 this.updateContent(); 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 3905 3906 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 3907 this.$el.addClass( 'hide-sidebar' ); 3906 click: function() { 3907 var controller = this.controller, 3908 state = controller.state(), 3909 library = state.get('library'); 3908 3910 3909 if ( 'errors' === this.options.sidebar ) { 3910 this.$el.addClass( 'sidebar-for-errors' ); 3911 } 3912 } 3911 library.type = 'video'; 3913 3912 3914 this.collection.on( 'add remove reset', this.updateContent, this);3915 },3913 controller.close(); 3914 state.trigger( 'update', library ); 3916 3915 3917 editSelection: function( modal ) { 3918 modal.$( '.media-button-backToLibrary' ).focus(); 3916 // Restore and reset the default state. 3917 controller.setState( controller.options.state ); 3918 controller.reset(); 3919 } 3920 } 3921 } 3922 }) ); 3919 3923 }, 3920 3924 3921 /** 3922 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining 3923 */ 3924 dispose: function() { 3925 this.options.selection.off( null, null, this ); 3926 View.prototype.dispose.apply( this, arguments ); 3927 return this; 3928 }, 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 }, 3929 3934 3930 createToolbar: function() { 3931 var LibraryViewSwitcher, Filters, toolbarOptions; 3935 click: function() { 3936 var controller = this.controller, 3937 state = controller.state(), 3938 edit = controller.state('video-playlist-edit'); 3932 3939 3933 toolbarOptions = { 3934 controller: this.controller 3935 }; 3940 edit.get('library').add( state.get('selection').models ); 3941 state.trigger('reset'); 3942 controller.setState('video-playlist-edit'); 3943 } 3944 } 3945 } 3946 }) ); 3947 } 3948 }); 3936 3949 3937 if ( this.controller.isModeActive( 'grid' ) ) { 3938 toolbarOptions.className = 'media-toolbar wp-filter'; 3939 } 3950 module.exports = Post; 3940 3951 3941 /**3942 * @member {wp.media.view.Toolbar}3943 */3944 this.toolbar = new wp.media.view.Toolbar( toolbarOptions );3945 3952 3946 this.views.add( this.toolbar ); 3953 /***/ }), 3954 /* 50 */ 3955 /***/ (function(module, exports) { 3947 3956 3948 this.toolbar.set( 'spinner', new wp.media.view.Spinner({ 3949 priority: -603950 }) );3957 var Select = wp.media.view.MediaFrame.Select, 3958 l10n = wp.media.view.l10n, 3959 ImageDetails; 3951 3960 3952 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) { 3953 // "Filters" will return a <select>, need to render 3954 // screen reader text before 3955 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ 3956 value: l10n.filterByType, 3957 attributes: { 3958 'for': 'media-attachment-filters' 3959 }, 3960 priority: -80 3961 }).render() ); 3961 /** 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 3977 */ 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 }, 3962 3989 3963 if ( 'uploaded' === this.options.filters ) { 3964 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({ 3965 controller: this.controller, 3966 model: this.collection.props, 3967 priority: -80 3968 }).render() ); 3969 } else { 3970 Filters = new wp.media.view.AttachmentFilters.All({ 3971 controller: this.controller, 3972 model: this.collection.props, 3973 priority: -80 3974 }); 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 }, 3975 3995 3976 this.toolbar.set( 'filters', Filters.render() ); 3977 } 3978 } 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 }, 3979 4005 3980 // Feels odd to bring the global media library switcher into the Attachment 3981 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 3982 // which the controller can tap into and add this view? 3983 if ( this.controller.isModeActive( 'grid' ) ) { 3984 LibraryViewSwitcher = View.extend({ 3985 className: 'view-switch media-grid-view-switch', 3986 template: wp.template( 'media-library-view-switcher') 3987 }); 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 }, 3988 4028 3989 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 3990 controller: this.controller, 3991 priority: -90 3992 }).render() ); 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 }, 3993 4036 3994 // DateFilter is a <select>, screen reader text needs to be rendered before 3995 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 3996 value: l10n.filterByDate, 3997 attributes: { 3998 'for': 'media-attachment-date-filters' 3999 }, 4000 priority: -75 4001 }).render() ); 4002 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 4003 controller: this.controller, 4004 model: this.collection.props, 4005 priority: -75 4006 }).render() ); 4037 editImageContent: function() { 4038 var state = this.state(), 4039 model = state.get('image'), 4040 view; 4007 4041 4008 // BulkSelection is a <div> with subviews, including screen reader text 4009 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 4010 text: l10n.bulkSelect, 4011 controller: this.controller, 4012 priority: -70 4013 }).render() ); 4042 if ( ! model ) { 4043 return; 4044 } 4014 4045 4015 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 4016 filters: Filters, 4017 style: 'primary', 4018 disabled: true, 4019 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected, 4020 controller: this.controller, 4021 priority: -60, 4022 click: function() { 4023 var changed = [], removed = [], 4024 selection = this.controller.state().get( 'selection' ), 4025 library = this.controller.state().get( 'library' ); 4046 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 4026 4047 4027 if ( ! selection.length ) { 4028 return; 4029 } 4048 this.content.set( view ); 4030 4049 4031 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 4032 return; 4033 } 4050 // after bringing in the frame, load the actual editor via an ajax call 4051 view.loadEditor(); 4034 4052 4035 if ( mediaTrash && 4036 'trash' !== selection.at( 0 ).get( 'status' ) && 4037 ! window.confirm( l10n.warnBulkTrash ) ) { 4053 }, 4038 4054 4039 return; 4040 } 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, 4041 4063 4042 selection.each( function( model ) { 4043 if ( ! model.get( 'nonces' )['delete'] ) { 4044 removed.push( model ); 4045 return; 4046 } 4064 click: function() { 4065 var controller = this.controller, 4066 state = controller.state(); 4047 4067 4048 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 4049 model.set( 'status', 'inherit' ); 4050 changed.push( model.save() ); 4051 removed.push( model ); 4052 } else if ( mediaTrash ) { 4053 model.set( 'status', 'trash' ); 4054 changed.push( model.save() ); 4055 removed.push( model ); 4056 } else { 4057 model.destroy({wait: true}); 4058 } 4059 } ); 4068 controller.close(); 4060 4069 4061 if ( changed.length ) { 4062 selection.remove( removed ); 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() ); 4063 4073 4064 $.when.apply( null, changed ).then( _.bind( function() { 4065 library._requery( true ); 4066 this.controller.trigger( 'selection:action:done' ); 4067 }, this ) ); 4068 } else { 4069 this.controller.trigger( 'selection:action:done' ); 4074 // Restore and reset the default state. 4075 controller.setState( controller.options.state ); 4076 controller.reset(); 4070 4077 } 4071 4078 } 4072 }).render() ); 4079 } 4080 }) ); 4081 }, 4073 4082 4074 if ( mediaTrash ) { 4075 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 4076 filters: Filters, 4077 style: 'primary', 4078 disabled: true, 4079 text: l10n.deleteSelected, 4080 controller: this.controller, 4081 priority: -55, 4082 click: function() { 4083 var removed = [], 4084 destroy = [], 4085 selection = this.controller.state().get( 'selection' ); 4083 renderReplaceImageToolbar: function() { 4084 var frame = this, 4085 lastState = frame.lastState(), 4086 previous = lastState && lastState.id; 4086 4087 4087 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 4088 return; 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(); 4089 4099 } 4100 } 4101 }, 4090 4102 4091 selection.each( function( model ){4092 if ( ! model.get( 'nonces' )['delete'] ) {4093 removed.push( model );4094 return;4095 }4103 replace: { 4104 style: 'primary', 4105 text: l10n.replace, 4106 priority: 80, 4107 requires: { selection: true }, 4096 4108 4097 destroy.push( model ); 4098 } ); 4109 click: function() { 4110 var controller = this.controller, 4111 state = controller.state(), 4112 selection = state.get( 'selection' ), 4113 attachment = selection.single(); 4099 4114 4100 if ( removed.length ) { 4101 selection.remove( removed ); 4102 } 4115 controller.close(); 4103 4116 4104 if ( destroy.length ) { 4105 $.when.apply( null, destroy.map( function (item) { 4106 return item.destroy(); 4107 } ) ).then( _.bind( function() { 4108 this.controller.trigger( 'selection:action:done' ); 4109 }, this ) ); 4110 } 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(); 4111 4126 } 4112 } ).render() );4127 } 4113 4128 } 4129 }) ); 4130 } 4114 4131 4115 } else if ( this.options.date ) { 4116 // DateFilter is a <select>, screen reader text needs to be rendered before 4117 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 4118 value: l10n.filterByDate, 4119 attributes: { 4120 'for': 'media-attachment-date-filters' 4121 }, 4122 priority: -75 4123 }).render() ); 4124 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 4125 controller: this.controller, 4126 model: this.collection.props, 4127 priority: -75 4128 }).render() ); 4129 } 4132 }); 4130 4133 4131 if ( this.options.search ) { 4132 // Search is an input, screen reader text needs to be rendered before 4133 this.toolbar.set( 'searchLabel', new wp.media.view.Label({ 4134 value: l10n.searchMediaLabel, 4135 attributes: { 4136 'for': 'media-search-input' 4137 }, 4138 priority: 60 4139 }).render() ); 4140 this.toolbar.set( 'search', new wp.media.view.Search({ 4141 controller: this.controller, 4142 model: this.collection.props, 4143 priority: 60 4144 }).render() ); 4145 } 4134 module.exports = ImageDetails; 4146 4135 4147 if ( this.options.dragInfo ) {4148 this.toolbar.set( 'dragInfo', new View({4149 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],4150 priority: -404151 }) );4152 }4153 4136 4154 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 4155 this.toolbar.set( 'suggestedDimensions', new View({ 4156 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0], 4157 priority: -40 4158 }) ); 4159 } 4160 }, 4137 /***/ }), 4138 /* 51 */ 4139 /***/ (function(module, exports) { 4161 4140 4162 updateContent: function() { 4163 var view = this, 4164 noItemsView; 4141 var $ = jQuery, 4142 Modal; 4165 4143 4166 if ( this.controller.isModeActive( 'grid' ) ) { 4167 noItemsView = view.attachmentsNoResults; 4168 } else { 4169 noItemsView = view.uploader; 4170 } 4144 /** 4145 * wp.media.view.Modal 4146 * 4147 * A modal view, which the media modal uses as its default container. 4148 * 4149 * @memberOf wp.media.view 4150 * 4151 * @class 4152 * @augments wp.media.View 4153 * @augments wp.Backbone.View 4154 * @augments Backbone.View 4155 */ 4156 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ 4157 tagName: 'div', 4158 template: wp.template('media-modal'), 4171 4159 4172 if ( ! this.collection.length ) { 4173 this.toolbar.get( 'spinner' ).show(); 4174 this.dfd = this.collection.more().done( function() { 4175 if ( ! view.collection.length ) { 4176 noItemsView.$el.removeClass( 'hidden' ); 4177 } else { 4178 noItemsView.$el.addClass( 'hidden' ); 4179 } 4180 view.toolbar.get( 'spinner' ).hide(); 4181 } ); 4182 } else { 4183 noItemsView.$el.addClass( 'hidden' ); 4184 view.toolbar.get( 'spinner' ).hide(); 4185 } 4160 attributes: { 4161 tabindex: 0 4186 4162 }, 4187 4163 4188 createUploader: function() { 4189 this.uploader = new wp.media.view.UploaderInline({ 4190 controller: this.controller, 4191 status: false, 4192 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 4193 canClose: this.controller.isModeActive( 'grid' ) 4164 events: { 4165 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 4166 'keydown': 'keydown' 4167 }, 4168 4169 clickedOpenerEl: null, 4170 4171 initialize: function() { 4172 _.defaults( this.options, { 4173 container: document.body, 4174 title: '', 4175 propagate: true, 4176 freeze: true 4194 4177 }); 4195 4178 4196 this.uploader.$el.addClass( 'hidden' ); 4197 this.views.add( this.uploader ); 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 }; 4198 4190 }, 4199 4191 4200 toggleUploader: function() { 4201 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 4202 this.uploader.show(); 4203 } else { 4204 this.uploader.hide(); 4192 /** 4193 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4194 */ 4195 attach: function() { 4196 if ( this.views.attached ) { 4197 return this; 4205 4198 } 4206 },4207 4199 4208 createAttachments: function() { 4209 this.attachments = new wp.media.view.Attachments({ 4210 controller: this.controller, 4211 collection: this.collection, 4212 selection: this.options.selection, 4213 model: this.model, 4214 sortable: this.options.sortable, 4215 scrollElement: this.options.scrollElement, 4216 idealColumnWidth: this.options.idealColumnWidth, 4200 if ( ! this.views.rendered ) { 4201 this.render(); 4202 } 4217 4203 4218 // The single `Attachment` view to be used in the `Attachments` view. 4219 AttachmentView: this.options.AttachmentView 4220 }); 4204 this.$el.appendTo( this.options.container ); 4221 4205 4222 // Add keydown listener to the instance of the Attachments view4223 this. controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) );4224 this. controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ));4206 // Manually mark the view as attached and trigger ready. 4207 this.views.attached = true; 4208 this.views.ready(); 4225 4209 4226 this.views.add( this.attachments ); 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; 4227 4233 4234 if ( $el.is(':visible') ) { 4235 return this; 4236 } 4228 4237 4229 if ( this.controller.isModeActive( 'grid' ) ) { 4230 this.attachmentsNoResults = new View({ 4231 controller: this.controller, 4232 tagName: 'p' 4233 }); 4238 this.clickedOpenerEl = document.activeElement; 4234 4239 4235 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 4236 this.attachmentsNoResults.$el.html( l10n.noMedia ); 4240 if ( ! this.views.attached ) { 4241 this.attach(); 4242 } 4237 4243 4238 this.views.add( this.attachmentsNoResults ); 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 }; 4239 4249 } 4240 },4241 4250 4242 createSidebar: function() { 4243 var options = this.options, 4244 selection = options.selection, 4245 sidebar = this.sidebar = new wp.media.view.Sidebar({ 4246 controller: this.controller 4247 }); 4251 // Disable page scrolling. 4252 $( 'body' ).addClass( 'modal-open' ); 4248 4253 4249 this.views.add( sidebar);4254 $el.show(); 4250 4255 4251 if ( this.controller.uploader ) { 4252 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({ 4253 controller: this.controller, 4254 priority: 40 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 } 4256 4266 } 4257 4267 4258 selection.on( 'selection:single', this.createSingle, this ); 4259 selection.on( 'selection:unsingle', this.disposeSingle, this ); 4268 this.$el.focus(); 4260 4269 4261 if ( selection.single() ) { 4262 this.createSingle(); 4263 } 4270 return this.propagate('open'); 4264 4271 }, 4265 4272 4266 createSingle: function() { 4267 var sidebar = this.sidebar, 4268 single = this.options.selection.single(); 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; 4269 4279 4270 sidebar.set( 'details', new wp.media.view.Attachment.Details({ 4271 controller: this.controller, 4272 model: single, 4273 priority: 80 4274 }) ); 4280 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 4281 return this; 4282 } 4275 4283 4276 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({ 4277 controller: this.controller, 4278 model: single, 4279 priority: 120 4280 }) ); 4284 // Enable page scrolling. 4285 $( 'body' ).removeClass( 'modal-open' ); 4281 4286 4282 if ( this.options.display ) {4283 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({4284 controller: this.controller, 4285 model: this.model.display( single ),4286 attachment: single,4287 priority: 160,4288 userSettings: this.model.get('displayUserSettings')4289 }));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(); 4290 4295 } 4291 4296 4292 // Show the sidebar on mobile 4293 if ( this.model.id === 'insert' ) { 4294 sidebar.$el.addClass( 'visible' ); 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 ); 4295 4302 } 4296 },4297 4303 4298 disposeSingle: function() { 4299 var sidebar = this.sidebar; 4300 sidebar.unset('details'); 4301 sidebar.unset('compat'); 4302 sidebar.unset('display'); 4303 // Hide the sidebar on mobile 4304 sidebar.$el.removeClass( 'visible' ); 4305 } 4306 }); 4304 if ( options && options.escape ) { 4305 this.propagate('escape'); 4306 } 4307 4307 4308 module.exports = AttachmentsBrowser; 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 }, 4309 4323 4310 },{}],33:[function(require,module,exports){ 4311 var Attachments = wp.media.view.Attachments, 4312 Selection; 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 }, 4313 4332 4314 /** 4315 * wp.media.view.Attachments.Selection 4316 * 4317 * @memberOf wp.media.view.Attachments 4318 * 4319 * @class 4320 * @augments wp.media.view.Attachments 4321 * @augments wp.media.View 4322 * @augments wp.Backbone.View 4323 * @augments Backbone.View 4324 */ 4325 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{ 4326 events: {}, 4327 initialize: function() { 4328 _.defaults( this.options, { 4329 sortable: false, 4330 resize: false, 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 ); 4331 4342 4332 // The single `Attachment` view to be used in the `Attachments` view. 4333 AttachmentView: wp.media.view.Attachment.Selection 4334 }); 4335 // Call 'initialize' directly on the parent class. 4336 return Attachments.prototype.initialize.apply( this, arguments ); 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 } 4337 4358 } 4338 4359 }); 4339 4360 4340 module.exports = Selection;4361 module.exports = Modal; 4341 4362 4342 },{}],34:[function(require,module,exports){ 4343 var $ = Backbone.$, 4344 ButtonGroup; 4363 4364 /***/ }), 4365 /* 52 */ 4366 /***/ (function(module, exports) { 4345 4367 4346 4368 /** 4347 * wp.media.view. ButtonGroup4369 * wp.media.view.FocusManager 4348 4370 * 4349 4371 * @memberOf wp.media.view 4350 4372 * … … var $ = Backbone.$, 4353 4375 * @augments wp.Backbone.View 4354 4376 * @augments Backbone.View 4355 4377 */ 4356 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{ 4357 tagName: 'div', 4358 className: 'button-group button-large media-button-group', 4359 4360 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; 4367 } else { 4368 return new wp.media.view.Button( button ).render(); 4369 } 4370 }); 4371 4372 delete this.options.buttons; 4378 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 4373 4379 4374 if ( this.options.classes ) { 4375 this.$el.addClass( this.options.classes ); 4376 } 4380 events: { 4381 'keydown': 'constrainTabbing' 4377 4382 }, 4378 4383 4384 focus: function() { // Reset focus on first left menu item 4385 this.$('.media-menu-item').first().focus(); 4386 }, 4379 4387 /** 4380 * @ returns {wp.media.view.ButtonGroup}4388 * @param {Object} event 4381 4389 */ 4382 render: function() { 4383 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 4384 return this; 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 } 4385 4409 } 4410 4386 4411 }); 4387 4412 4388 module.exports = ButtonGroup; 4413 module.exports = FocusManager; 4414 4415 4416 /***/ }), 4417 /* 53 */ 4418 /***/ (function(module, exports) { 4419 4420 var $ = jQuery, 4421 UploaderWindow; 4389 4422 4390 },{}],35:[function(require,module,exports){4391 4423 /** 4392 * wp.media.view.Button 4424 * wp.media.view.UploaderWindow 4425 * 4426 * An uploader window that allows for dragging and dropping media. 4393 4427 * 4394 4428 * @memberOf wp.media.view 4395 4429 * … … module.exports = ButtonGroup; 4397 4431 * @augments wp.media.View 4398 4432 * @augments wp.Backbone.View 4399 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] 4400 4440 */ 4401 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{4402 tagName: 'button',4403 className: 'media-button',4404 attributes: { type: 'button' },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'), 4405 4445 4406 events: { 4407 'click': 'click' 4408 }, 4446 initialize: function() { 4447 var uploader; 4409 4448 4410 defaults: { 4411 text: '', 4412 style: '', 4413 size: 'large', 4414 disabled: false 4415 }, 4449 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 4416 4450 4417 initialize: function() { 4418 /** 4419 * Create a model with the provided `defaults`. 4420 * 4421 * @member {Backbone.Model} 4422 */ 4423 this.model = new Backbone.Model( this.defaults ); 4451 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 4452 dropzone: this.$el, 4453 browser: this.$browser, 4454 params: {} 4455 }); 4424 4456 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 } 4457 // Ensure the dropzone is a jQuery collection. 4458 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 4459 uploader.dropzone = $( uploader.dropzone ); 4460 } 4432 4461 4433 this.model.set( key, value ); 4434 delete this.options[ key ]; 4462 this.controller.on( 'activate', this.refresh, this ); 4463 4464 this.controller.on( 'detach', function() { 4465 this.$browser.remove(); 4435 4466 }, this ); 4467 }, 4436 4468 4437 this.listenTo( this.model, 'change', this.render ); 4469 refresh: function() { 4470 if ( this.uploader ) { 4471 this.uploader.refresh(); 4472 } 4438 4473 }, 4439 /**4440 * @returns {wp.media.view.Button} Returns itself to allow chaining4441 */4442 render: function() {4443 var classes = [ 'button', this.className ],4444 model = this.model.toJSON();4445 4474 4446 if ( model.style ) { 4447 classes.push( 'button-' + model.style ); 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; 4448 4482 } 4449 4483 4450 if ( model.size) {4451 classes.push( 'button-' + model.size );4484 if ( postId ) { 4485 this.options.uploader.params.post_id = postId; 4452 4486 } 4487 this.uploader = new wp.Uploader( this.options.uploader ); 4453 4488 4454 classes = _.uniq( classes.concat( this.options.classes ) ); 4455 this.el.className = classes.join(' '); 4489 dropzone = this.uploader.dropzone; 4490 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 4491 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 4456 4492 4457 this.$el.attr( 'disabled', model.disabled);4458 this.$el.text( this.model.get('text') );4493 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 4494 }, 4459 4495 4460 return this; 4496 _ready: function() { 4497 this.controller.trigger( 'uploader:ready' ); 4461 4498 }, 4462 /**4463 * @param {Object} event4464 */4465 click: function( event ) {4466 if ( '#' === this.attributes.href ) {4467 event.preventDefault();4468 }4469 4499 4470 if ( this.options.click && ! this.model.get('disabled') ) { 4471 this.options.click.apply( this, arguments ); 4472 } 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 ); 4473 4527 } 4474 4528 }); 4475 4529 4476 module.exports = Button; 4530 module.exports = UploaderWindow; 4531 4532 4533 /***/ }), 4534 /* 54 */ 4535 /***/ (function(module, exports) { 4477 4536 4478 },{}],36:[function(require,module,exports){4479 4537 var View = wp.media.View, 4480 UploaderStatus = wp.media.view.UploaderStatus,4481 4538 l10n = wp.media.view.l10n, 4482 4539 $ = jQuery, 4483 Cropper;4540 EditorUploader; 4484 4541 4485 4542 /** 4486 * wp.media.view.Cropper 4487 * 4488 * Uses the imgAreaSelect plugin to allow a user to crop an image. 4543 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 4544 * and relays drag'n'dropped files to a media workflow. 4489 4545 * 4490 * Takes imgAreaSelect options from 4491 * wp.customize.HeaderControl.calculateImageSelectOptions via 4492 * wp.customize.HeaderControl.openMM. 4546 * wp.media.view.EditorUploader 4493 4547 * 4494 4548 * @memberOf wp.media.view 4495 4549 * … … var View = wp.media.View, 4498 4552 * @augments wp.Backbone.View 4499 4553 * @augments Backbone.View 4500 4554 */ 4501 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{ 4502 className: 'crop-content', 4503 template: wp.template('crop-content'), 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 */ 4504 4568 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; 4569 this.initialized = false; 4528 4570 4529 if (typeof imgOptions === 'function') { 4530 imgOptions = imgOptions(this.options.attachment, this.controller); 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; 4531 4574 } 4532 4575 4533 imgOptions = _.extend(imgOptions, { 4534 parent: this.$el, 4535 onInit: function() { 4536 this.parent.children().on( 'mousedown touchstart', function( e ){ 4576 this.$document = $(document); 4577 this.dropzones = []; 4578 this.files = []; 4537 4579 4538 if ( e.shiftKey ) { 4539 imgSelect.setOptions( { 4540 aspectRatio: '1:1' 4541 } ); 4542 } else { 4543 imgSelect.setOptions( { 4544 aspectRatio: false 4545 } ); 4546 } 4547 } ); 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(); 4548 4593 } 4549 } ); 4550 this.trigger('image-loaded'); 4551 imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 4594 }, this ) ); 4595 4596 this.initialized = true; 4597 return this; 4552 4598 }, 4553 onError: function() {4554 var filename = this.options.attachment.get('filename');4555 4599 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 }); 4560 } 4561 }); 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; 4562 4624 4563 module.exports = Cropper; 4625 return this.draggingFile; 4626 }, 4564 4627 4565 },{}],37:[function(require,module,exports){ 4566 var View = wp.media.View, 4567 EditImage; 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 } 4568 4634 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'), 4635 if ( ! _.isUndefined( e ) ) { 4636 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 4637 } 4582 4638 4583 initialize: function( options ) { 4584 this.editor = window.imageEdit; 4585 this.controller = options.controller; 4586 View.prototype.initialize.apply( this, arguments ); 4587 }, 4639 if ( ! this.overContainer && ! this.overDropzone ) { 4640 this.draggingFile = null; 4641 } 4588 4642 4589 prepare: function() { 4590 return this.model.toJSON(); 4643 return this; 4591 4644 }, 4592 4645 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 },4646 render: function() { 4647 if ( ! this.initialized ) { 4648 return this; 4649 } 4597 4650 4598 focus: function() { 4599 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 4651 View.prototype.render.apply( this, arguments ); 4652 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 4653 return this; 4600 4654 }, 4601 4655 4602 back: function() { 4603 var lastState = this.controller.lastState(); 4604 this.controller.setState( lastState ); 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; 4605 4662 }, 4606 4663 4607 refresh: function() { 4608 this.model.fetch(); 4609 }, 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; 4610 4672 4611 save: function() {4612 var lastState = this.controller.lastState();4673 this.containerDragleave( event ); 4674 this.dropzoneDragleave( event ); 4613 4675 4614 this. model.fetch().done( _.bind( function() {4615 this.controller.setState( lastState );4616 }, this ) );4617 }4676 this.files = event.originalEvent.dataTransfer.files; 4677 if ( this.files.length < 1 ) { 4678 return; 4679 } 4618 4680 4619 }); 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 } 4620 4686 4621 module.exports = EditImage; 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 }); 4622 4694 4623 },{}],38:[function(require,module,exports){ 4624 /** 4625 * wp.media.view.Embed 4626 * 4627 * @memberOf wp.media.view 4628 * 4629 * @class 4630 * @augments wp.media.View 4631 * @augments wp.Backbone.View 4632 * @augments Backbone.View 4633 */ 4634 var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{ 4635 className: 'media-embed', 4695 uploadView = this.workflow.uploader; 4636 4696 4637 initialize: function() { 4638 /** 4639 * @member {wp.media.view.EmbedUrl} 4640 */ 4641 this.url = new wp.media.view.EmbedUrl({ 4642 controller: this.controller, 4643 model: this.model.props 4644 }).render(); 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 } 4645 4707 4646 this.views.set([ this.url ]); 4647 this.refresh(); 4648 this.listenTo( this.model, 'change:type', this.refresh ); 4649 this.listenTo( this.model, 'change:loading', this.loading ); 4708 return false; 4650 4709 }, 4651 4710 4652 4711 /** 4653 * @param {Object} view4712 * Add the files to the uploader. 4654 4713 */ 4655 settings: function( view ) { 4656 if ( this._settings ) { 4657 this._settings.remove(); 4714 addFiles: function() { 4715 if ( this.files.length ) { 4716 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 4717 this.files = []; 4658 4718 } 4659 this._settings = view; 4660 this.views.add( view ); 4719 return this; 4661 4720 }, 4662 4721 4663 refresh: function() { 4664 var type = this.model.get('type'), 4665 constructor; 4666 4667 if ( 'image' === type ) { 4668 constructor = wp.media.view.EmbedImage; 4669 } else if ( 'link' === type ) { 4670 constructor = wp.media.view.EmbedLink; 4671 } else { 4722 containerDragover: function( event ) { 4723 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4672 4724 return; 4673 4725 } 4674 4726 4675 this.settings( new constructor({ 4676 controller: this.controller, 4677 model: this.model.props, 4678 priority: 40 4679 }) ); 4727 this.overContainer = true; 4728 this.refresh(); 4680 4729 }, 4681 4730 4682 loading: function() { 4683 this.$el.toggleClass( 'embed-loading', this.model.get('loading') ); 4684 } 4685 }); 4731 containerDragleave: function() { 4732 this.overContainer = false; 4686 4733 4687 module.exports = Embed; 4734 // Throttle dragleave because it's called when bouncing from some elements to others. 4735 _.delay( _.bind( this.refresh, this ), 50 ); 4736 }, 4688 4737 4689 },{}],39:[function(require,module,exports){ 4690 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 4691 EmbedImage; 4738 dropzoneDragover: function( event ) { 4739 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4740 return; 4741 } 4692 4742 4693 /** 4694 * wp.media.view.EmbedImage 4695 * 4696 * @memberOf wp.media.view 4697 * 4698 * @class 4699 * @augments wp.media.view.Settings.AttachmentDisplay 4700 * @augments wp.media.view.Settings 4701 * @augments wp.media.View 4702 * @augments wp.Backbone.View 4703 * @augments Backbone.View 4704 */ 4705 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{ 4706 className: 'embed-media-settings', 4707 template: wp.template('embed-image-settings'), 4743 this.overDropzone = true; 4744 this.refresh( event ); 4745 return false; 4746 }, 4708 4747 4709 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 ); 4748 dropzoneDragleave: function( e ) { 4749 this.overDropzone = false; 4750 _.delay( _.bind( this.refresh, this, e ), 50 ); 4715 4751 }, 4716 4752 4717 updateImage: function() { 4718 this.$('img').attr( 'src', this.model.get('url') ); 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; 4719 4758 } 4720 4759 }); 4721 4760 4722 module.exports = E mbedImage;4761 module.exports = EditorUploader; 4723 4762 4724 },{}],40:[function(require,module,exports){ 4725 var $ = jQuery, 4726 EmbedLink; 4763 4764 /***/ }), 4765 /* 55 */ 4766 /***/ (function(module, exports) { 4767 4768 var View = wp.media.View, 4769 UploaderInline; 4727 4770 4728 4771 /** 4729 * wp.media.view.EmbedLink 4772 * wp.media.view.UploaderInline 4773 * 4774 * The inline uploader that shows up in the 'Upload Files' tab. 4730 4775 * 4731 4776 * @memberOf wp.media.view 4732 4777 * 4733 4778 * @class 4734 * @augments wp.media.view.Settings4735 4779 * @augments wp.media.View 4736 4780 * @augments wp.Backbone.View 4737 4781 * @augments Backbone.View 4738 4782 */ 4739 EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{ 4740 className: 'embed-link-settings', 4741 template: wp.template('embed-link-settings'), 4783 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 4784 tagName: 'div', 4785 className: 'uploader-inline', 4786 template: wp.template('uploader-inline'), 4742 4787 4743 initialize: function(){4744 this.listenTo( this.model, 'change:url', this.updateoEmbed );4788 events: { 4789 'click .close': 'hide' 4745 4790 }, 4746 4791 4747 updateoEmbed: _.debounce( function() { 4748 var url = this.model.get( 'url' ); 4792 initialize: function() { 4793 _.defaults( this.options, { 4794 message: '', 4795 status: true, 4796 canClose: false 4797 }); 4749 4798 4750 // clear out previous results 4751 this.$('.embed-container').hide().find('.embed-preview').empty(); 4752 this.$( '.setting' ).hide(); 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 } 4753 4806 4754 // only proceed with embed if the field contains more than 11 characters4755 // Example: http://a.io is 11 chars4756 if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {4757 return;4807 if ( this.options.status ) { 4808 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 4809 controller: this.controller 4810 }) ); 4758 4811 } 4812 }, 4759 4813 4760 this.fetch(); 4761 }, wp.media.controller.Embed.sensitivity ), 4814 prepare: function() { 4815 var suggestedWidth = this.controller.state().get('suggestedWidth'), 4816 suggestedHeight = this.controller.state().get('suggestedHeight'), 4817 data = {}; 4762 4818 4763 fetch: function() {4764 var url = this.model.get( 'url' ), re, youTubeEmbedMatch;4819 data.message = this.options.message; 4820 data.canClose = this.options.canClose; 4765 4821 4766 // check if they haven't typed in 500 ms4767 if ( $('#embed-url-field').val() !== url ) {4768 return;4822 if ( suggestedWidth && suggestedHeight ) { 4823 data.suggestedWidth = suggestedWidth; 4824 data.suggestedHeight = suggestedHeight; 4769 4825 } 4770 4826 4771 if ( this.dfd && 'pending' === this.dfd.state() ) { 4772 this.dfd.abort(); 4827 return data; 4828 }, 4829 /** 4830 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 4831 */ 4832 dispose: function() { 4833 if ( this.disposing ) { 4834 /** 4835 * call 'dispose' directly on the parent class 4836 */ 4837 return View.prototype.dispose.apply( this, arguments ); 4773 4838 } 4774 4839 4775 // Support YouTube embed urls, since they work once in the editor. 4776 re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; 4777 youTubeEmbedMatch = re.exec( url ); 4778 if ( youTubeEmbedMatch ) { 4779 url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; 4780 } 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 ); 4781 4854 4782 this.dfd = wp.apiRequest({ 4783 url: wp.media.view.settings.oEmbedProxyUrl, 4784 data: { 4785 url: url, 4786 maxwidth: this.model.get( 'width' ), 4787 maxheight: this.model.get( 'height' ) 4788 }, 4789 type: 'GET', 4790 dataType: 'json', 4791 context: this 4792 }) 4793 .done( function( response ) { 4794 this.renderoEmbed( { 4795 data: { 4796 body: response.html || '' 4797 } 4798 } ); 4799 } ) 4800 .fail( this.renderFail ); 4855 _.defer( _.bind( this.refresh, this ) ); 4856 return result; 4801 4857 }, 4802 4858 4803 renderFail: function ( response, status ) { 4804 if ( 'abort' === status ) { 4805 return; 4859 refresh: function() { 4860 var uploader = this.controller.uploader; 4861 4862 if ( uploader ) { 4863 uploader.refresh(); 4806 4864 } 4807 this.$( '.link-text' ).show();4808 4865 }, 4866 /** 4867 * @returns {wp.media.view.UploaderInline} 4868 */ 4869 ready: function() { 4870 var $browser = this.options.$browser, 4871 $placeholder; 4809 4872 4810 renderoEmbed: function( response) {4811 var html = ( response && response.data && response.data.body ) || '';4873 if ( this.controller.uploader ) { 4874 $placeholder = this.$('.browser'); 4812 4875 4813 if ( html ) { 4814 this.$('.embed-container').show().find('.embed-preview').html( html ); 4815 } else { 4816 this.renderFail(); 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(); 4817 4902 } 4818 4903 } 4904 4819 4905 }); 4820 4906 4821 module.exports = EmbedLink; 4907 module.exports = UploaderInline; 4908 4909 4910 /***/ }), 4911 /* 56 */ 4912 /***/ (function(module, exports) { 4822 4913 4823 },{}],41:[function(require,module,exports){4824 4914 var View = wp.media.View, 4825 $ = jQuery, 4826 EmbedUrl; 4915 UploaderStatus; 4827 4916 4828 4917 /** 4829 * wp.media.view.EmbedUrl 4918 * wp.media.view.UploaderStatus 4919 * 4920 * An uploader status for on-going uploads. 4830 4921 * 4831 4922 * @memberOf wp.media.view 4832 4923 * … … var View = wp.media.View, 4835 4926 * @augments wp.Backbone.View 4836 4927 * @augments Backbone.View 4837 4928 */ 4838 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{4839 tagName: 'label',4840 className: 'embed-url',4929 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{ 4930 className: 'media-uploader-status', 4931 template: wp.template('uploader-status'), 4841 4932 4842 4933 events: { 4843 'input': 'url', 4844 'keyup': 'url', 4845 'change': 'url' 4934 'click .upload-dismiss-errors': 'dismiss' 4846 4935 }, 4847 4936 4848 4937 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 ); 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 ); 4856 4942 4857 if ( this.model.get( 'url' ) ) { 4858 _.delay( _.bind( function () { 4859 this.model.trigger( 'change:url' ); 4860 }, this ), 500 ); 4861 } 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 ); 4862 4947 }, 4863 4948 /** 4864 * @returns {wp.media.view. EmbedUrl} Returns itself to allow chaining4949 * @returns {wp.media.view.UploaderStatus} 4865 4950 */ 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://'; 4951 dispose: function() { 4952 wp.Uploader.queue.off( null, null, this ); 4874 4953 /** 4875 * Call `render` directly on parent class with passed arguments4954 * call 'dispose' directly on the parent class 4876 4955 */ 4877 View.prototype. render.apply( this, arguments );4956 View.prototype.dispose.apply( this, arguments ); 4878 4957 return this; 4879 4958 }, 4880 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 4881 4966 ready: function() { 4882 if ( ! wp.media.isTouchDevice ) { 4883 this.focus(); 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; 4884 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 ) + '%' ); 4885 4997 }, 4886 4998 4887 url: function( event ) { 4888 this.model.set( 'url', $.trim( event.target.value ) ); 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 }); 4889 5031 }, 4890 5032 4891 5033 /** 4892 * If the input is visible, focus and select its contents.5034 * @param {Object} event 4893 5035 */ 4894 focus: function() { 4895 var $input = this.$input; 4896 if ( $input.is(':visible') ) { 4897 $input.focus()[0].select(); 5036 dismiss: function( event ) { 5037 var errors = this.views.get('.upload-errors'); 5038 5039 event.preventDefault(); 5040 5041 if ( errors ) { 5042 _.invoke( errors, 'remove' ); 4898 5043 } 5044 wp.Uploader.errors.reset(); 4899 5045 } 4900 5046 }); 4901 5047 4902 module.exports = EmbedUrl; 5048 module.exports = UploaderStatus; 5049 5050 5051 /***/ }), 5052 /* 57 */ 5053 /***/ (function(module, exports) { 4903 5054 4904 },{}],42:[function(require,module,exports){4905 5055 /** 4906 * wp.media.view. FocusManager5056 * wp.media.view.UploaderStatusError 4907 5057 * 4908 5058 * @memberOf wp.media.view 4909 5059 * … … module.exports = EmbedUrl; 4912 5062 * @augments wp.Backbone.View 4913 5063 * @augments Backbone.View 4914 5064 */ 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 } 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 }); 4934 5069 4935 // Skip the file input added by Plupload. 4936 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 5070 module.exports = UploaderStatusError; 4937 5071 4938 // Keep tab focus within media modal while it's open4939 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 5072 4948 }); 5073 /***/ }), 5074 /* 58 */ 5075 /***/ (function(module, exports) { 4949 5076 4950 module.exports = FocusManager; 5077 var View = wp.media.View, 5078 Toolbar; 4951 5079 4952 },{}],43:[function(require,module,exports){4953 5080 /** 4954 * wp.media.view. Frame5081 * wp.media.view.Toolbar 4955 5082 * 4956 * A frame is a composite view consisting of one or more regions and one or more4957 * states.5083 * A toolbar which consists of a primary and a secondary section. Each sections 5084 * can be filled with views. 4958 5085 * 4959 5086 * @memberOf wp.media.view 4960 5087 * 4961 * @see wp.media.controller.State4962 * @see wp.media.controller.Region4963 *4964 5088 * @class 4965 5089 * @augments wp.media.View 4966 5090 * @augments wp.Backbone.View 4967 5091 * @augments Backbone.View 4968 * @mixes wp.media.controller.StateMachine4969 5092 */ 4970 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{ 5093 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ 5094 tagName: 'div', 5095 className: 'media-toolbar', 5096 4971 5097 initialize: function() { 4972 _.defaults( this.options, { 4973 mode: [ 'select' ] 4974 }); 4975 this._createRegions(); 4976 this._createStates(); 4977 this._createModes(); 4978 }, 5098 var state = this.controller.state(), 5099 selection = this.selection = state.get('selection'), 5100 library = this.library = state.get('library'); 4979 5101 4980 _createRegions: function() { 4981 // Clone the regions array. 4982 this.regions = this.regions ? this.regions.slice() : []; 5102 this._views = {}; 4983 5103 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 }); 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'); 5006 5109 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 ); 5110 this.views.set([ this.secondary, this.primary ]); 5012 5111 5013 if ( this.options. states ) {5014 this.s tates.add( this.options.states);5112 if ( this.options.items ) { 5113 this.set( this.options.items, { silent: true }); 5015 5114 } 5016 },5017 5115 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 ) ); 5116 if ( ! this.options.silent ) { 5117 this.render(); 5118 } 5027 5119 5028 _.each( this.options.mode, function( mode ) { 5029 this.activateMode( mode ); 5030 }, this ); 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 } 5031 5127 }, 5032 5128 /** 5033 * Reset all states on the frame to their defaults. 5034 * 5035 * @returns {wp.media.view.Frame} Returns itself to allow chaining 5129 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 5036 5130 */ 5037 reset: function() { 5038 this.states.invoke( 'trigger', 'reset' ); 5039 return this; 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 ); 5138 } 5139 /** 5140 * call 'dispose' directly on the parent class 5141 */ 5142 return View.prototype.dispose.apply( this, arguments ); 5143 }, 5144 5145 ready: function() { 5146 this.refresh(); 5040 5147 }, 5148 5041 5149 /** 5042 * Map activeMode collection events to the frame. 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 5043 5154 */ 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; 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(); 5055 5169 } 5056 } );5057 5170 5058 if ( ! _.has( modeEventMap, collectionEvent ) ) { 5059 return; 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 ); 5060 5177 } 5061 5178 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; 5179 if ( ! options.silent ) { 5180 this.refresh(); 5075 5181 } 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 5182 5080 5183 return this; 5081 5184 }, 5082 5185 /** 5083 * Deactivate a mode on the frame. 5084 * 5085 * @param string mode Mode ID. 5086 * @returns {this} Returns itself to allow chaining. 5186 * @param {string} id 5187 * @returns {wp.media.view.Button} 5087 5188 */ 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' ); 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 ); 5101 5201 5202 if ( ! options || ! options.silent ) { 5203 this.refresh(); 5204 } 5102 5205 return this; 5103 5206 }, 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 ); 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 }); 5112 5235 } 5113 5236 }); 5114 5237 5115 // Make the `Frame` a `StateMachine`. 5116 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 5238 module.exports = Toolbar; 5117 5239 5118 module.exports = Frame;5119 5240 5120 },{}],44:[function(require,module,exports){ 5121 var Select = wp.media.view.MediaFrame.Select, 5241 /***/ }), 5242 /* 59 */ 5243 /***/ (function(module, exports) { 5244 5245 var Toolbar = wp.media.view.Toolbar, 5122 5246 l10n = wp.media.view.l10n, 5123 ImageDetails;5247 Select; 5124 5248 5125 5249 /** 5126 * wp.media.view.MediaFrame.ImageDetails 5127 * 5128 * A media frame for manipulating an image that's already been inserted 5129 * into a post. 5250 * wp.media.view.Toolbar.Select 5130 5251 * 5131 * @memberOf wp.media.view. MediaFrame5252 * @memberOf wp.media.view.Toolbar 5132 5253 * 5133 5254 * @class 5134 * @augments wp.media.view.MediaFrame.Select 5135 * @augments wp.media.view.MediaFrame 5136 * @augments wp.media.view.Frame 5255 * @augments wp.media.view.Toolbar 5137 5256 * @augments wp.media.View 5138 5257 * @augments wp.Backbone.View 5139 5258 * @augments Backbone.View 5140 * @mixes wp.media.controller.StateMachine5141 5259 */ 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 }, 5260 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{ 5261 initialize: function() { 5262 var options = this.options; 5159 5263 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 }, 5264 _.bindAll( this, 'clickSelect' ); 5169 5265 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 }, 5266 _.defaults( options, { 5267 event: 'select', 5268 state: false, 5269 reset: true, 5270 close: true, 5271 text: l10n.select, 5192 5272 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 5273 // Does the button rely on the selection? 5274 requires: { 5275 selection: true 5276 } 5198 5277 }); 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 call5215 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 5278 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 } 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 5243 5286 } 5244 }) ); 5287 }); 5288 // Call 'initialize' directly on the parent class. 5289 Toolbar.prototype.initialize.apply( this, arguments ); 5245 5290 }, 5246 5291 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(); 5292 clickSelect: function() { 5293 var options = this.options, 5294 controller = this.controller; 5278 5295 5279 controller.close(); 5296 if ( options.close ) { 5297 controller.close(); 5298 } 5280 5299 5281 controller.image.changeAttachment( attachment, state.display( attachment ) ); 5300 if ( options.event ) { 5301 controller.state().trigger( options.event ); 5302 } 5282 5303 5283 // not sure if we want to use wp.media.string.image which will create a shortcode or5284 // perhaps wp.html.string to at least to build the <img />5285 state.trigger( 'replace', controller.image.toJSON() );5304 if ( options.state ) { 5305 controller.setState( options.state ); 5306 } 5286 5307 5287 // Restore and reset the default state. 5288 controller.setState( controller.options.state ); 5289 controller.reset(); 5290 } 5291 } 5292 } 5293 }) ); 5308 if ( options.reset ) { 5309 controller.reset(); 5310 } 5294 5311 } 5295 5296 5312 }); 5297 5313 5298 module.exports = ImageDetails;5314 module.exports = Select; 5299 5315 5300 },{}],45:[function(require,module,exports){ 5301 var Select = wp.media.view.MediaFrame.Select, 5302 Library = wp.media.controller.Library, 5316 5317 /***/ }), 5318 /* 60 */ 5319 /***/ (function(module, exports) { 5320 5321 var Select = wp.media.view.Toolbar.Select, 5303 5322 l10n = wp.media.view.l10n, 5304 Post;5323 Embed; 5305 5324 5306 5325 /** 5307 * wp.media.view.MediaFrame.Post 5308 * 5309 * The frame for manipulating media on the Edit Post page. 5326 * wp.media.view.Toolbar.Embed 5310 5327 * 5311 * @memberOf wp.media.view. MediaFrame5328 * @memberOf wp.media.view.Toolbar 5312 5329 * 5313 5330 * @class 5314 * @augments wp.media.view.MediaFrame.Select 5315 * @augments wp.media.view.MediaFrame 5316 * @augments wp.media.view.Frame 5331 * @augments wp.media.view.Toolbar.Select 5332 * @augments wp.media.view.Toolbar 5317 5333 * @augments wp.media.View 5318 5334 * @augments wp.Backbone.View 5319 5335 * @augments Backbone.View 5320 * @mixes wp.media.controller.StateMachine5321 5336 */ 5322 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{5337 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{ 5323 5338 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 5339 _.defaults( this.options, { 5336 multiple: true, 5337 editing: false, 5338 state: 'insert', 5339 metadata: {} 5340 text: l10n.insertIntoPost, 5341 requires: false 5340 5342 }); 5341 5342 5343 // Call 'initialize' directly on the parent class. 5343 5344 Select.prototype.initialize.apply( this, arguments ); 5344 this.createIframeStates(); 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) { 5345 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' 5346 5381 }, 5347 5382 5348 /** 5349 * Create the default states. 5350 */ 5351 createStates: function() { 5352 var options = this.options; 5383 defaults: { 5384 text: '', 5385 style: '', 5386 size: 'large', 5387 disabled: false 5388 }, 5353 5389 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, 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 ); 5365 5397 5366 // If the user isn't allowed to edit fields, 5367 // can they still edit it locally? 5368 allowLocalEdits: true, 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 } 5369 5405 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 }), 5406 this.model.set( key, value ); 5407 delete this.options[ key ]; 5408 }, this ); 5376 5409 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,5410 this.listenTo( this.model, 'change', this.render ); 5411 }, 5412 /** 5413 * @returns {wp.media.view.Button} Returns itself to allow chaining 5414 */ 5415 render: function() { 5416 var classes = [ 'button', this.className ], 5417 model = this.model.toJSON(); 5385 5418 5386 library: wp.media.query( _.defaults({ 5387 type: 'image' 5388 }, options.library ) ) 5389 }), 5419 if ( model.style ) { 5420 classes.push( 'button-' + model.style ); 5421 } 5390 5422 5391 // Embed states. 5392 new wp.media.controller.Embed( { metadata: options.metadata } ), 5423 if ( model.size ) { 5424 classes.push( 'button-' + model.size ); 5425 } 5393 5426 5394 new wp.media.controller.EditImage( { model: options.editImage } ), 5427 classes = _.uniq( classes.concat( this.options.classes ) ); 5428 this.el.className = classes.join(' '); 5395 5429 5396 // Gallery states. 5397 new wp.media.controller.GalleryEdit({ 5398 library: options.selection, 5399 editing: options.editing, 5400 menu: 'gallery' 5401 }), 5430 this.$el.attr( 'disabled', model.disabled ); 5431 this.$el.text( this.model.get('text') ); 5402 5432 5403 new wp.media.controller.GalleryAdd(), 5433 return this; 5434 }, 5435 /** 5436 * @param {Object} event 5437 */ 5438 click: function( event ) { 5439 if ( '#' === this.attributes.href ) { 5440 event.preventDefault(); 5441 } 5404 5442 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, 5443 if ( this.options.click && ! this.model.get('disabled') ) { 5444 this.options.click.apply( this, arguments ); 5445 } 5446 } 5447 }); 5413 5448 5414 library: wp.media.query( _.defaults({ 5415 type: 'audio' 5416 }, options.library ) ) 5417 }), 5449 module.exports = Button; 5418 5450 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: false5430 }),5431 5451 5432 new wp.media.controller.CollectionAdd({ 5433 type: 'audio', 5434 collectionType: 'playlist', 5435 title: l10n.addToPlaylistTitle 5436 }), 5452 /***/ }), 5453 /* 62 */ 5454 /***/ (function(module, exports) { 5437 5455 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, 5456 var $ = Backbone.$, 5457 ButtonGroup; 5446 5458 5447 library: wp.media.query( _.defaults({ 5448 type: 'video' 5449 }, options.library ) ) 5450 }), 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', 5451 5472 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: false5462 }),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 }); 5463 5484 5464 new wp.media.controller.CollectionAdd({ 5465 type: 'video', 5466 collectionType: 'playlist', 5467 title: l10n.addToVideoPlaylistTitle 5468 }) 5469 ]); 5485 delete this.options.buttons; 5470 5486 5471 if ( wp.media.view.settings.post.featuredImageId) {5472 this. states.add( new wp.media.controller.FeaturedImage());5487 if ( this.options.classes ) { 5488 this.$el.addClass( this.options.classes ); 5473 5489 } 5474 5490 }, 5475 5491 5476 bindHandlers: function() { 5477 var handlers, checkCounts; 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 }); 5478 5500 5479 Select.prototype.bindHandlers.apply( this, arguments );5501 module.exports = ButtonGroup; 5480 5502 5481 this.on( 'activate', this.activate, this );5482 5503 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 } ); 5504 /***/ }), 5505 /* 63 */ 5506 /***/ (function(module, exports) { 5487 5507 5488 if ( typeof checkCounts !== 'undefined' ) { 5489 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 5490 } 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', 5491 5520 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 ); 5521 initialize: function() { 5522 this._views = {}; 5501 5523 5502 handlers = { 5503 menu: { 5504 'default': 'mainMenu', 5505 'gallery': 'galleryMenu', 5506 'playlist': 'playlistMenu', 5507 'video-playlist': 'videoPlaylistMenu' 5508 }, 5524 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 5525 delete this.options.views; 5509 5526 5510 content: { 5511 'embed': 'embedContent', 5512 'edit-image': 'editImageContent', 5513 'edit-selection': 'editSelectionContent' 5514 }, 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; 5515 5539 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 }; 5540 options = options || {}; 5529 5541 5530 _.each( handlers, function( regionHandlers, region ) { 5531 _.each( regionHandlers, function( callback, handler ) { 5532 this.on( region + ':render:' + handler, this[ callback ], this ); 5542 // Accept an object with an `id` : `view` mapping. 5543 if ( _.isObject( id ) ) { 5544 _.each( id, function( view, id ) { 5545 this.set( id, view ); 5533 5546 }, this ); 5534 }, this );5535 },5547 return this; 5548 } 5536 5549 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' ); 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; 5542 5564 } 5543 }, this ); 5544 }, 5565 }); 5545 5566 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 }, 5567 this._views[ id ] = view; 5568 this.views.add( view, { 5569 at: _.isNumber( index ) ? index : views.length || 0 5570 }); 5552 5571 5553 // Menus 5572 return this; 5573 }, 5554 5574 /** 5555 * @param {wp.Backbone.View} view 5575 * @param {string} id 5576 * @returns {wp.media.View} 5556 5577 */ 5557 mainMenu: function( view ) { 5558 view.set({ 5559 'library-separator': new wp.media.View({ 5560 className: 'separator', 5561 priority: 100 5562 }) 5563 }); 5578 get: function( id ) { 5579 return this._views[ id ]; 5564 5580 }, 5581 /** 5582 * @param {string} id 5583 * @returns {wp.media.view.PriorityList} 5584 */ 5585 unset: function( id ) { 5586 var view = this.get( id ); 5565 5587 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 ); 5588 if ( view ) { 5589 view.remove(); 5572 5590 } 5591 5592 delete this._views[ id ]; 5593 return this; 5573 5594 }, 5574 5595 /** 5575 * @param {wp.Backbone.View} view 5596 * @param {Object} options 5597 * @returns {wp.media.View} 5576 5598 */ 5577 galleryMenu: function( view) {5578 var lastState = this.lastState(),5579 previous = lastState && lastState.id,5580 frame = this;5599 toView: function( options ) { 5600 return new wp.media.View( options ); 5601 } 5602 }); 5581 5603 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 } 5604 module.exports = PriorityList; 5592 5605 5593 // Keep focus inside media modal5594 // after canceling a gallery5595 this.controller.modal.focusManager.focus();5596 }5597 },5598 separateCancel: new wp.media.View({5599 className: 'separator',5600 priority: 405601 })5602 });5603 },5604 5606 5605 playlistMenu: function( view ) { 5606 var lastState = this.lastState(), 5607 previous = lastState && lastState.id, 5608 frame = this; 5607 /***/ }), 5608 /* 64 */ 5609 /***/ (function(module, exports) { 5609 5610 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 }, 5611 var $ = jQuery, 5612 MenuItem; 5628 5613 5629 videoPlaylistMenu: function( view ) { 5630 var lastState = this.lastState(), 5631 previous = lastState && lastState.id, 5632 frame = this; 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', 5633 5627 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 }); 5628 attributes: { 5629 href: '#' 5630 }, 5631 5632 events: { 5633 'click': '_click' 5651 5634 }, 5635 /** 5636 * @param {Object} event 5637 */ 5638 _click: function( event ) { 5639 var clickOverride = this.options.click; 5652 5640 5653 // Content 5654 embedContent: function() { 5655 var view = new wp.media.view.Embed({ 5656 controller: this, 5657 model: this.state() 5658 }).render(); 5641 if ( event ) { 5642 event.preventDefault(); 5643 } 5659 5644 5660 this.content.set( view ); 5645 if ( clickOverride ) { 5646 clickOverride.call( this ); 5647 } else { 5648 this.click(); 5649 } 5661 5650 5651 // When selecting a tab along the left side, 5652 // focus should be transferred into the main panel 5662 5653 if ( ! wp.media.isTouchDevice ) { 5663 view.url.focus();5654 $('.media-frame-content input').first().focus(); 5664 5655 } 5665 5656 }, 5666 5657 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, 5658 click: function() { 5659 var state = this.options.state; 5681 5660 5682 AttachmentView: wp.media.view.Attachments.EditSelection 5683 }).render(); 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; 5684 5671 5685 view.toolbar.set( 'backToLibrary', { 5686 text: l10n.returnToLibrary, 5687 priority: -100, 5672 if ( options.text ) { 5673 this.$el.text( options.text ); 5674 } else if ( options.html ) { 5675 this.$el.html( options.html ); 5676 } 5688 5677 5689 click: function() { 5690 this.controller.content.mode('browse'); 5691 } 5692 }); 5678 return this; 5679 } 5680 }); 5693 5681 5694 // Browse our library of attachments. 5695 this.content.set( view ); 5682 module.exports = MenuItem; 5696 5683 5697 // Trigger the controller to set focus5698 this.trigger( 'edit:selection', this );5699 },5700 5684 5701 editImageContent: function() { 5702 var image = this.state().get('image'), 5703 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 5685 /***/ }), 5686 /* 65 */ 5687 /***/ (function(module, exports) { 5704 5688 5705 this.content.set( view ); 5689 var MenuItem = wp.media.view.MenuItem, 5690 PriorityList = wp.media.view.PriorityList, 5691 Menu; 5706 5692 5707 // after creating the wrapper view, load the actual editor via an ajax call 5708 view.loadEditor(); 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', 5709 5710 5711 /* TODO: alternatively hide on any click anywhere 5712 events: { 5713 'click': 'click' 5710 5714 }, 5711 5715 5712 // Toolbars 5716 click: function() { 5717 this.$el.removeClass( 'visible' ); 5718 }, 5719 */ 5713 5720 5714 5721 /** 5715 * @param {wp.Backbone.View} view 5722 * @param {Object} options 5723 * @param {string} id 5724 * @returns {wp.media.View} 5716 5725 */ 5717 selectionStatusToolbar: function( view ) { 5718 var editable = this.state().get('editable'); 5726 toView: function( options, id ) { 5727 options = options || {}; 5728 options[ this.property ] = options[ this.property ] || id; 5729 return new this.ItemView( options ).render(); 5730 }, 5719 5731 5720 view.set( 'selection', new wp.media.view.Selection({ 5721 controller: this, 5722 collection: this.state().get('selection'), 5723 priority: -40, 5732 ready: function() { 5733 /** 5734 * call 'ready' directly on the parent class 5735 */ 5736 PriorityList.prototype.ready.apply( this, arguments ); 5737 this.visibility(); 5738 }, 5724 5739 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() ); 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(); 5731 5754 }, 5732 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 }, 5733 5766 /** 5734 * @param { wp.Backbone.View} view5767 * @param {string} id 5735 5768 */ 5736 mainInsertToolbar: function( view) {5737 var controller = this;5769 select: function( id ) { 5770 var view = this.get( id ); 5738 5771 5739 this.selectionStatusToolbar( view ); 5772 if ( ! view ) { 5773 return; 5774 } 5740 5775 5741 view.set( 'insert', { 5742 style: 'primary', 5743 priority: 80, 5744 text: l10n.insertIntoPost, 5745 requires: { selection: true }, 5776 this.deselect(); 5777 view.$el.addClass('active'); 5778 }, 5746 5779 5747 /** 5748 * @callback 5749 * @fires wp.media.controller.State#insert 5750 */ 5751 click: function() { 5752 var state = controller.state(), 5753 selection = state.get('selection'); 5780 deselect: function() { 5781 this.$el.children().removeClass('active'); 5782 }, 5754 5783 5755 controller.close(); 5756 state.trigger( 'insert', selection ).reset(); 5757 } 5758 }); 5784 hide: function( id ) { 5785 var view = this.get( id ); 5786 5787 if ( ! view ) { 5788 return; 5789 } 5790 5791 view.$el.addClass('hidden'); 5759 5792 }, 5760 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 */{ 5761 5824 /** 5762 * @param {wp.Backbone.View} view5825 * On click handler to activate the content region's corresponding mode. 5763 5826 */ 5764 mainGalleryToolbar: function( view ) { 5765 var controller = this; 5827 click: function() { 5828 var contentMode = this.options.contentMode; 5829 if ( contentMode ) { 5830 this.controller.content.mode( contentMode ); 5831 } 5832 } 5833 }); 5766 5834 5767 this.selectionStatusToolbar( view ); 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', 5768 5863 5769 view.set( 'gallery',{5770 style: 'primary',5771 text: l10n.createNewGallery,5772 priority: 60,5773 requires: { selection: true},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 }, 5774 5869 5775 click: function() { 5776 var selection = controller.state().get('selection'), 5777 edit = controller.state('gallery-edit'), 5778 models = selection.where({ type: 'image' }); 5870 update: function() { 5871 var mode = this.controller.content.mode(); 5872 if ( mode ) { 5873 this.select( mode ); 5874 } 5875 } 5876 }); 5779 5877 5780 edit.set( 'library', new wp.media.model.Selection( models, { 5781 props: selection.props.toJSON(), 5782 multiple: true 5783 }) ); 5878 module.exports = Router; 5784 5879 5785 this.controller.setState('gallery-edit');5786 5880 5787 // Keep focus inside media modal 5788 // after jumping to gallery view 5789 this.controller.modal.focusManager.focus(); 5790 } 5791 }); 5792 }, 5881 /***/ }), 5882 /* 68 */ 5883 /***/ (function(module, exports) { 5793 5884 5794 mainPlaylistToolbar: function( view ) { 5795 var controller = this; 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 }); 5796 5899 5797 this.selectionStatusToolbar( view );5900 module.exports = Sidebar; 5798 5901 5799 view.set( 'playlist', {5800 style: 'primary',5801 text: l10n.createNewPlaylist,5802 priority: 100,5803 requires: { selection: true },5804 5902 5805 click: function() { 5806 var selection = controller.state().get('selection'), 5807 edit = controller.state('playlist-edit'), 5808 models = selection.where({ type: 'audio' }); 5903 /***/ }), 5904 /* 69 */ 5905 /***/ (function(module, exports) { 5809 5906 5810 edit.set( 'library', new wp.media.model.Selection( models, { 5811 props: selection.props.toJSON(), 5812 multiple: true 5813 }) ); 5907 var View = wp.media.View, 5908 $ = jQuery, 5909 Attachment; 5814 5910 5815 this.controller.setState('playlist-edit'); 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'), 5816 5925 5817 // Keep focus inside media modal 5818 // after jumping to playlist view 5819 this.controller.modal.focusManager.focus(); 5820 } 5821 }); 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 }; 5822 5934 }, 5823 5935 5824 mainVideoPlaylistToolbar: function( view ) { 5825 var controller = this; 5826 5827 this.selectionStatusToolbar( view ); 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 }, 5828 5946 5829 view.set( 'video-playlist', { 5830 style: 'primary', 5831 text: l10n.createNewVideoPlaylist, 5832 priority: 100, 5833 requires: { selection: true }, 5947 buttons: {}, 5834 5948 5835 click: function() { 5836 var selection = controller.state().get('selection'), 5837 edit = controller.state('video-playlist-edit'), 5838 models = selection.where({ type: 'video' }); 5949 initialize: function() { 5950 var selection = this.options.selection, 5951 options = _.defaults( this.options, { 5952 rerenderOnModelChange: true 5953 } ); 5839 5954 5840 edit.set( 'library', new wp.media.model.Selection( models, { 5841 props: selection.props.toJSON(), 5842 multiple: true 5843 }) ); 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 ); 5844 5964 5845 this.controller.setState('video-playlist-edit'); 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 } 5846 5974 5847 // Keep focus inside media modal 5848 // after jumping to video playlist view 5849 this.controller.modal.focusManager.focus(); 5850 } 5851 }); 5975 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 5852 5976 }, 5977 /** 5978 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5979 */ 5980 dispose: function() { 5981 var selection = this.options.selection; 5853 5982 5854 featuredImageToolbar: function( toolbar ) { 5855 this.createSelectToolbar( toolbar, { 5856 text: l10n.setFeaturedImage, 5857 state: this.options.state 5858 }); 5859 }, 5983 // Make sure all settings are saved before removing the view. 5984 this.updateAll(); 5860 5985 5861 mainEmbedToolbar: function( toolbar ) { 5862 toolbar.view = new wp.media.view.Toolbar.Embed({ 5863 controller: this 5864 }); 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; 5865 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 ); 5866 6015 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 }, 6016 options.buttons = this.buttons; 6017 options.describe = this.controller.state().get('describe'); 5877 6018 5878 /** 5879 * @fires wp.media.controller.State#update 5880 */ 5881 click: function() { 5882 var controller = this.controller, 5883 state = controller.state(); 6019 if ( 'image' === options.type ) { 6020 options.size = this.imageSize(); 6021 } 5884 6022 5885 controller.close(); 5886 state.trigger( 'update', state.get('library') ); 6023 options.can = {}; 6024 if ( options.nonces ) { 6025 options.can.remove = !! options.nonces['delete']; 6026 options.can.save = !! options.nonces.update; 6027 } 5887 6028 5888 // Restore and reset the default state. 5889 controller.setState( controller.options.state ); 5890 controller.reset(); 5891 } 5892 } 5893 } 5894 }) ); 5895 }, 6029 if ( this.controller.state().get('allowLocalEdits') ) { 6030 options.allowLocalEdits = true; 6031 } 5896 6032 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 }, 6033 if ( options.uploading && ! options.percent ) { 6034 options.percent = 0; 6035 } 5906 6036 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'); 6037 this.views.detach(); 6038 this.$el.html( this.template( options ) ); 5914 6039 5915 edit.get('library').add( state.get('selection').models ); 5916 state.trigger('reset'); 5917 controller.setState('gallery-edit'); 5918 } 5919 } 5920 } 5921 }) ); 5922 }, 6040 this.$el.toggleClass( 'uploading', options.uploading ); 5923 6041 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 }, 6042 if ( options.uploading ) { 6043 this.$bar = this.$('.media-progress-bar div'); 6044 } else { 6045 delete this.$bar; 6046 } 5934 6047 5935 /** 5936 * @fires wp.media.controller.State#update 5937 */ 5938 click: function() { 5939 var controller = this.controller, 5940 state = controller.state(); 6048 // Check if the model is selected. 6049 this.updateSelect(); 5941 6050 5942 controller.close();5943 state.trigger( 'update', state.get('library'));6051 // Update the save status. 6052 this.updateSave(); 5944 6053 5945 // Restore and reset the default state. 5946 controller.setState( controller.options.state ); 5947 controller.reset(); 5948 } 5949 } 5950 } 5951 }) ); 6054 this.views.render(); 6055 6056 return this; 5952 6057 }, 5953 6058 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 }, 6059 progress: function() { 6060 if ( this.$bar && this.$bar.length ) { 6061 this.$bar.width( this.model.get('percent') + '%' ); 6062 } 6063 }, 5963 6064 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'); 6065 /** 6066 * @param {Object} event 6067 */ 6068 toggleSelectionHandler: function( event ) { 6069 var method; 5971 6070 5972 edit.get('library').add( state.get('selection').models ); 5973 state.trigger('reset'); 5974 controller.setState('playlist-edit'); 5975 } 5976 } 5977 } 5978 }) ); 5979 }, 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 } 5980 6075 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 }, 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 } 5991 6081 5992 click: function() {5993 var controller = this.controller,5994 state = controller.state(),5995 library = state.get('library');6082 // Catch enter and space events 6083 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6084 return; 6085 } 5996 6086 5997 library.type = 'video';6087 event.preventDefault(); 5998 6088 5999 controller.close(); 6000 state.trigger( 'update', library ); 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 } 6001 6096 6002 // Restore and reset the default state. 6003 controller.setState( controller.options.state ); 6004 controller.reset(); 6005 } 6006 } 6097 if ( this.controller.isModeActive( 'select' ) ) { 6098 method = 'toggle'; 6007 6099 } 6008 }) ); 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' ); 6009 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; 6010 6123 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 }, 6124 if ( ! selection ) { 6125 return; 6126 } 6020 6127 6021 click: function() { 6022 var controller = this.controller, 6023 state = controller.state(), 6024 edit = controller.state('video-playlist-edit'); 6128 single = selection.single(); 6129 method = _.isUndefined( method ) ? selection.multiple : method; 6025 6130 6026 edit.get('library').add( state.get('selection').models ); 6027 state.trigger('reset'); 6028 controller.setState('video-playlist-edit'); 6029 } 6030 } 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; 6031 6137 } 6032 }) );6033 }6034 });6035 6138 6036 module.exports = Post; 6139 singleIndex = collection.indexOf( single ); 6140 modelIndex = collection.indexOf( this.model ); 6037 6141 6038 },{}],46:[function(require,module,exports){ 6039 var MediaFrame = wp.media.view.MediaFrame, 6040 l10n = wp.media.view.l10n, 6041 Select; 6142 if ( singleIndex < modelIndex ) { 6143 models = collection.models.slice( singleIndex, modelIndex + 1 ); 6144 } else { 6145 models = collection.models.slice( modelIndex, singleIndex + 1 ); 6146 } 6042 6147 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 ); 6148 selection.add( models ); 6149 selection.single( model ); 6150 return; 6062 6151 6063 _.defaults( this.options, { 6064 selection: [], 6065 library: {}, 6066 multiple: false, 6067 state: 'library' 6068 }); 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 } 6069 6163 6070 this.createSelection(); 6071 this.createStates(); 6072 this.bindHandlers(); 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 } 6073 6185 }, 6074 6186 6187 updateSelect: function() { 6188 this[ this.selected() ? 'select' : 'deselect' ](); 6189 }, 6075 6190 /** 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 6191 * @returns {unresolved|Boolean} 6083 6192 */ 6084 createSelection: function() {6193 selected: function() { 6085 6194 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 }); 6195 if ( selection ) { 6196 return !! selection.get( this.model.cid ); 6091 6197 } 6092 6093 this._selection = {6094 attachments: new wp.media.model.Attachments(),6095 difference: []6096 };6097 6198 }, 6098 6099 6199 /** 6100 * Create the default states on the frame. 6200 * @param {Backbone.Model} model 6201 * @param {Backbone.Collection} collection 6101 6202 */ 6102 createStates: function() { 6103 var options = this.options; 6203 select: function( model, collection ) { 6204 var selection = this.options.selection, 6205 controller = this.controller; 6104 6206 6105 if ( this.options.states ) { 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 ) ) { 6106 6211 return; 6107 6212 } 6108 6213 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 }, 6214 // Bail if the model is already selected. 6215 if ( this.$el.hasClass( 'selected' ) ) { 6216 return; 6217 } 6120 6218 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 ); 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 } 6132 6225 }, 6133 6134 6226 /** 6135 * Render callback for the router region in the `browse` mode. 6136 * 6137 * @param {wp.media.view.Router} routerView 6227 * @param {Backbone.Model} model 6228 * @param {Backbone.Collection} collection 6138 6229 */ 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 }, 6230 deselect: function( model, collection ) { 6231 var selection = this.options.selection; 6151 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 }, 6152 6242 /** 6153 * Render callback for the content region in the `browse` mode. 6154 * 6155 * @param {wp.media.controller.Region} contentRegion 6243 * @param {Backbone.Model} model 6244 * @param {Backbone.Collection} collection 6156 6245 */ 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'), 6246 details: function( model, collection ) { 6247 var selection = this.options.selection, 6248 details; 6174 6249 6175 idealColumnWidth: state.get('idealColumnWidth'),6176 suggestedWidth: state.get('suggestedWidth'),6177 suggestedHeight: state.get('suggestedHeight'),6250 if ( selection !== collection ) { 6251 return; 6252 } 6178 6253 6179 AttachmentView: state.get('AttachmentView')6180 });6254 details = selection.single(); 6255 this.$el.toggleClass( 'details', details === this.model ); 6181 6256 }, 6182 6183 6257 /** 6184 * Render callback for the content region in the `upload` mode. 6258 * @param {string} size 6259 * @returns {Object} 6185 6260 */ 6186 uploadContent: function() { 6187 this.$el.removeClass( 'hide-toolbar' ); 6188 this.content.set( new wp.media.view.UploaderInline({ 6189 controller: this 6190 }) ); 6191 }, 6261 imageSize: function( size ) { 6262 var sizes = this.model.get('sizes'), matched = false; 6192 6263 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; 6264 size = size || 'medium'; 6203 6265 6204 toolbar.view = new wp.media.view.Toolbar.Select( options ); 6205 } 6206 }); 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 } 6207 6277 6208 module.exports = Select; 6278 if ( matched ) { 6279 return _.clone( matched ); 6280 } 6281 } 6209 6282 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', 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 }, 6223 6290 /** 6224 * @ returns {wp.media.view.Iframe} Returns itself to allow chaining6291 * @param {Object} event 6225 6292 */ 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){ 6237 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 6238 $ = jQuery, 6239 ImageDetails; 6240 6241 /** 6242 * wp.media.view.ImageDetails 6243 * 6244 * @memberOf wp.media.view 6245 * 6246 * @class 6247 * @augments wp.media.view.Settings.AttachmentDisplay 6248 * @augments wp.media.view.Settings 6249 * @augments wp.media.View 6250 * @augments wp.Backbone.View 6251 * @augments Backbone.View 6252 */ 6253 ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{ 6254 className: 'image-details', 6255 template: wp.template('image-details'), 6256 events: _.defaults( AttachmentDisplay.prototype.events, { 6257 'click .edit-attachment': 'editAttachment', 6258 'click .replace-attachment': 'replaceAttachment', 6259 'click .advanced-toggle': 'onToggleAdvanced', 6260 'change [data-setting="customWidth"]': 'onCustomSize', 6261 'change [data-setting="customHeight"]': 'onCustomSize', 6262 'keyup [data-setting="customWidth"]': 'onCustomSize', 6263 'keyup [data-setting="customHeight"]': 'onCustomSize' 6264 } ), 6265 initialize: function() { 6266 // used in AttachmentDisplay.prototype.updateLinkTo 6267 this.options.attachment = this.model.attachment; 6268 this.listenTo( this.model, 'change:url', this.updateUrl ); 6269 this.listenTo( this.model, 'change:link', this.toggleLinkSettings ); 6270 this.listenTo( this.model, 'change:size', this.toggleCustomSize ); 6293 updateSetting: function( event ) { 6294 var $setting = $( event.target ).closest('[data-setting]'), 6295 setting, value; 6271 6296 6272 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 6273 }, 6297 if ( ! $setting.length ) { 6298 return; 6299 } 6274 6300 6275 prepare: function() {6276 va r attachment = false;6301 setting = $setting.data('setting'); 6302 value = event.target.value; 6277 6303 6278 if ( this.model. attachment) {6279 attachment = this.model.attachment.toJSON();6304 if ( this.model.get( setting ) !== value ) { 6305 this.save( setting, value ); 6280 6306 } 6281 return _.defaults({6282 model: this.model.toJSON(),6283 attachment: attachment6284 }, this.options );6285 6307 }, 6286 6308 6287 render: function() { 6288 var args = arguments; 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; 6289 6320 6290 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) { 6291 this.model.dfd 6292 .done( _.bind( function() { 6293 AttachmentDisplay.prototype.render.apply( this, args ); 6294 this.postRender(); 6295 }, this ) ) 6296 .fail( _.bind( function() { 6297 this.model.attachment = false; 6298 AttachmentDisplay.prototype.render.apply( this, args ); 6299 this.postRender(); 6300 }, this ) ); 6301 } else { 6302 AttachmentDisplay.prototype.render.apply( this, arguments ); 6303 this.postRender(); 6321 // If we're waiting to remove 'Saved.', stop. 6322 if ( save.savedTimer ) { 6323 clearTimeout( save.savedTimer ); 6304 6324 } 6305 6325 6306 return this; 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 }); 6307 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' }; 6308 6347 6309 postRender: function() { 6310 setTimeout( _.bind( this.resetFocus, this ), 10 ); 6311 this.toggleLinkSettings(); 6312 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) { 6313 this.toggleAdvanced( true ); 6348 if ( status && status !== save.status ) { 6349 this.$el.removeClass( 'save-' + save.status ); 6350 save.status = status; 6314 6351 } 6315 this.trigger( 'post-render' );6316 },6317 6352 6318 resetFocus: function() { 6319 this.$( '.link-to-custom' ).blur(); 6320 this.$( '.embed-media-settings' ).scrollTop( 0 ); 6353 this.$el.addClass( 'save-' + save.status ); 6354 return this; 6321 6355 }, 6322 6356 6323 update Url: function() {6324 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );6325 this.$( '.url' ).val( this.model.get( 'url' ) );6326 },6357 updateAll: function() { 6358 var $settings = this.$('[data-setting]'), 6359 model = this.model, 6360 changed; 6327 6361 6328 toggleLinkSettings: function() { 6329 if ( this.model.get( 'link' ) === 'none' ) { 6330 this.$( '.link-settings' ).addClass('hidden'); 6331 } else { 6332 this.$( '.link-settings' ).removeClass('hidden'); 6333 } 6334 }, 6362 changed = _.chain( $settings ).map( function( el ) { 6363 var $input = $('input, textarea, select, [value]', el ), 6364 setting, value; 6335 6365 6336 toggleCustomSize: function() { 6337 if ( this.model.get( 'size' ) !== 'custom' ) { 6338 this.$( '.custom-size' ).addClass('hidden'); 6339 } else { 6340 this.$( '.custom-size' ).removeClass('hidden'); 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 ); 6341 6381 } 6342 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 } 6343 6391 6344 onCustomSize: function( event ) { 6345 var dimension = $( event.target ).data('setting'), 6346 num = $( event.target ).val(), 6347 value; 6392 // Stop propagation so the model isn't selected. 6393 event.stopPropagation(); 6348 6394 6349 // Ignore bogus input 6350 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) { 6351 event.preventDefault(); 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 ) { 6352 6408 return; 6353 6409 } 6354 6355 if ( dimension === 'customWidth') {6356 value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num);6357 this.model.set( 'customHeight', value, { silent: true } );6358 this.$ ( '[data-setting="customHeight"]' ).val( value);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(); 6359 6415 } else { 6360 value = Math.round( this.model.get( 'aspectRatio' ) * num ); 6361 this.model.set( 'customWidth', value, { silent: true } ); 6362 this.$( '[data-setting="customWidth"]' ).val( value ); 6416 selection.add( this.model ); 6363 6417 } 6364 }, 6418 } 6419 }); 6365 6420 6366 onToggleAdvanced: function( event ) { 6367 event.preventDefault(); 6368 this.toggleAdvanced(); 6369 }, 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 + '"]'); 6370 6466 6371 toggleAdvanced: function( show) {6372 var $advanced = this.$el.find( '.advanced-section' ),6373 mode;6467 if ( ! $setting.length ) { 6468 return this; 6469 } 6374 6470 6375 if ( $advanced.hasClass('advanced-visible') || show === false ) { 6376 $advanced.removeClass('advanced-visible'); 6377 $advanced.find('.advanced-settings').addClass('hidden'); 6378 mode = 'hide'; 6379 } else { 6380 $advanced.addClass('advanced-visible'); 6381 $advanced.find('.advanced-settings').removeClass('hidden'); 6382 mode = 'show'; 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; 6383 6477 } 6384 6478 6385 window.setUserSetting( 'advImgDetails', mode ); 6386 }, 6479 return this.render(); 6480 }; 6481 }); 6387 6482 6388 editAttachment: function( event ) { 6389 var editState = this.controller.states.get( 'edit-image' ); 6483 module.exports = Attachment; 6390 6484 6391 if ( window.imageEdit && editState ) {6392 event.preventDefault();6393 editState.set( 'image', this.model.attachment );6394 this.controller.setState( 'edit-image' );6395 }6396 },6397 6485 6398 replaceAttachment: function( event ) { 6399 event.preventDefault(); 6400 this.controller.setState( 'replace-image' ); 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 6401 6504 } 6402 6505 }); 6403 6506 6404 module.exports = ImageDetails; 6507 module.exports = Library; 6508 6509 6510 /***/ }), 6511 /* 71 */ 6512 /***/ (function(module, exports) { 6405 6513 6406 },{}],49:[function(require,module,exports){6407 6514 /** 6408 * wp.media.view. Label6515 * wp.media.view.Attachment.EditLibrary 6409 6516 * 6410 * @memberOf wp.media.view 6517 * @memberOf wp.media.view.Attachment 6411 6518 * 6412 6519 * @class 6520 * @augments wp.media.view.Attachment 6413 6521 * @augments wp.media.View 6414 6522 * @augments wp.Backbone.View 6415 6523 * @augments Backbone.View 6416 6524 */ 6417 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{ 6418 tagName: 'label', 6419 className: 'screen-reader-text', 6420 6421 initialize: function() { 6422 this.value = this.options.value; 6423 }, 6424 6425 render: function() { 6426 this.$el.html( this.value ); 6427 6428 return this; 6525 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{ 6526 buttons: { 6527 close: true 6429 6528 } 6430 6529 }); 6431 6530 6432 module.exports = Label;6531 module.exports = EditLibrary; 6433 6532 6434 },{}],50:[function(require,module,exports){ 6435 var Frame = wp.media.view.Frame, 6533 6534 /***/ }), 6535 /* 72 */ 6536 /***/ (function(module, exports) { 6537 6538 var View = wp.media.View, 6436 6539 $ = jQuery, 6437 MediaFrame;6540 Attachments; 6438 6541 6439 6542 /** 6440 * wp.media.view.MediaFrame 6441 * 6442 * The frame used to create the media modal. 6543 * wp.media.view.Attachments 6443 6544 * 6444 6545 * @memberOf wp.media.view 6445 6546 * 6446 6547 * @class 6447 * @augments wp.media.view.Frame6448 6548 * @augments wp.media.View 6449 6549 * @augments wp.Backbone.View 6450 6550 * @augments Backbone.View 6451 * @mixes wp.media.controller.StateMachine6452 6551 */ 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'], 6552 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 6553 tagName: 'ul', 6554 className: 'attachments', 6457 6555 6458 events: {6459 'click div.media-frame-title h1': 'toggleMenu'6556 attributes: { 6557 tabIndex: -1 6460 6558 }, 6461 6559 6462 /**6463 * @constructs6464 */6465 6560 initialize: function() { 6466 Frame.prototype.initialize.apply( this, arguments);6561 this.el.id = _.uniqueId('__attachments-view-'); 6467 6562 6468 6563 _.defaults( this.options, { 6469 title: '', 6470 modal: true, 6471 uploader: true 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 6472 6570 }); 6473 6571 6474 // Ensure core UI is enabled. 6475 this.$el.addClass('wp-core-ui'); 6572 this._viewsByCid = {}; 6573 this.$window = $( window ); 6574 this.resizeEvent = 'resize.media-modal-columns'; 6476 6575 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 6576 this.collection.on( 'add', function( attachment ) { 6577 this.views.add( this.createAttachmentView( attachment ), { 6578 at: this.collection.indexOf( attachment ) 6482 6579 }); 6580 }, this ); 6483 6581 6484 this.modal.content( this ); 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 ); 6485 6612 } 6613 }, 6486 6614 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; 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; 6491 6635 } 6492 6636 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 ); 6637 // Left arrow 6638 if ( 37 === event.keyCode ) { 6639 if ( 0 === index ) { 6640 return; 6641 } 6642 attachments.eq( index - 1 ).focus(); 6503 6643 } 6504 6644 6505 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 6645 // Up arrow 6646 if ( 38 === event.keyCode ) { 6647 if ( 1 === row ) { 6648 return; 6649 } 6650 attachments.eq( index - perRow ).focus(); 6651 } 6506 6652 6507 // Bind default title creation. 6508 this.on( 'title:create:default', this.createTitle, this ); 6509 this.title.mode('default'); 6653 // Right arrow 6654 if ( 39 === event.keyCode ) { 6655 if ( attachments.length === index ) { 6656 return; 6657 } 6658 attachments.eq( index + 1 ).focus(); 6659 } 6510 6660 6511 this.on( 'title:render', function( view ) { 6512 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 6513 }); 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 ); 6514 6739 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 }, 6740 // If the collection is sorted by menu order, 6741 // update the menu order. 6742 collection.saveMenuOrder(); 6743 } 6744 }, this.options.sortable ) ); 6550 6745 6551 toggleMenu: function() { 6552 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 6553 }, 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 ); 6554 6751 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 }); 6752 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 6753 this.refreshSortable(); 6572 6754 }, 6573 /**6574 * @param {Object} options6575 */6576 createIframeStates: function( options ) {6577 var settings = wp.media.view.settings,6578 tabs = settings.tabs,6579 tabUrl = settings.tabUrl,6580 $postId;6581 6755 6582 if ( ! tabs || ! tabUrl ) { 6756 refreshSortable: function() { 6757 if ( ! this.options.sortable || ! $.fn.sortable ) { 6583 6758 return; 6584 6759 } 6585 6760 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 ); 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; 6602 6765 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 ); 6766 this.$el.sortable( 'option', 'disabled', ! enabled ); 6608 6767 }, 6609 6768 6610 6769 /** 6611 * @param { Object} content6612 * @ this wp.media.controller.Region6770 * @param {wp.media.model.Attachment} attachment 6771 * @returns {wp.media.View} 6613 6772 */ 6614 iframeContent: function( content ) { 6615 this.$el.addClass('hide-toolbar'); 6616 content.view = new wp.media.view.Iframe({ 6617 controller: this 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 6618 6779 }); 6619 },6620 6780 6621 iframeContentCleanup: function() { 6622 this.$el.removeClass('hide-toolbar'); 6781 return this._viewsByCid[ attachment.cid ] = view; 6623 6782 }, 6624 6783 6625 iframeMenu: function( view ) { 6626 var views = {}; 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 ) ); 6627 6789 6628 if ( ! view ) { 6629 return; 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 ); 6630 6794 } 6795 }, 6631 6796 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 ); 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(); 6640 6801 }, 6641 6802 6642 hijackThickbox: function() { 6643 var frame = this; 6803 scroll: function() { 6804 var view = this, 6805 el = this.options.scrollElement, 6806 scrollTop = el.scrollTop, 6807 toolbar; 6644 6808 6645 if ( ! window.tb_remove || this._tb_remove ) { 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() ) { 6646 6817 return; 6647 6818 } 6648 6819 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 }, 6820 toolbar = this.views.parent.toolbar; 6657 6821 6658 restoreThickbox: function() {6659 if ( ! this._tb_remove) {6660 return;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(); 6661 6825 } 6662 6826 6663 window.tb_remove = this._tb_remove; 6664 delete this._tb_remove; 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 } 6665 6833 } 6666 6834 }); 6667 6835 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 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 6704 6873 */ 6705 MediaFrame.prototype[ method ] = function() { 6706 if ( this.modal ) { 6707 this.modal[ method ].apply( this.modal, arguments ); 6708 } 6874 render: function() { 6875 this.el.value = this.model.escape('search'); 6709 6876 return this; 6710 }; 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 ) 6711 6886 }); 6712 6887 6713 module.exports = MediaFrame; 6888 module.exports = Search; 6889 6890 6891 /***/ }), 6892 /* 74 */ 6893 /***/ (function(module, exports) { 6714 6894 6715 },{}],51:[function(require,module,exports){6716 6895 var $ = jQuery, 6717 MenuItem;6896 AttachmentFilters; 6718 6897 6719 6898 /** 6720 * wp.media.view. MenuItem6899 * wp.media.view.AttachmentFilters 6721 6900 * 6722 6901 * @memberOf wp.media.view 6723 6902 * … … var $ = jQuery, 6726 6905 * @augments wp.Backbone.View 6727 6906 * @augments Backbone.View 6728 6907 */ 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 }, 6908 AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{ 6909 tagName: 'select', 6910 className: 'attachment-filters', 6911 id: 'media-attachment-filters', 6736 6912 6737 6913 events: { 6738 'click': '_click'6914 change: 'change' 6739 6915 }, 6740 /**6741 * @param {Object} event6742 */6743 _click: function( event ) {6744 var clickOverride = this.options.click;6745 6746 if ( event ) {6747 event.preventDefault();6748 }6749 6916 6750 if ( clickOverride ) { 6751 clickOverride.call( this ); 6752 } else { 6753 this.click(); 6754 } 6917 keys: [], 6755 6918 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 }, 6919 initialize: function() { 6920 this.createFilters(); 6921 _.extend( this.filters, this.options.filters ); 6762 6922 6763 click: function() { 6764 var state = this.options.state; 6923 // Build `<option>` elements. 6924 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 6925 return { 6926 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 6927 priority: filter.priority || 50 6928 }; 6929 }, this ).sortBy('priority').pluck('el').value() ); 6765 6930 6766 if ( state ) { 6767 this.controller.setState( state ); 6768 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 6769 } 6931 this.listenTo( this.model, 'change', this.select ); 6932 this.select(); 6770 6933 }, 6934 6771 6935 /** 6772 * @ returns {wp.media.view.MenuItem} returns itself to allow chaining6936 * @abstract 6773 6937 */ 6774 render: function() { 6775 var options = this.options; 6938 createFilters: function() { 6939 this.filters = {}; 6940 }, 6776 6941 6777 if ( options.text ) { 6778 this.$el.text( options.text ); 6779 } else if ( options.html ) { 6780 this.$el.html( options.html ); 6942 /** 6943 * When the selected filter changes, update the Attachment Query properties to match. 6944 */ 6945 change: function() { 6946 var filter = this.filters[ this.el.value ]; 6947 if ( filter ) { 6948 this.model.set( filter.props ); 6781 6949 } 6950 }, 6782 6951 6783 return this; 6952 select: function() { 6953 var model = this.model, 6954 value = 'all', 6955 props = model.toJSON(); 6956 6957 _.find( this.filters, function( filter, id ) { 6958 var equal = _.all( filter.props, function( prop, key ) { 6959 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); 6960 }); 6961 6962 if ( equal ) { 6963 return value = id; 6964 } 6965 }); 6966 6967 this.$el.val( value ); 6784 6968 } 6785 6969 }); 6786 6970 6787 module.exports = MenuItem;6971 module.exports = AttachmentFilters; 6788 6972 6789 },{}],52:[function(require,module,exports){ 6790 var MenuItem = wp.media.view.MenuItem, 6791 PriorityList = wp.media.view.PriorityList, 6792 Menu; 6973 6974 /***/ }), 6975 /* 75 */ 6976 /***/ (function(module, exports) { 6977 6978 var l10n = wp.media.view.l10n, 6979 DateFilter; 6793 6980 6794 6981 /** 6795 * wp.media.view.Menu6982 * A filter dropdown for month/dates. 6796 6983 * 6797 * @memberOf wp.media.view 6984 * @memberOf wp.media.view.AttachmentFilters 6798 6985 * 6799 6986 * @class 6800 * @augments wp.media.view. PriorityList6987 * @augments wp.media.view.AttachmentFilters 6801 6988 * @augments wp.media.View 6802 6989 * @augments wp.Backbone.View 6803 6990 * @augments Backbone.View 6804 6991 */ 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', 6992 DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{ 6993 id: 'media-attachment-date-filters', 6811 6994 6812 /* TODO: alternatively hide on any click anywhere 6813 events: { 6814 'click': 'click' 6815 }, 6995 createFilters: function() { 6996 var filters = {}; 6997 _.each( wp.media.view.settings.months || {}, function( value, index ) { 6998 filters[ index ] = { 6999 text: value.text, 7000 props: { 7001 year: value.year, 7002 monthnum: value.month 7003 } 7004 }; 7005 }); 7006 filters.all = { 7007 text: l10n.allDates, 7008 props: { 7009 monthnum: false, 7010 year: false 7011 }, 7012 priority: 10 7013 }; 7014 this.filters = filters; 7015 } 7016 }); 6816 7017 6817 click: function() { 6818 this.$el.removeClass( 'visible' ); 6819 }, 6820 */ 7018 module.exports = DateFilter; 6821 7019 6822 /**6823 * @param {Object} options6824 * @param {string} id6825 * @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 7020 6833 ready: function() { 6834 /** 6835 * call 'ready' directly on the parent class 6836 */ 6837 PriorityList.prototype.ready.apply( this, arguments ); 6838 this.visibility(); 6839 }, 7021 /***/ }), 7022 /* 76 */ 7023 /***/ (function(module, exports) { 6840 7024 6841 set: function() { 6842 /** 6843 * call 'set' directly on the parent class 6844 */ 6845 PriorityList.prototype.set.apply( this, arguments ); 6846 this.visibility(); 6847 }, 7025 var l10n = wp.media.view.l10n, 7026 Uploaded; 6848 7027 6849 unset: function() { 6850 /** 6851 * call 'unset' directly on the parent class 6852 */ 6853 PriorityList.prototype.unset.apply( this, arguments ); 6854 this.visibility(); 6855 }, 7028 /** 7029 * wp.media.view.AttachmentFilters.Uploaded 7030 * 7031 * @memberOf wp.media.view.AttachmentFilters 7032 * 7033 * @class 7034 * @augments wp.media.view.AttachmentFilters 7035 * @augments wp.media.View 7036 * @augments wp.Backbone.View 7037 * @augments Backbone.View 7038 */ 7039 Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{ 7040 createFilters: function() { 7041 var type = this.model.get('type'), 7042 types = wp.media.view.settings.mimeTypes, 7043 text; 7044 7045 if ( types && type ) { 7046 text = types[ type ]; 7047 } 7048 7049 this.filters = { 7050 all: { 7051 text: text || l10n.allMediaItems, 7052 props: { 7053 uploadedTo: null, 7054 orderby: 'date', 7055 order: 'DESC' 7056 }, 7057 priority: 10 7058 }, 7059 7060 uploaded: { 7061 text: l10n.uploadedToThisPost, 7062 props: { 7063 uploadedTo: wp.media.view.settings.post.id, 7064 orderby: 'menuOrder', 7065 order: 'ASC' 7066 }, 7067 priority: 20 7068 }, 7069 7070 unattached: { 7071 text: l10n.unattached, 7072 props: { 7073 uploadedTo: 0, 7074 orderby: 'menuOrder', 7075 order: 'ASC' 7076 }, 7077 priority: 50 7078 } 7079 }; 7080 } 7081 }); 6856 7082 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; 7083 module.exports = Uploaded; 6862 7084 6863 if ( this === view ) {6864 this.controller.$el.toggleClass( 'hide-' + region, hide );6865 }6866 },6867 /**6868 * @param {string} id6869 */6870 select: function( id ) {6871 var view = this.get( id );6872 7085 6873 if ( ! view ) { 6874 return; 6875 } 7086 /***/ }), 7087 /* 77 */ 7088 /***/ (function(module, exports) { 6876 7089 6877 this.deselect(); 6878 view.$el.addClass('active'); 6879 }, 7090 var l10n = wp.media.view.l10n, 7091 All; 6880 7092 6881 deselect: function() { 6882 this.$el.children().removeClass('active'); 6883 }, 7093 /** 7094 * wp.media.view.AttachmentFilters.All 7095 * 7096 * @memberOf wp.media.view.AttachmentFilters 7097 * 7098 * @class 7099 * @augments wp.media.view.AttachmentFilters 7100 * @augments wp.media.View 7101 * @augments wp.Backbone.View 7102 * @augments Backbone.View 7103 */ 7104 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{ 7105 createFilters: function() { 7106 var filters = {}; 6884 7107 6885 hide: function( id ) { 6886 var view = this.get( id ); 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 }); 6887 7120 6888 if ( ! view ) { 6889 return; 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 7131 }; 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 }; 6890 7145 } 6891 7146 6892 view.$el.addClass('hidden'); 6893 }, 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 7157 }; 6894 7158 6895 show: function( id ) {6896 var view = this.get( id );7159 if ( wp.media.view.settings.mediaTrash && 7160 this.controller.isModeActive( 'grid' ) ) { 6897 7161 6898 if ( ! view ) { 6899 return; 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 }; 6900 7173 } 6901 7174 6902 view.$el.removeClass('hidden');7175 this.filters = filters; 6903 7176 } 6904 7177 }); 6905 7178 6906 module.exports = Menu;7179 module.exports = All; 6907 7180 6908 },{}],53:[function(require,module,exports){ 6909 var $ = jQuery, 6910 Modal; 7181 7182 /***/ }), 7183 /* 78 */ 7184 /***/ (function(module, exports) { 7185 7186 var View = wp.media.View, 7187 mediaTrash = wp.media.view.settings.mediaTrash, 7188 l10n = wp.media.view.l10n, 7189 $ = jQuery, 7190 AttachmentsBrowser; 6911 7191 6912 7192 /** 6913 * wp.media.view.Modal 6914 * 6915 * A modal view, which the media modal uses as its default container. 7193 * wp.media.view.AttachmentsBrowser 6916 7194 * 6917 7195 * @memberOf wp.media.view 6918 7196 * … … var $ = jQuery, 6920 7198 * @augments wp.media.View 6921 7199 * @augments wp.Backbone.View 6922 7200 * @augments Backbone.View 7201 * 7202 * @param {object} [options] The options hash passed to the view. 7203 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar. 7204 * Accepts 'uploaded' and 'all'. 7205 * @param {boolean} [options.search=true] Whether to show the search interface in the 7206 * browser's toolbar. 7207 * @param {boolean} [options.date=true] Whether to show the date filter in the 7208 * browser's toolbar. 7209 * @param {boolean} [options.display=false] Whether to show the attachments display settings 7210 * view in the sidebar. 7211 * @param {boolean|string} [options.sidebar=true] Whether to create a sidebar for the browser. 7212 * Accepts true, false, and 'errors'. 6923 7213 */ 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, 7214 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{ 7215 tagName: 'div', 7216 className: 'attachments-browser', 6938 7217 6939 7218 initialize: function() { 6940 7219 _.defaults( this.options, { 6941 container: document.body, 6942 title: '', 6943 propagate: true, 6944 freeze: true 7220 filters: false, 7221 search: true, 7222 date: true, 7223 display: false, 7224 sidebar: true, 7225 AttachmentView: wp.media.view.Attachment.Library 6945 7226 }); 6946 7227 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 }, 7228 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this ); 7229 this.controller.on( 'edit:selection', this.editSelection ); 6959 7230 6960 /** 6961 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6962 */ 6963 attach: function() { 6964 if ( this.views.attached ) { 6965 return this; 7231 // In the Media Library, the sidebar is used to display errors before the attachments grid. 7232 if ( this.options.sidebar && 'errors' === this.options.sidebar ) { 7233 this.createSidebar(); 6966 7234 } 6967 7235 6968 if ( ! this.views.rendered ) { 6969 this.render(); 7236 /* 7237 * For accessibility reasons, place the Inline Uploader before other sections. 7238 * This way, in the Media Library, it's right after the Add New button, see ticket #37188. 7239 */ 7240 this.createUploader(); 7241 7242 /* 7243 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library 7244 * and also for other things, for example the "Drag and drop to reorder" and 7245 * "Suggested dimensions" info in the media modal. 7246 */ 7247 this.createToolbar(); 7248 7249 // Create the list of attachments. 7250 this.createAttachments(); 7251 7252 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. 7253 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { 7254 this.createSidebar(); 6970 7255 } 6971 7256 6972 this. $el.appendTo( this.options.container);7257 this.updateContent(); 6973 7258 6974 // Manually mark the view as attached and trigger ready. 6975 this.views.attached = true; 6976 this.views.ready(); 7259 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 7260 this.$el.addClass( 'hide-sidebar' ); 6977 7261 6978 return this.propagate('attach'); 7262 if ( 'errors' === this.options.sidebar ) { 7263 this.$el.addClass( 'sidebar-for-errors' ); 7264 } 7265 } 7266 7267 this.collection.on( 'add remove reset', this.updateContent, this ); 7268 }, 7269 7270 editSelection: function( modal ) { 7271 modal.$( '.media-button-backToLibrary' ).focus(); 6979 7272 }, 6980 7273 6981 7274 /** 6982 * @returns {wp.media.view. Modal} Returns itself to allow chaining7275 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining 6983 7276 */ 6984 detach: function() { 6985 if ( this.$el.is(':visible') ) { 6986 this.close(); 7277 dispose: function() { 7278 this.options.selection.off( null, null, this ); 7279 View.prototype.dispose.apply( this, arguments ); 7280 return this; 7281 }, 7282 7283 createToolbar: function() { 7284 var LibraryViewSwitcher, Filters, toolbarOptions; 7285 7286 toolbarOptions = { 7287 controller: this.controller 7288 }; 7289 7290 if ( this.controller.isModeActive( 'grid' ) ) { 7291 toolbarOptions.className = 'media-toolbar wp-filter'; 6987 7292 } 6988 7293 6989 this.$el.detach();6990 this.views.attached = false;6991 return this.propagate('detach');6992 },7294 /** 7295 * @member {wp.media.view.Toolbar} 7296 */ 7297 this.toolbar = new wp.media.view.Toolbar( toolbarOptions ); 6993 7298 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; 7299 this.views.add( this.toolbar ); 7001 7300 7002 if ( $el.is(':visible') ){7003 return this;7004 } 7301 this.toolbar.set( 'spinner', new wp.media.view.Spinner({ 7302 priority: -60 7303 }) ); 7005 7304 7006 this.clickedOpenerEl = document.activeElement; 7305 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) { 7306 // "Filters" will return a <select>, need to render 7307 // screen reader text before 7308 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ 7309 value: l10n.filterByType, 7310 attributes: { 7311 'for': 'media-attachment-filters' 7312 }, 7313 priority: -80 7314 }).render() ); 7007 7315 7008 if ( ! this.views.attached ) { 7009 this.attach(); 7010 } 7316 if ( 'uploaded' === this.options.filters ) { 7317 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({ 7318 controller: this.controller, 7319 model: this.collection.props, 7320 priority: -80 7321 }).render() ); 7322 } else { 7323 Filters = new wp.media.view.AttachmentFilters.All({ 7324 controller: this.controller, 7325 model: this.collection.props, 7326 priority: -80 7327 }); 7011 7328 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 }; 7329 this.toolbar.set( 'filters', Filters.render() ); 7330 } 7017 7331 } 7018 7332 7019 // Disable page scrolling. 7020 $( 'body' ).addClass( 'modal-open' ); 7333 // Feels odd to bring the global media library switcher into the Attachment 7334 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 7335 // which the controller can tap into and add this view? 7336 if ( this.controller.isModeActive( 'grid' ) ) { 7337 LibraryViewSwitcher = View.extend({ 7338 className: 'view-switch media-grid-view-switch', 7339 template: wp.template( 'media-library-view-switcher') 7340 }); 7021 7341 7022 $el.show(); 7342 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 7343 controller: this.controller, 7344 priority: -90 7345 }).render() ); 7023 7346 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(); 7347 // DateFilter is a <select>, screen reader text needs to be rendered before 7348 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 7349 value: l10n.filterByDate, 7350 attributes: { 7351 'for': 'media-attachment-date-filters' 7352 }, 7353 priority: -75 7354 }).render() ); 7355 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 7356 controller: this.controller, 7357 model: this.collection.props, 7358 priority: -75 7359 }).render() ); 7029 7360 7030 setTimeout( function() { 7031 mceEditor.iframeElement.blur(); 7032 }, 100 ); 7033 } 7034 } 7361 // BulkSelection is a <div> with subviews, including screen reader text 7362 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 7363 text: l10n.bulkSelect, 7364 controller: this.controller, 7365 priority: -70 7366 }).render() ); 7035 7367 7036 this.$el.focus(); 7368 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 7369 filters: Filters, 7370 style: 'primary', 7371 disabled: true, 7372 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected, 7373 controller: this.controller, 7374 priority: -60, 7375 click: function() { 7376 var changed = [], removed = [], 7377 selection = this.controller.state().get( 'selection' ), 7378 library = this.controller.state().get( 'library' ); 7037 7379 7038 return this.propagate('open'); 7039 }, 7380 if ( ! selection.length ) { 7381 return; 7382 } 7040 7383 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; 7384 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 7385 return; 7386 } 7047 7387 7048 if ( ! this.views.attached || ! this.$el.is(':visible') ) {7049 return this;7050 }7388 if ( mediaTrash && 7389 'trash' !== selection.at( 0 ).get( 'status' ) && 7390 ! window.confirm( l10n.warnBulkTrash ) ) { 7051 7391 7052 // Enable page scrolling.7053 $( 'body' ).removeClass( 'modal-open' );7392 return; 7393 } 7054 7394 7055 // Hide modal and remove restricted media modal tab focus once it's closed 7056 this.$el.hide().undelegate( 'keydown' ); 7395 selection.each( function( model ) { 7396 if ( ! model.get( 'nonces' )['delete'] ) { 7397 removed.push( model ); 7398 return; 7399 } 7057 7400 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 } 7401 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 7402 model.set( 'status', 'inherit' ); 7403 changed.push( model.save() ); 7404 removed.push( model ); 7405 } else if ( mediaTrash ) { 7406 model.set( 'status', 'trash' ); 7407 changed.push( model.save() ); 7408 removed.push( model ); 7409 } else { 7410 model.destroy({wait: true}); 7411 } 7412 } ); 7064 7413 7065 this.propagate('close'); 7414 if ( changed.length ) { 7415 selection.remove( removed ); 7066 7416 7067 // If the `freeze` option is set, restore the container's scroll position. 7068 if ( freeze ) { 7069 $( window ).scrollTop( freeze.scrollTop ); 7070 } 7417 $.when.apply( null, changed ).then( _.bind( function() { 7418 library._requery( true ); 7419 this.controller.trigger( 'selection:action:done' ); 7420 }, this ) ); 7421 } else { 7422 this.controller.trigger( 'selection:action:done' ); 7423 } 7424 } 7425 }).render() ); 7071 7426 7072 if ( options && options.escape ) { 7073 this.propagate('escape'); 7074 } 7427 if ( mediaTrash ) { 7428 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 7429 filters: Filters, 7430 style: 'primary', 7431 disabled: true, 7432 text: l10n.deleteSelected, 7433 controller: this.controller, 7434 priority: -55, 7435 click: function() { 7436 var removed = [], 7437 destroy = [], 7438 selection = this.controller.state().get( 'selection' ); 7075 7439 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 }, 7440 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 7441 return; 7442 } 7091 7443 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 }, 7444 selection.each( function( model ) { 7445 if ( ! model.get( 'nonces' )['delete'] ) { 7446 removed.push( model ); 7447 return; 7448 } 7100 7449 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 ); 7450 destroy.push( model ); 7451 } ); 7110 7452 7111 if ( this.options.propagate ) { 7112 this.controller.trigger( id ); 7453 if ( removed.length ) { 7454 selection.remove( removed ); 7455 } 7456 7457 if ( destroy.length ) { 7458 $.when.apply( null, destroy.map( function (item) { 7459 return item.destroy(); 7460 } ) ).then( _.bind( function() { 7461 this.controller.trigger( 'selection:action:done' ); 7462 }, this ) ); 7463 } 7464 } 7465 }).render() ); 7466 } 7467 7468 } else if ( this.options.date ) { 7469 // DateFilter is a <select>, screen reader text needs to be rendered before 7470 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 7471 value: l10n.filterByDate, 7472 attributes: { 7473 'for': 'media-attachment-date-filters' 7474 }, 7475 priority: -75 7476 }).render() ); 7477 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 7478 controller: this.controller, 7479 model: this.collection.props, 7480 priority: -75 7481 }).render() ); 7113 7482 } 7114 7483 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(); 7484 if ( this.options.search ) { 7485 // Search is an input, screen reader text needs to be rendered before 7486 this.toolbar.set( 'searchLabel', new wp.media.view.Label({ 7487 value: l10n.searchMediaLabel, 7488 attributes: { 7489 'for': 'media-search-input' 7490 }, 7491 priority: 60 7492 }).render() ); 7493 this.toolbar.set( 'search', new wp.media.view.Search({ 7494 controller: this.controller, 7495 model: this.collection.props, 7496 priority: 60 7497 }).render() ); 7125 7498 } 7126 }7127 });7128 7499 7129 module.exports = Modal; 7500 if ( this.options.dragInfo ) { 7501 this.toolbar.set( 'dragInfo', new View({ 7502 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0], 7503 priority: -40 7504 }) ); 7505 } 7130 7506 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', 7507 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 7508 this.toolbar.set( 'suggestedDimensions', new View({ 7509 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0], 7510 priority: -40 7511 }) ); 7512 } 7513 }, 7144 7514 7145 initialize: function() { 7146 this._views = {}; 7515 updateContent: function() { 7516 var view = this, 7517 noItemsView; 7147 7518 7148 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 7149 delete this.options.views; 7519 if ( this.controller.isModeActive( 'grid' ) ) { 7520 noItemsView = view.attachmentsNoResults; 7521 } else { 7522 noItemsView = view.uploader; 7523 } 7150 7524 7151 if ( ! this.options.silent ) { 7152 this.render(); 7525 if ( ! this.collection.length ) { 7526 this.toolbar.get( 'spinner' ).show(); 7527 this.dfd = this.collection.more().done( function() { 7528 if ( ! view.collection.length ) { 7529 noItemsView.$el.removeClass( 'hidden' ); 7530 } else { 7531 noItemsView.$el.addClass( 'hidden' ); 7532 } 7533 view.toolbar.get( 'spinner' ).hide(); 7534 } ); 7535 } else { 7536 noItemsView.$el.addClass( 'hidden' ); 7537 view.toolbar.get( 'spinner' ).hide(); 7153 7538 } 7154 7539 }, 7155 /**7156 * @param {string} id7157 * @param {wp.media.View|Object} view7158 * @param {Object} options7159 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining7160 */7161 set: function( id, view, options ) {7162 var priority, views, index;7163 7540 7164 options = options || {}; 7541 createUploader: function() { 7542 this.uploader = new wp.media.view.UploaderInline({ 7543 controller: this.controller, 7544 status: false, 7545 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 7546 canClose: this.controller.isModeActive( 'grid' ) 7547 }); 7165 7548 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 } 7549 this.uploader.$el.addClass( 'hidden' ); 7550 this.views.add( this.uploader ); 7551 }, 7173 7552 7174 if ( ! (view instanceof Backbone.View) ) { 7175 view = this.toView( view, id, options ); 7553 toggleUploader: function() { 7554 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 7555 this.uploader.show(); 7556 } else { 7557 this.uploader.hide(); 7176 7558 } 7177 view.controller = view.controller || this.controller; 7178 7179 this.unset( id ); 7559 }, 7180 7560 7181 priority = view.options.priority || 10; 7182 views = this.views.get() || []; 7561 createAttachments: function() { 7562 this.attachments = new wp.media.view.Attachments({ 7563 controller: this.controller, 7564 collection: this.collection, 7565 selection: this.options.selection, 7566 model: this.model, 7567 sortable: this.options.sortable, 7568 scrollElement: this.options.scrollElement, 7569 idealColumnWidth: this.options.idealColumnWidth, 7183 7570 7184 _.find( views, function( existing, i ) { 7185 if ( existing.options.priority > priority ) { 7186 index = i; 7187 return true; 7188 } 7571 // The single `Attachment` view to be used in the `Attachments` view. 7572 AttachmentView: this.options.AttachmentView 7189 7573 }); 7190 7574 7191 this._views[ id ] = view; 7192 this.views.add( view, { 7193 at: _.isNumber( index ) ? index : views.length || 0 7194 }); 7575 // Add keydown listener to the instance of the Attachments view 7576 this.controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) ); 7577 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) ); 7195 7578 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 ); 7579 this.views.add( this.attachments ); 7211 7580 7212 if ( view ) {7213 view.remove();7214 }7215 7581 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 }); 7582 if ( this.controller.isModeActive( 'grid' ) ) { 7583 this.attachmentsNoResults = new View({ 7584 controller: this.controller, 7585 tagName: 'p' 7586 }); 7227 7587 7228 module.exports = PriorityList; 7588 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 7589 this.attachmentsNoResults.$el.html( l10n.noMedia ); 7229 7590 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 ); 7591 this.views.add( this.attachmentsNoResults ); 7250 7592 } 7251 } 7252 }); 7593 }, 7253 7594 7254 module.exports = RouterItem; 7595 createSidebar: function() { 7596 var options = this.options, 7597 selection = options.selection, 7598 sidebar = this.sidebar = new wp.media.view.Sidebar({ 7599 controller: this.controller 7600 }); 7255 7601 7256 },{}],56:[function(require,module,exports){ 7257 var Menu = wp.media.view.Menu, 7258 Router; 7602 this.views.add( sidebar ); 7259 7603 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', 7604 if ( this.controller.uploader ) { 7605 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({ 7606 controller: this.controller, 7607 priority: 40 7608 }) ); 7609 } 7278 7610 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 }, 7611 selection.on( 'selection:single', this.createSingle, this ); 7612 selection.on( 'selection:unsingle', this.disposeSingle, this ); 7284 7613 7285 update: function() { 7286 var mode = this.controller.content.mode(); 7287 if ( mode ) { 7288 this.select( mode ); 7614 if ( selection.single() ) { 7615 this.createSingle(); 7289 7616 } 7290 } 7291 }); 7617 }, 7618 7619 createSingle: function() { 7620 var sidebar = this.sidebar, 7621 single = this.options.selection.single(); 7292 7622 7293 module.exports = Router; 7623 sidebar.set( 'details', new wp.media.view.Attachment.Details({ 7624 controller: this.controller, 7625 model: single, 7626 priority: 80 7627 }) ); 7294 7628 7295 },{}],57:[function(require,module,exports){ 7296 var l10n = wp.media.view.l10n, 7297 Search; 7629 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({ 7630 controller: this.controller, 7631 model: single, 7632 priority: 120 7633 }) ); 7298 7634 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', 7635 if ( this.options.display ) { 7636 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({ 7637 controller: this.controller, 7638 model: this.model.display( single ), 7639 attachment: single, 7640 priority: 160, 7641 userSettings: this.model.get('displayUserSettings') 7642 }) ); 7643 } 7313 7644 7314 attributes: { 7315 type: 'search', 7316 placeholder: l10n.searchMediaPlaceholder 7645 // Show the sidebar on mobile 7646 if ( this.model.id === 'insert' ) { 7647 sidebar.$el.addClass( 'visible' ); 7648 } 7317 7649 }, 7318 7650 7319 events: { 7320 'input': 'search', 7321 'keyup': 'search' 7322 }, 7651 disposeSingle: function() { 7652 var sidebar = this.sidebar; 7653 sidebar.unset('details'); 7654 sidebar.unset('compat'); 7655 sidebar.unset('display'); 7656 // Hide the sidebar on mobile 7657 sidebar.$el.removeClass( 'visible' ); 7658 } 7659 }); 7323 7660 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 }, 7661 module.exports = AttachmentsBrowser; 7331 7662 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 7663 7341 module.exports = Search; 7664 /***/ }), 7665 /* 79 */ 7666 /***/ (function(module, exports) { 7342 7667 7343 },{}],58:[function(require,module,exports){7344 7668 var l10n = wp.media.view.l10n, 7345 7669 Selection; 7346 7670 … … Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */ 7425 7749 7426 7750 module.exports = Selection; 7427 7751 7428 },{}],59:[function(require,module,exports){ 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 7785 var Attachments = wp.media.view.Attachments, 7786 Selection; 7787 7788 /** 7789 * wp.media.view.Attachments.Selection 7790 * 7791 * @memberOf wp.media.view.Attachments 7792 * 7793 * @class 7794 * @augments wp.media.view.Attachments 7795 * @augments wp.media.View 7796 * @augments wp.Backbone.View 7797 * @augments Backbone.View 7798 */ 7799 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{ 7800 events: {}, 7801 initialize: function() { 7802 _.defaults( this.options, { 7803 sortable: false, 7804 resize: false, 7805 7806 // The single `Attachment` view to be used in the `Attachments` view. 7807 AttachmentView: wp.media.view.Attachment.Selection 7808 }); 7809 // Call 'initialize' directly on the parent class. 7810 return Attachments.prototype.initialize.apply( this, arguments ); 7811 } 7812 }); 7813 7814 module.exports = Selection; 7815 7816 7817 /***/ }), 7818 /* 82 */ 7819 /***/ (function(module, exports) { 7820 7821 /** 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 7429 7846 var View = wp.media.View, 7430 7847 $ = Backbone.$, 7431 7848 Settings; … … Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 7548 7965 7549 7966 module.exports = Settings; 7550 7967 7551 },{}],60:[function(require,module,exports){ 7968 7969 /***/ }), 7970 /* 84 */ 7971 /***/ (function(module, exports) { 7972 7552 7973 var Settings = wp.media.view.Settings, 7553 7974 AttachmentDisplay; 7554 7975 … … AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.Attachment 7625 8046 if ( 'post' === linkTo ) { 7626 8047 $input.val( attachment.get('link') ); 7627 8048 } 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){ 7704 var View = wp.media.view, 7705 SiteIconCropper; 7706 7707 /** 7708 * wp.media.view.SiteIconCropper 7709 * 7710 * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon. 7711 * 7712 * Takes imgAreaSelect options from 7713 * wp.customize.SiteIconControl.calculateImageSelectOptions. 7714 * 7715 * @memberOf wp.media.view 7716 * 7717 * @class 7718 * @augments wp.media.view.Cropper 7719 * @augments wp.media.View 7720 * @augments wp.Backbone.View 7721 * @augments Backbone.View 7722 */ 7723 SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{ 7724 className: 'crop-content site-icon', 7725 7726 ready: function () { 7727 View.Cropper.prototype.ready.apply( this, arguments ); 7728 7729 this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) ); 7730 }, 8049 $input.val( attachment.get('url') ); 8050 } else if ( ! this.model.get('linkUrl') ) { 8051 $input.val('http://'); 8052 } 7731 8053 7732 addSidebar: function() { 7733 this.sidebar = new wp.media.view.Sidebar({ 7734 controller: this.controller 7735 }); 8054 $input.prop( 'readonly', 'custom' !== linkTo ); 8055 } 7736 8056 7737 this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({ 7738 controller: this.controller, 7739 attachment: this.options.attachment 7740 }) ); 8057 $input.removeClass( 'hidden' ); 7741 8058 7742 this.controller.cropperView.views.add( this.sidebar ); 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 } 7743 8063 } 7744 8064 }); 7745 8065 7746 module.exports = SiteIconCropper;8066 module.exports = AttachmentDisplay; 7747 8067 7748 },{}],65:[function(require,module,exports){ 7749 var View = wp.media.View,7750 $ = jQuery, 7751 SiteIconPreview; 8068 8069 /***/ }), 8070 /* 85 */ 8071 /***/ (function(module, exports) { 7752 8072 7753 8073 /** 7754 * wp.media.view.SiteIconPreview 7755 * 7756 * Shows a preview of the Site Icon as a favicon and app icon while cropping. 8074 * wp.media.view.Settings.Gallery 7757 8075 * 7758 * @memberOf wp.media.view 8076 * @memberOf wp.media.view.Settings 7759 8077 * 7760 8078 * @class 8079 * @augments wp.media.view.Settings 7761 8080 * @augments wp.media.View 7762 8081 * @augments wp.Backbone.View 7763 8082 * @augments Backbone.View 7764 8083 */ 7765 SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{ 7766 className: 'site-icon-preview', 7767 template: wp.template( 'site-icon-preview' ), 7768 7769 ready: function() { 7770 this.controller.imgSelect.setOptions({ 7771 onInit: this.updatePreview, 7772 onSelectChange: this.updatePreview 7773 }); 7774 }, 7775 7776 prepare: function() { 7777 return { 7778 url: this.options.attachment.get( 'url' ) 7779 }; 7780 }, 7781 7782 updatePreview: function( img, coords ) { 7783 var rx = 64 / coords.width, 7784 ry = 64 / coords.height, 7785 preview_rx = 16 / coords.width, 7786 preview_ry = 16 / coords.height; 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 }); 7787 8088 7788 $( '#preview-app-icon' ).css({ 7789 width: Math.round(rx * this.imageWidth ) + 'px', 7790 height: Math.round(ry * this.imageHeight ) + 'px', 7791 marginLeft: '-' + Math.round(rx * coords.x1) + 'px', 7792 marginTop: '-' + Math.round(ry * coords.y1) + 'px' 7793 }); 8089 module.exports = Gallery; 7794 8090 7795 $( '#preview-favicon' ).css({7796 width: Math.round( preview_rx * this.imageWidth ) + 'px',7797 height: Math.round( preview_ry * this.imageHeight ) + 'px',7798 marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',7799 marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'7800 });7801 }7802 });7803 8091 7804 module.exports = SiteIconPreview; 8092 /***/ }), 8093 /* 86 */ 8094 /***/ (function(module, exports) { 7805 8095 7806 },{}],66:[function(require,module,exports){7807 8096 /** 7808 * wp.media.view.S pinner8097 * wp.media.view.Settings.Playlist 7809 8098 * 7810 * @memberOf wp.media.view 8099 * @memberOf wp.media.view.Settings 7811 8100 * 7812 8101 * @class 8102 * @augments wp.media.view.Settings 7813 8103 * @augments wp.media.View 7814 8104 * @augments wp.Backbone.View 7815 8105 * @augments Backbone.View 7816 8106 */ 7817 var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{ 7818 tagName: 'span', 7819 className: 'spinner', 7820 spinnerTimeout: false, 7821 delay: 400, 7822 7823 show: function() { 7824 if ( ! this.spinnerTimeout ) { 7825 this.spinnerTimeout = _.delay(function( $el ) { 7826 $el.addClass( 'is-active' ); 7827 }, this.delay, this.$el ); 7828 } 7829 7830 return this; 7831 }, 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 }); 7832 8111 7833 hide: function() { 7834 this.$el.removeClass( 'is-active' ); 7835 this.spinnerTimeout = clearTimeout( this.spinnerTimeout ); 8112 module.exports = Playlist; 7836 8113 7837 return this;7838 }7839 });7840 8114 7841 module.exports = Spinner; 8115 /***/ }), 8116 /* 87 */ 8117 /***/ (function(module, exports) { 7842 8118 7843 },{}],67:[function(require,module,exports){ 7844 var View = wp.media.View,7845 Toolbar;8119 var Attachment = wp.media.view.Attachment, 8120 l10n = wp.media.view.l10n, 8121 Details; 7846 8122 7847 8123 /** 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. 8124 * wp.media.view.Attachment.Details 7852 8125 * 7853 * @memberOf wp.media.view 8126 * @memberOf wp.media.view.Attachment 7854 8127 * 7855 8128 * @class 8129 * @augments wp.media.view.Attachment 7856 8130 * @augments wp.media.View 7857 8131 * @augments wp.Backbone.View 7858 8132 * @augments Backbone.View 7859 8133 */ 7860 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{8134 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{ 7861 8135 tagName: 'div', 7862 className: 'media-toolbar', 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 }, 7863 8157 7864 8158 initialize: function() { 7865 var state = this.controller.state(),7866 selection = this.selection = state.get('selection'),7867 library = this.library = state.get('library');8159 this.options = _.defaults( this.options, { 8160 rerenderOnModelChange: false 8161 }); 7868 8162 7869 this._views = {}; 8163 this.on( 'ready', this.initialFocus ); 8164 // Call 'initialize' directly on the parent class. 8165 Attachment.prototype.initialize.apply( this, arguments ); 8166 }, 7870 8167 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'); 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(); 7876 8185 7877 this.views.set([ this.secondary, this.primary ]); 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(); 7878 8199 7879 if ( this.options.items ) { 7880 this.set( this.options.items, { silent: true }); 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(); 7881 8209 } 8210 }, 8211 /** 8212 * @param {Object} event 8213 */ 8214 untrashAttachment: function( event ) { 8215 var library = this.controller.library; 8216 event.preventDefault(); 7882 8217 7883 if ( ! this.options.silent ) { 7884 this.render(); 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; 7885 8247 } 7886 8248 7887 if ( selection ) { 7888 selection.on( 'add remove reset', this.refresh, this ); 7889 } 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. 8270 * 8271 * @memberOf wp.media.view 8272 * 8273 * @class 8274 * @augments wp.media.View 8275 * @augments wp.Backbone.View 8276 * @augments Backbone.View 8277 */ 8278 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{ 8279 tagName: 'form', 8280 className: 'compat-item', 8281 8282 events: { 8283 'submit': 'preventDefault', 8284 'change input': 'save', 8285 'change select': 'save', 8286 'change textarea': 'save' 8287 }, 7890 8288 7891 if ( library ) { 7892 library.on( 'add remove reset', this.refresh, this ); 7893 } 8289 initialize: function() { 8290 this.listenTo( this.model, 'change:compat', this.render ); 7894 8291 }, 7895 8292 /** 7896 * @returns {wp.media.view. Toolbar} Returns itsef to allow chaining8293 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 7897 8294 */ 7898 8295 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 ); 8296 if ( this.$(':focus').length ) { 8297 this.save(); 7905 8298 } 7906 8299 /** 7907 8300 * call 'dispose' directly on the parent class 7908 8301 */ 7909 8302 return View.prototype.dispose.apply( this, arguments ); 7910 8303 }, 7911 7912 ready: function() {7913 this.refresh();7914 },7915 7916 8304 /** 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 8305 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 7921 8306 */ 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(); 8307 render: function() { 8308 var compat = this.model.get('compat'); 8309 if ( ! compat || ! compat.item ) { 8310 return; 7948 8311 } 7949 8312 8313 this.views.detach(); 8314 this.$el.html( compat.item ); 8315 this.views.render(); 7950 8316 return this; 7951 8317 }, 7952 8318 /** 7953 * @param {string} id 7954 * @returns {wp.media.view.Button} 8319 * @param {Object} event 7955 8320 */ 7956 get: function( id) {7957 return this._views[ id ];8321 preventDefault: function( event ) { 8322 event.preventDefault(); 7958 8323 }, 7959 8324 /** 7960 * @param {string} id 7961 * @param {Object} options 7962 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 8325 * @param {Object} event 7963 8326 */ 7964 unset: function( id, options ) { 7965 delete this._views[ id ]; 7966 this.primary.unset( id, options ); 7967 this.secondary.unset( id, options ); 8327 save: function( event ) { 8328 var data = {}; 7968 8329 7969 if ( ! options || ! options.silent ) {7970 this.refresh();8330 if ( event ) { 8331 event.preventDefault(); 7971 8332 } 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 8333 7985 var requires = button.options.requires, 7986 disabled = false; 8334 _.each( this.$el.serializeArray(), function( pair ) { 8335 data[ pair.name ] = pair.value; 8336 }); 7987 8337 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 } 8338 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 8339 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 8340 }, 7994 8341 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 }); 8342 postSave: function() { 8343 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 8002 8344 } 8003 8345 }); 8004 8346 8005 module.exports = Toolbar;8347 module.exports = AttachmentCompat; 8006 8348 8007 },{}],68:[function(require,module,exports){ 8008 var Select = wp.media.view.Toolbar.Select,8009 l10n = wp.media.view.l10n, 8010 Embed; 8349 8350 /***/ }), 8351 /* 89 */ 8352 /***/ (function(module, exports) { 8011 8353 8012 8354 /** 8013 * wp.media.view. Toolbar.Embed8355 * wp.media.view.Iframe 8014 8356 * 8015 * @memberOf wp.media.view .Toolbar8357 * @memberOf wp.media.view 8016 8358 * 8017 8359 * @class 8018 * @augments wp.media.view.Toolbar.Select8019 * @augments wp.media.view.Toolbar8020 8360 * @augments wp.media.View 8021 8361 * @augments wp.Backbone.View 8022 8362 * @augments Backbone.View 8023 8363 */ 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 ); 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; 8041 8374 } 8042 8375 }); 8043 8376 8044 module.exports = Embed;8377 module.exports = Iframe; 8045 8378 8046 },{}],69:[function(require,module,exports){ 8047 var Toolbar = wp.media.view.Toolbar,8048 l10n = wp.media.view.l10n, 8049 Select; 8379 8380 /***/ }), 8381 /* 90 */ 8382 /***/ (function(module, exports) { 8050 8383 8051 8384 /** 8052 * wp.media.view. Toolbar.Select8385 * wp.media.view.Embed 8053 8386 * 8054 * @memberOf wp.media.view .Toolbar8387 * @memberOf wp.media.view 8055 8388 * 8056 8389 * @class 8057 * @augments wp.media.view.Toolbar8058 8390 * @augments wp.media.View 8059 8391 * @augments wp.Backbone.View 8060 8392 * @augments Backbone.View 8061 8393 */ 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, 8394 var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{ 8395 className: 'media-embed', 8074 8396 8075 // Does the button rely on the selection? 8076 requires: { 8077 selection: true 8078 } 8079 }); 8397 initialize: function() { 8398 /** 8399 * @member {wp.media.view.EmbedUrl} 8400 */ 8401 this.url = new wp.media.view.EmbedUrl({ 8402 controller: this.controller, 8403 model: this.model.props 8404 }).render(); 8080 8405 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 ); 8406 this.views.set([ this.url ]); 8407 this.refresh(); 8408 this.listenTo( this.model, 'change:type', this.refresh ); 8409 this.listenTo( this.model, 'change:loading', this.loading ); 8092 8410 }, 8093 8411 8094 clickSelect: function() {8095 var options = this.options,8096 controller = this.controller;8097 8098 if ( options.close) {8099 controller.close();8412 /** 8413 * @param {Object} view 8414 */ 8415 settings: function( view ) { 8416 if ( this._settings ) { 8417 this._settings.remove(); 8100 8418 } 8419 this._settings = view; 8420 this.views.add( view ); 8421 }, 8101 8422 8102 if ( options.event ) { 8103 controller.state().trigger( options.event ); 8423 refresh: function() { 8424 var type = this.model.get('type'), 8425 constructor; 8426 8427 if ( 'image' === type ) { 8428 constructor = wp.media.view.EmbedImage; 8429 } else if ( 'link' === type ) { 8430 constructor = wp.media.view.EmbedLink; 8431 } else { 8432 return; 8104 8433 } 8105 8434 8106 if ( options.state ) { 8107 controller.setState( options.state ); 8108 } 8435 this.settings( new constructor({ 8436 controller: this.controller, 8437 model: this.model.props, 8438 priority: 40 8439 }) ); 8440 }, 8109 8441 8110 if ( options.reset ) { 8111 controller.reset(); 8112 } 8442 loading: function() { 8443 this.$el.toggleClass( 'embed-loading', this.model.get('loading') ); 8113 8444 } 8114 8445 }); 8115 8446 8116 module.exports = Select;8447 module.exports = Embed; 8117 8448 8118 },{}],70:[function(require,module,exports){ 8119 var View = wp.media.View, 8120 l10n = wp.media.view.l10n, 8121 $ = jQuery, 8122 EditorUploader; 8449 8450 /***/ }), 8451 /* 91 */ 8452 /***/ (function(module, exports) { 8123 8453 8124 8454 /** 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 8455 * wp.media.view.Label 8129 8456 * 8130 8457 * @memberOf wp.media.view 8131 8458 * … … var View = wp.media.View, 8134 8461 * @augments wp.Backbone.View 8135 8462 * @augments Backbone.View 8136 8463 */ 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, 8464 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{ 8465 tagName: 'label', 8466 className: 'screen-reader-text', 8146 8467 8147 /**8148 * Bind drag'n'drop events to callbacks.8149 */8150 8468 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 } 8469 this.value = this.options.value; 8470 }, 8157 8471 8158 this.$document = $(document); 8159 this.dropzones = []; 8160 this.files = []; 8472 render: function() { 8473 this.$el.html( this.value ); 8161 8474 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 ) ); 8475 return this; 8476 } 8477 }); 8166 8478 8167 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 8168 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 8479 module.exports = Label; 8169 8480 8170 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {8171 this.localDrag = event.type === 'dragstart';8172 8481 8173 if ( event.type === 'drop' ) { 8174 this.containerDragleave(); 8175 } 8176 }, this ) ); 8482 /***/ }), 8483 /* 92 */ 8484 /***/ (function(module, exports) { 8177 8485 8178 this.initialized = true; 8179 return this;8180 },8486 var View = wp.media.View, 8487 $ = jQuery, 8488 EmbedUrl; 8181 8489 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'); 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', 8189 8503 8190 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 8191 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 8192 return supports; 8504 events: { 8505 'input': 'url', 8506 'keyup': 'url', 8507 'change': 'url' 8193 8508 }, 8194 8509 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; 8510 initialize: function() { 8511 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 8512 this.input = this.$input[0]; 8206 8513 8207 return this.draggingFile;8208 },8514 this.spinner = $('<span class="spinner" />')[0]; 8515 this.$el.append([ this.input, this.spinner ]); 8209 8516 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 } 8517 this.listenTo( this.model, 'change:url', this.render ); 8216 8518 8217 if ( ! _.isUndefined( e ) ) { 8218 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 8519 if ( this.model.get( 'url' ) ) { 8520 _.delay( _.bind( function () { 8521 this.model.trigger( 'change:url' ); 8522 }, this ), 500 ); 8219 8523 } 8524 }, 8525 /** 8526 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 8527 */ 8528 render: function() { 8529 var $input = this.$input; 8220 8530 8221 if ( ! this.overContainer && ! this.overDropzone) {8222 this.draggingFile = null;8531 if ( $input.is(':focus') ) { 8532 return; 8223 8533 } 8224 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 ); 8225 8540 return this; 8226 8541 }, 8227 8542 8228 re nder: function() {8229 if ( ! this.initialized) {8230 return this;8543 ready: function() { 8544 if ( ! wp.media.isTouchDevice ) { 8545 this.focus(); 8231 8546 } 8232 8233 View.prototype.render.apply( this, arguments );8234 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );8235 return this;8236 8547 }, 8237 8548 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; 8549 url: function( event ) { 8550 this.model.set( 'url', $.trim( event.target.value ) ); 8244 8551 }, 8245 8552 8246 8553 /** 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. 8554 * If the input is visible, focus and select its contents. 8251 8555 */ 8252 drop: function( event ) { 8253 var $wrap, uploadView; 8556 focus: function() { 8557 var $input = this.$input; 8558 if ( $input.is(':visible') ) { 8559 $input.focus()[0].select(); 8560 } 8561 } 8562 }); 8254 8563 8255 this.containerDragleave( event ); 8256 this.dropzoneDragleave( event ); 8564 module.exports = EmbedUrl; 8257 8565 8258 this.files = event.originalEvent.dataTransfer.files; 8259 if ( this.files.length < 1 ) { 8566 8567 /***/ }), 8568 /* 93 */ 8569 /***/ (function(module, exports) { 8570 8571 var $ = jQuery, 8572 EmbedLink; 8573 8574 /** 8575 * wp.media.view.EmbedLink 8576 * 8577 * @memberOf wp.media.view 8578 * 8579 * @class 8580 * @augments wp.media.view.Settings 8581 * @augments wp.media.View 8582 * @augments wp.Backbone.View 8583 * @augments Backbone.View 8584 */ 8585 EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{ 8586 className: 'embed-link-settings', 8587 template: wp.template('embed-link-settings'), 8588 8589 initialize: function() { 8590 this.listenTo( this.model, 'change:url', this.updateoEmbed ); 8591 }, 8592 8593 updateoEmbed: _.debounce( function() { 8594 var url = this.model.get( 'url' ); 8595 8596 // clear out previous results 8597 this.$('.embed-container').hide().find('.embed-preview').empty(); 8598 this.$( '.setting' ).hide(); 8599 8600 // only proceed with embed if the field contains more than 11 characters 8601 // Example: http://a.io is 11 chars 8602 if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) { 8260 8603 return; 8261 8604 } 8262 8605 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 } 8606 this.fetch(); 8607 }, wp.media.controller.Embed.sensitivity ), 8268 8608 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 }); 8609 fetch: function() { 8610 var url = this.model.get( 'url' ), re, youTubeEmbedMatch; 8276 8611 8277 uploadView = this.workflow.uploader; 8612 // check if they haven't typed in 500 ms 8613 if ( $('#embed-url-field').val() !== url ) { 8614 return; 8615 } 8278 8616 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();8617 if ( this.dfd && 'pending' === this.dfd.state() ) { 8618 this.dfd.abort(); 8619 } 8620 8621 // Support YouTube embed urls, since they work once in the editor. 8622 re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; 8623 youTubeEmbedMatch = re.exec( url ); 8624 if ( youTubeEmbedMatch ) { 8625 url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; 8288 8626 } 8289 8627 8290 return false; 8628 this.dfd = wp.apiRequest({ 8629 url: wp.media.view.settings.oEmbedProxyUrl, 8630 data: { 8631 url: url, 8632 maxwidth: this.model.get( 'width' ), 8633 maxheight: this.model.get( 'height' ) 8634 }, 8635 type: 'GET', 8636 dataType: 'json', 8637 context: this 8638 }) 8639 .done( function( response ) { 8640 this.renderoEmbed( { 8641 data: { 8642 body: response.html || '' 8643 } 8644 } ); 8645 } ) 8646 .fail( this.renderFail ); 8291 8647 }, 8292 8648 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 = []; 8649 renderFail: function ( response, status ) { 8650 if ( 'abort' === status ) { 8651 return; 8300 8652 } 8301 return this;8653 this.$( '.link-text' ).show(); 8302 8654 }, 8303 8655 8304 containerDragover: function( event ) { 8305 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 8306 return; 8656 renderoEmbed: function( response ) { 8657 var html = ( response && response.data && response.data.body ) || ''; 8658 8659 if ( html ) { 8660 this.$('.embed-container').show().find('.embed-preview').html( html ); 8661 } else { 8662 this.renderFail(); 8307 8663 } 8664 } 8665 }); 8308 8666 8309 this.overContainer = true; 8310 this.refresh(); 8311 }, 8667 module.exports = EmbedLink; 8312 8668 8313 containerDragleave: function() {8314 this.overContainer = false;8315 8669 8316 // Throttle dragleave because it's called when bouncing from some elements to others. 8317 _.delay( _.bind( this.refresh, this ), 50 ); 8318 }, 8670 /***/ }), 8671 /* 94 */ 8672 /***/ (function(module, exports) { 8319 8673 8320 dropzoneDragover: function( event ) { 8321 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 8322 return; 8323 } 8674 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 8675 EmbedImage; 8324 8676 8325 this.overDropzone = true; 8326 this.refresh( event ); 8327 return false; 8328 }, 8677 /** 8678 * wp.media.view.EmbedImage 8679 * 8680 * @memberOf wp.media.view 8681 * 8682 * @class 8683 * @augments wp.media.view.Settings.AttachmentDisplay 8684 * @augments wp.media.view.Settings 8685 * @augments wp.media.View 8686 * @augments wp.Backbone.View 8687 * @augments Backbone.View 8688 */ 8689 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{ 8690 className: 'embed-media-settings', 8691 template: wp.template('embed-image-settings'), 8329 8692 8330 dropzoneDragleave: function( e ) { 8331 this.overDropzone = false; 8332 _.delay( _.bind( this.refresh, this, e ), 50 ); 8693 initialize: function() { 8694 /** 8695 * Call `initialize` directly on parent class with passed arguments 8696 */ 8697 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 8698 this.listenTo( this.model, 'change:url', this.updateImage ); 8333 8699 }, 8334 8700 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; 8701 updateImage: function() { 8702 this.$('img').attr( 'src', this.model.get('url') ); 8340 8703 } 8341 8704 }); 8342 8705 8343 module.exports = E ditorUploader;8706 module.exports = EmbedImage; 8344 8707 8345 },{}],71:[function(require,module,exports){ 8346 var View = wp.media.View, 8347 UploaderInline; 8708 8709 /***/ }), 8710 /* 95 */ 8711 /***/ (function(module, exports) { 8712 8713 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 8714 $ = jQuery, 8715 ImageDetails; 8348 8716 8349 8717 /** 8350 * wp.media.view.UploaderInline 8351 * 8352 * The inline uploader that shows up in the 'Upload Files' tab. 8718 * wp.media.view.ImageDetails 8353 8719 * 8354 8720 * @memberOf wp.media.view 8355 8721 * 8356 8722 * @class 8723 * @augments wp.media.view.Settings.AttachmentDisplay 8724 * @augments wp.media.view.Settings 8357 8725 * @augments wp.media.View 8358 8726 * @augments wp.Backbone.View 8359 8727 * @augments Backbone.View 8360 8728 */ 8361 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 8362 tagName: 'div', 8363 className: 'uploader-inline', 8364 template: wp.template('uploader-inline'), 8729 ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{ 8730 className: 'image-details', 8731 template: wp.template('image-details'), 8732 events: _.defaults( AttachmentDisplay.prototype.events, { 8733 'click .edit-attachment': 'editAttachment', 8734 'click .replace-attachment': 'replaceAttachment', 8735 'click .advanced-toggle': 'onToggleAdvanced', 8736 'change [data-setting="customWidth"]': 'onCustomSize', 8737 'change [data-setting="customHeight"]': 'onCustomSize', 8738 'keyup [data-setting="customWidth"]': 'onCustomSize', 8739 'keyup [data-setting="customHeight"]': 'onCustomSize' 8740 } ), 8741 initialize: function() { 8742 // used in AttachmentDisplay.prototype.updateLinkTo 8743 this.options.attachment = this.model.attachment; 8744 this.listenTo( this.model, 'change:url', this.updateUrl ); 8745 this.listenTo( this.model, 'change:link', this.toggleLinkSettings ); 8746 this.listenTo( this.model, 'change:size', this.toggleCustomSize ); 8365 8747 8366 events: { 8367 'click .close': 'hide' 8748 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 8368 8749 }, 8369 8750 8370 initialize: function() { 8371 _.defaults( this.options, { 8372 message: '', 8373 status: true, 8374 canClose: false 8375 }); 8751 prepare: function() { 8752 var attachment = false; 8376 8753 8377 if ( ! this.options.$browser && this.controller.uploader) {8378 this.options.$browser = this.controller.uploader.$browser;8754 if ( this.model.attachment ) { 8755 attachment = this.model.attachment.toJSON(); 8379 8756 } 8757 return _.defaults({ 8758 model: this.model.toJSON(), 8759 attachment: attachment 8760 }, this.options ); 8761 }, 8380 8762 8381 if ( _.isUndefined( this.options.postId ) ) { 8382 this.options.postId = wp.media.view.settings.post.id; 8763 render: function() { 8764 var args = arguments; 8765 8766 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) { 8767 this.model.dfd 8768 .done( _.bind( function() { 8769 AttachmentDisplay.prototype.render.apply( this, args ); 8770 this.postRender(); 8771 }, this ) ) 8772 .fail( _.bind( function() { 8773 this.model.attachment = false; 8774 AttachmentDisplay.prototype.render.apply( this, args ); 8775 this.postRender(); 8776 }, this ) ); 8777 } else { 8778 AttachmentDisplay.prototype.render.apply( this, arguments ); 8779 this.postRender(); 8383 8780 } 8384 8781 8385 if ( this.options.status ) { 8386 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 8387 controller: this.controller 8388 }) ); 8782 return this; 8783 }, 8784 8785 postRender: function() { 8786 setTimeout( _.bind( this.resetFocus, this ), 10 ); 8787 this.toggleLinkSettings(); 8788 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) { 8789 this.toggleAdvanced( true ); 8389 8790 } 8791 this.trigger( 'post-render' ); 8390 8792 }, 8391 8793 8392 prepare: function() {8393 var suggestedWidth = this.controller.state().get('suggestedWidth'),8394 suggestedHeight = this.controller.state().get('suggestedHeight'),8395 data = {};8794 resetFocus: function() { 8795 this.$( '.link-to-custom' ).blur(); 8796 this.$( '.embed-media-settings' ).scrollTop( 0 ); 8797 }, 8396 8798 8397 data.message = this.options.message; 8398 data.canClose = this.options.canClose; 8799 updateUrl: function() { 8800 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) ); 8801 this.$( '.url' ).val( this.model.get( 'url' ) ); 8802 }, 8399 8803 8400 if ( suggestedWidth && suggestedHeight ) { 8401 data.suggestedWidth = suggestedWidth; 8402 data.suggestedHeight = suggestedHeight; 8804 toggleLinkSettings: function() { 8805 if ( this.model.get( 'link' ) === 'none' ) { 8806 this.$( '.link-settings' ).addClass('hidden'); 8807 } else { 8808 this.$( '.link-settings' ).removeClass('hidden'); 8403 8809 } 8404 8405 return data;8406 8810 }, 8407 /**8408 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining8409 */8410 dispose: function() {8411 if ( this.disposing ) {8412 /**8413 * call 'dispose' directly on the parent class8414 */8415 return View.prototype.dispose.apply( this, arguments );8416 }8417 8811 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(); 8812 toggleCustomSize: function() { 8813 if ( this.model.get( 'size' ) !== 'custom' ) { 8814 this.$( '.custom-size' ).addClass('hidden'); 8815 } else { 8816 this.$( '.custom-size' ).removeClass('hidden'); 8817 } 8423 8818 }, 8424 /**8425 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining8426 */8427 remove: function() {8428 /**8429 * call 'remove' directly on the parent class8430 */8431 var result = View.prototype.remove.apply( this, arguments );8432 8819 8433 _.defer( _.bind( this.refresh, this ) ); 8434 return result; 8435 }, 8820 onCustomSize: function( event ) { 8821 var dimension = $( event.target ).data('setting'), 8822 num = $( event.target ).val(), 8823 value; 8436 8824 8437 refresh: function() { 8438 var uploader = this.controller.uploader; 8825 // Ignore bogus input 8826 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) { 8827 event.preventDefault(); 8828 return; 8829 } 8439 8830 8440 if ( uploader ) { 8441 uploader.refresh(); 8831 if ( dimension === 'customWidth' ) { 8832 value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num ); 8833 this.model.set( 'customHeight', value, { silent: true } ); 8834 this.$( '[data-setting="customHeight"]' ).val( value ); 8835 } else { 8836 value = Math.round( this.model.get( 'aspectRatio' ) * num ); 8837 this.model.set( 'customWidth', value, { silent: true } ); 8838 this.$( '[data-setting="customWidth"]' ).val( value ); 8442 8839 } 8443 8840 }, 8444 /**8445 * @returns {wp.media.view.UploaderInline}8446 */8447 ready: function() {8448 var $browser = this.options.$browser,8449 $placeholder;8450 8841 8451 if ( this.controller.uploader ) { 8452 $placeholder = this.$('.browser'); 8842 onToggleAdvanced: function( event ) { 8843 event.preventDefault(); 8844 this.toggleAdvanced(); 8845 }, 8453 8846 8454 // Check if we've already replaced the placeholder. 8455 if ( $placeholder[0] === $browser[0] ) { 8456 return; 8457 } 8847 toggleAdvanced: function( show ) { 8848 var $advanced = this.$el.find( '.advanced-section' ), 8849 mode; 8458 8850 8459 $browser.detach().text( $placeholder.text() ); 8460 $browser[0].className = $placeholder[0].className; 8461 $placeholder.replaceWith( $browser.show() ); 8851 if ( $advanced.hasClass('advanced-visible') || show === false ) { 8852 $advanced.removeClass('advanced-visible'); 8853 $advanced.find('.advanced-settings').addClass('hidden'); 8854 mode = 'hide'; 8855 } else { 8856 $advanced.addClass('advanced-visible'); 8857 $advanced.find('.advanced-settings').removeClass('hidden'); 8858 mode = 'show'; 8462 8859 } 8463 8860 8464 this.refresh(); 8465 return this; 8861 window.setUserSetting( 'advImgDetails', mode ); 8466 8862 }, 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' ); 8863 8864 editAttachment: function( event ) { 8865 var editState = this.controller.states.get( 'edit-image' ); 8866 8867 if ( window.imageEdit && editState ) { 8868 event.preventDefault(); 8869 editState.set( 'image', this.model.attachment ); 8870 this.controller.setState( 'edit-image' ); 8471 8871 } 8472 8872 }, 8473 hide: function() {8474 this.$el.addClass( 'hidden' );8475 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {8476 this.controller.$uploaderToggler8477 .attr( 'aria-expanded', 'false' )8478 // Move focus back to the toggle button when closing the uploader.8479 .focus();8480 }8481 }8482 8873 8874 replaceAttachment: function( event ) { 8875 event.preventDefault(); 8876 this.controller.setState( 'replace-image' ); 8877 } 8483 8878 }); 8484 8879 8485 module.exports = UploaderInline;8880 module.exports = ImageDetails; 8486 8881 8487 },{}],72:[function(require,module,exports){8488 /**8489 * wp.media.view.UploaderStatusError8490 *8491 * @memberOf wp.media.view8492 *8493 * @class8494 * @augments wp.media.View8495 * @augments wp.Backbone.View8496 * @augments Backbone.View8497 */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 8882 8503 module.exports = UploaderStatusError; 8883 /***/ }), 8884 /* 96 */ 8885 /***/ (function(module, exports) { 8504 8886 8505 },{}],73:[function(require,module,exports){8506 8887 var View = wp.media.View, 8507 UploaderStatus; 8888 UploaderStatus = wp.media.view.UploaderStatus, 8889 l10n = wp.media.view.l10n, 8890 $ = jQuery, 8891 Cropper; 8508 8892 8509 8893 /** 8510 * wp.media.view. UploaderStatus8894 * wp.media.view.Cropper 8511 8895 * 8512 * An uploader status for on-going uploads. 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. 8513 8901 * 8514 8902 * @memberOf wp.media.view 8515 8903 * … … var View = wp.media.View, 8518 8906 * @augments wp.Backbone.View 8519 8907 * @augments Backbone.View 8520 8908 */ 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 8909 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{ 8910 className: 'crop-content', 8911 template: wp.template('crop-content'), 8529 8912 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 ); 8913 _.bindAll(this, 'onImageLoad'); 8539 8914 }, 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; 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)); 8550 8920 }, 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);8921 remove: function() { 8922 $(window).off('resize.cropper'); 8923 this.$el.remove(); 8924 this.$el.off(); 8925 View.prototype.remove.apply(this, arguments); 8556 8926 }, 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(); 8927 prepare: function() { 8928 return { 8929 title: l10n.cropYourImage, 8930 url: this.options.attachment.get('url') 8931 }; 8571 8932 }, 8933 onImageLoad: function() { 8934 var imgOptions = this.controller.get('imgSelectOptions'), 8935 imgSelect; 8572 8936 8573 progress: function() { 8574 var queue = this.queue, 8575 $bar = this.$bar; 8576 8577 if ( ! $bar || ! queue.length ) { 8578 return; 8937 if (typeof imgOptions === 'function') { 8938 imgOptions = imgOptions(this.options.attachment, this.controller); 8579 8939 } 8580 8940 8581 $bar.width( ( queue.reduce( function( memo, attachment ){8582 if ( ! attachment.get('uploading') ) {8583 return memo + 100;8584 }8941 imgOptions = _.extend(imgOptions, { 8942 parent: this.$el, 8943 onInit: function() { 8944 this.parent.children().on( 'mousedown touchstart', function( e ){ 8585 8945 8586 var percent = attachment.get('percent'); 8587 return memo + ( _.isNumber( percent ) ? percent : 100 ); 8588 }, 0 ) / queue.length ) + '%' ); 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); 8589 8960 }, 8961 onError: function() { 8962 var filename = this.options.attachment.get('filename'); 8590 8963 8591 info: function() { 8592 var queue = this.queue, 8593 index = 0, active; 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 }); 8968 } 8969 }); 8594 8970 8595 if ( ! queue.length ) { 8596 return; 8597 } 8971 module.exports = Cropper; 8598 8972 8599 active = this.queue.find( function( attachment, i ) {8600 index = i;8601 return attachment.get('uploading');8602 });8603 8973 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 }); 8974 /***/ }), 8975 /* 97 */ 8976 /***/ (function(module, exports) { 8977 8978 var View = wp.media.view, 8979 SiteIconCropper; 8980 8981 /** 8982 * wp.media.view.SiteIconCropper 8983 * 8984 * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon. 8985 * 8986 * Takes imgAreaSelect options from 8987 * wp.customize.SiteIconControl.calculateImageSelectOptions. 8988 * 8989 * @memberOf wp.media.view 8990 * 8991 * @class 8992 * @augments wp.media.view.Cropper 8993 * @augments wp.media.View 8994 * @augments wp.Backbone.View 8995 * @augments Backbone.View 8996 */ 8997 SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{ 8998 className: 'crop-content site-icon', 8999 9000 ready: function () { 9001 View.Cropper.prototype.ready.apply( this, arguments ); 9002 9003 this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) ); 8623 9004 }, 8624 9005 8625 /** 8626 * @param {Object} event 8627 */ 8628 dismiss: function( event ) { 8629 var errors = this.views.get('.upload-errors'); 9006 addSidebar: function() { 9007 this.sidebar = new wp.media.view.Sidebar({ 9008 controller: this.controller 9009 }); 8630 9010 8631 event.preventDefault(); 9011 this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({ 9012 controller: this.controller, 9013 attachment: this.options.attachment 9014 }) ); 8632 9015 8633 if ( errors ) { 8634 _.invoke( errors, 'remove' ); 8635 } 8636 wp.Uploader.errors.reset(); 9016 this.controller.cropperView.views.add( this.sidebar ); 8637 9017 } 8638 9018 }); 8639 9019 8640 module.exports = UploaderStatus;9020 module.exports = SiteIconCropper; 8641 9021 8642 },{}],74:[function(require,module,exports){ 8643 var $ = jQuery, 8644 UploaderWindow; 9022 9023 /***/ }), 9024 /* 98 */ 9025 /***/ (function(module, exports) { 9026 9027 var View = wp.media.View, 9028 $ = jQuery, 9029 SiteIconPreview; 8645 9030 8646 9031 /** 8647 * wp.media.view. UploaderWindow9032 * wp.media.view.SiteIconPreview 8648 9033 * 8649 * An uploader window that allows for dragging and dropping media.9034 * Shows a preview of the Site Icon as a favicon and app icon while cropping. 8650 9035 * 8651 9036 * @memberOf wp.media.view 8652 9037 * … … var $ = jQuery, 8654 9039 * @augments wp.media.View 8655 9040 * @augments wp.Backbone.View 8656 9041 * @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 9042 */ 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'), 9043 SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{ 9044 className: 'site-icon-preview', 9045 template: wp.template( 'site-icon-preview' ), 8668 9046 8669 initialize: function() { 8670 var uploader; 9047 ready: function() { 9048 this.controller.imgSelect.setOptions({ 9049 onInit: this.updatePreview, 9050 onSelectChange: this.updatePreview 9051 }); 9052 }, 8671 9053 8672 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 9054 prepare: function() { 9055 return { 9056 url: this.options.attachment.get( 'url' ) 9057 }; 9058 }, 8673 9059 8674 uploader = this.options.uploader = _.defaults( this.options.uploader || {},{8675 dropzone: this.$el,8676 browser: this.$browser,8677 p arams: {}8678 });9060 updatePreview: function( img, coords ) { 9061 var rx = 64 / coords.width, 9062 ry = 64 / coords.height, 9063 preview_rx = 16 / coords.width, 9064 preview_ry = 16 / coords.height; 8679 9065 8680 // Ensure the dropzone is a jQuery collection. 8681 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 8682 uploader.dropzone = $( uploader.dropzone ); 8683 } 9066 $( '#preview-app-icon' ).css({ 9067 width: Math.round(rx * this.imageWidth ) + 'px', 9068 height: Math.round(ry * this.imageHeight ) + 'px', 9069 marginLeft: '-' + Math.round(rx * coords.x1) + 'px', 9070 marginTop: '-' + Math.round(ry * coords.y1) + 'px' 9071 }); 8684 9072 8685 this.controller.on( 'activate', this.refresh, this ); 9073 $( '#preview-favicon' ).css({ 9074 width: Math.round( preview_rx * this.imageWidth ) + 'px', 9075 height: Math.round( preview_ry * this.imageHeight ) + 'px', 9076 marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px', 9077 marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px' 9078 }); 9079 } 9080 }); 8686 9081 8687 this.controller.on( 'detach', function() { 8688 this.$browser.remove(); 8689 }, this ); 8690 }, 9082 module.exports = SiteIconPreview; 8691 9083 8692 refresh: function() {8693 if ( this.uploader ) {8694 this.uploader.refresh();8695 }8696 },8697 9084 8698 ready: function() { 8699 var postId = wp.media.view.settings.post.id, 8700 dropzone; 9085 /***/ }), 9086 /* 99 */ 9087 /***/ (function(module, exports) { 8701 9088 8702 // If the uploader already exists, bail. 8703 if ( this.uploader ) { 8704 return; 8705 } 9089 var View = wp.media.View, 9090 EditImage; 8706 9091 8707 if ( postId ) { 8708 this.options.uploader.params.post_id = postId; 8709 } 8710 this.uploader = new wp.Uploader( this.options.uploader ); 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'), 8711 9105 8712 dropzone = this.uploader.dropzone; 8713 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 8714 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 9106 initialize: function( options ) { 9107 this.editor = window.imageEdit; 9108 this.controller = options.controller; 9109 View.prototype.initialize.apply( this, arguments ); 9110 }, 8715 9111 8716 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 9112 prepare: function() { 9113 return this.model.toJSON(); 8717 9114 }, 8718 9115 8719 _ready: function() { 8720 this.controller.trigger( 'uploader:ready' ); 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 ) ); 8721 9119 }, 8722 9120 8723 show: function() { 8724 var $el = this.$el.show(); 9121 focus: function() { 9122 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 9123 }, 8725 9124 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 }); 9125 back: function() { 9126 var lastState = this.controller.lastState(); 9127 this.controller.setState( lastState ); 8731 9128 }, 8732 9129 8733 hide: function() { 8734 var $el = this.$el.css({ opacity: 0 }); 9130 refresh: function() { 9131 this.model.fetch(); 9132 }, 8735 9133 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 }); 9134 save: function() { 9135 var lastState = this.controller.lastState(); 8743 9136 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 ); 9137 this.model.fetch().done( _.bind( function() { 9138 this.controller.setState( lastState ); 9139 }, this ) ); 8750 9140 } 9141 8751 9142 }); 8752 9143 8753 module.exports = UploaderWindow; 9144 module.exports = EditImage; 9145 9146 9147 /***/ }), 9148 /* 100 */ 9149 /***/ (function(module, exports) { 8754 9150 8755 },{}],75:[function(require,module,exports){8756 9151 /** 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. 9152 * wp.media.view.Spinner 8767 9153 * 8768 * @memberOf wp.media 9154 * @memberOf wp.media.view 8769 9155 * 8770 9156 * @class 9157 * @augments wp.media.View 8771 9158 * @augments wp.Backbone.View 8772 9159 * @augments Backbone.View 8773 9160 */ 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(); 9161 var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{ 9162 tagName: 'span', 9163 className: 'spinner', 9164 spinnerTimeout: false, 9165 delay: 400, 8793 9166 8794 if ( this.model && this.model.off ) { 8795 this.model.off( null, null, this ); 9167 show: function() { 9168 if ( ! this.spinnerTimeout ) { 9169 this.spinnerTimeout = _.delay(function( $el ) { 9170 $el.addClass( 'is-active' ); 9171 }, this.delay, this.$el ); 8796 9172 } 8797 9173 8798 if ( this.collection && this.collection.off ) { 8799 this.collection.off( null, null, this ); 8800 } 9174 return this; 9175 }, 8801 9176 8802 // Unbind controller events. 8803 if ( this.controller && this.controller.off ) { 8804 this.controller.off( null, null, this ); 8805 } 9177 hide: function() { 9178 this.$el.removeClass( 'is-active' ); 9179 this.spinnerTimeout = clearTimeout( this.spinnerTimeout ); 8806 9180 8807 9181 return this; 8808 },8809 /**8810 * @returns {wp.media.View} Returns itself to allow chaining8811 */8812 remove: function() {8813 this.dispose();8814 /**8815 * call 'remove' directly on the parent class8816 */8817 return wp.Backbone.View.prototype.remove.apply( this, arguments );8818 9182 } 8819 9183 }); 8820 9184 8821 module.exports = View; 9185 module.exports = Spinner; 9186 8822 9187 8823 },{}]},{},[19]); 9188 /***/ }) 9189 /******/ ])); 9190 No newline at end of file -
new file webpack.config.dev.js
diff --git webpack.config.dev.js webpack.config.dev.js new file mode 100644 index 0000000000..27c8e74b23
- + 1 var path = require( 'path' ), 2 SOURCE_DIR = 'src/', 3 mediaConfig = {}, 4 mediaBuilds = [ 'audiovideo', 'grid', 'models', 'views' ], 5 webpack = require( 'webpack' ); 6 7 8 mediaBuilds.forEach( function ( build ) { 9 var path = SOURCE_DIR + 'wp-includes/js/media'; 10 mediaConfig[ build ] = './' + path + '/' + build + '.manifest.js'; 11 } ); 12 13 module.exports = { 14 cache: true, 15 watch: true, 16 entry: mediaConfig, 17 output: { 18 path: path.join( __dirname, 'src/wp-includes/js' ), 19 filename: 'media-[name].js' 20 } 21 }; -
new file webpack.config.prod.js
diff --git webpack.config.prod.js webpack.config.prod.js new file mode 100644 index 0000000000..8a0b1e4c5c
- + 1 var path = require( 'path' ), 2 SOURCE_DIR = 'src/', 3 mediaConfig = {}, 4 mediaBuilds = [ 'audiovideo', 'grid', 'models', 'views' ], 5 webpack = require( 'webpack' ); 6 7 8 mediaBuilds.forEach( function ( build ) { 9 var path = SOURCE_DIR + 'wp-includes/js/media'; 10 mediaConfig[ build ] = './' + path + '/' + build + '.manifest.js'; 11 } ); 12 13 module.exports = { 14 cache: true, 15 entry: mediaConfig, 16 output: { 17 path: path.join( __dirname, 'src/wp-includes/js' ), 18 filename: 'media-[name].js' 19 }, 20 plugins: [ 21 new webpack.optimize.ModuleConcatenationPlugin() 22 ] 23 };