[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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, { enumerable: true, get: getter }); 40 /******/ } 41 /******/ }; 42 /******/ 43 /******/ // define __esModule on exports 44 /******/ __webpack_require__.r = function(exports) { 45 /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 46 /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 47 /******/ } 48 /******/ Object.defineProperty(exports, '__esModule', { value: true }); 49 /******/ }; 50 /******/ 51 /******/ // create a fake namespace object 52 /******/ // mode & 1: value is a module id, require it 53 /******/ // mode & 2: merge all properties of value into the ns 54 /******/ // mode & 4: return value when already ns object 55 /******/ // mode & 8|1: behave like require 56 /******/ __webpack_require__.t = function(value, mode) { 57 /******/ if(mode & 1) value = __webpack_require__(value); 58 /******/ if(mode & 8) return value; 59 /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 60 /******/ var ns = Object.create(null); 61 /******/ __webpack_require__.r(ns); 62 /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 63 /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 64 /******/ return ns; 65 /******/ }; 66 /******/ 67 /******/ // getDefaultExport function for compatibility with non-harmony modules 68 /******/ __webpack_require__.n = function(module) { 69 /******/ var getter = module && module.__esModule ? 70 /******/ function getDefault() { return module['default']; } : 71 /******/ function getModuleExports() { return module; }; 72 /******/ __webpack_require__.d(getter, 'a', getter); 73 /******/ return getter; 74 /******/ }; 75 /******/ 76 /******/ // Object.prototype.hasOwnProperty.call 77 /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 78 /******/ 79 /******/ // __webpack_public_path__ 80 /******/ __webpack_require__.p = ""; 81 /******/ 82 /******/ 83 /******/ // Load entry module and return exports 84 /******/ return __webpack_require__(__webpack_require__.s = 29); 85 /******/ }) 86 /************************************************************************/ 87 /******/ (Array(29).concat([ 88 /* 29 */ 89 /***/ (function(module, exports, __webpack_require__) { 90 91 module.exports = __webpack_require__(30); 92 93 94 /***/ }), 95 /* 30 */ 96 /***/ (function(module, exports, __webpack_require__) { 97 98 /** 99 * @output wp-includes/js/media-views.js 100 */ 101 102 var media = wp.media, 103 $ = jQuery, 104 l10n; 105 106 media.isTouchDevice = ( 'ontouchend' in document ); 107 108 // Link any localized strings. 109 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 110 111 // Link any settings. 112 media.view.settings = l10n.settings || {}; 113 delete l10n.settings; 114 115 // Copy the `post` setting over to the model settings. 116 media.model.settings.post = media.view.settings.post; 117 118 // Check if the browser supports CSS 3.0 transitions. 119 $.support.transition = (function(){ 120 var style = document.documentElement.style, 121 transitions = { 122 WebkitTransition: 'webkitTransitionEnd', 123 MozTransition: 'transitionend', 124 OTransition: 'oTransitionEnd otransitionend', 125 transition: 'transitionend' 126 }, transition; 127 128 transition = _.find( _.keys( transitions ), function( transition ) { 129 return ! _.isUndefined( style[ transition ] ); 130 }); 131 132 return transition && { 133 end: transitions[ transition ] 134 }; 135 }()); 136 137 /** 138 * A shared event bus used to provide events into 139 * the media workflows that 3rd-party devs can use to hook 140 * in. 141 */ 142 media.events = _.extend( {}, Backbone.Events ); 143 144 /** 145 * Makes it easier to bind events using transitions. 146 * 147 * @param {string} selector 148 * @param {number} sensitivity 149 * @return {Promise} 150 */ 151 media.transition = function( selector, sensitivity ) { 152 var deferred = $.Deferred(); 153 154 sensitivity = sensitivity || 2000; 155 156 if ( $.support.transition ) { 157 if ( ! (selector instanceof $) ) { 158 selector = $( selector ); 159 } 160 161 // Resolve the deferred when the first element finishes animating. 162 selector.first().one( $.support.transition.end, deferred.resolve ); 163 164 // Just in case the event doesn't trigger, fire a callback. 165 _.delay( deferred.resolve, sensitivity ); 166 167 // Otherwise, execute on the spot. 168 } else { 169 deferred.resolve(); 170 } 171 172 return deferred.promise(); 173 }; 174 175 media.controller.Region = __webpack_require__( 31 ); 176 media.controller.StateMachine = __webpack_require__( 32 ); 177 media.controller.State = __webpack_require__( 33 ); 178 179 media.selectionSync = __webpack_require__( 34 ); 180 media.controller.Library = __webpack_require__( 35 ); 181 media.controller.ImageDetails = __webpack_require__( 36 ); 182 media.controller.GalleryEdit = __webpack_require__( 37 ); 183 media.controller.GalleryAdd = __webpack_require__( 38 ); 184 media.controller.CollectionEdit = __webpack_require__( 39 ); 185 media.controller.CollectionAdd = __webpack_require__( 40 ); 186 media.controller.FeaturedImage = __webpack_require__( 41 ); 187 media.controller.ReplaceImage = __webpack_require__( 42 ); 188 media.controller.EditImage = __webpack_require__( 43 ); 189 media.controller.MediaLibrary = __webpack_require__( 44 ); 190 media.controller.Embed = __webpack_require__( 45 ); 191 media.controller.Cropper = __webpack_require__( 46 ); 192 media.controller.CustomizeImageCropper = __webpack_require__( 47 ); 193 media.controller.SiteIconCropper = __webpack_require__( 48 ); 194 195 media.View = __webpack_require__( 49 ); 196 media.view.Frame = __webpack_require__( 50 ); 197 media.view.MediaFrame = __webpack_require__( 51 ); 198 media.view.MediaFrame.Select = __webpack_require__( 52 ); 199 media.view.MediaFrame.Post = __webpack_require__( 53 ); 200 media.view.MediaFrame.ImageDetails = __webpack_require__( 54 ); 201 media.view.Modal = __webpack_require__( 55 ); 202 media.view.FocusManager = __webpack_require__( 56 ); 203 media.view.UploaderWindow = __webpack_require__( 57 ); 204 media.view.EditorUploader = __webpack_require__( 58 ); 205 media.view.UploaderInline = __webpack_require__( 59 ); 206 media.view.UploaderStatus = __webpack_require__( 60 ); 207 media.view.UploaderStatusError = __webpack_require__( 61 ); 208 media.view.Toolbar = __webpack_require__( 62 ); 209 media.view.Toolbar.Select = __webpack_require__( 63 ); 210 media.view.Toolbar.Embed = __webpack_require__( 64 ); 211 media.view.Button = __webpack_require__( 65 ); 212 media.view.ButtonGroup = __webpack_require__( 66 ); 213 media.view.PriorityList = __webpack_require__( 67 ); 214 media.view.MenuItem = __webpack_require__( 68 ); 215 media.view.Menu = __webpack_require__( 69 ); 216 media.view.RouterItem = __webpack_require__( 70 ); 217 media.view.Router = __webpack_require__( 71 ); 218 media.view.Sidebar = __webpack_require__( 72 ); 219 media.view.Attachment = __webpack_require__( 73 ); 220 media.view.Attachment.Library = __webpack_require__( 74 ); 221 media.view.Attachment.EditLibrary = __webpack_require__( 75 ); 222 media.view.Attachments = __webpack_require__( 76 ); 223 media.view.Search = __webpack_require__( 77 ); 224 media.view.AttachmentFilters = __webpack_require__( 78 ); 225 media.view.DateFilter = __webpack_require__( 79 ); 226 media.view.AttachmentFilters.Uploaded = __webpack_require__( 80 ); 227 media.view.AttachmentFilters.All = __webpack_require__( 81 ); 228 media.view.AttachmentsBrowser = __webpack_require__( 82 ); 229 media.view.Selection = __webpack_require__( 83 ); 230 media.view.Attachment.Selection = __webpack_require__( 84 ); 231 media.view.Attachments.Selection = __webpack_require__( 85 ); 232 media.view.Attachment.EditSelection = __webpack_require__( 86 ); 233 media.view.Settings = __webpack_require__( 87 ); 234 media.view.Settings.AttachmentDisplay = __webpack_require__( 88 ); 235 media.view.Settings.Gallery = __webpack_require__( 89 ); 236 media.view.Settings.Playlist = __webpack_require__( 90 ); 237 media.view.Attachment.Details = __webpack_require__( 91 ); 238 media.view.AttachmentCompat = __webpack_require__( 92 ); 239 media.view.Iframe = __webpack_require__( 93 ); 240 media.view.Embed = __webpack_require__( 94 ); 241 media.view.Label = __webpack_require__( 95 ); 242 media.view.EmbedUrl = __webpack_require__( 96 ); 243 media.view.EmbedLink = __webpack_require__( 97 ); 244 media.view.EmbedImage = __webpack_require__( 98 ); 245 media.view.ImageDetails = __webpack_require__( 99 ); 246 media.view.Cropper = __webpack_require__( 100 ); 247 media.view.SiteIconCropper = __webpack_require__( 101 ); 248 media.view.SiteIconPreview = __webpack_require__( 102 ); 249 media.view.EditImage = __webpack_require__( 103 ); 250 media.view.Spinner = __webpack_require__( 104 ); 251 media.view.Heading = __webpack_require__( 105 ); 252 253 254 /***/ }), 255 /* 31 */ 256 /***/ (function(module, exports) { 257 258 /** 259 * wp.media.controller.Region 260 * 261 * A region is a persistent application layout area. 262 * 263 * A region assumes one mode at any time, and can be switched to another. 264 * 265 * When mode changes, events are triggered on the region's parent view. 266 * The parent view will listen to specific events and fill the region with an 267 * appropriate view depending on mode. For example, a frame listens for the 268 * 'browse' mode t be activated on the 'content' view and then fills the region 269 * with an AttachmentsBrowser view. 270 * 271 * @memberOf wp.media.controller 272 * 273 * @class 274 * 275 * @param {Object} options Options hash for the region. 276 * @param {string} options.id Unique identifier for the region. 277 * @param {Backbone.View} options.view A parent view the region exists within. 278 * @param {string} options.selector jQuery selector for the region within the parent view. 279 */ 280 var Region = function( options ) { 281 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 282 }; 283 284 // Use Backbone's self-propagating `extend` inheritance method. 285 Region.extend = Backbone.Model.extend; 286 287 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{ 288 /** 289 * Activate a mode. 290 * 291 * @since 3.5.0 292 * 293 * @param {string} mode 294 * 295 * @fires Region#activate 296 * @fires Region#deactivate 297 * 298 * @return {wp.media.controller.Region} Returns itself to allow chaining. 299 */ 300 mode: function( mode ) { 301 if ( ! mode ) { 302 return this._mode; 303 } 304 // Bail if we're trying to change to the current mode. 305 if ( mode === this._mode ) { 306 return this; 307 } 308 309 /** 310 * Region mode deactivation event. 311 * 312 * @event wp.media.controller.Region#deactivate 313 */ 314 this.trigger('deactivate'); 315 316 this._mode = mode; 317 this.render( mode ); 318 319 /** 320 * Region mode activation event. 321 * 322 * @event wp.media.controller.Region#activate 323 */ 324 this.trigger('activate'); 325 return this; 326 }, 327 /** 328 * Render a mode. 329 * 330 * @since 3.5.0 331 * 332 * @param {string} mode 333 * 334 * @fires Region#create 335 * @fires Region#render 336 * 337 * @return {wp.media.controller.Region} Returns itself to allow chaining. 338 */ 339 render: function( mode ) { 340 // If the mode isn't active, activate it. 341 if ( mode && mode !== this._mode ) { 342 return this.mode( mode ); 343 } 344 345 var set = { view: null }, 346 view; 347 348 /** 349 * Create region view event. 350 * 351 * Region view creation takes place in an event callback on the frame. 352 * 353 * @event wp.media.controller.Region#create 354 * @type {object} 355 * @property {object} view 356 */ 357 this.trigger( 'create', set ); 358 view = set.view; 359 360 /** 361 * Render region view event. 362 * 363 * Region view creation takes place in an event callback on the frame. 364 * 365 * @event wp.media.controller.Region#render 366 * @type {object} 367 */ 368 this.trigger( 'render', view ); 369 if ( view ) { 370 this.set( view ); 371 } 372 return this; 373 }, 374 375 /** 376 * Get the region's view. 377 * 378 * @since 3.5.0 379 * 380 * @return {wp.media.View} 381 */ 382 get: function() { 383 return this.view.views.first( this.selector ); 384 }, 385 386 /** 387 * Set the region's view as a subview of the frame. 388 * 389 * @since 3.5.0 390 * 391 * @param {Array|Object} views 392 * @param {Object} [options={}] 393 * @return {wp.Backbone.Subviews} Subviews is returned to allow chaining. 394 */ 395 set: function( views, options ) { 396 if ( options ) { 397 options.add = false; 398 } 399 return this.view.views.set( this.selector, views, options ); 400 }, 401 402 /** 403 * Trigger regional view events on the frame. 404 * 405 * @since 3.5.0 406 * 407 * @param {string} event 408 * @return {undefined|wp.media.controller.Region} Returns itself to allow chaining. 409 */ 410 trigger: function( event ) { 411 var base, args; 412 413 if ( ! this._mode ) { 414 return; 415 } 416 417 args = _.toArray( arguments ); 418 base = this.id + ':' + event; 419 420 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 421 args[0] = base + ':' + this._mode; 422 this.view.trigger.apply( this.view, args ); 423 424 // Trigger `{this.id}:{event}` event on the frame. 425 args[0] = base; 426 this.view.trigger.apply( this.view, args ); 427 return this; 428 } 429 }); 430 431 module.exports = Region; 432 433 434 /***/ }), 435 /* 32 */ 436 /***/ (function(module, exports) { 437 438 /** 439 * wp.media.controller.StateMachine 440 * 441 * A state machine keeps track of state. It is in one state at a time, 442 * and can change from one state to another. 443 * 444 * States are stored as models in a Backbone collection. 445 * 446 * @memberOf wp.media.controller 447 * 448 * @since 3.5.0 449 * 450 * @class 451 * @augments Backbone.Model 452 * @mixin 453 * @mixes Backbone.Events 454 */ 455 var StateMachine = function() { 456 return { 457 // Use Backbone's self-propagating `extend` inheritance method. 458 extend: Backbone.Model.extend 459 }; 460 }; 461 462 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{ 463 /** 464 * Fetch a state. 465 * 466 * If no `id` is provided, returns the active state. 467 * 468 * Implicitly creates states. 469 * 470 * Ensure that the `states` collection exists so the `StateMachine` 471 * can be used as a mixin. 472 * 473 * @since 3.5.0 474 * 475 * @param {string} id 476 * @return {wp.media.controller.State} Returns a State model from 477 * the StateMachine collection. 478 */ 479 state: function( id ) { 480 this.states = this.states || new Backbone.Collection(); 481 482 // Default to the active state. 483 id = id || this._state; 484 485 if ( id && ! this.states.get( id ) ) { 486 this.states.add({ id: id }); 487 } 488 return this.states.get( id ); 489 }, 490 491 /** 492 * Sets the active state. 493 * 494 * Bail if we're trying to select the current state, if we haven't 495 * created the `states` collection, or are trying to select a state 496 * that does not exist. 497 * 498 * @since 3.5.0 499 * 500 * @param {string} id 501 * 502 * @fires wp.media.controller.State#deactivate 503 * @fires wp.media.controller.State#activate 504 * 505 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 506 */ 507 setState: function( id ) { 508 var previous = this.state(); 509 510 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 511 return this; 512 } 513 514 if ( previous ) { 515 previous.trigger('deactivate'); 516 this._lastState = previous.id; 517 } 518 519 this._state = id; 520 this.state().trigger('activate'); 521 522 return this; 523 }, 524 525 /** 526 * Returns the previous active state. 527 * 528 * Call the `state()` method with no parameters to retrieve the current 529 * active state. 530 * 531 * @since 3.5.0 532 * 533 * @return {wp.media.controller.State} Returns a State model from 534 * the StateMachine collection. 535 */ 536 lastState: function() { 537 if ( this._lastState ) { 538 return this.state( this._lastState ); 539 } 540 } 541 }); 542 543 // Map all event binding and triggering on a StateMachine to its `states` collection. 544 _.each([ 'on', 'off', 'trigger' ], function( method ) { 545 /** 546 * @function on 547 * @memberOf wp.media.controller.StateMachine 548 * @instance 549 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 550 */ 551 /** 552 * @function off 553 * @memberOf wp.media.controller.StateMachine 554 * @instance 555 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 556 */ 557 /** 558 * @function trigger 559 * @memberOf wp.media.controller.StateMachine 560 * @instance 561 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 562 */ 563 StateMachine.prototype[ method ] = function() { 564 // Ensure that the `states` collection exists so the `StateMachine` 565 // can be used as a mixin. 566 this.states = this.states || new Backbone.Collection(); 567 // Forward the method to the `states` collection. 568 this.states[ method ].apply( this.states, arguments ); 569 return this; 570 }; 571 }); 572 573 module.exports = StateMachine; 574 575 576 /***/ }), 577 /* 33 */ 578 /***/ (function(module, exports) { 579 580 /** 581 * wp.media.controller.State 582 * 583 * A state is a step in a workflow that when set will trigger the controllers 584 * for the regions to be updated as specified in the frame. 585 * 586 * A state has an event-driven lifecycle: 587 * 588 * 'ready' triggers when a state is added to a state machine's collection. 589 * 'activate' triggers when a state is activated by a state machine. 590 * 'deactivate' triggers when a state is deactivated by a state machine. 591 * 'reset' is not triggered automatically. It should be invoked by the 592 * proper controller to reset the state to its default. 593 * 594 * @memberOf wp.media.controller 595 * 596 * @class 597 * @augments Backbone.Model 598 */ 599 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{ 600 /** 601 * Constructor. 602 * 603 * @since 3.5.0 604 */ 605 constructor: function() { 606 this.on( 'activate', this._preActivate, this ); 607 this.on( 'activate', this.activate, this ); 608 this.on( 'activate', this._postActivate, this ); 609 this.on( 'deactivate', this._deactivate, this ); 610 this.on( 'deactivate', this.deactivate, this ); 611 this.on( 'reset', this.reset, this ); 612 this.on( 'ready', this._ready, this ); 613 this.on( 'ready', this.ready, this ); 614 /** 615 * Call parent constructor with passed arguments 616 */ 617 Backbone.Model.apply( this, arguments ); 618 this.on( 'change:menu', this._updateMenu, this ); 619 }, 620 /** 621 * Ready event callback. 622 * 623 * @abstract 624 * @since 3.5.0 625 */ 626 ready: function() {}, 627 628 /** 629 * Activate event callback. 630 * 631 * @abstract 632 * @since 3.5.0 633 */ 634 activate: function() {}, 635 636 /** 637 * Deactivate event callback. 638 * 639 * @abstract 640 * @since 3.5.0 641 */ 642 deactivate: function() {}, 643 644 /** 645 * Reset event callback. 646 * 647 * @abstract 648 * @since 3.5.0 649 */ 650 reset: function() {}, 651 652 /** 653 * @since 3.5.0 654 * @access private 655 */ 656 _ready: function() { 657 this._updateMenu(); 658 }, 659 660 /** 661 * @since 3.5.0 662 * @access private 663 */ 664 _preActivate: function() { 665 this.active = true; 666 }, 667 668 /** 669 * @since 3.5.0 670 * @access private 671 */ 672 _postActivate: function() { 673 this.on( 'change:menu', this._menu, this ); 674 this.on( 'change:titleMode', this._title, this ); 675 this.on( 'change:content', this._content, this ); 676 this.on( 'change:toolbar', this._toolbar, this ); 677 678 this.frame.on( 'title:render:default', this._renderTitle, this ); 679 680 this._title(); 681 this._menu(); 682 this._toolbar(); 683 this._content(); 684 this._router(); 685 }, 686 687 /** 688 * @since 3.5.0 689 * @access private 690 */ 691 _deactivate: function() { 692 this.active = false; 693 694 this.frame.off( 'title:render:default', this._renderTitle, this ); 695 696 this.off( 'change:menu', this._menu, this ); 697 this.off( 'change:titleMode', this._title, this ); 698 this.off( 'change:content', this._content, this ); 699 this.off( 'change:toolbar', this._toolbar, this ); 700 }, 701 702 /** 703 * @since 3.5.0 704 * @access private 705 */ 706 _title: function() { 707 this.frame.title.render( this.get('titleMode') || 'default' ); 708 }, 709 710 /** 711 * @since 3.5.0 712 * @access private 713 */ 714 _renderTitle: function( view ) { 715 view.$el.text( this.get('title') || '' ); 716 }, 717 718 /** 719 * @since 3.5.0 720 * @access private 721 */ 722 _router: function() { 723 var router = this.frame.router, 724 mode = this.get('router'), 725 view; 726 727 this.frame.$el.toggleClass( 'hide-router', ! mode ); 728 if ( ! mode ) { 729 return; 730 } 731 732 this.frame.router.render( mode ); 733 734 view = router.get(); 735 if ( view && view.select ) { 736 view.select( this.frame.content.mode() ); 737 } 738 }, 739 740 /** 741 * @since 3.5.0 742 * @access private 743 */ 744 _menu: function() { 745 var menu = this.frame.menu, 746 mode = this.get('menu'), 747 view; 748 749 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 750 if ( ! mode ) { 751 return; 752 } 753 754 menu.mode( mode ); 755 756 view = menu.get(); 757 if ( view && view.select ) { 758 view.select( this.id ); 759 } 760 }, 761 762 /** 763 * @since 3.5.0 764 * @access private 765 */ 766 _updateMenu: function() { 767 var previous = this.previous('menu'), 768 menu = this.get('menu'); 769 770 if ( previous ) { 771 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 772 } 773 774 if ( menu ) { 775 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 776 } 777 }, 778 779 /** 780 * Create a view in the media menu for the state. 781 * 782 * @since 3.5.0 783 * @access private 784 * 785 * @param {media.view.Menu} view The menu view. 786 */ 787 _renderMenu: function( view ) { 788 var menuItem = this.get('menuItem'), 789 title = this.get('title'), 790 priority = this.get('priority'); 791 792 if ( ! menuItem && title ) { 793 menuItem = { text: title }; 794 795 if ( priority ) { 796 menuItem.priority = priority; 797 } 798 } 799 800 if ( ! menuItem ) { 801 return; 802 } 803 804 view.set( this.id, menuItem ); 805 } 806 }); 807 808 _.each(['toolbar','content'], function( region ) { 809 /** 810 * @access private 811 */ 812 State.prototype[ '_' + region ] = function() { 813 var mode = this.get( region ); 814 if ( mode ) { 815 this.frame[ region ].render( mode ); 816 } 817 }; 818 }); 819 820 module.exports = State; 821 822 823 /***/ }), 824 /* 34 */ 825 /***/ (function(module, exports) { 826 827 /** 828 * wp.media.selectionSync 829 * 830 * Sync an attachments selection in a state with another state. 831 * 832 * Allows for selecting multiple images in the Add Media workflow, and then 833 * switching to the Insert Gallery workflow while preserving the attachments selection. 834 * 835 * @memberOf wp.media 836 * 837 * @mixin 838 */ 839 var selectionSync = { 840 /** 841 * @since 3.5.0 842 */ 843 syncSelection: function() { 844 var selection = this.get('selection'), 845 manager = this.frame._selection; 846 847 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 848 return; 849 } 850 851 /* 852 * If the selection supports multiple items, validate the stored 853 * attachments based on the new selection's conditions. Record 854 * the attachments that are not included; we'll maintain a 855 * reference to those. Other attachments are considered in flux. 856 */ 857 if ( selection.multiple ) { 858 selection.reset( [], { silent: true }); 859 selection.validateAll( manager.attachments ); 860 manager.difference = _.difference( manager.attachments.models, selection.models ); 861 } 862 863 // Sync the selection's single item with the master. 864 selection.single( manager.single ); 865 }, 866 867 /** 868 * Record the currently active attachments, which is a combination 869 * of the selection's attachments and the set of selected 870 * attachments that this specific selection considered invalid. 871 * Reset the difference and record the single attachment. 872 * 873 * @since 3.5.0 874 */ 875 recordSelection: function() { 876 var selection = this.get('selection'), 877 manager = this.frame._selection; 878 879 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 880 return; 881 } 882 883 if ( selection.multiple ) { 884 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 885 manager.difference = []; 886 } else { 887 manager.attachments.add( selection.toArray() ); 888 } 889 890 manager.single = selection._single; 891 } 892 }; 893 894 module.exports = selectionSync; 895 896 897 /***/ }), 898 /* 35 */ 899 /***/ (function(module, exports) { 900 901 var l10n = wp.media.view.l10n, 902 getUserSetting = window.getUserSetting, 903 setUserSetting = window.setUserSetting, 904 Library; 905 906 /** 907 * wp.media.controller.Library 908 * 909 * A state for choosing an attachment or group of attachments from the media library. 910 * 911 * @memberOf wp.media.controller 912 * 913 * @class 914 * @augments wp.media.controller.State 915 * @augments Backbone.Model 916 * @mixes media.selectionSync 917 * 918 * @param {object} [attributes] The attributes hash passed to the state. 919 * @param {string} [attributes.id=library] Unique identifier. 920 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 921 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 922 * If one is not supplied, a collection of all attachments will be created. 923 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 924 * If the 'selection' attribute is a plain JS object, 925 * a Selection will be created using its values as the selection instance's `props` model. 926 * Otherwise, it will copy the library's `props` model. 927 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 928 * @param {string} [attributes.content=upload] Initial mode for the content region. 929 * Overridden by persistent user setting if 'contentUserSetting' is true. 930 * @param {string} [attributes.menu=default] Initial mode for the menu region. 931 * @param {string} [attributes.router=browse] Initial mode for the router region. 932 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 933 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 934 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 935 * Accepts 'all', 'uploaded', or 'unattached'. 936 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 937 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 938 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 939 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 940 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 941 */ 942 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{ 943 defaults: { 944 id: 'library', 945 title: l10n.mediaLibraryTitle, 946 multiple: false, 947 content: 'upload', 948 menu: 'default', 949 router: 'browse', 950 toolbar: 'select', 951 searchable: true, 952 filterable: false, 953 sortable: true, 954 autoSelect: true, 955 describe: false, 956 contentUserSetting: true, 957 syncSelection: true 958 }, 959 960 /** 961 * If a library isn't provided, query all media items. 962 * If a selection instance isn't provided, create one. 963 * 964 * @since 3.5.0 965 */ 966 initialize: function() { 967 var selection = this.get('selection'), 968 props; 969 970 if ( ! this.get('library') ) { 971 this.set( 'library', wp.media.query() ); 972 } 973 974 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 975 props = selection; 976 977 if ( ! props ) { 978 props = this.get('library').props.toJSON(); 979 props = _.omit( props, 'orderby', 'query' ); 980 } 981 982 this.set( 'selection', new wp.media.model.Selection( null, { 983 multiple: this.get('multiple'), 984 props: props 985 }) ); 986 } 987 988 this.resetDisplays(); 989 }, 990 991 /** 992 * @since 3.5.0 993 */ 994 activate: function() { 995 this.syncSelection(); 996 997 wp.Uploader.queue.on( 'add', this.uploading, this ); 998 999 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 1000 1001 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 1002 this.frame.on( 'content:activate', this.saveContentMode, this ); 1003 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 1004 } 1005 }, 1006 1007 /** 1008 * @since 3.5.0 1009 */ 1010 deactivate: function() { 1011 this.recordSelection(); 1012 1013 this.frame.off( 'content:activate', this.saveContentMode, this ); 1014 1015 // Unbind all event handlers that use this state as the context 1016 // from the selection. 1017 this.get('selection').off( null, null, this ); 1018 1019 wp.Uploader.queue.off( null, null, this ); 1020 }, 1021 1022 /** 1023 * Reset the library to its initial state. 1024 * 1025 * @since 3.5.0 1026 */ 1027 reset: function() { 1028 this.get('selection').reset(); 1029 this.resetDisplays(); 1030 this.refreshContent(); 1031 }, 1032 1033 /** 1034 * Reset the attachment display settings defaults to the site options. 1035 * 1036 * If site options don't define them, fall back to a persistent user setting. 1037 * 1038 * @since 3.5.0 1039 */ 1040 resetDisplays: function() { 1041 var defaultProps = wp.media.view.settings.defaultProps; 1042 this._displays = []; 1043 this._defaultDisplaySettings = { 1044 align: getUserSetting( 'align', defaultProps.align ) || 'none', 1045 size: getUserSetting( 'imgsize', defaultProps.size ) || 'medium', 1046 link: getUserSetting( 'urlbutton', defaultProps.link ) || 'none' 1047 }; 1048 }, 1049 1050 /** 1051 * Create a model to represent display settings (alignment, etc.) for an attachment. 1052 * 1053 * @since 3.5.0 1054 * 1055 * @param {wp.media.model.Attachment} attachment 1056 * @return {Backbone.Model} 1057 */ 1058 display: function( attachment ) { 1059 var displays = this._displays; 1060 1061 if ( ! displays[ attachment.cid ] ) { 1062 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 1063 } 1064 return displays[ attachment.cid ]; 1065 }, 1066 1067 /** 1068 * Given an attachment, create attachment display settings properties. 1069 * 1070 * @since 3.6.0 1071 * 1072 * @param {wp.media.model.Attachment} attachment 1073 * @return {Object} 1074 */ 1075 defaultDisplaySettings: function( attachment ) { 1076 var settings = _.clone( this._defaultDisplaySettings ); 1077 1078 settings.canEmbed = this.canEmbed( attachment ); 1079 if ( settings.canEmbed ) { 1080 settings.link = 'embed'; 1081 } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) { 1082 settings.link = 'file'; 1083 } 1084 1085 return settings; 1086 }, 1087 1088 /** 1089 * Whether an attachment is image. 1090 * 1091 * @since 4.4.1 1092 * 1093 * @param {wp.media.model.Attachment} attachment 1094 * @return {boolean} 1095 */ 1096 isImageAttachment: function( attachment ) { 1097 // If uploading, we know the filename but not the mime type. 1098 if ( attachment.get('uploading') ) { 1099 return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') ); 1100 } 1101 1102 return attachment.get('type') === 'image'; 1103 }, 1104 1105 /** 1106 * Whether an attachment can be embedded (audio or video). 1107 * 1108 * @since 3.6.0 1109 * 1110 * @param {wp.media.model.Attachment} attachment 1111 * @return {boolean} 1112 */ 1113 canEmbed: function( attachment ) { 1114 // If uploading, we know the filename but not the mime type. 1115 if ( ! attachment.get('uploading') ) { 1116 var type = attachment.get('type'); 1117 if ( type !== 'audio' && type !== 'video' ) { 1118 return false; 1119 } 1120 } 1121 1122 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1123 }, 1124 1125 1126 /** 1127 * If the state is active, no items are selected, and the current 1128 * content mode is not an option in the state's router (provided 1129 * the state has a router), reset the content mode to the default. 1130 * 1131 * @since 3.5.0 1132 */ 1133 refreshContent: function() { 1134 var selection = this.get('selection'), 1135 frame = this.frame, 1136 router = frame.router.get(), 1137 mode = frame.content.mode(); 1138 1139 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1140 this.frame.content.render( this.get('content') ); 1141 } 1142 }, 1143 1144 /** 1145 * Callback handler when an attachment is uploaded. 1146 * 1147 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 1148 * 1149 * Adds any uploading attachments to the selection. 1150 * 1151 * If the state only supports one attachment to be selected and multiple 1152 * attachments are uploaded, the last attachment in the upload queue will 1153 * be selected. 1154 * 1155 * @since 3.5.0 1156 * 1157 * @param {wp.media.model.Attachment} attachment 1158 */ 1159 uploading: function( attachment ) { 1160 var content = this.frame.content; 1161 1162 if ( 'upload' === content.mode() ) { 1163 this.frame.content.mode('browse'); 1164 } 1165 1166 if ( this.get( 'autoSelect' ) ) { 1167 this.get('selection').add( attachment ); 1168 this.frame.trigger( 'library:selection:add' ); 1169 } 1170 }, 1171 1172 /** 1173 * Persist the mode of the content region as a user setting. 1174 * 1175 * @since 3.5.0 1176 */ 1177 saveContentMode: function() { 1178 if ( 'browse' !== this.get('router') ) { 1179 return; 1180 } 1181 1182 var mode = this.frame.content.mode(), 1183 view = this.frame.router.get(); 1184 1185 if ( view && view.get( mode ) ) { 1186 setUserSetting( 'libraryContent', mode ); 1187 } 1188 } 1189 1190 }); 1191 1192 // Make selectionSync available on any Media Library state. 1193 _.extend( Library.prototype, wp.media.selectionSync ); 1194 1195 module.exports = Library; 1196 1197 1198 /***/ }), 1199 /* 36 */ 1200 /***/ (function(module, exports) { 1201 1202 var State = wp.media.controller.State, 1203 Library = wp.media.controller.Library, 1204 l10n = wp.media.view.l10n, 1205 ImageDetails; 1206 1207 /** 1208 * wp.media.controller.ImageDetails 1209 * 1210 * A state for editing the attachment display settings of an image that's been 1211 * inserted into the editor. 1212 * 1213 * @memberOf wp.media.controller 1214 * 1215 * @class 1216 * @augments wp.media.controller.State 1217 * @augments Backbone.Model 1218 * 1219 * @param {object} [attributes] The attributes hash passed to the state. 1220 * @param {string} [attributes.id=image-details] Unique identifier. 1221 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 1222 * @param {wp.media.model.Attachment} attributes.image The image's model. 1223 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 1224 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 1225 * @param {string|false} [attributes.router=false] Initial mode for the router region. 1226 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1227 * @param {boolean} [attributes.editing=false] Unused. 1228 * @param {int} [attributes.priority=60] Unused. 1229 * 1230 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1231 * however this may not do anything. 1232 */ 1233 ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{ 1234 defaults: _.defaults({ 1235 id: 'image-details', 1236 title: l10n.imageDetailsTitle, 1237 content: 'image-details', 1238 menu: false, 1239 router: false, 1240 toolbar: 'image-details', 1241 editing: false, 1242 priority: 60 1243 }, Library.prototype.defaults ), 1244 1245 /** 1246 * @since 3.9.0 1247 * 1248 * @param options Attributes 1249 */ 1250 initialize: function( options ) { 1251 this.image = options.image; 1252 State.prototype.initialize.apply( this, arguments ); 1253 }, 1254 1255 /** 1256 * @since 3.9.0 1257 */ 1258 activate: function() { 1259 this.frame.modal.$el.addClass('image-details'); 1260 } 1261 }); 1262 1263 module.exports = ImageDetails; 1264 1265 1266 /***/ }), 1267 /* 37 */ 1268 /***/ (function(module, exports) { 1269 1270 var Library = wp.media.controller.Library, 1271 l10n = wp.media.view.l10n, 1272 GalleryEdit; 1273 1274 /** 1275 * wp.media.controller.GalleryEdit 1276 * 1277 * A state for editing a gallery's images and settings. 1278 * 1279 * @since 3.5.0 1280 * 1281 * @class 1282 * @augments wp.media.controller.Library 1283 * @augments wp.media.controller.State 1284 * @augments Backbone.Model 1285 * 1286 * @memberOf wp.media.controller 1287 * 1288 * @param {Object} [attributes] The attributes hash passed to the state. 1289 * @param {string} [attributes.id=gallery-edit] Unique identifier. 1290 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 1291 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 1292 * If one is not supplied, an empty media.model.Selection collection is created. 1293 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1294 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1295 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1296 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1297 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 1298 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1299 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1300 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 1301 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1302 * @param {number} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1303 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1304 * @param {number} [attributes.priority=60] The priority for the state link in the media menu. 1305 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1306 * Defaults to false for this state, because the library passed in *is* the selection. 1307 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1308 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1309 */ 1310 GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{ 1311 defaults: { 1312 id: 'gallery-edit', 1313 title: l10n.editGalleryTitle, 1314 multiple: false, 1315 searchable: false, 1316 sortable: true, 1317 date: false, 1318 display: false, 1319 content: 'browse', 1320 toolbar: 'gallery-edit', 1321 describe: true, 1322 displaySettings: true, 1323 dragInfo: true, 1324 idealColumnWidth: 170, 1325 editing: false, 1326 priority: 60, 1327 syncSelection: false 1328 }, 1329 1330 /** 1331 * Initializes the library. 1332 * 1333 * Creates a selection if a library isn't supplied and creates an attachment 1334 * view if no attachment view is supplied. 1335 * 1336 * @since 3.5.0 1337 * 1338 * @return {void} 1339 */ 1340 initialize: function() { 1341 // If we haven't been provided a `library`, create a `Selection`. 1342 if ( ! this.get('library') ) { 1343 this.set( 'library', new wp.media.model.Selection() ); 1344 } 1345 1346 // The single `Attachment` view to be used in the `Attachments` view. 1347 if ( ! this.get('AttachmentView') ) { 1348 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1349 } 1350 1351 Library.prototype.initialize.apply( this, arguments ); 1352 }, 1353 1354 /** 1355 * Activates the library. 1356 * 1357 * Limits the library to images, watches for uploaded attachments. Watches for 1358 * the browse event on the frame and binds it to gallerySettings. 1359 * 1360 * @since 3.5.0 1361 * 1362 * @return {void} 1363 */ 1364 activate: function() { 1365 var library = this.get('library'); 1366 1367 // Limit the library to images only. 1368 library.props.set( 'type', 'image' ); 1369 1370 // Watch for uploaded attachments. 1371 this.get('library').observe( wp.Uploader.queue ); 1372 1373 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 1374 1375 Library.prototype.activate.apply( this, arguments ); 1376 }, 1377 1378 /** 1379 * Deactivates the library. 1380 * 1381 * Stops watching for uploaded attachments and browse events. 1382 * 1383 * @since 3.5.0 1384 * 1385 * @return {void} 1386 */ 1387 deactivate: function() { 1388 // Stop watching for uploaded attachments. 1389 this.get('library').unobserve( wp.Uploader.queue ); 1390 1391 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 1392 1393 Library.prototype.deactivate.apply( this, arguments ); 1394 }, 1395 1396 /** 1397 * Adds the gallery settings to the sidebar and adds a reverse button to the 1398 * toolbar. 1399 * 1400 * @since 3.5.0 1401 * 1402 * @param {wp.media.view.Frame} browser The file browser. 1403 * 1404 * @return {void} 1405 */ 1406 gallerySettings: function( browser ) { 1407 if ( ! this.get('displaySettings') ) { 1408 return; 1409 } 1410 1411 var library = this.get('library'); 1412 1413 if ( ! library || ! browser ) { 1414 return; 1415 } 1416 1417 library.gallery = library.gallery || new Backbone.Model(); 1418 1419 browser.sidebar.set({ 1420 gallery: new wp.media.view.Settings.Gallery({ 1421 controller: this, 1422 model: library.gallery, 1423 priority: 40 1424 }) 1425 }); 1426 1427 browser.toolbar.set( 'reverse', { 1428 text: l10n.reverseOrder, 1429 priority: 80, 1430 1431 click: function() { 1432 library.reset( library.toArray().reverse() ); 1433 } 1434 }); 1435 } 1436 }); 1437 1438 module.exports = GalleryEdit; 1439 1440 1441 /***/ }), 1442 /* 38 */ 1443 /***/ (function(module, exports) { 1444 1445 var Selection = wp.media.model.Selection, 1446 Library = wp.media.controller.Library, 1447 l10n = wp.media.view.l10n, 1448 GalleryAdd; 1449 1450 /** 1451 * wp.media.controller.GalleryAdd 1452 * 1453 * A state for selecting more images to add to a gallery. 1454 * 1455 * @since 3.5.0 1456 * 1457 * @class 1458 * @augments wp.media.controller.Library 1459 * @augments wp.media.controller.State 1460 * @augments Backbone.Model 1461 * 1462 * @memberof wp.media.controller 1463 * 1464 * @param {Object} [attributes] The attributes hash passed to the state. 1465 * @param {string} [attributes.id=gallery-library] Unique identifier. 1466 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 1467 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1468 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1469 * If one is not supplied, a collection of all images will be created. 1470 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1471 * Accepts 'all', 'uploaded', or 'unattached'. 1472 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1473 * @param {string} [attributes.content=upload] Initial mode for the content region. 1474 * Overridden by persistent user setting if 'contentUserSetting' is true. 1475 * @param {string} [attributes.router=browse] Initial mode for the router region. 1476 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1477 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1478 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1479 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1480 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1481 * @param {number} [attributes.priority=100] The priority for the state link in the media menu. 1482 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1483 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1484 */ 1485 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{ 1486 defaults: _.defaults({ 1487 id: 'gallery-library', 1488 title: l10n.addToGalleryTitle, 1489 multiple: 'add', 1490 filterable: 'uploaded', 1491 menu: 'gallery', 1492 toolbar: 'gallery-add', 1493 priority: 100, 1494 syncSelection: false 1495 }, Library.prototype.defaults ), 1496 1497 /** 1498 * Initializes the library. Creates a library of images if a library isn't supplied. 1499 * 1500 * @since 3.5.0 1501 * 1502 * @return {void} 1503 */ 1504 initialize: function() { 1505 if ( ! this.get('library') ) { 1506 this.set( 'library', wp.media.query({ type: 'image' }) ); 1507 } 1508 1509 Library.prototype.initialize.apply( this, arguments ); 1510 }, 1511 1512 /** 1513 * Activates the library. 1514 * 1515 * Removes all event listeners if in edit mode. Creates a validator to check an attachment. 1516 * Resets library and re-enables event listeners. Activates edit mode. Calls the parent's activate method. 1517 * 1518 * @since 3.5.0 1519 * 1520 * @return {void} 1521 */ 1522 activate: function() { 1523 var library = this.get('library'), 1524 edit = this.frame.state('gallery-edit').get('library'); 1525 1526 if ( this.editLibrary && this.editLibrary !== edit ) { 1527 library.unobserve( this.editLibrary ); 1528 } 1529 1530 /* 1531 * Accept attachments that exist in the original library but 1532 * that do not exist in gallery's library yet. 1533 */ 1534 library.validator = function( attachment ) { 1535 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1536 }; 1537 1538 /* 1539 * Reset the library to ensure that all attachments are re-added 1540 * to the collection. Do so silently, as calling `observe` will 1541 * trigger the `reset` event. 1542 */ 1543 library.reset( library.mirroring.models, { silent: true }); 1544 library.observe( edit ); 1545 this.editLibrary = edit; 1546 1547 Library.prototype.activate.apply( this, arguments ); 1548 } 1549 }); 1550 1551 module.exports = GalleryAdd; 1552 1553 1554 /***/ }), 1555 /* 39 */ 1556 /***/ (function(module, exports) { 1557 1558 var Library = wp.media.controller.Library, 1559 l10n = wp.media.view.l10n, 1560 $ = jQuery, 1561 CollectionEdit; 1562 1563 /** 1564 * wp.media.controller.CollectionEdit 1565 * 1566 * A state for editing a collection, which is used by audio and video playlists, 1567 * and can be used for other collections. 1568 * 1569 * @memberOf wp.media.controller 1570 * 1571 * @class 1572 * @augments wp.media.controller.Library 1573 * @augments wp.media.controller.State 1574 * @augments Backbone.Model 1575 * 1576 * @param {object} [attributes] The attributes hash passed to the state. 1577 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 1578 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 1579 * If one is not supplied, an empty media.model.Selection collection is created. 1580 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1581 * @param {string} [attributes.content=browse] Initial mode for the content region. 1582 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 1583 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1584 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1585 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1586 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 1587 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1588 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 1589 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1590 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1591 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1592 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1593 * Defaults to false for this state, because the library passed in *is* the selection. 1594 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 1595 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1596 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1597 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1598 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1599 */ 1600 CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{ 1601 defaults: { 1602 multiple: false, 1603 sortable: true, 1604 date: false, 1605 searchable: false, 1606 content: 'browse', 1607 describe: true, 1608 dragInfo: true, 1609 idealColumnWidth: 170, 1610 editing: false, 1611 priority: 60, 1612 SettingsView: false, 1613 syncSelection: false 1614 }, 1615 1616 /** 1617 * @since 3.9.0 1618 */ 1619 initialize: function() { 1620 var collectionType = this.get('collectionType'); 1621 1622 if ( 'video' === this.get( 'type' ) ) { 1623 collectionType = 'video-' + collectionType; 1624 } 1625 1626 this.set( 'id', collectionType + '-edit' ); 1627 this.set( 'toolbar', collectionType + '-edit' ); 1628 1629 // If we haven't been provided a `library`, create a `Selection`. 1630 if ( ! this.get('library') ) { 1631 this.set( 'library', new wp.media.model.Selection() ); 1632 } 1633 // The single `Attachment` view to be used in the `Attachments` view. 1634 if ( ! this.get('AttachmentView') ) { 1635 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1636 } 1637 Library.prototype.initialize.apply( this, arguments ); 1638 }, 1639 1640 /** 1641 * @since 3.9.0 1642 */ 1643 activate: function() { 1644 var library = this.get('library'); 1645 1646 // Limit the library to images only. 1647 library.props.set( 'type', this.get( 'type' ) ); 1648 1649 // Watch for uploaded attachments. 1650 this.get('library').observe( wp.Uploader.queue ); 1651 1652 this.frame.on( 'content:render:browse', this.renderSettings, this ); 1653 1654 Library.prototype.activate.apply( this, arguments ); 1655 }, 1656 1657 /** 1658 * @since 3.9.0 1659 */ 1660 deactivate: function() { 1661 // Stop watching for uploaded attachments. 1662 this.get('library').unobserve( wp.Uploader.queue ); 1663 1664 this.frame.off( 'content:render:browse', this.renderSettings, this ); 1665 1666 Library.prototype.deactivate.apply( this, arguments ); 1667 }, 1668 1669 /** 1670 * Render the collection embed settings view in the browser sidebar. 1671 * 1672 * @todo This is against the pattern elsewhere in media. Typically the frame 1673 * is responsible for adding region mode callbacks. Explain. 1674 * 1675 * @since 3.9.0 1676 * 1677 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 1678 */ 1679 renderSettings: function( attachmentsBrowserView ) { 1680 var library = this.get('library'), 1681 collectionType = this.get('collectionType'), 1682 dragInfoText = this.get('dragInfoText'), 1683 SettingsView = this.get('SettingsView'), 1684 obj = {}; 1685 1686 if ( ! library || ! attachmentsBrowserView ) { 1687 return; 1688 } 1689 1690 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 1691 1692 obj[ collectionType ] = new SettingsView({ 1693 controller: this, 1694 model: library[ collectionType ], 1695 priority: 40 1696 }); 1697 1698 attachmentsBrowserView.sidebar.set( obj ); 1699 1700 if ( dragInfoText ) { 1701 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 1702 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 1703 priority: -40 1704 }) ); 1705 } 1706 1707 // Add the 'Reverse order' button to the toolbar. 1708 attachmentsBrowserView.toolbar.set( 'reverse', { 1709 text: l10n.reverseOrder, 1710 priority: 80, 1711 1712 click: function() { 1713 library.reset( library.toArray().reverse() ); 1714 } 1715 }); 1716 } 1717 }); 1718 1719 module.exports = CollectionEdit; 1720 1721 1722 /***/ }), 1723 /* 40 */ 1724 /***/ (function(module, exports) { 1725 1726 var Selection = wp.media.model.Selection, 1727 Library = wp.media.controller.Library, 1728 CollectionAdd; 1729 1730 /** 1731 * wp.media.controller.CollectionAdd 1732 * 1733 * A state for adding attachments to a collection (e.g. video playlist). 1734 * 1735 * @memberOf wp.media.controller 1736 * 1737 * @class 1738 * @augments wp.media.controller.Library 1739 * @augments wp.media.controller.State 1740 * @augments Backbone.Model 1741 * 1742 * @param {object} [attributes] The attributes hash passed to the state. 1743 * @param {string} [attributes.id=library] Unique identifier. 1744 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 1745 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1746 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1747 * If one is not supplied, a collection of attachments of the specified type will be created. 1748 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1749 * Accepts 'all', 'uploaded', or 'unattached'. 1750 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1751 * @param {string} [attributes.content=upload] Initial mode for the content region. 1752 * Overridden by persistent user setting if 'contentUserSetting' is true. 1753 * @param {string} [attributes.router=browse] Initial mode for the router region. 1754 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1755 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1756 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1757 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1758 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1759 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1760 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1761 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1762 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1763 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1764 */ 1765 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{ 1766 defaults: _.defaults( { 1767 // Selection defaults. @see media.model.Selection 1768 multiple: 'add', 1769 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1770 filterable: 'uploaded', 1771 1772 priority: 100, 1773 syncSelection: false 1774 }, Library.prototype.defaults ), 1775 1776 /** 1777 * @since 3.9.0 1778 */ 1779 initialize: function() { 1780 var collectionType = this.get('collectionType'); 1781 1782 if ( 'video' === this.get( 'type' ) ) { 1783 collectionType = 'video-' + collectionType; 1784 } 1785 1786 this.set( 'id', collectionType + '-library' ); 1787 this.set( 'toolbar', collectionType + '-add' ); 1788 this.set( 'menu', collectionType ); 1789 1790 // If we haven't been provided a `library`, create a `Selection`. 1791 if ( ! this.get('library') ) { 1792 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 1793 } 1794 Library.prototype.initialize.apply( this, arguments ); 1795 }, 1796 1797 /** 1798 * @since 3.9.0 1799 */ 1800 activate: function() { 1801 var library = this.get('library'), 1802 editLibrary = this.get('editLibrary'), 1803 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 1804 1805 if ( editLibrary && editLibrary !== edit ) { 1806 library.unobserve( editLibrary ); 1807 } 1808 1809 // Accepts attachments that exist in the original library and 1810 // that do not exist in gallery's library. 1811 library.validator = function( attachment ) { 1812 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1813 }; 1814 1815 /* 1816 * Reset the library to ensure that all attachments are re-added 1817 * to the collection. Do so silently, as calling `observe` will 1818 * trigger the `reset` event. 1819 */ 1820 library.reset( library.mirroring.models, { silent: true }); 1821 library.observe( edit ); 1822 this.set('editLibrary', edit); 1823 1824 Library.prototype.activate.apply( this, arguments ); 1825 } 1826 }); 1827 1828 module.exports = CollectionAdd; 1829 1830 1831 /***/ }), 1832 /* 41 */ 1833 /***/ (function(module, exports) { 1834 1835 var Attachment = wp.media.model.Attachment, 1836 Library = wp.media.controller.Library, 1837 l10n = wp.media.view.l10n, 1838 FeaturedImage; 1839 1840 /** 1841 * wp.media.controller.FeaturedImage 1842 * 1843 * A state for selecting a featured image for a post. 1844 * 1845 * @memberOf wp.media.controller 1846 * 1847 * @class 1848 * @augments wp.media.controller.Library 1849 * @augments wp.media.controller.State 1850 * @augments Backbone.Model 1851 * 1852 * @param {object} [attributes] The attributes hash passed to the state. 1853 * @param {string} [attributes.id=featured-image] Unique identifier. 1854 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 1855 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1856 * If one is not supplied, a collection of all images will be created. 1857 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1858 * @param {string} [attributes.content=upload] Initial mode for the content region. 1859 * Overridden by persistent user setting if 'contentUserSetting' is true. 1860 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1861 * @param {string} [attributes.router=browse] Initial mode for the router region. 1862 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 1863 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1864 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1865 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1866 * Accepts 'all', 'uploaded', or 'unattached'. 1867 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1868 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1869 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1870 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1871 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1872 */ 1873 FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{ 1874 defaults: _.defaults({ 1875 id: 'featured-image', 1876 title: l10n.setFeaturedImageTitle, 1877 multiple: false, 1878 filterable: 'uploaded', 1879 toolbar: 'featured-image', 1880 priority: 60, 1881 syncSelection: true 1882 }, Library.prototype.defaults ), 1883 1884 /** 1885 * @since 3.5.0 1886 */ 1887 initialize: function() { 1888 var library, comparator; 1889 1890 // If we haven't been provided a `library`, create a `Selection`. 1891 if ( ! this.get('library') ) { 1892 this.set( 'library', wp.media.query({ type: 'image' }) ); 1893 } 1894 1895 Library.prototype.initialize.apply( this, arguments ); 1896 1897 library = this.get('library'); 1898 comparator = library.comparator; 1899 1900 // Overload the library's comparator to push items that are not in 1901 // the mirrored query to the front of the aggregate collection. 1902 library.comparator = function( a, b ) { 1903 var aInQuery = !! this.mirroring.get( a.cid ), 1904 bInQuery = !! this.mirroring.get( b.cid ); 1905 1906 if ( ! aInQuery && bInQuery ) { 1907 return -1; 1908 } else if ( aInQuery && ! bInQuery ) { 1909 return 1; 1910 } else { 1911 return comparator.apply( this, arguments ); 1912 } 1913 }; 1914 1915 // Add all items in the selection to the library, so any featured 1916 // images that are not initially loaded still appear. 1917 library.observe( this.get('selection') ); 1918 }, 1919 1920 /** 1921 * @since 3.5.0 1922 */ 1923 activate: function() { 1924 this.updateSelection(); 1925 this.frame.on( 'open', this.updateSelection, this ); 1926 1927 Library.prototype.activate.apply( this, arguments ); 1928 }, 1929 1930 /** 1931 * @since 3.5.0 1932 */ 1933 deactivate: function() { 1934 this.frame.off( 'open', this.updateSelection, this ); 1935 1936 Library.prototype.deactivate.apply( this, arguments ); 1937 }, 1938 1939 /** 1940 * @since 3.5.0 1941 */ 1942 updateSelection: function() { 1943 var selection = this.get('selection'), 1944 id = wp.media.view.settings.post.featuredImageId, 1945 attachment; 1946 1947 if ( '' !== id && -1 !== id ) { 1948 attachment = Attachment.get( id ); 1949 attachment.fetch(); 1950 } 1951 1952 selection.reset( attachment ? [ attachment ] : [] ); 1953 } 1954 }); 1955 1956 module.exports = FeaturedImage; 1957 1958 1959 /***/ }), 1960 /* 42 */ 1961 /***/ (function(module, exports) { 1962 1963 var Library = wp.media.controller.Library, 1964 l10n = wp.media.view.l10n, 1965 ReplaceImage; 1966 1967 /** 1968 * wp.media.controller.ReplaceImage 1969 * 1970 * A state for replacing an image. 1971 * 1972 * @memberOf wp.media.controller 1973 * 1974 * @class 1975 * @augments wp.media.controller.Library 1976 * @augments wp.media.controller.State 1977 * @augments Backbone.Model 1978 * 1979 * @param {object} [attributes] The attributes hash passed to the state. 1980 * @param {string} [attributes.id=replace-image] Unique identifier. 1981 * @param {string} [attributes.title=Replace Image] Title for the state. Displays in the media menu and the frame's title region. 1982 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1983 * If one is not supplied, a collection of all images will be created. 1984 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1985 * @param {string} [attributes.content=upload] Initial mode for the content region. 1986 * Overridden by persistent user setting if 'contentUserSetting' is true. 1987 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1988 * @param {string} [attributes.router=browse] Initial mode for the router region. 1989 * @param {string} [attributes.toolbar=replace] Initial mode for the toolbar region. 1990 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1991 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1992 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1993 * Accepts 'all', 'uploaded', or 'unattached'. 1994 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1995 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1996 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1997 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1998 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1999 */ 2000 ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{ 2001 defaults: _.defaults({ 2002 id: 'replace-image', 2003 title: l10n.replaceImageTitle, 2004 multiple: false, 2005 filterable: 'uploaded', 2006 toolbar: 'replace', 2007 menu: false, 2008 priority: 60, 2009 syncSelection: true 2010 }, Library.prototype.defaults ), 2011 2012 /** 2013 * @since 3.9.0 2014 * 2015 * @param options 2016 */ 2017 initialize: function( options ) { 2018 var library, comparator; 2019 2020 this.image = options.image; 2021 // If we haven't been provided a `library`, create a `Selection`. 2022 if ( ! this.get('library') ) { 2023 this.set( 'library', wp.media.query({ type: 'image' }) ); 2024 } 2025 2026 Library.prototype.initialize.apply( this, arguments ); 2027 2028 library = this.get('library'); 2029 comparator = library.comparator; 2030 2031 // Overload the library's comparator to push items that are not in 2032 // the mirrored query to the front of the aggregate collection. 2033 library.comparator = function( a, b ) { 2034 var aInQuery = !! this.mirroring.get( a.cid ), 2035 bInQuery = !! this.mirroring.get( b.cid ); 2036 2037 if ( ! aInQuery && bInQuery ) { 2038 return -1; 2039 } else if ( aInQuery && ! bInQuery ) { 2040 return 1; 2041 } else { 2042 return comparator.apply( this, arguments ); 2043 } 2044 }; 2045 2046 // Add all items in the selection to the library, so any featured 2047 // images that are not initially loaded still appear. 2048 library.observe( this.get('selection') ); 2049 }, 2050 2051 /** 2052 * @since 3.9.0 2053 */ 2054 activate: function() { 2055 this.updateSelection(); 2056 Library.prototype.activate.apply( this, arguments ); 2057 }, 2058 2059 /** 2060 * @since 3.9.0 2061 */ 2062 updateSelection: function() { 2063 var selection = this.get('selection'), 2064 attachment = this.image.attachment; 2065 2066 selection.reset( attachment ? [ attachment ] : [] ); 2067 } 2068 }); 2069 2070 module.exports = ReplaceImage; 2071 2072 2073 /***/ }), 2074 /* 43 */ 2075 /***/ (function(module, exports) { 2076 2077 var l10n = wp.media.view.l10n, 2078 EditImage; 2079 2080 /** 2081 * wp.media.controller.EditImage 2082 * 2083 * A state for editing (cropping, etc.) an image. 2084 * 2085 * @memberOf wp.media.controller 2086 * 2087 * @class 2088 * @augments wp.media.controller.State 2089 * @augments Backbone.Model 2090 * 2091 * @param {object} attributes The attributes hash passed to the state. 2092 * @param {wp.media.model.Attachment} attributes.model The attachment. 2093 * @param {string} [attributes.id=edit-image] Unique identifier. 2094 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 2095 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 2096 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 2097 * @param {string} [attributes.menu=false] Initial mode for the menu region. 2098 * @param {string} [attributes.url] Unused. @todo Consider removal. 2099 */ 2100 EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{ 2101 defaults: { 2102 id: 'edit-image', 2103 title: l10n.editImage, 2104 menu: false, 2105 toolbar: 'edit-image', 2106 content: 'edit-image', 2107 url: '' 2108 }, 2109 2110 /** 2111 * Activates a frame for editing a featured image. 2112 * 2113 * @since 3.9.0 2114 * 2115 * @return {void} 2116 */ 2117 activate: function() { 2118 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) ); 2119 }, 2120 2121 /** 2122 * Deactivates a frame for editing a featured image. 2123 * 2124 * @since 3.9.0 2125 * 2126 * @return {void} 2127 */ 2128 deactivate: function() { 2129 this.frame.off( 'toolbar:render:edit-image' ); 2130 }, 2131 2132 /** 2133 * Adds a toolbar with a back button. 2134 * 2135 * When the back button is pressed it checks whether there is a previous state. 2136 * In case there is a previous state it sets that previous state otherwise it 2137 * closes the frame. 2138 * 2139 * @since 3.9.0 2140 * 2141 * @return {void} 2142 */ 2143 toolbar: function() { 2144 var frame = this.frame, 2145 lastState = frame.lastState(), 2146 previous = lastState && lastState.id; 2147 2148 frame.toolbar.set( new wp.media.view.Toolbar({ 2149 controller: frame, 2150 items: { 2151 back: { 2152 style: 'primary', 2153 text: l10n.back, 2154 priority: 20, 2155 click: function() { 2156 if ( previous ) { 2157 frame.setState( previous ); 2158 } else { 2159 frame.close(); 2160 } 2161 } 2162 } 2163 } 2164 }) ); 2165 } 2166 }); 2167 2168 module.exports = EditImage; 2169 2170 2171 /***/ }), 2172 /* 44 */ 2173 /***/ (function(module, exports) { 2174 2175 /** 2176 * wp.media.controller.MediaLibrary 2177 * 2178 * @memberOf wp.media.controller 2179 * 2180 * @class 2181 * @augments wp.media.controller.Library 2182 * @augments wp.media.controller.State 2183 * @augments Backbone.Model 2184 */ 2185 var Library = wp.media.controller.Library, 2186 MediaLibrary; 2187 2188 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{ 2189 defaults: _.defaults({ 2190 // Attachments browser defaults. @see media.view.AttachmentsBrowser 2191 filterable: 'uploaded', 2192 2193 displaySettings: false, 2194 priority: 80, 2195 syncSelection: false 2196 }, Library.prototype.defaults ), 2197 2198 /** 2199 * @since 3.9.0 2200 * 2201 * @param options 2202 */ 2203 initialize: function( options ) { 2204 this.media = options.media; 2205 this.type = options.type; 2206 this.set( 'library', wp.media.query({ type: this.type }) ); 2207 2208 Library.prototype.initialize.apply( this, arguments ); 2209 }, 2210 2211 /** 2212 * @since 3.9.0 2213 */ 2214 activate: function() { 2215 // @todo this should use this.frame. 2216 if ( wp.media.frame.lastMime ) { 2217 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 2218 delete wp.media.frame.lastMime; 2219 } 2220 Library.prototype.activate.apply( this, arguments ); 2221 } 2222 }); 2223 2224 module.exports = MediaLibrary; 2225 2226 2227 /***/ }), 2228 /* 45 */ 2229 /***/ (function(module, exports) { 2230 2231 var l10n = wp.media.view.l10n, 2232 $ = Backbone.$, 2233 Embed; 2234 2235 /** 2236 * wp.media.controller.Embed 2237 * 2238 * A state for embedding media from a URL. 2239 * 2240 * @memberOf wp.media.controller 2241 * 2242 * @class 2243 * @augments wp.media.controller.State 2244 * @augments Backbone.Model 2245 * 2246 * @param {object} attributes The attributes hash passed to the state. 2247 * @param {string} [attributes.id=embed] Unique identifier. 2248 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 2249 * @param {string} [attributes.content=embed] Initial mode for the content region. 2250 * @param {string} [attributes.menu=default] Initial mode for the menu region. 2251 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 2252 * @param {string} [attributes.menu=false] Initial mode for the menu region. 2253 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 2254 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 2255 * @param {string} [attributes.url] The embed URL. 2256 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 2257 */ 2258 Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{ 2259 defaults: { 2260 id: 'embed', 2261 title: l10n.insertFromUrlTitle, 2262 content: 'embed', 2263 menu: 'default', 2264 toolbar: 'main-embed', 2265 priority: 120, 2266 type: 'link', 2267 url: '', 2268 metadata: {} 2269 }, 2270 2271 // The amount of time used when debouncing the scan. 2272 sensitivity: 400, 2273 2274 initialize: function(options) { 2275 this.metadata = options.metadata; 2276 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 2277 this.props = new Backbone.Model( this.metadata || { url: '' }); 2278 this.props.on( 'change:url', this.debouncedScan, this ); 2279 this.props.on( 'change:url', this.refresh, this ); 2280 this.on( 'scan', this.scanImage, this ); 2281 }, 2282 2283 /** 2284 * Trigger a scan of the embedded URL's content for metadata required to embed. 2285 * 2286 * @fires wp.media.controller.Embed#scan 2287 */ 2288 scan: function() { 2289 var scanners, 2290 embed = this, 2291 attributes = { 2292 type: 'link', 2293 scanners: [] 2294 }; 2295 2296 /* 2297 * Scan is triggered with the list of `attributes` to set on the 2298 * state, useful for the 'type' attribute and 'scanners' attribute, 2299 * an array of promise objects for asynchronous scan operations. 2300 */ 2301 if ( this.props.get('url') ) { 2302 this.trigger( 'scan', attributes ); 2303 } 2304 2305 if ( attributes.scanners.length ) { 2306 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 2307 scanners.always( function() { 2308 if ( embed.get('scanners') === scanners ) { 2309 embed.set( 'loading', false ); 2310 } 2311 }); 2312 } else { 2313 attributes.scanners = null; 2314 } 2315 2316 attributes.loading = !! attributes.scanners; 2317 this.set( attributes ); 2318 }, 2319 /** 2320 * Try scanning the embed as an image to discover its dimensions. 2321 * 2322 * @param {Object} attributes 2323 */ 2324 scanImage: function( attributes ) { 2325 var frame = this.frame, 2326 state = this, 2327 url = this.props.get('url'), 2328 image = new Image(), 2329 deferred = $.Deferred(); 2330 2331 attributes.scanners.push( deferred.promise() ); 2332 2333 // Try to load the image and find its width/height. 2334 image.onload = function() { 2335 deferred.resolve(); 2336 2337 if ( state !== frame.state() || url !== state.props.get('url') ) { 2338 return; 2339 } 2340 2341 state.set({ 2342 type: 'image' 2343 }); 2344 2345 state.props.set({ 2346 width: image.width, 2347 height: image.height 2348 }); 2349 }; 2350 2351 image.onerror = deferred.reject; 2352 image.src = url; 2353 }, 2354 2355 refresh: function() { 2356 this.frame.toolbar.get().refresh(); 2357 }, 2358 2359 reset: function() { 2360 this.props.clear().set({ url: '' }); 2361 2362 if ( this.active ) { 2363 this.refresh(); 2364 } 2365 } 2366 }); 2367 2368 module.exports = Embed; 2369 2370 2371 /***/ }), 2372 /* 46 */ 2373 /***/ (function(module, exports) { 2374 2375 var l10n = wp.media.view.l10n, 2376 Cropper; 2377 2378 /** 2379 * wp.media.controller.Cropper 2380 * 2381 * A class for cropping an image when called from the header media customization panel. 2382 * 2383 * @memberOf wp.media.controller 2384 * 2385 * @class 2386 * @augments wp.media.controller.State 2387 * @augments Backbone.Model 2388 */ 2389 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{ 2390 defaults: { 2391 id: 'cropper', 2392 title: l10n.cropImage, 2393 // Region mode defaults. 2394 toolbar: 'crop', 2395 content: 'crop', 2396 router: false, 2397 canSkipCrop: false, 2398 2399 // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state. 2400 doCropArgs: {} 2401 }, 2402 2403 /** 2404 * Shows the crop image window when called from the Add new image button. 2405 * 2406 * @since 4.2.0 2407 * 2408 * @return {void} 2409 */ 2410 activate: function() { 2411 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2412 this.frame.on( 'close', this.removeCropper, this ); 2413 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2414 }, 2415 2416 /** 2417 * Changes the state of the toolbar window to browse mode. 2418 * 2419 * @since 4.2.0 2420 * 2421 * @return {void} 2422 */ 2423 deactivate: function() { 2424 this.frame.toolbar.mode('browse'); 2425 }, 2426 2427 /** 2428 * Creates the crop image window. 2429 * 2430 * Initialized when clicking on the Select and Crop button. 2431 * 2432 * @since 4.2.0 2433 * 2434 * @fires crop window 2435 * 2436 * @return {void} 2437 */ 2438 createCropContent: function() { 2439 this.cropperView = new wp.media.view.Cropper({ 2440 controller: this, 2441 attachment: this.get('selection').first() 2442 }); 2443 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2444 this.frame.content.set(this.cropperView); 2445 2446 }, 2447 2448 /** 2449 * Removes the image selection and closes the cropping window. 2450 * 2451 * @since 4.2.0 2452 * 2453 * @return {void} 2454 */ 2455 removeCropper: function() { 2456 this.imgSelect.cancelSelection(); 2457 this.imgSelect.setOptions({remove: true}); 2458 this.imgSelect.update(); 2459 this.cropperView.remove(); 2460 }, 2461 2462 /** 2463 * Checks if cropping can be skipped and creates crop toolbar accordingly. 2464 * 2465 * @since 4.2.0 2466 * 2467 * @return {void} 2468 */ 2469 createCropToolbar: function() { 2470 var canSkipCrop, toolbarOptions; 2471 2472 canSkipCrop = this.get('canSkipCrop') || false; 2473 2474 toolbarOptions = { 2475 controller: this.frame, 2476 items: { 2477 insert: { 2478 style: 'primary', 2479 text: l10n.cropImage, 2480 priority: 80, 2481 requires: { library: false, selection: false }, 2482 2483 click: function() { 2484 var controller = this.controller, 2485 selection; 2486 2487 selection = controller.state().get('selection').first(); 2488 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 2489 2490 this.$el.text(l10n.cropping); 2491 this.$el.attr('disabled', true); 2492 2493 controller.state().doCrop( selection ).done( function( croppedImage ) { 2494 controller.trigger('cropped', croppedImage ); 2495 controller.close(); 2496 }).fail( function() { 2497 controller.trigger('content:error:crop'); 2498 }); 2499 } 2500 } 2501 } 2502 }; 2503 2504 if ( canSkipCrop ) { 2505 _.extend( toolbarOptions.items, { 2506 skip: { 2507 style: 'secondary', 2508 text: l10n.skipCropping, 2509 priority: 70, 2510 requires: { library: false, selection: false }, 2511 click: function() { 2512 var selection = this.controller.state().get('selection').first(); 2513 this.controller.state().cropperView.remove(); 2514 this.controller.trigger('skippedcrop', selection); 2515 this.controller.close(); 2516 } 2517 } 2518 }); 2519 } 2520 2521 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 2522 }, 2523 2524 /** 2525 * Creates an object with the image attachment and crop properties. 2526 * 2527 * @since 4.2.0 2528 * 2529 * @return {$.promise} A jQuery promise with the custom header crop details. 2530 */ 2531 doCrop: function( attachment ) { 2532 return wp.ajax.post( 'custom-header-crop', _.extend( 2533 {}, 2534 this.defaults.doCropArgs, 2535 { 2536 nonce: attachment.get( 'nonces' ).edit, 2537 id: attachment.get( 'id' ), 2538 cropDetails: attachment.get( 'cropDetails' ) 2539 } 2540 ) ); 2541 } 2542 }); 2543 2544 module.exports = Cropper; 2545 2546 2547 /***/ }), 2548 /* 47 */ 2549 /***/ (function(module, exports) { 2550 2551 var Controller = wp.media.controller, 2552 CustomizeImageCropper; 2553 2554 /** 2555 * A state for cropping an image in the customizer. 2556 * 2557 * @since 4.3.0 2558 * 2559 * @constructs wp.media.controller.CustomizeImageCropper 2560 * @memberOf wp.media.controller 2561 * @augments wp.media.controller.CustomizeImageCropper.Cropper 2562 * @inheritDoc 2563 */ 2564 CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{ 2565 /** 2566 * Posts the crop details to the admin. 2567 * 2568 * Uses crop measurements when flexible in both directions. 2569 * Constrains flexible side based on image ratio and size of the fixed side. 2570 * 2571 * @since 4.3.0 2572 * 2573 * @param {Object} attachment The attachment to crop. 2574 * 2575 * @return {$.promise} A jQuery promise that represents the crop image request. 2576 */ 2577 doCrop: function( attachment ) { 2578 var cropDetails = attachment.get( 'cropDetails' ), 2579 control = this.get( 'control' ), 2580 ratio = cropDetails.width / cropDetails.height; 2581 2582 // Use crop measurements when flexible in both directions. 2583 if ( control.params.flex_width && control.params.flex_height ) { 2584 cropDetails.dst_width = cropDetails.width; 2585 cropDetails.dst_height = cropDetails.height; 2586 2587 // Constrain flexible side based on image ratio and size of the fixed side. 2588 } else { 2589 cropDetails.dst_width = control.params.flex_width ? control.params.height * ratio : control.params.width; 2590 cropDetails.dst_height = control.params.flex_height ? control.params.width / ratio : control.params.height; 2591 } 2592 2593 return wp.ajax.post( 'crop-image', { 2594 wp_customize: 'on', 2595 nonce: attachment.get( 'nonces' ).edit, 2596 id: attachment.get( 'id' ), 2597 context: control.id, 2598 cropDetails: cropDetails 2599 } ); 2600 } 2601 }); 2602 2603 module.exports = CustomizeImageCropper; 2604 2605 2606 /***/ }), 2607 /* 48 */ 2608 /***/ (function(module, exports) { 2609 2610 var Controller = wp.media.controller, 2611 SiteIconCropper; 2612 2613 /** 2614 * wp.media.controller.SiteIconCropper 2615 * 2616 * A state for cropping a Site Icon. 2617 * 2618 * @memberOf wp.media.controller 2619 * 2620 * @class 2621 * @augments wp.media.controller.Cropper 2622 * @augments wp.media.controller.State 2623 * @augments Backbone.Model 2624 */ 2625 SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{ 2626 activate: function() { 2627 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2628 this.frame.on( 'close', this.removeCropper, this ); 2629 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2630 }, 2631 2632 createCropContent: function() { 2633 this.cropperView = new wp.media.view.SiteIconCropper({ 2634 controller: this, 2635 attachment: this.get('selection').first() 2636 }); 2637 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2638 this.frame.content.set(this.cropperView); 2639 2640 }, 2641 2642 doCrop: function( attachment ) { 2643 var cropDetails = attachment.get( 'cropDetails' ), 2644 control = this.get( 'control' ); 2645 2646 cropDetails.dst_width = control.params.width; 2647 cropDetails.dst_height = control.params.height; 2648 2649 return wp.ajax.post( 'crop-image', { 2650 nonce: attachment.get( 'nonces' ).edit, 2651 id: attachment.get( 'id' ), 2652 context: 'site-icon', 2653 cropDetails: cropDetails 2654 } ); 2655 } 2656 }); 2657 2658 module.exports = SiteIconCropper; 2659 2660 2661 /***/ }), 2662 /* 49 */ 2663 /***/ (function(module, exports) { 2664 2665 /** 2666 * wp.media.View 2667 * 2668 * The base view class for media. 2669 * 2670 * Undelegating events, removing events from the model, and 2671 * removing events from the controller mirror the code for 2672 * `Backbone.View.dispose` in Backbone 0.9.8 development. 2673 * 2674 * This behavior has since been removed, and should not be used 2675 * outside of the media manager. 2676 * 2677 * @memberOf wp.media 2678 * 2679 * @class 2680 * @augments wp.Backbone.View 2681 * @augments Backbone.View 2682 */ 2683 var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{ 2684 constructor: function( options ) { 2685 if ( options && options.controller ) { 2686 this.controller = options.controller; 2687 } 2688 wp.Backbone.View.apply( this, arguments ); 2689 }, 2690 /** 2691 * @todo The internal comment mentions this might have been a stop-gap 2692 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 2693 * care of this in Backbone.View now. 2694 * 2695 * @return {wp.media.View} Returns itself to allow chaining. 2696 */ 2697 dispose: function() { 2698 /* 2699 * Undelegating events, removing events from the model, and 2700 * removing events from the controller mirror the code for 2701 * `Backbone.View.dispose` in Backbone 0.9.8 development. 2702 */ 2703 this.undelegateEvents(); 2704 2705 if ( this.model && this.model.off ) { 2706 this.model.off( null, null, this ); 2707 } 2708 2709 if ( this.collection && this.collection.off ) { 2710 this.collection.off( null, null, this ); 2711 } 2712 2713 // Unbind controller events. 2714 if ( this.controller && this.controller.off ) { 2715 this.controller.off( null, null, this ); 2716 } 2717 2718 return this; 2719 }, 2720 /** 2721 * @return {wp.media.View} Returns itself to allow chaining. 2722 */ 2723 remove: function() { 2724 this.dispose(); 2725 /** 2726 * call 'remove' directly on the parent class 2727 */ 2728 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 2729 } 2730 }); 2731 2732 module.exports = View; 2733 2734 2735 /***/ }), 2736 /* 50 */ 2737 /***/ (function(module, exports) { 2738 2739 /** 2740 * wp.media.view.Frame 2741 * 2742 * A frame is a composite view consisting of one or more regions and one or more 2743 * states. 2744 * 2745 * @memberOf wp.media.view 2746 * 2747 * @see wp.media.controller.State 2748 * @see wp.media.controller.Region 2749 * 2750 * @class 2751 * @augments wp.media.View 2752 * @augments wp.Backbone.View 2753 * @augments Backbone.View 2754 * @mixes wp.media.controller.StateMachine 2755 */ 2756 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{ 2757 initialize: function() { 2758 _.defaults( this.options, { 2759 mode: [ 'select' ] 2760 }); 2761 this._createRegions(); 2762 this._createStates(); 2763 this._createModes(); 2764 }, 2765 2766 _createRegions: function() { 2767 // Clone the regions array. 2768 this.regions = this.regions ? this.regions.slice() : []; 2769 2770 // Initialize regions. 2771 _.each( this.regions, function( region ) { 2772 this[ region ] = new wp.media.controller.Region({ 2773 view: this, 2774 id: region, 2775 selector: '.media-frame-' + region 2776 }); 2777 }, this ); 2778 }, 2779 /** 2780 * Create the frame's states. 2781 * 2782 * @see wp.media.controller.State 2783 * @see wp.media.controller.StateMachine 2784 * 2785 * @fires wp.media.controller.State#ready 2786 */ 2787 _createStates: function() { 2788 // Create the default `states` collection. 2789 this.states = new Backbone.Collection( null, { 2790 model: wp.media.controller.State 2791 }); 2792 2793 // Ensure states have a reference to the frame. 2794 this.states.on( 'add', function( model ) { 2795 model.frame = this; 2796 model.trigger('ready'); 2797 }, this ); 2798 2799 if ( this.options.states ) { 2800 this.states.add( this.options.states ); 2801 } 2802 }, 2803 2804 /** 2805 * A frame can be in a mode or multiple modes at one time. 2806 * 2807 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 2808 */ 2809 _createModes: function() { 2810 // Store active "modes" that the frame is in. Unrelated to region modes. 2811 this.activeModes = new Backbone.Collection(); 2812 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 2813 2814 _.each( this.options.mode, function( mode ) { 2815 this.activateMode( mode ); 2816 }, this ); 2817 }, 2818 /** 2819 * Reset all states on the frame to their defaults. 2820 * 2821 * @return {wp.media.view.Frame} Returns itself to allow chaining. 2822 */ 2823 reset: function() { 2824 this.states.invoke( 'trigger', 'reset' ); 2825 return this; 2826 }, 2827 /** 2828 * Map activeMode collection events to the frame. 2829 */ 2830 triggerModeEvents: function( model, collection, options ) { 2831 var collectionEvent, 2832 modeEventMap = { 2833 add: 'activate', 2834 remove: 'deactivate' 2835 }, 2836 eventToTrigger; 2837 // Probably a better way to do this. 2838 _.each( options, function( value, key ) { 2839 if ( value ) { 2840 collectionEvent = key; 2841 } 2842 } ); 2843 2844 if ( ! _.has( modeEventMap, collectionEvent ) ) { 2845 return; 2846 } 2847 2848 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 2849 this.trigger( eventToTrigger ); 2850 }, 2851 /** 2852 * Activate a mode on the frame. 2853 * 2854 * @param string mode Mode ID. 2855 * @return {this} Returns itself to allow chaining. 2856 */ 2857 activateMode: function( mode ) { 2858 // Bail if the mode is already active. 2859 if ( this.isModeActive( mode ) ) { 2860 return; 2861 } 2862 this.activeModes.add( [ { id: mode } ] ); 2863 // Add a CSS class to the frame so elements can be styled for the mode. 2864 this.$el.addClass( 'mode-' + mode ); 2865 2866 return this; 2867 }, 2868 /** 2869 * Deactivate a mode on the frame. 2870 * 2871 * @param string mode Mode ID. 2872 * @return {this} Returns itself to allow chaining. 2873 */ 2874 deactivateMode: function( mode ) { 2875 // Bail if the mode isn't active. 2876 if ( ! this.isModeActive( mode ) ) { 2877 return this; 2878 } 2879 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 2880 this.$el.removeClass( 'mode-' + mode ); 2881 /** 2882 * Frame mode deactivation event. 2883 * 2884 * @event wp.media.view.Frame#{mode}:deactivate 2885 */ 2886 this.trigger( mode + ':deactivate' ); 2887 2888 return this; 2889 }, 2890 /** 2891 * Check if a mode is enabled on the frame. 2892 * 2893 * @param string mode Mode ID. 2894 * @return bool 2895 */ 2896 isModeActive: function( mode ) { 2897 return Boolean( this.activeModes.where( { id: mode } ).length ); 2898 } 2899 }); 2900 2901 // Make the `Frame` a `StateMachine`. 2902 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 2903 2904 module.exports = Frame; 2905 2906 2907 /***/ }), 2908 /* 51 */ 2909 /***/ (function(module, exports) { 2910 2911 var Frame = wp.media.view.Frame, 2912 l10n = wp.media.view.l10n, 2913 $ = jQuery, 2914 MediaFrame; 2915 2916 /** 2917 * wp.media.view.MediaFrame 2918 * 2919 * The frame used to create the media modal. 2920 * 2921 * @memberOf wp.media.view 2922 * 2923 * @class 2924 * @augments wp.media.view.Frame 2925 * @augments wp.media.View 2926 * @augments wp.Backbone.View 2927 * @augments Backbone.View 2928 * @mixes wp.media.controller.StateMachine 2929 */ 2930 MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{ 2931 className: 'media-frame', 2932 template: wp.template('media-frame'), 2933 regions: ['menu','title','content','toolbar','router'], 2934 2935 events: { 2936 'click .media-frame-menu-toggle': 'toggleMenu' 2937 }, 2938 2939 /** 2940 * @constructs 2941 */ 2942 initialize: function() { 2943 Frame.prototype.initialize.apply( this, arguments ); 2944 2945 _.defaults( this.options, { 2946 title: l10n.mediaFrameDefaultTitle, 2947 modal: true, 2948 uploader: true 2949 }); 2950 2951 // Ensure core UI is enabled. 2952 this.$el.addClass('wp-core-ui'); 2953 2954 // Initialize modal container view. 2955 if ( this.options.modal ) { 2956 this.modal = new wp.media.view.Modal({ 2957 controller: this, 2958 title: this.options.title 2959 }); 2960 2961 this.modal.content( this ); 2962 } 2963 2964 // Force the uploader off if the upload limit has been exceeded or 2965 // if the browser isn't supported. 2966 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 2967 this.options.uploader = false; 2968 } 2969 2970 // Initialize window-wide uploader. 2971 if ( this.options.uploader ) { 2972 this.uploader = new wp.media.view.UploaderWindow({ 2973 controller: this, 2974 uploader: { 2975 dropzone: this.modal ? this.modal.$el : this.$el, 2976 container: this.$el 2977 } 2978 }); 2979 this.views.set( '.media-frame-uploader', this.uploader ); 2980 } 2981 2982 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 2983 2984 // Bind default title creation. 2985 this.on( 'title:create:default', this.createTitle, this ); 2986 this.title.mode('default'); 2987 2988 // Bind default menu. 2989 this.on( 'menu:create:default', this.createMenu, this ); 2990 2991 // Set the menu ARIA tab panel attributes when the modal opens. 2992 this.on( 'open', this.setMenuTabPanelAriaAttributes, this ); 2993 // Set the router ARIA tab panel attributes when the modal opens. 2994 this.on( 'open', this.setRouterTabPanelAriaAttributes, this ); 2995 2996 // Update the menu ARIA tab panel attributes when the content updates. 2997 this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this ); 2998 // Update the router ARIA tab panel attributes when the content updates. 2999 this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this ); 3000 }, 3001 3002 /** 3003 * Sets the attributes to be used on the menu ARIA tab panel. 3004 * 3005 * @since 5.3.0 3006 * 3007 * @return {void} 3008 */ 3009 setMenuTabPanelAriaAttributes: function() { 3010 var stateId = this.state().get( 'id' ), 3011 tabPanelEl = this.$el.find( '.media-frame-tab-panel' ), 3012 ariaLabelledby; 3013 3014 tabPanelEl.removeAttr( 'role aria-labelledby tabindex' ); 3015 3016 if ( this.state().get( 'menu' ) && this.menuView && this.menuView.isVisible ) { 3017 ariaLabelledby = 'menu-item-' + stateId; 3018 3019 // Set the tab panel attributes only if the tabs are visible. 3020 tabPanelEl 3021 .attr( { 3022 role: 'tabpanel', 3023 'aria-labelledby': ariaLabelledby, 3024 tabIndex: '0' 3025 } ); 3026 } 3027 }, 3028 3029 /** 3030 * Sets the attributes to be used on the router ARIA tab panel. 3031 * 3032 * @since 5.3.0 3033 * 3034 * @return {void} 3035 */ 3036 setRouterTabPanelAriaAttributes: function() { 3037 var tabPanelEl = this.$el.find( '.media-frame-content' ), 3038 ariaLabelledby; 3039 3040 tabPanelEl.removeAttr( 'role aria-labelledby tabindex' ); 3041 3042 // Set the tab panel attributes only if the tabs are visible. 3043 if ( this.state().get( 'router' ) && this.routerView && this.routerView.isVisible && this.content._mode ) { 3044 ariaLabelledby = 'menu-item-' + this.content._mode; 3045 3046 tabPanelEl 3047 .attr( { 3048 role: 'tabpanel', 3049 'aria-labelledby': ariaLabelledby, 3050 tabIndex: '0' 3051 } ); 3052 } 3053 }, 3054 3055 /** 3056 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 3057 */ 3058 render: function() { 3059 // Activate the default state if no active state exists. 3060 if ( ! this.state() && this.options.state ) { 3061 this.setState( this.options.state ); 3062 } 3063 /** 3064 * call 'render' directly on the parent class 3065 */ 3066 return Frame.prototype.render.apply( this, arguments ); 3067 }, 3068 /** 3069 * @param {Object} title 3070 * @this wp.media.controller.Region 3071 */ 3072 createTitle: function( title ) { 3073 title.view = new wp.media.View({ 3074 controller: this, 3075 tagName: 'h1' 3076 }); 3077 }, 3078 /** 3079 * @param {Object} menu 3080 * @this wp.media.controller.Region 3081 */ 3082 createMenu: function( menu ) { 3083 menu.view = new wp.media.view.Menu({ 3084 controller: this, 3085 3086 attributes: { 3087 role: 'tablist', 3088 'aria-orientation': 'vertical' 3089 } 3090 }); 3091 3092 this.menuView = menu.view; 3093 }, 3094 3095 toggleMenu: function( event ) { 3096 var menu = this.$el.find( '.media-menu' ); 3097 3098 menu.toggleClass( 'visible' ); 3099 $( event.target ).attr( 'aria-expanded', menu.hasClass( 'visible' ) ); 3100 }, 3101 3102 /** 3103 * @param {Object} toolbar 3104 * @this wp.media.controller.Region 3105 */ 3106 createToolbar: function( toolbar ) { 3107 toolbar.view = new wp.media.view.Toolbar({ 3108 controller: this 3109 }); 3110 }, 3111 /** 3112 * @param {Object} router 3113 * @this wp.media.controller.Region 3114 */ 3115 createRouter: function( router ) { 3116 router.view = new wp.media.view.Router({ 3117 controller: this, 3118 3119 attributes: { 3120 role: 'tablist', 3121 'aria-orientation': 'horizontal' 3122 } 3123 }); 3124 3125 this.routerView = router.view; 3126 }, 3127 /** 3128 * @param {Object} options 3129 */ 3130 createIframeStates: function( options ) { 3131 var settings = wp.media.view.settings, 3132 tabs = settings.tabs, 3133 tabUrl = settings.tabUrl, 3134 $postId; 3135 3136 if ( ! tabs || ! tabUrl ) { 3137 return; 3138 } 3139 3140 // Add the post ID to the tab URL if it exists. 3141 $postId = $('#post_ID'); 3142 if ( $postId.length ) { 3143 tabUrl += '&post_id=' + $postId.val(); 3144 } 3145 3146 // Generate the tab states. 3147 _.each( tabs, function( title, id ) { 3148 this.state( 'iframe:' + id ).set( _.defaults({ 3149 tab: id, 3150 src: tabUrl + '&tab=' + id, 3151 title: title, 3152 content: 'iframe', 3153 menu: 'default' 3154 }, options ) ); 3155 }, this ); 3156 3157 this.on( 'content:create:iframe', this.iframeContent, this ); 3158 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 3159 this.on( 'menu:render:default', this.iframeMenu, this ); 3160 this.on( 'open', this.hijackThickbox, this ); 3161 this.on( 'close', this.restoreThickbox, this ); 3162 }, 3163 3164 /** 3165 * @param {Object} content 3166 * @this wp.media.controller.Region 3167 */ 3168 iframeContent: function( content ) { 3169 this.$el.addClass('hide-toolbar'); 3170 content.view = new wp.media.view.Iframe({ 3171 controller: this 3172 }); 3173 }, 3174 3175 iframeContentCleanup: function() { 3176 this.$el.removeClass('hide-toolbar'); 3177 }, 3178 3179 iframeMenu: function( view ) { 3180 var views = {}; 3181 3182 if ( ! view ) { 3183 return; 3184 } 3185 3186 _.each( wp.media.view.settings.tabs, function( title, id ) { 3187 views[ 'iframe:' + id ] = { 3188 text: this.state( 'iframe:' + id ).get('title'), 3189 priority: 200 3190 }; 3191 }, this ); 3192 3193 view.set( views ); 3194 }, 3195 3196 hijackThickbox: function() { 3197 var frame = this; 3198 3199 if ( ! window.tb_remove || this._tb_remove ) { 3200 return; 3201 } 3202 3203 this._tb_remove = window.tb_remove; 3204 window.tb_remove = function() { 3205 frame.close(); 3206 frame.reset(); 3207 frame.setState( frame.options.state ); 3208 frame._tb_remove.call( window ); 3209 }; 3210 }, 3211 3212 restoreThickbox: function() { 3213 if ( ! this._tb_remove ) { 3214 return; 3215 } 3216 3217 window.tb_remove = this._tb_remove; 3218 delete this._tb_remove; 3219 } 3220 }); 3221 3222 // Map some of the modal's methods to the frame. 3223 _.each(['open','close','attach','detach','escape'], function( method ) { 3224 /** 3225 * @function open 3226 * @memberOf wp.media.view.MediaFrame 3227 * @instance 3228 * 3229 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 3230 */ 3231 /** 3232 * @function close 3233 * @memberOf wp.media.view.MediaFrame 3234 * @instance 3235 * 3236 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 3237 */ 3238 /** 3239 * @function attach 3240 * @memberOf wp.media.view.MediaFrame 3241 * @instance 3242 * 3243 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 3244 */ 3245 /** 3246 * @function detach 3247 * @memberOf wp.media.view.MediaFrame 3248 * @instance 3249 * 3250 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 3251 */ 3252 /** 3253 * @function escape 3254 * @memberOf wp.media.view.MediaFrame 3255 * @instance 3256 * 3257 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 3258 */ 3259 MediaFrame.prototype[ method ] = function() { 3260 if ( this.modal ) { 3261 this.modal[ method ].apply( this.modal, arguments ); 3262 } 3263 return this; 3264 }; 3265 }); 3266 3267 module.exports = MediaFrame; 3268 3269 3270 /***/ }), 3271 /* 52 */ 3272 /***/ (function(module, exports) { 3273 3274 var MediaFrame = wp.media.view.MediaFrame, 3275 l10n = wp.media.view.l10n, 3276 Select; 3277 3278 /** 3279 * wp.media.view.MediaFrame.Select 3280 * 3281 * A frame for selecting an item or items from the media library. 3282 * 3283 * @memberOf wp.media.view.MediaFrame 3284 * 3285 * @class 3286 * @augments wp.media.view.MediaFrame 3287 * @augments wp.media.view.Frame 3288 * @augments wp.media.View 3289 * @augments wp.Backbone.View 3290 * @augments Backbone.View 3291 * @mixes wp.media.controller.StateMachine 3292 */ 3293 Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{ 3294 initialize: function() { 3295 // Call 'initialize' directly on the parent class. 3296 MediaFrame.prototype.initialize.apply( this, arguments ); 3297 3298 _.defaults( this.options, { 3299 selection: [], 3300 library: {}, 3301 multiple: false, 3302 state: 'library' 3303 }); 3304 3305 this.createSelection(); 3306 this.createStates(); 3307 this.bindHandlers(); 3308 }, 3309 3310 /** 3311 * Attach a selection collection to the frame. 3312 * 3313 * A selection is a collection of attachments used for a specific purpose 3314 * by a media frame. e.g. Selecting an attachment (or many) to insert into 3315 * post content. 3316 * 3317 * @see media.model.Selection 3318 */ 3319 createSelection: function() { 3320 var selection = this.options.selection; 3321 3322 if ( ! (selection instanceof wp.media.model.Selection) ) { 3323 this.options.selection = new wp.media.model.Selection( selection, { 3324 multiple: this.options.multiple 3325 }); 3326 } 3327 3328 this._selection = { 3329 attachments: new wp.media.model.Attachments(), 3330 difference: [] 3331 }; 3332 }, 3333 3334 editImageContent: function() { 3335 var image = this.state().get('image'), 3336 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 3337 3338 this.content.set( view ); 3339 3340 // After creating the wrapper view, load the actual editor via an Ajax call. 3341 view.loadEditor(); 3342 }, 3343 3344 /** 3345 * Create the default states on the frame. 3346 */ 3347 createStates: function() { 3348 var options = this.options; 3349 3350 if ( this.options.states ) { 3351 return; 3352 } 3353 3354 // Add the default states. 3355 this.states.add([ 3356 // Main states. 3357 new wp.media.controller.Library({ 3358 library: wp.media.query( options.library ), 3359 multiple: options.multiple, 3360 title: options.title, 3361 priority: 20 3362 }), 3363 new wp.media.controller.EditImage( { model: options.editImage } ) 3364 ]); 3365 }, 3366 3367 /** 3368 * Bind region mode event callbacks. 3369 * 3370 * @see media.controller.Region.render 3371 */ 3372 bindHandlers: function() { 3373 this.on( 'router:create:browse', this.createRouter, this ); 3374 this.on( 'router:render:browse', this.browseRouter, this ); 3375 this.on( 'content:create:browse', this.browseContent, this ); 3376 this.on( 'content:render:upload', this.uploadContent, this ); 3377 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 3378 this.on( 'content:render:edit-image', this.editImageContent, this ); 3379 }, 3380 3381 /** 3382 * Render callback for the router region in the `browse` mode. 3383 * 3384 * @param {wp.media.view.Router} routerView 3385 */ 3386 browseRouter: function( routerView ) { 3387 routerView.set({ 3388 upload: { 3389 text: l10n.uploadFilesTitle, 3390 priority: 20 3391 }, 3392 browse: { 3393 text: l10n.mediaLibraryTitle, 3394 priority: 40 3395 } 3396 }); 3397 }, 3398 3399 /** 3400 * Render callback for the content region in the `browse` mode. 3401 * 3402 * @param {wp.media.controller.Region} contentRegion 3403 */ 3404 browseContent: function( contentRegion ) { 3405 var state = this.state(); 3406 3407 this.$el.removeClass('hide-toolbar'); 3408 3409 // Browse our library of attachments. 3410 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 3411 controller: this, 3412 collection: state.get('library'), 3413 selection: state.get('selection'), 3414 model: state, 3415 sortable: state.get('sortable'), 3416 search: state.get('searchable'), 3417 filters: state.get('filterable'), 3418 date: state.get('date'), 3419 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 3420 dragInfo: state.get('dragInfo'), 3421 3422 idealColumnWidth: state.get('idealColumnWidth'), 3423 suggestedWidth: state.get('suggestedWidth'), 3424 suggestedHeight: state.get('suggestedHeight'), 3425 3426 AttachmentView: state.get('AttachmentView') 3427 }); 3428 }, 3429 3430 /** 3431 * Render callback for the content region in the `upload` mode. 3432 */ 3433 uploadContent: function() { 3434 this.$el.removeClass( 'hide-toolbar' ); 3435 this.content.set( new wp.media.view.UploaderInline({ 3436 controller: this 3437 }) ); 3438 }, 3439 3440 /** 3441 * Toolbars 3442 * 3443 * @param {Object} toolbar 3444 * @param {Object} [options={}] 3445 * @this wp.media.controller.Region 3446 */ 3447 createSelectToolbar: function( toolbar, options ) { 3448 options = options || this.options.button || {}; 3449 options.controller = this; 3450 3451 toolbar.view = new wp.media.view.Toolbar.Select( options ); 3452 } 3453 }); 3454 3455 module.exports = Select; 3456 3457 3458 /***/ }), 3459 /* 53 */ 3460 /***/ (function(module, exports) { 3461 3462 var Select = wp.media.view.MediaFrame.Select, 3463 Library = wp.media.controller.Library, 3464 l10n = wp.media.view.l10n, 3465 Post; 3466 3467 /** 3468 * wp.media.view.MediaFrame.Post 3469 * 3470 * The frame for manipulating media on the Edit Post page. 3471 * 3472 * @memberOf wp.media.view.MediaFrame 3473 * 3474 * @class 3475 * @augments wp.media.view.MediaFrame.Select 3476 * @augments wp.media.view.MediaFrame 3477 * @augments wp.media.view.Frame 3478 * @augments wp.media.View 3479 * @augments wp.Backbone.View 3480 * @augments Backbone.View 3481 * @mixes wp.media.controller.StateMachine 3482 */ 3483 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{ 3484 initialize: function() { 3485 this.counts = { 3486 audio: { 3487 count: wp.media.view.settings.attachmentCounts.audio, 3488 state: 'playlist' 3489 }, 3490 video: { 3491 count: wp.media.view.settings.attachmentCounts.video, 3492 state: 'video-playlist' 3493 } 3494 }; 3495 3496 _.defaults( this.options, { 3497 multiple: true, 3498 editing: false, 3499 state: 'insert', 3500 metadata: {} 3501 }); 3502 3503 // Call 'initialize' directly on the parent class. 3504 Select.prototype.initialize.apply( this, arguments ); 3505 this.createIframeStates(); 3506 3507 }, 3508 3509 /** 3510 * Create the default states. 3511 */ 3512 createStates: function() { 3513 var options = this.options; 3514 3515 this.states.add([ 3516 // Main states. 3517 new Library({ 3518 id: 'insert', 3519 title: l10n.insertMediaTitle, 3520 priority: 20, 3521 toolbar: 'main-insert', 3522 filterable: 'all', 3523 library: wp.media.query( options.library ), 3524 multiple: options.multiple ? 'reset' : false, 3525 editable: true, 3526 3527 // If the user isn't allowed to edit fields, 3528 // can they still edit it locally? 3529 allowLocalEdits: true, 3530 3531 // Show the attachment display settings. 3532 displaySettings: true, 3533 // Update user settings when users adjust the 3534 // attachment display settings. 3535 displayUserSettings: true 3536 }), 3537 3538 new Library({ 3539 id: 'gallery', 3540 title: l10n.createGalleryTitle, 3541 priority: 40, 3542 toolbar: 'main-gallery', 3543 filterable: 'uploaded', 3544 multiple: 'add', 3545 editable: false, 3546 3547 library: wp.media.query( _.defaults({ 3548 type: 'image' 3549 }, options.library ) ) 3550 }), 3551 3552 // Embed states. 3553 new wp.media.controller.Embed( { metadata: options.metadata } ), 3554 3555 new wp.media.controller.EditImage( { model: options.editImage } ), 3556 3557 // Gallery states. 3558 new wp.media.controller.GalleryEdit({ 3559 library: options.selection, 3560 editing: options.editing, 3561 menu: 'gallery' 3562 }), 3563 3564 new wp.media.controller.GalleryAdd(), 3565 3566 new Library({ 3567 id: 'playlist', 3568 title: l10n.createPlaylistTitle, 3569 priority: 60, 3570 toolbar: 'main-playlist', 3571 filterable: 'uploaded', 3572 multiple: 'add', 3573 editable: false, 3574 3575 library: wp.media.query( _.defaults({ 3576 type: 'audio' 3577 }, options.library ) ) 3578 }), 3579 3580 // Playlist states. 3581 new wp.media.controller.CollectionEdit({ 3582 type: 'audio', 3583 collectionType: 'playlist', 3584 title: l10n.editPlaylistTitle, 3585 SettingsView: wp.media.view.Settings.Playlist, 3586 library: options.selection, 3587 editing: options.editing, 3588 menu: 'playlist', 3589 dragInfoText: l10n.playlistDragInfo, 3590 dragInfo: false 3591 }), 3592 3593 new wp.media.controller.CollectionAdd({ 3594 type: 'audio', 3595 collectionType: 'playlist', 3596 title: l10n.addToPlaylistTitle 3597 }), 3598 3599 new Library({ 3600 id: 'video-playlist', 3601 title: l10n.createVideoPlaylistTitle, 3602 priority: 60, 3603 toolbar: 'main-video-playlist', 3604 filterable: 'uploaded', 3605 multiple: 'add', 3606 editable: false, 3607 3608 library: wp.media.query( _.defaults({ 3609 type: 'video' 3610 }, options.library ) ) 3611 }), 3612 3613 new wp.media.controller.CollectionEdit({ 3614 type: 'video', 3615 collectionType: 'playlist', 3616 title: l10n.editVideoPlaylistTitle, 3617 SettingsView: wp.media.view.Settings.Playlist, 3618 library: options.selection, 3619 editing: options.editing, 3620 menu: 'video-playlist', 3621 dragInfoText: l10n.videoPlaylistDragInfo, 3622 dragInfo: false 3623 }), 3624 3625 new wp.media.controller.CollectionAdd({ 3626 type: 'video', 3627 collectionType: 'playlist', 3628 title: l10n.addToVideoPlaylistTitle 3629 }) 3630 ]); 3631 3632 if ( wp.media.view.settings.post.featuredImageId ) { 3633 this.states.add( new wp.media.controller.FeaturedImage() ); 3634 } 3635 }, 3636 3637 bindHandlers: function() { 3638 var handlers, checkCounts; 3639 3640 Select.prototype.bindHandlers.apply( this, arguments ); 3641 3642 this.on( 'activate', this.activate, this ); 3643 3644 // Only bother checking media type counts if one of the counts is zero. 3645 checkCounts = _.find( this.counts, function( type ) { 3646 return type.count === 0; 3647 } ); 3648 3649 if ( typeof checkCounts !== 'undefined' ) { 3650 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 3651 } 3652 3653 this.on( 'menu:create:gallery', this.createMenu, this ); 3654 this.on( 'menu:create:playlist', this.createMenu, this ); 3655 this.on( 'menu:create:video-playlist', this.createMenu, this ); 3656 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 3657 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 3658 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 3659 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 3660 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 3661 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 3662 3663 handlers = { 3664 menu: { 3665 'default': 'mainMenu', 3666 'gallery': 'galleryMenu', 3667 'playlist': 'playlistMenu', 3668 'video-playlist': 'videoPlaylistMenu' 3669 }, 3670 3671 content: { 3672 'embed': 'embedContent', 3673 'edit-image': 'editImageContent', 3674 'edit-selection': 'editSelectionContent' 3675 }, 3676 3677 toolbar: { 3678 'main-insert': 'mainInsertToolbar', 3679 'main-gallery': 'mainGalleryToolbar', 3680 'gallery-edit': 'galleryEditToolbar', 3681 'gallery-add': 'galleryAddToolbar', 3682 'main-playlist': 'mainPlaylistToolbar', 3683 'playlist-edit': 'playlistEditToolbar', 3684 'playlist-add': 'playlistAddToolbar', 3685 'main-video-playlist': 'mainVideoPlaylistToolbar', 3686 'video-playlist-edit': 'videoPlaylistEditToolbar', 3687 'video-playlist-add': 'videoPlaylistAddToolbar' 3688 } 3689 }; 3690 3691 _.each( handlers, function( regionHandlers, region ) { 3692 _.each( regionHandlers, function( callback, handler ) { 3693 this.on( region + ':render:' + handler, this[ callback ], this ); 3694 }, this ); 3695 }, this ); 3696 }, 3697 3698 activate: function() { 3699 // Hide menu items for states tied to particular media types if there are no items. 3700 _.each( this.counts, function( type ) { 3701 if ( type.count < 1 ) { 3702 this.menuItemVisibility( type.state, 'hide' ); 3703 } 3704 }, this ); 3705 }, 3706 3707 mediaTypeCounts: function( model, attr ) { 3708 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 3709 this.counts[ attr ].count++; 3710 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 3711 } 3712 }, 3713 3714 // Menus. 3715 /** 3716 * @param {wp.Backbone.View} view 3717 */ 3718 mainMenu: function( view ) { 3719 view.set({ 3720 'library-separator': new wp.media.View({ 3721 className: 'separator', 3722 priority: 100, 3723 attributes: { 3724 role: 'presentation' 3725 } 3726 }) 3727 }); 3728 }, 3729 3730 menuItemVisibility: function( state, visibility ) { 3731 var menu = this.menu.get(); 3732 if ( visibility === 'hide' ) { 3733 menu.hide( state ); 3734 } else if ( visibility === 'show' ) { 3735 menu.show( state ); 3736 } 3737 }, 3738 /** 3739 * @param {wp.Backbone.View} view 3740 */ 3741 galleryMenu: function( view ) { 3742 var lastState = this.lastState(), 3743 previous = lastState && lastState.id, 3744 frame = this; 3745 3746 view.set({ 3747 cancel: { 3748 text: l10n.cancelGalleryTitle, 3749 priority: 20, 3750 click: function() { 3751 if ( previous ) { 3752 frame.setState( previous ); 3753 } else { 3754 frame.close(); 3755 } 3756 3757 // Move focus to the modal after canceling a Gallery. 3758 this.controller.modal.focusManager.focus(); 3759 } 3760 }, 3761 separateCancel: new wp.media.View({ 3762 className: 'separator', 3763 priority: 40 3764 }) 3765 }); 3766 }, 3767 3768 playlistMenu: function( view ) { 3769 var lastState = this.lastState(), 3770 previous = lastState && lastState.id, 3771 frame = this; 3772 3773 view.set({ 3774 cancel: { 3775 text: l10n.cancelPlaylistTitle, 3776 priority: 20, 3777 click: function() { 3778 if ( previous ) { 3779 frame.setState( previous ); 3780 } else { 3781 frame.close(); 3782 } 3783 3784 // Move focus to the modal after canceling an Audio Playlist. 3785 this.controller.modal.focusManager.focus(); 3786 } 3787 }, 3788 separateCancel: new wp.media.View({ 3789 className: 'separator', 3790 priority: 40 3791 }) 3792 }); 3793 }, 3794 3795 videoPlaylistMenu: function( view ) { 3796 var lastState = this.lastState(), 3797 previous = lastState && lastState.id, 3798 frame = this; 3799 3800 view.set({ 3801 cancel: { 3802 text: l10n.cancelVideoPlaylistTitle, 3803 priority: 20, 3804 click: function() { 3805 if ( previous ) { 3806 frame.setState( previous ); 3807 } else { 3808 frame.close(); 3809 } 3810 3811 // Move focus to the modal after canceling a Video Playlist. 3812 this.controller.modal.focusManager.focus(); 3813 } 3814 }, 3815 separateCancel: new wp.media.View({ 3816 className: 'separator', 3817 priority: 40 3818 }) 3819 }); 3820 }, 3821 3822 // Content. 3823 embedContent: function() { 3824 var view = new wp.media.view.Embed({ 3825 controller: this, 3826 model: this.state() 3827 }).render(); 3828 3829 this.content.set( view ); 3830 }, 3831 3832 editSelectionContent: function() { 3833 var state = this.state(), 3834 selection = state.get('selection'), 3835 view; 3836 3837 view = new wp.media.view.AttachmentsBrowser({ 3838 controller: this, 3839 collection: selection, 3840 selection: selection, 3841 model: state, 3842 sortable: true, 3843 search: false, 3844 date: false, 3845 dragInfo: true, 3846 3847 AttachmentView: wp.media.view.Attachments.EditSelection 3848 }).render(); 3849 3850 view.toolbar.set( 'backToLibrary', { 3851 text: l10n.returnToLibrary, 3852 priority: -100, 3853 3854 click: function() { 3855 this.controller.content.mode('browse'); 3856 // Move focus to the modal when jumping back from Edit Selection to Add Media view. 3857 this.controller.modal.focusManager.focus(); 3858 } 3859 }); 3860 3861 // Browse our library of attachments. 3862 this.content.set( view ); 3863 3864 // Trigger the controller to set focus. 3865 this.trigger( 'edit:selection', this ); 3866 }, 3867 3868 editImageContent: function() { 3869 var image = this.state().get('image'), 3870 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 3871 3872 this.content.set( view ); 3873 3874 // After creating the wrapper view, load the actual editor via an Ajax call. 3875 view.loadEditor(); 3876 3877 }, 3878 3879 // Toolbars. 3880 3881 /** 3882 * @param {wp.Backbone.View} view 3883 */ 3884 selectionStatusToolbar: function( view ) { 3885 var editable = this.state().get('editable'); 3886 3887 view.set( 'selection', new wp.media.view.Selection({ 3888 controller: this, 3889 collection: this.state().get('selection'), 3890 priority: -40, 3891 3892 // If the selection is editable, pass the callback to 3893 // switch the content mode. 3894 editable: editable && function() { 3895 this.controller.content.mode('edit-selection'); 3896 } 3897 }).render() ); 3898 }, 3899 3900 /** 3901 * @param {wp.Backbone.View} view 3902 */ 3903 mainInsertToolbar: function( view ) { 3904 var controller = this; 3905 3906 this.selectionStatusToolbar( view ); 3907 3908 view.set( 'insert', { 3909 style: 'primary', 3910 priority: 80, 3911 text: l10n.insertIntoPost, 3912 requires: { selection: true }, 3913 3914 /** 3915 * @ignore 3916 * 3917 * @fires wp.media.controller.State#insert 3918 */ 3919 click: function() { 3920 var state = controller.state(), 3921 selection = state.get('selection'); 3922 3923 controller.close(); 3924 state.trigger( 'insert', selection ).reset(); 3925 } 3926 }); 3927 }, 3928 3929 /** 3930 * @param {wp.Backbone.View} view 3931 */ 3932 mainGalleryToolbar: function( view ) { 3933 var controller = this; 3934 3935 this.selectionStatusToolbar( view ); 3936 3937 view.set( 'gallery', { 3938 style: 'primary', 3939 text: l10n.createNewGallery, 3940 priority: 60, 3941 requires: { selection: true }, 3942 3943 click: function() { 3944 var selection = controller.state().get('selection'), 3945 edit = controller.state('gallery-edit'), 3946 models = selection.where({ type: 'image' }); 3947 3948 edit.set( 'library', new wp.media.model.Selection( models, { 3949 props: selection.props.toJSON(), 3950 multiple: true 3951 }) ); 3952 3953 // Jump to Edit Gallery view. 3954 this.controller.setState( 'gallery-edit' ); 3955 3956 // Move focus to the modal after jumping to Edit Gallery view. 3957 this.controller.modal.focusManager.focus(); 3958 } 3959 }); 3960 }, 3961 3962 mainPlaylistToolbar: function( view ) { 3963 var controller = this; 3964 3965 this.selectionStatusToolbar( view ); 3966 3967 view.set( 'playlist', { 3968 style: 'primary', 3969 text: l10n.createNewPlaylist, 3970 priority: 100, 3971 requires: { selection: true }, 3972 3973 click: function() { 3974 var selection = controller.state().get('selection'), 3975 edit = controller.state('playlist-edit'), 3976 models = selection.where({ type: 'audio' }); 3977 3978 edit.set( 'library', new wp.media.model.Selection( models, { 3979 props: selection.props.toJSON(), 3980 multiple: true 3981 }) ); 3982 3983 // Jump to Edit Audio Playlist view. 3984 this.controller.setState( 'playlist-edit' ); 3985 3986 // Move focus to the modal after jumping to Edit Audio Playlist view. 3987 this.controller.modal.focusManager.focus(); 3988 } 3989 }); 3990 }, 3991 3992 mainVideoPlaylistToolbar: function( view ) { 3993 var controller = this; 3994 3995 this.selectionStatusToolbar( view ); 3996 3997 view.set( 'video-playlist', { 3998 style: 'primary', 3999 text: l10n.createNewVideoPlaylist, 4000 priority: 100, 4001 requires: { selection: true }, 4002 4003 click: function() { 4004 var selection = controller.state().get('selection'), 4005 edit = controller.state('video-playlist-edit'), 4006 models = selection.where({ type: 'video' }); 4007 4008 edit.set( 'library', new wp.media.model.Selection( models, { 4009 props: selection.props.toJSON(), 4010 multiple: true 4011 }) ); 4012 4013 // Jump to Edit Video Playlist view. 4014 this.controller.setState( 'video-playlist-edit' ); 4015 4016 // Move focus to the modal after jumping to Edit Video Playlist view. 4017 this.controller.modal.focusManager.focus(); 4018 } 4019 }); 4020 }, 4021 4022 featuredImageToolbar: function( toolbar ) { 4023 this.createSelectToolbar( toolbar, { 4024 text: l10n.setFeaturedImage, 4025 state: this.options.state 4026 }); 4027 }, 4028 4029 mainEmbedToolbar: function( toolbar ) { 4030 toolbar.view = new wp.media.view.Toolbar.Embed({ 4031 controller: this 4032 }); 4033 }, 4034 4035 galleryEditToolbar: function() { 4036 var editing = this.state().get('editing'); 4037 this.toolbar.set( new wp.media.view.Toolbar({ 4038 controller: this, 4039 items: { 4040 insert: { 4041 style: 'primary', 4042 text: editing ? l10n.updateGallery : l10n.insertGallery, 4043 priority: 80, 4044 requires: { library: true }, 4045 4046 /** 4047 * @fires wp.media.controller.State#update 4048 */ 4049 click: function() { 4050 var controller = this.controller, 4051 state = controller.state(); 4052 4053 controller.close(); 4054 state.trigger( 'update', state.get('library') ); 4055 4056 // Restore and reset the default state. 4057 controller.setState( controller.options.state ); 4058 controller.reset(); 4059 } 4060 } 4061 } 4062 }) ); 4063 }, 4064 4065 galleryAddToolbar: function() { 4066 this.toolbar.set( new wp.media.view.Toolbar({ 4067 controller: this, 4068 items: { 4069 insert: { 4070 style: 'primary', 4071 text: l10n.addToGallery, 4072 priority: 80, 4073 requires: { selection: true }, 4074 4075 /** 4076 * @fires wp.media.controller.State#reset 4077 */ 4078 click: function() { 4079 var controller = this.controller, 4080 state = controller.state(), 4081 edit = controller.state('gallery-edit'); 4082 4083 edit.get('library').add( state.get('selection').models ); 4084 state.trigger('reset'); 4085 controller.setState('gallery-edit'); 4086 // Move focus to the modal when jumping back from Add to Gallery to Edit Gallery view. 4087 this.controller.modal.focusManager.focus(); 4088 } 4089 } 4090 } 4091 }) ); 4092 }, 4093 4094 playlistEditToolbar: function() { 4095 var editing = this.state().get('editing'); 4096 this.toolbar.set( new wp.media.view.Toolbar({ 4097 controller: this, 4098 items: { 4099 insert: { 4100 style: 'primary', 4101 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 4102 priority: 80, 4103 requires: { library: true }, 4104 4105 /** 4106 * @fires wp.media.controller.State#update 4107 */ 4108 click: function() { 4109 var controller = this.controller, 4110 state = controller.state(); 4111 4112 controller.close(); 4113 state.trigger( 'update', state.get('library') ); 4114 4115 // Restore and reset the default state. 4116 controller.setState( controller.options.state ); 4117 controller.reset(); 4118 } 4119 } 4120 } 4121 }) ); 4122 }, 4123 4124 playlistAddToolbar: function() { 4125 this.toolbar.set( new wp.media.view.Toolbar({ 4126 controller: this, 4127 items: { 4128 insert: { 4129 style: 'primary', 4130 text: l10n.addToPlaylist, 4131 priority: 80, 4132 requires: { selection: true }, 4133 4134 /** 4135 * @fires wp.media.controller.State#reset 4136 */ 4137 click: function() { 4138 var controller = this.controller, 4139 state = controller.state(), 4140 edit = controller.state('playlist-edit'); 4141 4142 edit.get('library').add( state.get('selection').models ); 4143 state.trigger('reset'); 4144 controller.setState('playlist-edit'); 4145 // Move focus to the modal when jumping back from Add to Audio Playlist to Edit Audio Playlist view. 4146 this.controller.modal.focusManager.focus(); 4147 } 4148 } 4149 } 4150 }) ); 4151 }, 4152 4153 videoPlaylistEditToolbar: function() { 4154 var editing = this.state().get('editing'); 4155 this.toolbar.set( new wp.media.view.Toolbar({ 4156 controller: this, 4157 items: { 4158 insert: { 4159 style: 'primary', 4160 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 4161 priority: 140, 4162 requires: { library: true }, 4163 4164 click: function() { 4165 var controller = this.controller, 4166 state = controller.state(), 4167 library = state.get('library'); 4168 4169 library.type = 'video'; 4170 4171 controller.close(); 4172 state.trigger( 'update', library ); 4173 4174 // Restore and reset the default state. 4175 controller.setState( controller.options.state ); 4176 controller.reset(); 4177 } 4178 } 4179 } 4180 }) ); 4181 }, 4182 4183 videoPlaylistAddToolbar: function() { 4184 this.toolbar.set( new wp.media.view.Toolbar({ 4185 controller: this, 4186 items: { 4187 insert: { 4188 style: 'primary', 4189 text: l10n.addToVideoPlaylist, 4190 priority: 140, 4191 requires: { selection: true }, 4192 4193 click: function() { 4194 var controller = this.controller, 4195 state = controller.state(), 4196 edit = controller.state('video-playlist-edit'); 4197 4198 edit.get('library').add( state.get('selection').models ); 4199 state.trigger('reset'); 4200 controller.setState('video-playlist-edit'); 4201 // Move focus to the modal when jumping back from Add to Video Playlist to Edit Video Playlist view. 4202 this.controller.modal.focusManager.focus(); 4203 } 4204 } 4205 } 4206 }) ); 4207 } 4208 }); 4209 4210 module.exports = Post; 4211 4212 4213 /***/ }), 4214 /* 54 */ 4215 /***/ (function(module, exports) { 4216 4217 var Select = wp.media.view.MediaFrame.Select, 4218 l10n = wp.media.view.l10n, 4219 ImageDetails; 4220 4221 /** 4222 * wp.media.view.MediaFrame.ImageDetails 4223 * 4224 * A media frame for manipulating an image that's already been inserted 4225 * into a post. 4226 * 4227 * @memberOf wp.media.view.MediaFrame 4228 * 4229 * @class 4230 * @augments wp.media.view.MediaFrame.Select 4231 * @augments wp.media.view.MediaFrame 4232 * @augments wp.media.view.Frame 4233 * @augments wp.media.View 4234 * @augments wp.Backbone.View 4235 * @augments Backbone.View 4236 * @mixes wp.media.controller.StateMachine 4237 */ 4238 ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{ 4239 defaults: { 4240 id: 'image', 4241 url: '', 4242 menu: 'image-details', 4243 content: 'image-details', 4244 toolbar: 'image-details', 4245 type: 'link', 4246 title: l10n.imageDetailsTitle, 4247 priority: 120 4248 }, 4249 4250 initialize: function( options ) { 4251 this.image = new wp.media.model.PostImage( options.metadata ); 4252 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 4253 Select.prototype.initialize.apply( this, arguments ); 4254 }, 4255 4256 bindHandlers: function() { 4257 Select.prototype.bindHandlers.apply( this, arguments ); 4258 this.on( 'menu:create:image-details', this.createMenu, this ); 4259 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 4260 this.on( 'content:render:edit-image', this.editImageContent, this ); 4261 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 4262 // Override the select toolbar. 4263 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 4264 }, 4265 4266 createStates: function() { 4267 this.states.add([ 4268 new wp.media.controller.ImageDetails({ 4269 image: this.image, 4270 editable: false 4271 }), 4272 new wp.media.controller.ReplaceImage({ 4273 id: 'replace-image', 4274 library: wp.media.query( { type: 'image' } ), 4275 image: this.image, 4276 multiple: false, 4277 title: l10n.imageReplaceTitle, 4278 toolbar: 'replace', 4279 priority: 80, 4280 displaySettings: true 4281 }), 4282 new wp.media.controller.EditImage( { 4283 image: this.image, 4284 selection: this.options.selection 4285 } ) 4286 ]); 4287 }, 4288 4289 imageDetailsContent: function( options ) { 4290 options.view = new wp.media.view.ImageDetails({ 4291 controller: this, 4292 model: this.state().image, 4293 attachment: this.state().image.attachment 4294 }); 4295 }, 4296 4297 editImageContent: function() { 4298 var state = this.state(), 4299 model = state.get('image'), 4300 view; 4301 4302 if ( ! model ) { 4303 return; 4304 } 4305 4306 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 4307 4308 this.content.set( view ); 4309 4310 // After bringing in the frame, load the actual editor via an Ajax call. 4311 view.loadEditor(); 4312 4313 }, 4314 4315 renderImageDetailsToolbar: function() { 4316 this.toolbar.set( new wp.media.view.Toolbar({ 4317 controller: this, 4318 items: { 4319 select: { 4320 style: 'primary', 4321 text: l10n.update, 4322 priority: 80, 4323 4324 click: function() { 4325 var controller = this.controller, 4326 state = controller.state(); 4327 4328 controller.close(); 4329 4330 // Not sure if we want to use wp.media.string.image which will create a shortcode or 4331 // perhaps wp.html.string to at least to build the <img />. 4332 state.trigger( 'update', controller.image.toJSON() ); 4333 4334 // Restore and reset the default state. 4335 controller.setState( controller.options.state ); 4336 controller.reset(); 4337 } 4338 } 4339 } 4340 }) ); 4341 }, 4342 4343 renderReplaceImageToolbar: function() { 4344 var frame = this, 4345 lastState = frame.lastState(), 4346 previous = lastState && lastState.id; 4347 4348 this.toolbar.set( new wp.media.view.Toolbar({ 4349 controller: this, 4350 items: { 4351 back: { 4352 text: l10n.back, 4353 priority: 80, 4354 click: function() { 4355 if ( previous ) { 4356 frame.setState( previous ); 4357 } else { 4358 frame.close(); 4359 } 4360 } 4361 }, 4362 4363 replace: { 4364 style: 'primary', 4365 text: l10n.replace, 4366 priority: 20, 4367 requires: { selection: true }, 4368 4369 click: function() { 4370 var controller = this.controller, 4371 state = controller.state(), 4372 selection = state.get( 'selection' ), 4373 attachment = selection.single(); 4374 4375 controller.close(); 4376 4377 controller.image.changeAttachment( attachment, state.display( attachment ) ); 4378 4379 // Not sure if we want to use wp.media.string.image which will create a shortcode or 4380 // perhaps wp.html.string to at least to build the <img />. 4381 state.trigger( 'replace', controller.image.toJSON() ); 4382 4383 // Restore and reset the default state. 4384 controller.setState( controller.options.state ); 4385 controller.reset(); 4386 } 4387 } 4388 } 4389 }) ); 4390 } 4391 4392 }); 4393 4394 module.exports = ImageDetails; 4395 4396 4397 /***/ }), 4398 /* 55 */ 4399 /***/ (function(module, exports) { 4400 4401 var $ = jQuery, 4402 Modal; 4403 4404 /** 4405 * wp.media.view.Modal 4406 * 4407 * A modal view, which the media modal uses as its default container. 4408 * 4409 * @memberOf wp.media.view 4410 * 4411 * @class 4412 * @augments wp.media.View 4413 * @augments wp.Backbone.View 4414 * @augments Backbone.View 4415 */ 4416 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ 4417 tagName: 'div', 4418 template: wp.template('media-modal'), 4419 4420 events: { 4421 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 4422 'keydown': 'keydown' 4423 }, 4424 4425 clickedOpenerEl: null, 4426 4427 initialize: function() { 4428 _.defaults( this.options, { 4429 container: document.body, 4430 title: '', 4431 propagate: true, 4432 hasCloseButton: true 4433 }); 4434 4435 this.focusManager = new wp.media.view.FocusManager({ 4436 el: this.el 4437 }); 4438 }, 4439 /** 4440 * @return {Object} 4441 */ 4442 prepare: function() { 4443 return { 4444 title: this.options.title, 4445 hasCloseButton: this.options.hasCloseButton 4446 }; 4447 }, 4448 4449 /** 4450 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4451 */ 4452 attach: function() { 4453 if ( this.views.attached ) { 4454 return this; 4455 } 4456 4457 if ( ! this.views.rendered ) { 4458 this.render(); 4459 } 4460 4461 this.$el.appendTo( this.options.container ); 4462 4463 // Manually mark the view as attached and trigger ready. 4464 this.views.attached = true; 4465 this.views.ready(); 4466 4467 return this.propagate('attach'); 4468 }, 4469 4470 /** 4471 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4472 */ 4473 detach: function() { 4474 if ( this.$el.is(':visible') ) { 4475 this.close(); 4476 } 4477 4478 this.$el.detach(); 4479 this.views.attached = false; 4480 return this.propagate('detach'); 4481 }, 4482 4483 /** 4484 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4485 */ 4486 open: function() { 4487 var $el = this.$el, 4488 mceEditor; 4489 4490 if ( $el.is(':visible') ) { 4491 return this; 4492 } 4493 4494 this.clickedOpenerEl = document.activeElement; 4495 4496 if ( ! this.views.attached ) { 4497 this.attach(); 4498 } 4499 4500 // Disable page scrolling. 4501 $( 'body' ).addClass( 'modal-open' ); 4502 4503 $el.show(); 4504 4505 // Try to close the onscreen keyboard. 4506 if ( 'ontouchend' in document ) { 4507 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 4508 mceEditor.iframeElement.focus(); 4509 mceEditor.iframeElement.blur(); 4510 4511 setTimeout( function() { 4512 mceEditor.iframeElement.blur(); 4513 }, 100 ); 4514 } 4515 } 4516 4517 // Set initial focus on the content instead of this view element, to avoid page scrolling. 4518 this.$( '.media-modal' ).focus(); 4519 4520 // Hide the page content from assistive technologies. 4521 this.focusManager.setAriaHiddenOnBodyChildren( $el ); 4522 4523 return this.propagate('open'); 4524 }, 4525 4526 /** 4527 * @param {Object} options 4528 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4529 */ 4530 close: function( options ) { 4531 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 4532 return this; 4533 } 4534 4535 // Enable page scrolling. 4536 $( 'body' ).removeClass( 'modal-open' ); 4537 4538 // Hide modal and remove restricted media modal tab focus once it's closed. 4539 this.$el.hide().undelegate( 'keydown' ); 4540 4541 /* 4542 * Make visible again to assistive technologies all body children that 4543 * have been made hidden when the modal opened. 4544 */ 4545 this.focusManager.removeAriaHiddenFromBodyChildren(); 4546 4547 // Move focus back in useful location once modal is closed. 4548 if ( null !== this.clickedOpenerEl ) { 4549 // Move focus back to the element that opened the modal. 4550 this.clickedOpenerEl.focus(); 4551 } else { 4552 // Fallback to the admin page main element. 4553 $( '#wpbody-content' ) 4554 .attr( 'tabindex', '-1' ) 4555 .focus(); 4556 } 4557 4558 this.propagate('close'); 4559 4560 if ( options && options.escape ) { 4561 this.propagate('escape'); 4562 } 4563 4564 return this; 4565 }, 4566 /** 4567 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4568 */ 4569 escape: function() { 4570 return this.close({ escape: true }); 4571 }, 4572 /** 4573 * @param {Object} event 4574 */ 4575 escapeHandler: function( event ) { 4576 event.preventDefault(); 4577 this.escape(); 4578 }, 4579 4580 /** 4581 * @param {Array|Object} content Views to register to '.media-modal-content' 4582 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4583 */ 4584 content: function( content ) { 4585 this.views.set( '.media-modal-content', content ); 4586 return this; 4587 }, 4588 4589 /** 4590 * Triggers a modal event and if the `propagate` option is set, 4591 * forwards events to the modal's controller. 4592 * 4593 * @param {string} id 4594 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4595 */ 4596 propagate: function( id ) { 4597 this.trigger( id ); 4598 4599 if ( this.options.propagate ) { 4600 this.controller.trigger( id ); 4601 } 4602 4603 return this; 4604 }, 4605 /** 4606 * @param {Object} event 4607 */ 4608 keydown: function( event ) { 4609 // Close the modal when escape is pressed. 4610 if ( 27 === event.which && this.$el.is(':visible') ) { 4611 this.escape(); 4612 event.stopImmediatePropagation(); 4613 } 4614 } 4615 }); 4616 4617 module.exports = Modal; 4618 4619 4620 /***/ }), 4621 /* 56 */ 4622 /***/ (function(module, exports) { 4623 4624 var $ = jQuery; 4625 4626 /** 4627 * wp.media.view.FocusManager 4628 * 4629 * @memberOf wp.media.view 4630 * 4631 * @class 4632 * @augments wp.media.View 4633 * @augments wp.Backbone.View 4634 * @augments Backbone.View 4635 */ 4636 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 4637 4638 events: { 4639 'keydown': 'focusManagementMode' 4640 }, 4641 4642 /** 4643 * Initializes the Focus Manager. 4644 * 4645 * @param {Object} options The Focus Manager options. 4646 * 4647 * @since 5.3.0 4648 * 4649 * @return {void} 4650 */ 4651 initialize: function( options ) { 4652 this.mode = options.mode || 'constrainTabbing'; 4653 this.tabsAutomaticActivation = options.tabsAutomaticActivation || false; 4654 }, 4655 4656 /** 4657 * Determines which focus management mode to use. 4658 * 4659 * @since 5.3.0 4660 * 4661 * @param {Object} event jQuery event object. 4662 * 4663 * @return {void} 4664 */ 4665 focusManagementMode: function( event ) { 4666 if ( this.mode === 'constrainTabbing' ) { 4667 this.constrainTabbing( event ); 4668 } 4669 4670 if ( this.mode === 'tabsNavigation' ) { 4671 this.tabsNavigation( event ); 4672 } 4673 }, 4674 4675 /** 4676 * Gets all the tabbable elements. 4677 * 4678 * @since 5.3.0 4679 * 4680 * @return {Object} A jQuery collection of tabbable elements. 4681 */ 4682 getTabbables: function() { 4683 // Skip the file input added by Plupload. 4684 return this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4685 }, 4686 4687 /** 4688 * Moves focus to the modal dialog. 4689 * 4690 * @since 3.5.0 4691 * 4692 * @return {void} 4693 */ 4694 focus: function() { 4695 this.$( '.media-modal' ).focus(); 4696 }, 4697 4698 /** 4699 * Constrains navigation with the Tab key within the media view element. 4700 * 4701 * @since 4.0.0 4702 * 4703 * @param {Object} event A keydown jQuery event. 4704 * 4705 * @return {void} 4706 */ 4707 constrainTabbing: function( event ) { 4708 var tabbables; 4709 4710 // Look for the tab key. 4711 if ( 9 !== event.keyCode ) { 4712 return; 4713 } 4714 4715 tabbables = this.getTabbables(); 4716 4717 // Keep tab focus within media modal while it's open. 4718 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4719 tabbables.first().focus(); 4720 return false; 4721 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4722 tabbables.last().focus(); 4723 return false; 4724 } 4725 }, 4726 4727 /** 4728 * Hides from assistive technologies all the body children. 4729 * 4730 * Sets an `aria-hidden="true"` attribute on all the body children except 4731 * the provided element and other elements that should not be hidden. 4732 * 4733 * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy 4734 * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"` 4735 * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside 4736 * of the modal dialog and get hidden from assistive technologies. 4737 * 4738 * @since 5.2.3 4739 * 4740 * @param {Object} visibleElement The jQuery object representing the element that should not be hidden. 4741 * 4742 * @return {void} 4743 */ 4744 setAriaHiddenOnBodyChildren: function( visibleElement ) { 4745 var bodyChildren, 4746 self = this; 4747 4748 if ( this.isBodyAriaHidden ) { 4749 return; 4750 } 4751 4752 // Get all the body children. 4753 bodyChildren = document.body.children; 4754 4755 // Loop through the body children and hide the ones that should be hidden. 4756 _.each( bodyChildren, function( element ) { 4757 // Don't hide the modal element. 4758 if ( element === visibleElement[0] ) { 4759 return; 4760 } 4761 4762 // Determine the body children to hide. 4763 if ( self.elementShouldBeHidden( element ) ) { 4764 element.setAttribute( 'aria-hidden', 'true' ); 4765 // Store the hidden elements. 4766 self.ariaHiddenElements.push( element ); 4767 } 4768 } ); 4769 4770 this.isBodyAriaHidden = true; 4771 }, 4772 4773 /** 4774 * Unhides from assistive technologies all the body children. 4775 * 4776 * Makes visible again to assistive technologies all the body children 4777 * previously hidden and stored in this.ariaHiddenElements. 4778 * 4779 * @since 5.2.3 4780 * 4781 * @return {void} 4782 */ 4783 removeAriaHiddenFromBodyChildren: function() { 4784 _.each( this.ariaHiddenElements, function( element ) { 4785 element.removeAttribute( 'aria-hidden' ); 4786 } ); 4787 4788 this.ariaHiddenElements = []; 4789 this.isBodyAriaHidden = false; 4790 }, 4791 4792 /** 4793 * Determines if the passed element should not be hidden from assistive technologies. 4794 * 4795 * @since 5.2.3 4796 * 4797 * @param {Object} element The DOM element that should be checked. 4798 * 4799 * @return {boolean} Whether the element should not be hidden from assistive technologies. 4800 */ 4801 elementShouldBeHidden: function( element ) { 4802 var role = element.getAttribute( 'role' ), 4803 liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ]; 4804 4805 /* 4806 * Don't hide scripts, elements that already have `aria-hidden`, and 4807 * ARIA live regions. 4808 */ 4809 return ! ( 4810 element.tagName === 'SCRIPT' || 4811 element.hasAttribute( 'aria-hidden' ) || 4812 element.hasAttribute( 'aria-live' ) || 4813 liveRegionsRoles.indexOf( role ) !== -1 4814 ); 4815 }, 4816 4817 /** 4818 * Whether the body children are hidden from assistive technologies. 4819 * 4820 * @since 5.2.3 4821 */ 4822 isBodyAriaHidden: false, 4823 4824 /** 4825 * Stores an array of DOM elements that should be hidden from assistive 4826 * technologies, for example when the media modal dialog opens. 4827 * 4828 * @since 5.2.3 4829 */ 4830 ariaHiddenElements: [], 4831 4832 /** 4833 * Holds the jQuery collection of ARIA tabs. 4834 * 4835 * @since 5.3.0 4836 */ 4837 tabs: $(), 4838 4839 /** 4840 * Sets up tabs in an ARIA tabbed interface. 4841 * 4842 * @since 5.3.0 4843 * 4844 * @param {Object} event jQuery event object. 4845 * 4846 * @return {void} 4847 */ 4848 setupAriaTabs: function() { 4849 this.tabs = this.$( '[role="tab"]' ); 4850 4851 // Set up initial attributes. 4852 this.tabs.attr( { 4853 'aria-selected': 'false', 4854 tabIndex: '-1' 4855 } ); 4856 4857 // Set up attributes on the initially active tab. 4858 this.tabs.filter( '.active' ) 4859 .removeAttr( 'tabindex' ) 4860 .attr( 'aria-selected', 'true' ); 4861 }, 4862 4863 /** 4864 * Enables arrows navigation within the ARIA tabbed interface. 4865 * 4866 * @since 5.3.0 4867 * 4868 * @param {Object} event jQuery event object. 4869 * 4870 * @return {void} 4871 */ 4872 tabsNavigation: function( event ) { 4873 var orientation = 'horizontal', 4874 keys = [ 32, 35, 36, 37, 38, 39, 40 ]; 4875 4876 // Return if not Spacebar, End, Home, or Arrow keys. 4877 if ( keys.indexOf( event.which ) === -1 ) { 4878 return; 4879 } 4880 4881 // Determine navigation direction. 4882 if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) { 4883 orientation = 'vertical'; 4884 } 4885 4886 // Make Up and Down arrow keys do nothing with horizontal tabs. 4887 if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { 4888 return; 4889 } 4890 4891 // Make Left and Right arrow keys do nothing with vertical tabs. 4892 if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { 4893 return; 4894 } 4895 4896 this.switchTabs( event, this.tabs ); 4897 }, 4898 4899 /** 4900 * Switches tabs in the ARIA tabbed interface. 4901 * 4902 * @since 5.3.0 4903 * 4904 * @param {Object} event jQuery event object. 4905 * 4906 * @return {void} 4907 */ 4908 switchTabs: function( event ) { 4909 var key = event.which, 4910 index = this.tabs.index( $( event.target ) ), 4911 newIndex; 4912 4913 switch ( key ) { 4914 // Space bar: Activate current targeted tab. 4915 case 32: { 4916 this.activateTab( this.tabs[ index ] ); 4917 break; 4918 } 4919 // End key: Activate last tab. 4920 case 35: { 4921 event.preventDefault(); 4922 this.activateTab( this.tabs[ this.tabs.length - 1 ] ); 4923 break; 4924 } 4925 // Home key: Activate first tab. 4926 case 36: { 4927 event.preventDefault(); 4928 this.activateTab( this.tabs[ 0 ] ); 4929 break; 4930 } 4931 // Left and up keys: Activate previous tab. 4932 case 37: 4933 case 38: { 4934 event.preventDefault(); 4935 newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1; 4936 this.activateTab( this.tabs[ newIndex ] ); 4937 break; 4938 } 4939 // Right and down keys: Activate next tab. 4940 case 39: 4941 case 40: { 4942 event.preventDefault(); 4943 newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1; 4944 this.activateTab( this.tabs[ newIndex ] ); 4945 break; 4946 } 4947 } 4948 }, 4949 4950 /** 4951 * Sets a single tab to be focusable and semantically selected. 4952 * 4953 * @since 5.3.0 4954 * 4955 * @param {Object} tab The tab DOM element. 4956 * 4957 * @return {void} 4958 */ 4959 activateTab: function( tab ) { 4960 if ( ! tab ) { 4961 return; 4962 } 4963 4964 // The tab is a DOM element: no need for jQuery methods. 4965 tab.focus(); 4966 4967 // Handle automatic activation. 4968 if ( this.tabsAutomaticActivation ) { 4969 tab.removeAttribute( 'tabindex' ); 4970 tab.setAttribute( 'aria-selected', 'true' ); 4971 tab.click(); 4972 4973 return; 4974 } 4975 4976 // Handle manual activation. 4977 $( tab ).on( 'click', function() { 4978 tab.removeAttribute( 'tabindex' ); 4979 tab.setAttribute( 'aria-selected', 'true' ); 4980 } ); 4981 } 4982 }); 4983 4984 module.exports = FocusManager; 4985 4986 4987 /***/ }), 4988 /* 57 */ 4989 /***/ (function(module, exports) { 4990 4991 var $ = jQuery, 4992 UploaderWindow; 4993 4994 /** 4995 * wp.media.view.UploaderWindow 4996 * 4997 * An uploader window that allows for dragging and dropping media. 4998 * 4999 * @memberOf wp.media.view 5000 * 5001 * @class 5002 * @augments wp.media.View 5003 * @augments wp.Backbone.View 5004 * @augments Backbone.View 5005 * 5006 * @param {object} [options] Options hash passed to the view. 5007 * @param {object} [options.uploader] Uploader properties. 5008 * @param {jQuery} [options.uploader.browser] 5009 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 5010 * @param {object} [options.uploader.params] 5011 */ 5012 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{ 5013 tagName: 'div', 5014 className: 'uploader-window', 5015 template: wp.template('uploader-window'), 5016 5017 initialize: function() { 5018 var uploader; 5019 5020 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 5021 5022 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 5023 dropzone: this.$el, 5024 browser: this.$browser, 5025 params: {} 5026 }); 5027 5028 // Ensure the dropzone is a jQuery collection. 5029 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 5030 uploader.dropzone = $( uploader.dropzone ); 5031 } 5032 5033 this.controller.on( 'activate', this.refresh, this ); 5034 5035 this.controller.on( 'detach', function() { 5036 this.$browser.remove(); 5037 }, this ); 5038 }, 5039 5040 refresh: function() { 5041 if ( this.uploader ) { 5042 this.uploader.refresh(); 5043 } 5044 }, 5045 5046 ready: function() { 5047 var postId = wp.media.view.settings.post.id, 5048 dropzone; 5049 5050 // If the uploader already exists, bail. 5051 if ( this.uploader ) { 5052 return; 5053 } 5054 5055 if ( postId ) { 5056 this.options.uploader.params.post_id = postId; 5057 } 5058 this.uploader = new wp.Uploader( this.options.uploader ); 5059 5060 dropzone = this.uploader.dropzone; 5061 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 5062 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 5063 5064 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 5065 }, 5066 5067 _ready: function() { 5068 this.controller.trigger( 'uploader:ready' ); 5069 }, 5070 5071 show: function() { 5072 var $el = this.$el.show(); 5073 5074 // Ensure that the animation is triggered by waiting until 5075 // the transparent element is painted into the DOM. 5076 _.defer( function() { 5077 $el.css({ opacity: 1 }); 5078 }); 5079 }, 5080 5081 hide: function() { 5082 var $el = this.$el.css({ opacity: 0 }); 5083 5084 wp.media.transition( $el ).done( function() { 5085 // Transition end events are subject to race conditions. 5086 // Make sure that the value is set as intended. 5087 if ( '0' === $el.css('opacity') ) { 5088 $el.hide(); 5089 } 5090 }); 5091 5092 // https://core.trac.wordpress.org/ticket/27341 5093 _.delay( function() { 5094 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 5095 $el.hide(); 5096 } 5097 }, 500 ); 5098 } 5099 }); 5100 5101 module.exports = UploaderWindow; 5102 5103 5104 /***/ }), 5105 /* 58 */ 5106 /***/ (function(module, exports) { 5107 5108 var View = wp.media.View, 5109 l10n = wp.media.view.l10n, 5110 $ = jQuery, 5111 EditorUploader; 5112 5113 /** 5114 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 5115 * and relays drag'n'dropped files to a media workflow. 5116 * 5117 * wp.media.view.EditorUploader 5118 * 5119 * @memberOf wp.media.view 5120 * 5121 * @class 5122 * @augments wp.media.View 5123 * @augments wp.Backbone.View 5124 * @augments Backbone.View 5125 */ 5126 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{ 5127 tagName: 'div', 5128 className: 'uploader-editor', 5129 template: wp.template( 'uploader-editor' ), 5130 5131 localDrag: false, 5132 overContainer: false, 5133 overDropzone: false, 5134 draggingFile: null, 5135 5136 /** 5137 * Bind drag'n'drop events to callbacks. 5138 */ 5139 initialize: function() { 5140 this.initialized = false; 5141 5142 // Bail if not enabled or UA does not support drag'n'drop or File API. 5143 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 5144 return this; 5145 } 5146 5147 this.$document = $(document); 5148 this.dropzones = []; 5149 this.files = []; 5150 5151 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 5152 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 5153 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 5154 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 5155 5156 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 5157 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 5158 5159 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 5160 this.localDrag = event.type === 'dragstart'; 5161 5162 if ( event.type === 'drop' ) { 5163 this.containerDragleave(); 5164 } 5165 }, this ) ); 5166 5167 this.initialized = true; 5168 return this; 5169 }, 5170 5171 /** 5172 * Check browser support for drag'n'drop. 5173 * 5174 * @return {boolean} 5175 */ 5176 browserSupport: function() { 5177 var supports = false, div = document.createElement('div'); 5178 5179 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 5180 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 5181 return supports; 5182 }, 5183 5184 isDraggingFile: function( event ) { 5185 if ( this.draggingFile !== null ) { 5186 return this.draggingFile; 5187 } 5188 5189 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 5190 return false; 5191 } 5192 5193 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 5194 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 5195 5196 return this.draggingFile; 5197 }, 5198 5199 refresh: function( e ) { 5200 var dropzone_id; 5201 for ( dropzone_id in this.dropzones ) { 5202 // Hide the dropzones only if dragging has left the screen. 5203 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 5204 } 5205 5206 if ( ! _.isUndefined( e ) ) { 5207 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 5208 } 5209 5210 if ( ! this.overContainer && ! this.overDropzone ) { 5211 this.draggingFile = null; 5212 } 5213 5214 return this; 5215 }, 5216 5217 render: function() { 5218 if ( ! this.initialized ) { 5219 return this; 5220 } 5221 5222 View.prototype.render.apply( this, arguments ); 5223 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 5224 return this; 5225 }, 5226 5227 attach: function( index, editor ) { 5228 // Attach a dropzone to an editor. 5229 var dropzone = this.$el.clone(); 5230 this.dropzones.push( dropzone ); 5231 $( editor ).append( dropzone ); 5232 return this; 5233 }, 5234 5235 /** 5236 * When a file is dropped on the editor uploader, open up an editor media workflow 5237 * and upload the file immediately. 5238 * 5239 * @param {jQuery.Event} event The 'drop' event. 5240 */ 5241 drop: function( event ) { 5242 var $wrap, uploadView; 5243 5244 this.containerDragleave( event ); 5245 this.dropzoneDragleave( event ); 5246 5247 this.files = event.originalEvent.dataTransfer.files; 5248 if ( this.files.length < 1 ) { 5249 return; 5250 } 5251 5252 // Set the active editor to the drop target. 5253 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 5254 if ( $wrap.length > 0 && $wrap[0].id ) { 5255 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 5256 } 5257 5258 if ( ! this.workflow ) { 5259 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 5260 frame: 'post', 5261 state: 'insert', 5262 title: l10n.addMedia, 5263 multiple: true 5264 }); 5265 5266 uploadView = this.workflow.uploader; 5267 5268 if ( uploadView.uploader && uploadView.uploader.ready ) { 5269 this.addFiles.apply( this ); 5270 } else { 5271 this.workflow.on( 'uploader:ready', this.addFiles, this ); 5272 } 5273 } else { 5274 this.workflow.state().reset(); 5275 this.addFiles.apply( this ); 5276 this.workflow.open(); 5277 } 5278 5279 return false; 5280 }, 5281 5282 /** 5283 * Add the files to the uploader. 5284 */ 5285 addFiles: function() { 5286 if ( this.files.length ) { 5287 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 5288 this.files = []; 5289 } 5290 return this; 5291 }, 5292 5293 containerDragover: function( event ) { 5294 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 5295 return; 5296 } 5297 5298 this.overContainer = true; 5299 this.refresh(); 5300 }, 5301 5302 containerDragleave: function() { 5303 this.overContainer = false; 5304 5305 // Throttle dragleave because it's called when bouncing from some elements to others. 5306 _.delay( _.bind( this.refresh, this ), 50 ); 5307 }, 5308 5309 dropzoneDragover: function( event ) { 5310 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 5311 return; 5312 } 5313 5314 this.overDropzone = true; 5315 this.refresh( event ); 5316 return false; 5317 }, 5318 5319 dropzoneDragleave: function( e ) { 5320 this.overDropzone = false; 5321 _.delay( _.bind( this.refresh, this, e ), 50 ); 5322 }, 5323 5324 click: function( e ) { 5325 // In the rare case where the dropzone gets stuck, hide it on click. 5326 this.containerDragleave( e ); 5327 this.dropzoneDragleave( e ); 5328 this.localDrag = false; 5329 } 5330 }); 5331 5332 module.exports = EditorUploader; 5333 5334 5335 /***/ }), 5336 /* 59 */ 5337 /***/ (function(module, exports) { 5338 5339 var View = wp.media.View, 5340 UploaderInline; 5341 5342 /** 5343 * wp.media.view.UploaderInline 5344 * 5345 * The inline uploader that shows up in the 'Upload Files' tab. 5346 * 5347 * @memberOf wp.media.view 5348 * 5349 * @class 5350 * @augments wp.media.View 5351 * @augments wp.Backbone.View 5352 * @augments Backbone.View 5353 */ 5354 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 5355 tagName: 'div', 5356 className: 'uploader-inline', 5357 template: wp.template('uploader-inline'), 5358 5359 events: { 5360 'click .close': 'hide' 5361 }, 5362 5363 initialize: function() { 5364 _.defaults( this.options, { 5365 message: '', 5366 status: true, 5367 canClose: false 5368 }); 5369 5370 if ( ! this.options.$browser && this.controller.uploader ) { 5371 this.options.$browser = this.controller.uploader.$browser; 5372 } 5373 5374 if ( _.isUndefined( this.options.postId ) ) { 5375 this.options.postId = wp.media.view.settings.post.id; 5376 } 5377 5378 if ( this.options.status ) { 5379 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 5380 controller: this.controller 5381 }) ); 5382 } 5383 }, 5384 5385 prepare: function() { 5386 var suggestedWidth = this.controller.state().get('suggestedWidth'), 5387 suggestedHeight = this.controller.state().get('suggestedHeight'), 5388 data = {}; 5389 5390 data.message = this.options.message; 5391 data.canClose = this.options.canClose; 5392 5393 if ( suggestedWidth && suggestedHeight ) { 5394 data.suggestedWidth = suggestedWidth; 5395 data.suggestedHeight = suggestedHeight; 5396 } 5397 5398 return data; 5399 }, 5400 /** 5401 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 5402 */ 5403 dispose: function() { 5404 if ( this.disposing ) { 5405 /** 5406 * call 'dispose' directly on the parent class 5407 */ 5408 return View.prototype.dispose.apply( this, arguments ); 5409 } 5410 5411 /* 5412 * Run remove on `dispose`, so we can be sure to refresh the 5413 * uploader with a view-less DOM. Track whether we're disposing 5414 * so we don't trigger an infinite loop. 5415 */ 5416 this.disposing = true; 5417 return this.remove(); 5418 }, 5419 /** 5420 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 5421 */ 5422 remove: function() { 5423 /** 5424 * call 'remove' directly on the parent class 5425 */ 5426 var result = View.prototype.remove.apply( this, arguments ); 5427 5428 _.defer( _.bind( this.refresh, this ) ); 5429 return result; 5430 }, 5431 5432 refresh: function() { 5433 var uploader = this.controller.uploader; 5434 5435 if ( uploader ) { 5436 uploader.refresh(); 5437 } 5438 }, 5439 /** 5440 * @return {wp.media.view.UploaderInline} 5441 */ 5442 ready: function() { 5443 var $browser = this.options.$browser, 5444 $placeholder; 5445 5446 if ( this.controller.uploader ) { 5447 $placeholder = this.$('.browser'); 5448 5449 // Check if we've already replaced the placeholder. 5450 if ( $placeholder[0] === $browser[0] ) { 5451 return; 5452 } 5453 5454 $browser.detach().text( $placeholder.text() ); 5455 $browser[0].className = $placeholder[0].className; 5456 $placeholder.replaceWith( $browser.show() ); 5457 } 5458 5459 this.refresh(); 5460 return this; 5461 }, 5462 show: function() { 5463 this.$el.removeClass( 'hidden' ); 5464 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 5465 this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' ); 5466 } 5467 }, 5468 hide: function() { 5469 this.$el.addClass( 'hidden' ); 5470 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 5471 this.controller.$uploaderToggler 5472 .attr( 'aria-expanded', 'false' ) 5473 // Move focus back to the toggle button when closing the uploader. 5474 .focus(); 5475 } 5476 } 5477 5478 }); 5479 5480 module.exports = UploaderInline; 5481 5482 5483 /***/ }), 5484 /* 60 */ 5485 /***/ (function(module, exports) { 5486 5487 var View = wp.media.View, 5488 UploaderStatus; 5489 5490 /** 5491 * wp.media.view.UploaderStatus 5492 * 5493 * An uploader status for on-going uploads. 5494 * 5495 * @memberOf wp.media.view 5496 * 5497 * @class 5498 * @augments wp.media.View 5499 * @augments wp.Backbone.View 5500 * @augments Backbone.View 5501 */ 5502 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{ 5503 className: 'media-uploader-status', 5504 template: wp.template('uploader-status'), 5505 5506 events: { 5507 'click .upload-dismiss-errors': 'dismiss' 5508 }, 5509 5510 initialize: function() { 5511 this.queue = wp.Uploader.queue; 5512 this.queue.on( 'add remove reset', this.visibility, this ); 5513 this.queue.on( 'add remove reset change:percent', this.progress, this ); 5514 this.queue.on( 'add remove reset change:uploading', this.info, this ); 5515 5516 this.errors = wp.Uploader.errors; 5517 this.errors.reset(); 5518 this.errors.on( 'add remove reset', this.visibility, this ); 5519 this.errors.on( 'add', this.error, this ); 5520 }, 5521 /** 5522 * @return {wp.media.view.UploaderStatus} 5523 */ 5524 dispose: function() { 5525 wp.Uploader.queue.off( null, null, this ); 5526 /** 5527 * call 'dispose' directly on the parent class 5528 */ 5529 View.prototype.dispose.apply( this, arguments ); 5530 return this; 5531 }, 5532 5533 visibility: function() { 5534 this.$el.toggleClass( 'uploading', !! this.queue.length ); 5535 this.$el.toggleClass( 'errors', !! this.errors.length ); 5536 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 5537 }, 5538 5539 ready: function() { 5540 _.each({ 5541 '$bar': '.media-progress-bar div', 5542 '$index': '.upload-index', 5543 '$total': '.upload-total', 5544 '$filename': '.upload-filename' 5545 }, function( selector, key ) { 5546 this[ key ] = this.$( selector ); 5547 }, this ); 5548 5549 this.visibility(); 5550 this.progress(); 5551 this.info(); 5552 }, 5553 5554 progress: function() { 5555 var queue = this.queue, 5556 $bar = this.$bar; 5557 5558 if ( ! $bar || ! queue.length ) { 5559 return; 5560 } 5561 5562 $bar.width( ( queue.reduce( function( memo, attachment ) { 5563 if ( ! attachment.get('uploading') ) { 5564 return memo + 100; 5565 } 5566 5567 var percent = attachment.get('percent'); 5568 return memo + ( _.isNumber( percent ) ? percent : 100 ); 5569 }, 0 ) / queue.length ) + '%' ); 5570 }, 5571 5572 info: function() { 5573 var queue = this.queue, 5574 index = 0, active; 5575 5576 if ( ! queue.length ) { 5577 return; 5578 } 5579 5580 active = this.queue.find( function( attachment, i ) { 5581 index = i; 5582 return attachment.get('uploading'); 5583 }); 5584 5585 this.$index.text( index + 1 ); 5586 this.$total.text( queue.length ); 5587 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 5588 }, 5589 /** 5590 * @param {string} filename 5591 * @return {string} 5592 */ 5593 filename: function( filename ) { 5594 return _.escape( filename ); 5595 }, 5596 /** 5597 * @param {Backbone.Model} error 5598 */ 5599 error: function( error ) { 5600 var statusError = new wp.media.view.UploaderStatusError( { 5601 filename: this.filename( error.get( 'file' ).name ), 5602 message: error.get( 'message' ) 5603 } ); 5604 5605 // Can show additional info here while retrying to create image sub-sizes. 5606 this.views.add( '.upload-errors', statusError, { at: 0 } ); 5607 }, 5608 5609 dismiss: function() { 5610 var errors = this.views.get('.upload-errors'); 5611 5612 if ( errors ) { 5613 _.invoke( errors, 'remove' ); 5614 } 5615 wp.Uploader.errors.reset(); 5616 // Move focus to the modal after the dismiss button gets removed from the DOM. 5617 if ( this.controller.modal ) { 5618 this.controller.modal.focusManager.focus(); 5619 } 5620 } 5621 }); 5622 5623 module.exports = UploaderStatus; 5624 5625 5626 /***/ }), 5627 /* 61 */ 5628 /***/ (function(module, exports) { 5629 5630 /** 5631 * wp.media.view.UploaderStatusError 5632 * 5633 * @memberOf wp.media.view 5634 * 5635 * @class 5636 * @augments wp.media.View 5637 * @augments wp.Backbone.View 5638 * @augments Backbone.View 5639 */ 5640 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{ 5641 className: 'upload-error', 5642 template: wp.template('uploader-status-error') 5643 }); 5644 5645 module.exports = UploaderStatusError; 5646 5647 5648 /***/ }), 5649 /* 62 */ 5650 /***/ (function(module, exports) { 5651 5652 var View = wp.media.View, 5653 Toolbar; 5654 5655 /** 5656 * wp.media.view.Toolbar 5657 * 5658 * A toolbar which consists of a primary and a secondary section. Each sections 5659 * can be filled with views. 5660 * 5661 * @memberOf wp.media.view 5662 * 5663 * @class 5664 * @augments wp.media.View 5665 * @augments wp.Backbone.View 5666 * @augments Backbone.View 5667 */ 5668 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ 5669 tagName: 'div', 5670 className: 'media-toolbar', 5671 5672 initialize: function() { 5673 var state = this.controller.state(), 5674 selection = this.selection = state.get('selection'), 5675 library = this.library = state.get('library'); 5676 5677 this._views = {}; 5678 5679 // The toolbar is composed of two `PriorityList` views. 5680 this.primary = new wp.media.view.PriorityList(); 5681 this.secondary = new wp.media.view.PriorityList(); 5682 this.primary.$el.addClass('media-toolbar-primary search-form'); 5683 this.secondary.$el.addClass('media-toolbar-secondary'); 5684 5685 this.views.set([ this.secondary, this.primary ]); 5686 5687 if ( this.options.items ) { 5688 this.set( this.options.items, { silent: true }); 5689 } 5690 5691 if ( ! this.options.silent ) { 5692 this.render(); 5693 } 5694 5695 if ( selection ) { 5696 selection.on( 'add remove reset', this.refresh, this ); 5697 } 5698 5699 if ( library ) { 5700 library.on( 'add remove reset', this.refresh, this ); 5701 } 5702 }, 5703 /** 5704 * @return {wp.media.view.Toolbar} Returns itsef to allow chaining 5705 */ 5706 dispose: function() { 5707 if ( this.selection ) { 5708 this.selection.off( null, null, this ); 5709 } 5710 5711 if ( this.library ) { 5712 this.library.off( null, null, this ); 5713 } 5714 /** 5715 * call 'dispose' directly on the parent class 5716 */ 5717 return View.prototype.dispose.apply( this, arguments ); 5718 }, 5719 5720 ready: function() { 5721 this.refresh(); 5722 }, 5723 5724 /** 5725 * @param {string} id 5726 * @param {Backbone.View|Object} view 5727 * @param {Object} [options={}] 5728 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 5729 */ 5730 set: function( id, view, options ) { 5731 var list; 5732 options = options || {}; 5733 5734 // Accept an object with an `id` : `view` mapping. 5735 if ( _.isObject( id ) ) { 5736 _.each( id, function( view, id ) { 5737 this.set( id, view, { silent: true }); 5738 }, this ); 5739 5740 } else { 5741 if ( ! ( view instanceof Backbone.View ) ) { 5742 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 5743 view = new wp.media.view.Button( view ).render(); 5744 } 5745 5746 view.controller = view.controller || this.controller; 5747 5748 this._views[ id ] = view; 5749 5750 list = view.options.priority < 0 ? 'secondary' : 'primary'; 5751 this[ list ].set( id, view, options ); 5752 } 5753 5754 if ( ! options.silent ) { 5755 this.refresh(); 5756 } 5757 5758 return this; 5759 }, 5760 /** 5761 * @param {string} id 5762 * @return {wp.media.view.Button} 5763 */ 5764 get: function( id ) { 5765 return this._views[ id ]; 5766 }, 5767 /** 5768 * @param {string} id 5769 * @param {Object} options 5770 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 5771 */ 5772 unset: function( id, options ) { 5773 delete this._views[ id ]; 5774 this.primary.unset( id, options ); 5775 this.secondary.unset( id, options ); 5776 5777 if ( ! options || ! options.silent ) { 5778 this.refresh(); 5779 } 5780 return this; 5781 }, 5782 5783 refresh: function() { 5784 var state = this.controller.state(), 5785 library = state.get('library'), 5786 selection = state.get('selection'); 5787 5788 _.each( this._views, function( button ) { 5789 if ( ! button.model || ! button.options || ! button.options.requires ) { 5790 return; 5791 } 5792 5793 var requires = button.options.requires, 5794 disabled = false; 5795 5796 // Prevent insertion of attachments if any of them are still uploading. 5797 if ( selection && selection.models ) { 5798 disabled = _.some( selection.models, function( attachment ) { 5799 return attachment.get('uploading') === true; 5800 }); 5801 } 5802 5803 if ( requires.selection && selection && ! selection.length ) { 5804 disabled = true; 5805 } else if ( requires.library && library && ! library.length ) { 5806 disabled = true; 5807 } 5808 button.model.set( 'disabled', disabled ); 5809 }); 5810 } 5811 }); 5812 5813 module.exports = Toolbar; 5814 5815 5816 /***/ }), 5817 /* 63 */ 5818 /***/ (function(module, exports) { 5819 5820 var Toolbar = wp.media.view.Toolbar, 5821 l10n = wp.media.view.l10n, 5822 Select; 5823 5824 /** 5825 * wp.media.view.Toolbar.Select 5826 * 5827 * @memberOf wp.media.view.Toolbar 5828 * 5829 * @class 5830 * @augments wp.media.view.Toolbar 5831 * @augments wp.media.View 5832 * @augments wp.Backbone.View 5833 * @augments Backbone.View 5834 */ 5835 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{ 5836 initialize: function() { 5837 var options = this.options; 5838 5839 _.bindAll( this, 'clickSelect' ); 5840 5841 _.defaults( options, { 5842 event: 'select', 5843 state: false, 5844 reset: true, 5845 close: true, 5846 text: l10n.select, 5847 5848 // Does the button rely on the selection? 5849 requires: { 5850 selection: true 5851 } 5852 }); 5853 5854 options.items = _.defaults( options.items || {}, { 5855 select: { 5856 style: 'primary', 5857 text: options.text, 5858 priority: 80, 5859 click: this.clickSelect, 5860 requires: options.requires 5861 } 5862 }); 5863 // Call 'initialize' directly on the parent class. 5864 Toolbar.prototype.initialize.apply( this, arguments ); 5865 }, 5866 5867 clickSelect: function() { 5868 var options = this.options, 5869 controller = this.controller; 5870 5871 if ( options.close ) { 5872 controller.close(); 5873 } 5874 5875 if ( options.event ) { 5876 controller.state().trigger( options.event ); 5877 } 5878 5879 if ( options.state ) { 5880 controller.setState( options.state ); 5881 } 5882 5883 if ( options.reset ) { 5884 controller.reset(); 5885 } 5886 } 5887 }); 5888 5889 module.exports = Select; 5890 5891 5892 /***/ }), 5893 /* 64 */ 5894 /***/ (function(module, exports) { 5895 5896 var Select = wp.media.view.Toolbar.Select, 5897 l10n = wp.media.view.l10n, 5898 Embed; 5899 5900 /** 5901 * wp.media.view.Toolbar.Embed 5902 * 5903 * @memberOf wp.media.view.Toolbar 5904 * 5905 * @class 5906 * @augments wp.media.view.Toolbar.Select 5907 * @augments wp.media.view.Toolbar 5908 * @augments wp.media.View 5909 * @augments wp.Backbone.View 5910 * @augments Backbone.View 5911 */ 5912 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{ 5913 initialize: function() { 5914 _.defaults( this.options, { 5915 text: l10n.insertIntoPost, 5916 requires: false 5917 }); 5918 // Call 'initialize' directly on the parent class. 5919 Select.prototype.initialize.apply( this, arguments ); 5920 }, 5921 5922 refresh: function() { 5923 var url = this.controller.state().props.get('url'); 5924 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 5925 /** 5926 * call 'refresh' directly on the parent class 5927 */ 5928 Select.prototype.refresh.apply( this, arguments ); 5929 } 5930 }); 5931 5932 module.exports = Embed; 5933 5934 5935 /***/ }), 5936 /* 65 */ 5937 /***/ (function(module, exports) { 5938 5939 /** 5940 * wp.media.view.Button 5941 * 5942 * @memberOf wp.media.view 5943 * 5944 * @class 5945 * @augments wp.media.View 5946 * @augments wp.Backbone.View 5947 * @augments Backbone.View 5948 */ 5949 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{ 5950 tagName: 'button', 5951 className: 'media-button', 5952 attributes: { type: 'button' }, 5953 5954 events: { 5955 'click': 'click' 5956 }, 5957 5958 defaults: { 5959 text: '', 5960 style: '', 5961 size: 'large', 5962 disabled: false 5963 }, 5964 5965 initialize: function() { 5966 /** 5967 * Create a model with the provided `defaults`. 5968 * 5969 * @member {Backbone.Model} 5970 */ 5971 this.model = new Backbone.Model( this.defaults ); 5972 5973 // If any of the `options` have a key from `defaults`, apply its 5974 // value to the `model` and remove it from the `options object. 5975 _.each( this.defaults, function( def, key ) { 5976 var value = this.options[ key ]; 5977 if ( _.isUndefined( value ) ) { 5978 return; 5979 } 5980 5981 this.model.set( key, value ); 5982 delete this.options[ key ]; 5983 }, this ); 5984 5985 this.listenTo( this.model, 'change', this.render ); 5986 }, 5987 /** 5988 * @return {wp.media.view.Button} Returns itself to allow chaining. 5989 */ 5990 render: function() { 5991 var classes = [ 'button', this.className ], 5992 model = this.model.toJSON(); 5993 5994 if ( model.style ) { 5995 classes.push( 'button-' + model.style ); 5996 } 5997 5998 if ( model.size ) { 5999 classes.push( 'button-' + model.size ); 6000 } 6001 6002 classes = _.uniq( classes.concat( this.options.classes ) ); 6003 this.el.className = classes.join(' '); 6004 6005 this.$el.attr( 'disabled', model.disabled ); 6006 this.$el.text( this.model.get('text') ); 6007 6008 return this; 6009 }, 6010 /** 6011 * @param {Object} event 6012 */ 6013 click: function( event ) { 6014 if ( '#' === this.attributes.href ) { 6015 event.preventDefault(); 6016 } 6017 6018 if ( this.options.click && ! this.model.get('disabled') ) { 6019 this.options.click.apply( this, arguments ); 6020 } 6021 } 6022 }); 6023 6024 module.exports = Button; 6025 6026 6027 /***/ }), 6028 /* 66 */ 6029 /***/ (function(module, exports) { 6030 6031 var $ = Backbone.$, 6032 ButtonGroup; 6033 6034 /** 6035 * wp.media.view.ButtonGroup 6036 * 6037 * @memberOf wp.media.view 6038 * 6039 * @class 6040 * @augments wp.media.View 6041 * @augments wp.Backbone.View 6042 * @augments Backbone.View 6043 */ 6044 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{ 6045 tagName: 'div', 6046 className: 'button-group button-large media-button-group', 6047 6048 initialize: function() { 6049 /** 6050 * @member {wp.media.view.Button[]} 6051 */ 6052 this.buttons = _.map( this.options.buttons || [], function( button ) { 6053 if ( button instanceof Backbone.View ) { 6054 return button; 6055 } else { 6056 return new wp.media.view.Button( button ).render(); 6057 } 6058 }); 6059 6060 delete this.options.buttons; 6061 6062 if ( this.options.classes ) { 6063 this.$el.addClass( this.options.classes ); 6064 } 6065 }, 6066 6067 /** 6068 * @return {wp.media.view.ButtonGroup} 6069 */ 6070 render: function() { 6071 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 6072 return this; 6073 } 6074 }); 6075 6076 module.exports = ButtonGroup; 6077 6078 6079 /***/ }), 6080 /* 67 */ 6081 /***/ (function(module, exports) { 6082 6083 /** 6084 * wp.media.view.PriorityList 6085 * 6086 * @memberOf wp.media.view 6087 * 6088 * @class 6089 * @augments wp.media.View 6090 * @augments wp.Backbone.View 6091 * @augments Backbone.View 6092 */ 6093 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{ 6094 tagName: 'div', 6095 6096 initialize: function() { 6097 this._views = {}; 6098 6099 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6100 delete this.options.views; 6101 6102 if ( ! this.options.silent ) { 6103 this.render(); 6104 } 6105 }, 6106 /** 6107 * @param {string} id 6108 * @param {wp.media.View|Object} view 6109 * @param {Object} options 6110 * @return {wp.media.view.PriorityList} Returns itself to allow chaining. 6111 */ 6112 set: function( id, view, options ) { 6113 var priority, views, index; 6114 6115 options = options || {}; 6116 6117 // Accept an object with an `id` : `view` mapping. 6118 if ( _.isObject( id ) ) { 6119 _.each( id, function( view, id ) { 6120 this.set( id, view ); 6121 }, this ); 6122 return this; 6123 } 6124 6125 if ( ! (view instanceof Backbone.View) ) { 6126 view = this.toView( view, id, options ); 6127 } 6128 view.controller = view.controller || this.controller; 6129 6130 this.unset( id ); 6131 6132 priority = view.options.priority || 10; 6133 views = this.views.get() || []; 6134 6135 _.find( views, function( existing, i ) { 6136 if ( existing.options.priority > priority ) { 6137 index = i; 6138 return true; 6139 } 6140 }); 6141 6142 this._views[ id ] = view; 6143 this.views.add( view, { 6144 at: _.isNumber( index ) ? index : views.length || 0 6145 }); 6146 6147 return this; 6148 }, 6149 /** 6150 * @param {string} id 6151 * @return {wp.media.View} 6152 */ 6153 get: function( id ) { 6154 return this._views[ id ]; 6155 }, 6156 /** 6157 * @param {string} id 6158 * @return {wp.media.view.PriorityList} 6159 */ 6160 unset: function( id ) { 6161 var view = this.get( id ); 6162 6163 if ( view ) { 6164 view.remove(); 6165 } 6166 6167 delete this._views[ id ]; 6168 return this; 6169 }, 6170 /** 6171 * @param {Object} options 6172 * @return {wp.media.View} 6173 */ 6174 toView: function( options ) { 6175 return new wp.media.View( options ); 6176 } 6177 }); 6178 6179 module.exports = PriorityList; 6180 6181 6182 /***/ }), 6183 /* 68 */ 6184 /***/ (function(module, exports) { 6185 6186 var MenuItem; 6187 6188 /** 6189 * wp.media.view.MenuItem 6190 * 6191 * @memberOf wp.media.view 6192 * 6193 * @class 6194 * @augments wp.media.View 6195 * @augments wp.Backbone.View 6196 * @augments Backbone.View 6197 */ 6198 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{ 6199 tagName: 'button', 6200 className: 'media-menu-item', 6201 6202 attributes: { 6203 type: 'button', 6204 role: 'tab' 6205 }, 6206 6207 events: { 6208 'click': '_click' 6209 }, 6210 6211 /** 6212 * Allows to override the click event. 6213 */ 6214 _click: function() { 6215 var clickOverride = this.options.click; 6216 6217 if ( clickOverride ) { 6218 clickOverride.call( this ); 6219 } else { 6220 this.click(); 6221 } 6222 }, 6223 6224 click: function() { 6225 var state = this.options.state; 6226 6227 if ( state ) { 6228 this.controller.setState( state ); 6229 // Toggle the menu visibility in the responsive view. 6230 this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below. 6231 } 6232 }, 6233 6234 /** 6235 * @return {wp.media.view.MenuItem} returns itself to allow chaining. 6236 */ 6237 render: function() { 6238 var options = this.options, 6239 menuProperty = options.state || options.contentMode; 6240 6241 if ( options.text ) { 6242 this.$el.text( options.text ); 6243 } else if ( options.html ) { 6244 this.$el.html( options.html ); 6245 } 6246 6247 // Set the menu item ID based on the frame state associated to the menu item. 6248 this.$el.attr( 'id', 'menu-item-' + menuProperty ); 6249 6250 return this; 6251 } 6252 }); 6253 6254 module.exports = MenuItem; 6255 6256 6257 /***/ }), 6258 /* 69 */ 6259 /***/ (function(module, exports) { 6260 6261 var MenuItem = wp.media.view.MenuItem, 6262 PriorityList = wp.media.view.PriorityList, 6263 Menu; 6264 6265 /** 6266 * wp.media.view.Menu 6267 * 6268 * @memberOf wp.media.view 6269 * 6270 * @class 6271 * @augments wp.media.view.PriorityList 6272 * @augments wp.media.View 6273 * @augments wp.Backbone.View 6274 * @augments Backbone.View 6275 */ 6276 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{ 6277 tagName: 'div', 6278 className: 'media-menu', 6279 property: 'state', 6280 ItemView: MenuItem, 6281 region: 'menu', 6282 6283 attributes: { 6284 role: 'tablist', 6285 'aria-orientation': 'horizontal' 6286 }, 6287 6288 initialize: function() { 6289 this._views = {}; 6290 6291 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6292 delete this.options.views; 6293 6294 if ( ! this.options.silent ) { 6295 this.render(); 6296 } 6297 6298 // Initialize the Focus Manager. 6299 this.focusManager = new wp.media.view.FocusManager( { 6300 el: this.el, 6301 mode: 'tabsNavigation' 6302 } ); 6303 6304 // The menu is always rendered and can be visible or hidden on some frames. 6305 this.isVisible = true; 6306 }, 6307 6308 /** 6309 * @param {Object} options 6310 * @param {string} id 6311 * @return {wp.media.View} 6312 */ 6313 toView: function( options, id ) { 6314 options = options || {}; 6315 options[ this.property ] = options[ this.property ] || id; 6316 return new this.ItemView( options ).render(); 6317 }, 6318 6319 ready: function() { 6320 /** 6321 * call 'ready' directly on the parent class 6322 */ 6323 PriorityList.prototype.ready.apply( this, arguments ); 6324 this.visibility(); 6325 6326 // Set up aria tabs initial attributes. 6327 this.focusManager.setupAriaTabs(); 6328 }, 6329 6330 set: function() { 6331 /** 6332 * call 'set' directly on the parent class 6333 */ 6334 PriorityList.prototype.set.apply( this, arguments ); 6335 this.visibility(); 6336 }, 6337 6338 unset: function() { 6339 /** 6340 * call 'unset' directly on the parent class 6341 */ 6342 PriorityList.prototype.unset.apply( this, arguments ); 6343 this.visibility(); 6344 }, 6345 6346 visibility: function() { 6347 var region = this.region, 6348 view = this.controller[ region ].get(), 6349 views = this.views.get(), 6350 hide = ! views || views.length < 2; 6351 6352 if ( this === view ) { 6353 // Flag this menu as hidden or visible. 6354 this.isVisible = ! hide; 6355 // Set or remove a CSS class to hide the menu. 6356 this.controller.$el.toggleClass( 'hide-' + region, hide ); 6357 } 6358 }, 6359 /** 6360 * @param {string} id 6361 */ 6362 select: function( id ) { 6363 var view = this.get( id ); 6364 6365 if ( ! view ) { 6366 return; 6367 } 6368 6369 this.deselect(); 6370 view.$el.addClass('active'); 6371 6372 // Set up again the aria tabs initial attributes after the menu updates. 6373 this.focusManager.setupAriaTabs(); 6374 }, 6375 6376 deselect: function() { 6377 this.$el.children().removeClass('active'); 6378 }, 6379 6380 hide: function( id ) { 6381 var view = this.get( id ); 6382 6383 if ( ! view ) { 6384 return; 6385 } 6386 6387 view.$el.addClass('hidden'); 6388 }, 6389 6390 show: function( id ) { 6391 var view = this.get( id ); 6392 6393 if ( ! view ) { 6394 return; 6395 } 6396 6397 view.$el.removeClass('hidden'); 6398 } 6399 }); 6400 6401 module.exports = Menu; 6402 6403 6404 /***/ }), 6405 /* 70 */ 6406 /***/ (function(module, exports) { 6407 6408 /** 6409 * wp.media.view.RouterItem 6410 * 6411 * @memberOf wp.media.view 6412 * 6413 * @class 6414 * @augments wp.media.view.MenuItem 6415 * @augments wp.media.View 6416 * @augments wp.Backbone.View 6417 * @augments Backbone.View 6418 */ 6419 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{ 6420 /** 6421 * On click handler to activate the content region's corresponding mode. 6422 */ 6423 click: function() { 6424 var contentMode = this.options.contentMode; 6425 if ( contentMode ) { 6426 this.controller.content.mode( contentMode ); 6427 } 6428 } 6429 }); 6430 6431 module.exports = RouterItem; 6432 6433 6434 /***/ }), 6435 /* 71 */ 6436 /***/ (function(module, exports) { 6437 6438 var Menu = wp.media.view.Menu, 6439 Router; 6440 6441 /** 6442 * wp.media.view.Router 6443 * 6444 * @memberOf wp.media.view 6445 * 6446 * @class 6447 * @augments wp.media.view.Menu 6448 * @augments wp.media.view.PriorityList 6449 * @augments wp.media.View 6450 * @augments wp.Backbone.View 6451 * @augments Backbone.View 6452 */ 6453 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{ 6454 tagName: 'div', 6455 className: 'media-router', 6456 property: 'contentMode', 6457 ItemView: wp.media.view.RouterItem, 6458 region: 'router', 6459 6460 attributes: { 6461 role: 'tablist', 6462 'aria-orientation': 'horizontal' 6463 }, 6464 6465 initialize: function() { 6466 this.controller.on( 'content:render', this.update, this ); 6467 // Call 'initialize' directly on the parent class. 6468 Menu.prototype.initialize.apply( this, arguments ); 6469 }, 6470 6471 update: function() { 6472 var mode = this.controller.content.mode(); 6473 if ( mode ) { 6474 this.select( mode ); 6475 } 6476 } 6477 }); 6478 6479 module.exports = Router; 6480 6481 6482 /***/ }), 6483 /* 72 */ 6484 /***/ (function(module, exports) { 6485 6486 /** 6487 * wp.media.view.Sidebar 6488 * 6489 * @memberOf wp.media.view 6490 * 6491 * @class 6492 * @augments wp.media.view.PriorityList 6493 * @augments wp.media.View 6494 * @augments wp.Backbone.View 6495 * @augments Backbone.View 6496 */ 6497 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{ 6498 className: 'media-sidebar' 6499 }); 6500 6501 module.exports = Sidebar; 6502 6503 6504 /***/ }), 6505 /* 73 */ 6506 /***/ (function(module, exports) { 6507 6508 var View = wp.media.View, 6509 $ = jQuery, 6510 Attachment; 6511 6512 /** 6513 * wp.media.view.Attachment 6514 * 6515 * @memberOf wp.media.view 6516 * 6517 * @class 6518 * @augments wp.media.View 6519 * @augments wp.Backbone.View 6520 * @augments Backbone.View 6521 */ 6522 Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{ 6523 tagName: 'li', 6524 className: 'attachment', 6525 template: wp.template('attachment'), 6526 6527 attributes: function() { 6528 return { 6529 'tabIndex': 0, 6530 'role': 'checkbox', 6531 'aria-label': this.model.get( 'title' ), 6532 'aria-checked': false, 6533 'data-id': this.model.get( 'id' ) 6534 }; 6535 }, 6536 6537 events: { 6538 'click': 'toggleSelectionHandler', 6539 'change [data-setting]': 'updateSetting', 6540 'change [data-setting] input': 'updateSetting', 6541 'change [data-setting] select': 'updateSetting', 6542 'change [data-setting] textarea': 'updateSetting', 6543 'click .attachment-close': 'removeFromLibrary', 6544 'click .check': 'checkClickHandler', 6545 'keydown': 'toggleSelectionHandler' 6546 }, 6547 6548 buttons: {}, 6549 6550 initialize: function() { 6551 var selection = this.options.selection, 6552 options = _.defaults( this.options, { 6553 rerenderOnModelChange: true 6554 } ); 6555 6556 if ( options.rerenderOnModelChange ) { 6557 this.listenTo( this.model, 'change', this.render ); 6558 } else { 6559 this.listenTo( this.model, 'change:percent', this.progress ); 6560 } 6561 this.listenTo( this.model, 'change:title', this._syncTitle ); 6562 this.listenTo( this.model, 'change:caption', this._syncCaption ); 6563 this.listenTo( this.model, 'change:artist', this._syncArtist ); 6564 this.listenTo( this.model, 'change:album', this._syncAlbum ); 6565 6566 // Update the selection. 6567 this.listenTo( this.model, 'add', this.select ); 6568 this.listenTo( this.model, 'remove', this.deselect ); 6569 if ( selection ) { 6570 selection.on( 'reset', this.updateSelect, this ); 6571 // Update the model's details view. 6572 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 6573 this.details( this.model, this.controller.state().get('selection') ); 6574 } 6575 6576 this.listenTo( this.controller.states, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 6577 }, 6578 /** 6579 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 6580 */ 6581 dispose: function() { 6582 var selection = this.options.selection; 6583 6584 // Make sure all settings are saved before removing the view. 6585 this.updateAll(); 6586 6587 if ( selection ) { 6588 selection.off( null, null, this ); 6589 } 6590 /** 6591 * call 'dispose' directly on the parent class 6592 */ 6593 View.prototype.dispose.apply( this, arguments ); 6594 return this; 6595 }, 6596 /** 6597 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 6598 */ 6599 render: function() { 6600 var options = _.defaults( this.model.toJSON(), { 6601 orientation: 'landscape', 6602 uploading: false, 6603 type: '', 6604 subtype: '', 6605 icon: '', 6606 filename: '', 6607 caption: '', 6608 title: '', 6609 dateFormatted: '', 6610 width: '', 6611 height: '', 6612 compat: false, 6613 alt: '', 6614 description: '' 6615 }, this.options ); 6616 6617 options.buttons = this.buttons; 6618 options.describe = this.controller.state().get('describe'); 6619 6620 if ( 'image' === options.type ) { 6621 options.size = this.imageSize(); 6622 } 6623 6624 options.can = {}; 6625 if ( options.nonces ) { 6626 options.can.remove = !! options.nonces['delete']; 6627 options.can.save = !! options.nonces.update; 6628 } 6629 6630 if ( this.controller.state().get('allowLocalEdits') ) { 6631 options.allowLocalEdits = true; 6632 } 6633 6634 if ( options.uploading && ! options.percent ) { 6635 options.percent = 0; 6636 } 6637 6638 this.views.detach(); 6639 this.$el.html( this.template( options ) ); 6640 6641 this.$el.toggleClass( 'uploading', options.uploading ); 6642 6643 if ( options.uploading ) { 6644 this.$bar = this.$('.media-progress-bar div'); 6645 } else { 6646 delete this.$bar; 6647 } 6648 6649 // Check if the model is selected. 6650 this.updateSelect(); 6651 6652 // Update the save status. 6653 this.updateSave(); 6654 6655 this.views.render(); 6656 6657 return this; 6658 }, 6659 6660 progress: function() { 6661 if ( this.$bar && this.$bar.length ) { 6662 this.$bar.width( this.model.get('percent') + '%' ); 6663 } 6664 }, 6665 6666 /** 6667 * @param {Object} event 6668 */ 6669 toggleSelectionHandler: function( event ) { 6670 var method; 6671 6672 // Don't do anything inside inputs and on the attachment check and remove buttons. 6673 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 6674 return; 6675 } 6676 6677 // Catch arrow events. 6678 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 6679 this.controller.trigger( 'attachment:keydown:arrow', event ); 6680 return; 6681 } 6682 6683 // Catch enter and space events. 6684 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6685 return; 6686 } 6687 6688 event.preventDefault(); 6689 6690 // In the grid view, bubble up an edit:attachment event to the controller. 6691 if ( this.controller.isModeActive( 'grid' ) ) { 6692 if ( this.controller.isModeActive( 'edit' ) ) { 6693 // Pass the current target to restore focus when closing. 6694 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 6695 return; 6696 } 6697 6698 if ( this.controller.isModeActive( 'select' ) ) { 6699 method = 'toggle'; 6700 } 6701 } 6702 6703 if ( event.shiftKey ) { 6704 method = 'between'; 6705 } else if ( event.ctrlKey || event.metaKey ) { 6706 method = 'toggle'; 6707 } 6708 6709 this.toggleSelection({ 6710 method: method 6711 }); 6712 6713 this.controller.trigger( 'selection:toggle' ); 6714 }, 6715 /** 6716 * @param {Object} options 6717 */ 6718 toggleSelection: function( options ) { 6719 var collection = this.collection, 6720 selection = this.options.selection, 6721 model = this.model, 6722 method = options && options.method, 6723 single, models, singleIndex, modelIndex; 6724 6725 if ( ! selection ) { 6726 return; 6727 } 6728 6729 single = selection.single(); 6730 method = _.isUndefined( method ) ? selection.multiple : method; 6731 6732 // If the `method` is set to `between`, select all models that 6733 // exist between the current and the selected model. 6734 if ( 'between' === method && single && selection.multiple ) { 6735 // If the models are the same, short-circuit. 6736 if ( single === model ) { 6737 return; 6738 } 6739 6740 singleIndex = collection.indexOf( single ); 6741 modelIndex = collection.indexOf( this.model ); 6742 6743 if ( singleIndex < modelIndex ) { 6744 models = collection.models.slice( singleIndex, modelIndex + 1 ); 6745 } else { 6746 models = collection.models.slice( modelIndex, singleIndex + 1 ); 6747 } 6748 6749 selection.add( models ); 6750 selection.single( model ); 6751 return; 6752 6753 // If the `method` is set to `toggle`, just flip the selection 6754 // status, regardless of whether the model is the single model. 6755 } else if ( 'toggle' === method ) { 6756 selection[ this.selected() ? 'remove' : 'add' ]( model ); 6757 selection.single( model ); 6758 return; 6759 } else if ( 'add' === method ) { 6760 selection.add( model ); 6761 selection.single( model ); 6762 return; 6763 } 6764 6765 // Fixes bug that loses focus when selecting a featured image. 6766 if ( ! method ) { 6767 method = 'add'; 6768 } 6769 6770 if ( method !== 'add' ) { 6771 method = 'reset'; 6772 } 6773 6774 if ( this.selected() ) { 6775 /* 6776 * If the model is the single model, remove it. 6777 * If it is not the same as the single model, 6778 * it now becomes the single model. 6779 */ 6780 selection[ single === model ? 'remove' : 'single' ]( model ); 6781 } else { 6782 /* 6783 * If the model is not selected, run the `method` on the 6784 * selection. By default, we `reset` the selection, but the 6785 * `method` can be set to `add` the model to the selection. 6786 */ 6787 selection[ method ]( model ); 6788 selection.single( model ); 6789 } 6790 }, 6791 6792 updateSelect: function() { 6793 this[ this.selected() ? 'select' : 'deselect' ](); 6794 }, 6795 /** 6796 * @return {unresolved|boolean} 6797 */ 6798 selected: function() { 6799 var selection = this.options.selection; 6800 if ( selection ) { 6801 return !! selection.get( this.model.cid ); 6802 } 6803 }, 6804 /** 6805 * @param {Backbone.Model} model 6806 * @param {Backbone.Collection} collection 6807 */ 6808 select: function( model, collection ) { 6809 var selection = this.options.selection, 6810 controller = this.controller; 6811 6812 /* 6813 * Check if a selection exists and if it's the collection provided. 6814 * If they're not the same collection, bail; we're in another 6815 * selection's event loop. 6816 */ 6817 if ( ! selection || ( collection && collection !== selection ) ) { 6818 return; 6819 } 6820 6821 // Bail if the model is already selected. 6822 if ( this.$el.hasClass( 'selected' ) ) { 6823 return; 6824 } 6825 6826 // Add 'selected' class to model, set aria-checked to true. 6827 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 6828 // Make the checkbox tabable, except in media grid (bulk select mode). 6829 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 6830 this.$( '.check' ).attr( 'tabindex', '0' ); 6831 } 6832 }, 6833 /** 6834 * @param {Backbone.Model} model 6835 * @param {Backbone.Collection} collection 6836 */ 6837 deselect: function( model, collection ) { 6838 var selection = this.options.selection; 6839 6840 /* 6841 * Check if a selection exists and if it's the collection provided. 6842 * If they're not the same collection, bail; we're in another 6843 * selection's event loop. 6844 */ 6845 if ( ! selection || ( collection && collection !== selection ) ) { 6846 return; 6847 } 6848 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 6849 .find( '.check' ).attr( 'tabindex', '-1' ); 6850 }, 6851 /** 6852 * @param {Backbone.Model} model 6853 * @param {Backbone.Collection} collection 6854 */ 6855 details: function( model, collection ) { 6856 var selection = this.options.selection, 6857 details; 6858 6859 if ( selection !== collection ) { 6860 return; 6861 } 6862 6863 details = selection.single(); 6864 this.$el.toggleClass( 'details', details === this.model ); 6865 }, 6866 /** 6867 * @param {string} size 6868 * @return {Object} 6869 */ 6870 imageSize: function( size ) { 6871 var sizes = this.model.get('sizes'), matched = false; 6872 6873 size = size || 'medium'; 6874 6875 // Use the provided image size if possible. 6876 if ( sizes ) { 6877 if ( sizes[ size ] ) { 6878 matched = sizes[ size ]; 6879 } else if ( sizes.large ) { 6880 matched = sizes.large; 6881 } else if ( sizes.thumbnail ) { 6882 matched = sizes.thumbnail; 6883 } else if ( sizes.full ) { 6884 matched = sizes.full; 6885 } 6886 6887 if ( matched ) { 6888 return _.clone( matched ); 6889 } 6890 } 6891 6892 return { 6893 url: this.model.get('url'), 6894 width: this.model.get('width'), 6895 height: this.model.get('height'), 6896 orientation: this.model.get('orientation') 6897 }; 6898 }, 6899 /** 6900 * @param {Object} event 6901 */ 6902 updateSetting: function( event ) { 6903 var $setting = $( event.target ).closest('[data-setting]'), 6904 setting, value; 6905 6906 if ( ! $setting.length ) { 6907 return; 6908 } 6909 6910 setting = $setting.data('setting'); 6911 value = event.target.value; 6912 6913 if ( this.model.get( setting ) !== value ) { 6914 this.save( setting, value ); 6915 } 6916 }, 6917 6918 /** 6919 * Pass all the arguments to the model's save method. 6920 * 6921 * Records the aggregate status of all save requests and updates the 6922 * view's classes accordingly. 6923 */ 6924 save: function() { 6925 var view = this, 6926 save = this._save = this._save || { status: 'ready' }, 6927 request = this.model.save.apply( this.model, arguments ), 6928 requests = save.requests ? $.when( request, save.requests ) : request; 6929 6930 // If we're waiting to remove 'Saved.', stop. 6931 if ( save.savedTimer ) { 6932 clearTimeout( save.savedTimer ); 6933 } 6934 6935 this.updateSave('waiting'); 6936 save.requests = requests; 6937 requests.always( function() { 6938 // If we've performed another request since this one, bail. 6939 if ( save.requests !== requests ) { 6940 return; 6941 } 6942 6943 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 6944 save.savedTimer = setTimeout( function() { 6945 view.updateSave('ready'); 6946 delete save.savedTimer; 6947 }, 2000 ); 6948 }); 6949 }, 6950 /** 6951 * @param {string} status 6952 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 6953 */ 6954 updateSave: function( status ) { 6955 var save = this._save = this._save || { status: 'ready' }; 6956 6957 if ( status && status !== save.status ) { 6958 this.$el.removeClass( 'save-' + save.status ); 6959 save.status = status; 6960 } 6961 6962 this.$el.addClass( 'save-' + save.status ); 6963 return this; 6964 }, 6965 6966 updateAll: function() { 6967 var $settings = this.$('[data-setting]'), 6968 model = this.model, 6969 changed; 6970 6971 changed = _.chain( $settings ).map( function( el ) { 6972 var $input = $('input, textarea, select, [value]', el ), 6973 setting, value; 6974 6975 if ( ! $input.length ) { 6976 return; 6977 } 6978 6979 setting = $(el).data('setting'); 6980 value = $input.val(); 6981 6982 // Record the value if it changed. 6983 if ( model.get( setting ) !== value ) { 6984 return [ setting, value ]; 6985 } 6986 }).compact().object().value(); 6987 6988 if ( ! _.isEmpty( changed ) ) { 6989 model.save( changed ); 6990 } 6991 }, 6992 /** 6993 * @param {Object} event 6994 */ 6995 removeFromLibrary: function( event ) { 6996 // Catch enter and space events. 6997 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6998 return; 6999 } 7000 7001 // Stop propagation so the model isn't selected. 7002 event.stopPropagation(); 7003 7004 this.collection.remove( this.model ); 7005 }, 7006 7007 /** 7008 * Add the model if it isn't in the selection, if it is in the selection, 7009 * remove it. 7010 * 7011 * @param {[type]} event [description] 7012 * @return {[type]} [description] 7013 */ 7014 checkClickHandler: function ( event ) { 7015 var selection = this.options.selection; 7016 if ( ! selection ) { 7017 return; 7018 } 7019 event.stopPropagation(); 7020 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 7021 selection.remove( this.model ); 7022 // Move focus back to the attachment tile (from the check). 7023 this.$el.focus(); 7024 } else { 7025 selection.add( this.model ); 7026 } 7027 7028 // Trigger an action button update. 7029 this.controller.trigger( 'selection:toggle' ); 7030 } 7031 }); 7032 7033 // Ensure settings remain in sync between attachment views. 7034 _.each({ 7035 caption: '_syncCaption', 7036 title: '_syncTitle', 7037 artist: '_syncArtist', 7038 album: '_syncAlbum' 7039 }, function( method, setting ) { 7040 /** 7041 * @function _syncCaption 7042 * @memberOf wp.media.view.Attachment 7043 * @instance 7044 * 7045 * @param {Backbone.Model} model 7046 * @param {string} value 7047 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7048 */ 7049 /** 7050 * @function _syncTitle 7051 * @memberOf wp.media.view.Attachment 7052 * @instance 7053 * 7054 * @param {Backbone.Model} model 7055 * @param {string} value 7056 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7057 */ 7058 /** 7059 * @function _syncArtist 7060 * @memberOf wp.media.view.Attachment 7061 * @instance 7062 * 7063 * @param {Backbone.Model} model 7064 * @param {string} value 7065 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7066 */ 7067 /** 7068 * @function _syncAlbum 7069 * @memberOf wp.media.view.Attachment 7070 * @instance 7071 * 7072 * @param {Backbone.Model} model 7073 * @param {string} value 7074 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7075 */ 7076 Attachment.prototype[ method ] = function( model, value ) { 7077 var $setting = this.$('[data-setting="' + setting + '"]'); 7078 7079 if ( ! $setting.length ) { 7080 return this; 7081 } 7082 7083 /* 7084 * If the updated value is in sync with the value in the DOM, there 7085 * is no need to re-render. If we're currently editing the value, 7086 * it will automatically be in sync, suppressing the re-render for 7087 * the view we're editing, while updating any others. 7088 */ 7089 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 7090 return this; 7091 } 7092 7093 return this.render(); 7094 }; 7095 }); 7096 7097 module.exports = Attachment; 7098 7099 7100 /***/ }), 7101 /* 74 */ 7102 /***/ (function(module, exports) { 7103 7104 /** 7105 * wp.media.view.Attachment.Library 7106 * 7107 * @memberOf wp.media.view.Attachment 7108 * 7109 * @class 7110 * @augments wp.media.view.Attachment 7111 * @augments wp.media.View 7112 * @augments wp.Backbone.View 7113 * @augments Backbone.View 7114 */ 7115 var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{ 7116 buttons: { 7117 check: true 7118 } 7119 }); 7120 7121 module.exports = Library; 7122 7123 7124 /***/ }), 7125 /* 75 */ 7126 /***/ (function(module, exports) { 7127 7128 /** 7129 * wp.media.view.Attachment.EditLibrary 7130 * 7131 * @memberOf wp.media.view.Attachment 7132 * 7133 * @class 7134 * @augments wp.media.view.Attachment 7135 * @augments wp.media.View 7136 * @augments wp.Backbone.View 7137 * @augments Backbone.View 7138 */ 7139 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{ 7140 buttons: { 7141 close: true 7142 } 7143 }); 7144 7145 module.exports = EditLibrary; 7146 7147 7148 /***/ }), 7149 /* 76 */ 7150 /***/ (function(module, exports) { 7151 7152 var View = wp.media.View, 7153 $ = jQuery, 7154 Attachments; 7155 7156 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 7157 tagName: 'ul', 7158 className: 'attachments', 7159 7160 attributes: { 7161 tabIndex: -1 7162 }, 7163 7164 /** 7165 * Represents the overview of attachments in the Media Library. 7166 * 7167 * The constructor binds events to the collection this view represents when 7168 * adding or removing attachments or resetting the entire collection. 7169 * 7170 * @since 3.5.0 7171 * 7172 * @constructs 7173 * @memberof wp.media.view 7174 * 7175 * @augments wp.media.View 7176 * 7177 * @listens collection:add 7178 * @listens collection:remove 7179 * @listens collection:reset 7180 * @listens controller:library:selection:add 7181 * @listens scrollElement:scroll 7182 * @listens this:ready 7183 * @listens controller:open 7184 */ 7185 initialize: function() { 7186 this.el.id = _.uniqueId('__attachments-view-'); 7187 7188 /** 7189 * @param refreshSensitivity The time in milliseconds to throttle the scroll 7190 * handler. 7191 * @param refreshThreshold The amount of pixels that should be scrolled before 7192 * loading more attachments from the server. 7193 * @param AttachmentView The view class to be used for models in the 7194 * collection. 7195 * @param sortable A jQuery sortable options object 7196 * ( http://api.jqueryui.com/sortable/ ). 7197 * @param resize A boolean indicating whether or not to listen to 7198 * resize events. 7199 * @param idealColumnWidth The width in pixels which a column should have when 7200 * calculating the total number of columns. 7201 */ 7202 _.defaults( this.options, { 7203 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 7204 refreshThreshold: 3, 7205 AttachmentView: wp.media.view.Attachment, 7206 sortable: false, 7207 resize: true, 7208 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 7209 }); 7210 7211 this._viewsByCid = {}; 7212 this.$window = $( window ); 7213 this.resizeEvent = 'resize.media-modal-columns'; 7214 7215 this.collection.on( 'add', function( attachment ) { 7216 this.views.add( this.createAttachmentView( attachment ), { 7217 at: this.collection.indexOf( attachment ) 7218 }); 7219 }, this ); 7220 7221 /* 7222 * Find the view to be removed, delete it and call the remove function to clear 7223 * any set event handlers. 7224 */ 7225 this.collection.on( 'remove', function( attachment ) { 7226 var view = this._viewsByCid[ attachment.cid ]; 7227 delete this._viewsByCid[ attachment.cid ]; 7228 7229 if ( view ) { 7230 view.remove(); 7231 } 7232 }, this ); 7233 7234 this.collection.on( 'reset', this.render, this ); 7235 7236 this.controller.on( 'library:selection:add', this.attachmentFocus, this ); 7237 7238 // Throttle the scroll handler and bind this. 7239 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 7240 7241 this.options.scrollElement = this.options.scrollElement || this.el; 7242 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 7243 7244 this.initSortable(); 7245 7246 _.bindAll( this, 'setColumns' ); 7247 7248 if ( this.options.resize ) { 7249 this.on( 'ready', this.bindEvents ); 7250 this.controller.on( 'open', this.setColumns ); 7251 7252 /* 7253 * Call this.setColumns() after this view has been rendered in the 7254 * DOM so attachments get proper width applied. 7255 */ 7256 _.defer( this.setColumns, this ); 7257 } 7258 }, 7259 7260 /** 7261 * Listens to the resizeEvent on the window. 7262 * 7263 * Adjusts the amount of columns accordingly. First removes any existing event 7264 * handlers to prevent duplicate listeners. 7265 * 7266 * @since 4.0.0 7267 * 7268 * @listens window:resize 7269 * 7270 * @return {void} 7271 */ 7272 bindEvents: function() { 7273 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 7274 }, 7275 7276 /** 7277 * Focuses the first item in the collection. 7278 * 7279 * @since 4.0.0 7280 * 7281 * @return {void} 7282 */ 7283 attachmentFocus: function() { 7284 /* 7285 * @todo When uploading new attachments, this tries to move focus to 7286 * the attachments grid. Actually, a progress bar gets initially displayed 7287 * and then updated when uploading completes, so focus is lost. 7288 * Additionally: this view is used for both the attachments list and 7289 * the list of selected attachments in the bottom media toolbar. Thus, when 7290 * uploading attachments, it is called twice and returns two different `this`. 7291 * `this.columns` is truthy within the modal. 7292 */ 7293 if ( this.columns ) { 7294 // Move focus to the grid list within the modal. 7295 this.$el.focus(); 7296 } 7297 }, 7298 7299 /** 7300 * Restores focus to the selected item in the collection. 7301 * 7302 * Moves focus back to the first selected attachment in the grid. Used when 7303 * tabbing backwards from the attachment details sidebar. 7304 * See media.view.AttachmentsBrowser. 7305 * 7306 * @since 4.0.0 7307 * 7308 * @return {void} 7309 */ 7310 restoreFocus: function() { 7311 this.$( 'li.selected:first' ).focus(); 7312 }, 7313 7314 /** 7315 * Handles events for arrow key presses. 7316 * 7317 * Focuses the attachment in the direction of the used arrow key if it exists. 7318 * 7319 * @since 4.0.0 7320 * 7321 * @param {KeyboardEvent} event The keyboard event that triggered this function. 7322 * 7323 * @return {void} 7324 */ 7325 arrowEvent: function( event ) { 7326 var attachments = this.$el.children( 'li' ), 7327 perRow = this.columns, 7328 index = attachments.filter( ':focus' ).index(), 7329 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 7330 7331 if ( index === -1 ) { 7332 return; 7333 } 7334 7335 // Left arrow = 37. 7336 if ( 37 === event.keyCode ) { 7337 if ( 0 === index ) { 7338 return; 7339 } 7340 attachments.eq( index - 1 ).focus(); 7341 } 7342 7343 // Up arrow = 38. 7344 if ( 38 === event.keyCode ) { 7345 if ( 1 === row ) { 7346 return; 7347 } 7348 attachments.eq( index - perRow ).focus(); 7349 } 7350 7351 // Right arrow = 39. 7352 if ( 39 === event.keyCode ) { 7353 if ( attachments.length === index ) { 7354 return; 7355 } 7356 attachments.eq( index + 1 ).focus(); 7357 } 7358 7359 // Down arrow = 40. 7360 if ( 40 === event.keyCode ) { 7361 if ( Math.ceil( attachments.length / perRow ) === row ) { 7362 return; 7363 } 7364 attachments.eq( index + perRow ).focus(); 7365 } 7366 }, 7367 7368 /** 7369 * Clears any set event handlers. 7370 * 7371 * @since 3.5.0 7372 * 7373 * @return {void} 7374 */ 7375 dispose: function() { 7376 this.collection.props.off( null, null, this ); 7377 if ( this.options.resize ) { 7378 this.$window.off( this.resizeEvent ); 7379 } 7380 7381 // Call 'dispose' directly on the parent class. 7382 View.prototype.dispose.apply( this, arguments ); 7383 }, 7384 7385 /** 7386 * Calculates the amount of columns. 7387 * 7388 * Calculates the amount of columns and sets it on the data-columns attribute 7389 * of .media-frame-content. 7390 * 7391 * @since 4.0.0 7392 * 7393 * @return {void} 7394 */ 7395 setColumns: function() { 7396 var prev = this.columns, 7397 width = this.$el.width(); 7398 7399 if ( width ) { 7400 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 7401 7402 if ( ! prev || prev !== this.columns ) { 7403 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 7404 } 7405 } 7406 }, 7407 7408 /** 7409 * Initializes jQuery sortable on the attachment list. 7410 * 7411 * Fails gracefully if jQuery sortable doesn't exist or isn't passed 7412 * in the options. 7413 * 7414 * @since 3.5.0 7415 * 7416 * @fires collection:reset 7417 * 7418 * @return {void} 7419 */ 7420 initSortable: function() { 7421 var collection = this.collection; 7422 7423 if ( ! this.options.sortable || ! $.fn.sortable ) { 7424 return; 7425 } 7426 7427 this.$el.sortable( _.extend({ 7428 // If the `collection` has a `comparator`, disable sorting. 7429 disabled: !! collection.comparator, 7430 7431 /* 7432 * Change the position of the attachment as soon as the mouse pointer 7433 * overlaps a thumbnail. 7434 */ 7435 tolerance: 'pointer', 7436 7437 // Record the initial `index` of the dragged model. 7438 start: function( event, ui ) { 7439 ui.item.data('sortableIndexStart', ui.item.index()); 7440 }, 7441 7442 /* 7443 * Update the model's index in the collection. Do so silently, as the view 7444 * is already accurate. 7445 */ 7446 update: function( event, ui ) { 7447 var model = collection.at( ui.item.data('sortableIndexStart') ), 7448 comparator = collection.comparator; 7449 7450 // Temporarily disable the comparator to prevent `add` 7451 // from re-sorting. 7452 delete collection.comparator; 7453 7454 // Silently shift the model to its new index. 7455 collection.remove( model, { 7456 silent: true 7457 }); 7458 collection.add( model, { 7459 silent: true, 7460 at: ui.item.index() 7461 }); 7462 7463 // Restore the comparator. 7464 collection.comparator = comparator; 7465 7466 // Fire the `reset` event to ensure other collections sync. 7467 collection.trigger( 'reset', collection ); 7468 7469 // If the collection is sorted by menu order, update the menu order. 7470 collection.saveMenuOrder(); 7471 } 7472 }, this.options.sortable ) ); 7473 7474 /* 7475 * If the `orderby` property is changed on the `collection`, 7476 * check to see if we have a `comparator`. If so, disable sorting. 7477 */ 7478 collection.props.on( 'change:orderby', function() { 7479 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 7480 }, this ); 7481 7482 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 7483 this.refreshSortable(); 7484 }, 7485 7486 /** 7487 * Disables jQuery sortable if collection has a comparator or collection.orderby 7488 * equals menuOrder. 7489 * 7490 * @since 3.5.0 7491 * 7492 * @return {void} 7493 */ 7494 refreshSortable: function() { 7495 if ( ! this.options.sortable || ! $.fn.sortable ) { 7496 return; 7497 } 7498 7499 var collection = this.collection, 7500 orderby = collection.props.get('orderby'), 7501 enabled = 'menuOrder' === orderby || ! collection.comparator; 7502 7503 this.$el.sortable( 'option', 'disabled', ! enabled ); 7504 }, 7505 7506 /** 7507 * Creates a new view for an attachment and adds it to _viewsByCid. 7508 * 7509 * @since 3.5.0 7510 * 7511 * @param {wp.media.model.Attachment} attachment 7512 * 7513 * @return {wp.media.View} The created view. 7514 */ 7515 createAttachmentView: function( attachment ) { 7516 var view = new this.options.AttachmentView({ 7517 controller: this.controller, 7518 model: attachment, 7519 collection: this.collection, 7520 selection: this.options.selection 7521 }); 7522 7523 return this._viewsByCid[ attachment.cid ] = view; 7524 }, 7525 7526 /** 7527 * Prepares view for display. 7528 * 7529 * Creates views for every attachment in collection if the collection is not 7530 * empty, otherwise clears all views and loads more attachments. 7531 * 7532 * @since 3.5.0 7533 * 7534 * @return {void} 7535 */ 7536 prepare: function() { 7537 if ( this.collection.length ) { 7538 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 7539 } else { 7540 this.views.unset(); 7541 this.collection.more().done( this.scroll ); 7542 } 7543 }, 7544 7545 /** 7546 * Triggers the scroll function to check if we should query for additional 7547 * attachments right away. 7548 * 7549 * @since 3.5.0 7550 * 7551 * @return {void} 7552 */ 7553 ready: function() { 7554 this.scroll(); 7555 }, 7556 7557 /** 7558 * Handles scroll events. 7559 * 7560 * Shows the spinner if we're close to the bottom. Loads more attachments from 7561 * server if we're {refreshThreshold} times away from the bottom. 7562 * 7563 * @since 3.5.0 7564 * 7565 * @return {void} 7566 */ 7567 scroll: function() { 7568 var view = this, 7569 el = this.options.scrollElement, 7570 scrollTop = el.scrollTop, 7571 toolbar; 7572 7573 /* 7574 * The scroll event occurs on the document, but the element that should be 7575 * checked is the document body. 7576 */ 7577 if ( el === document ) { 7578 el = document.body; 7579 scrollTop = $(document).scrollTop(); 7580 } 7581 7582 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 7583 return; 7584 } 7585 7586 toolbar = this.views.parent.toolbar; 7587 7588 // Show the spinner only if we are close to the bottom. 7589 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 7590 toolbar.get('spinner').show(); 7591 } 7592 7593 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 7594 this.collection.more().done(function() { 7595 view.scroll(); 7596 toolbar.get('spinner').hide(); 7597 }); 7598 } 7599 } 7600 }); 7601 7602 module.exports = Attachments; 7603 7604 7605 /***/ }), 7606 /* 77 */ 7607 /***/ (function(module, exports) { 7608 7609 var Search; 7610 7611 /** 7612 * wp.media.view.Search 7613 * 7614 * @memberOf wp.media.view 7615 * 7616 * @class 7617 * @augments wp.media.View 7618 * @augments wp.Backbone.View 7619 * @augments Backbone.View 7620 */ 7621 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{ 7622 tagName: 'input', 7623 className: 'search', 7624 id: 'media-search-input', 7625 7626 attributes: { 7627 type: 'search' 7628 }, 7629 7630 events: { 7631 'input': 'search' 7632 }, 7633 7634 /** 7635 * @return {wp.media.view.Search} Returns itself to allow chaining. 7636 */ 7637 render: function() { 7638 this.el.value = this.model.escape('search'); 7639 return this; 7640 }, 7641 7642 search: _.debounce( function( event ) { 7643 var searchTerm = event.target.value.trim(); 7644 7645 // Trigger the search only after 2 ASCII characters. 7646 if ( searchTerm && searchTerm.length > 1 ) { 7647 this.model.set( 'search', searchTerm ); 7648 } else { 7649 this.model.unset( 'search' ); 7650 } 7651 }, 500 ) 7652 }); 7653 7654 module.exports = Search; 7655 7656 7657 /***/ }), 7658 /* 78 */ 7659 /***/ (function(module, exports) { 7660 7661 var $ = jQuery, 7662 AttachmentFilters; 7663 7664 /** 7665 * wp.media.view.AttachmentFilters 7666 * 7667 * @memberOf wp.media.view 7668 * 7669 * @class 7670 * @augments wp.media.View 7671 * @augments wp.Backbone.View 7672 * @augments Backbone.View 7673 */ 7674 AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{ 7675 tagName: 'select', 7676 className: 'attachment-filters', 7677 id: 'media-attachment-filters', 7678 7679 events: { 7680 change: 'change' 7681 }, 7682 7683 keys: [], 7684 7685 initialize: function() { 7686 this.createFilters(); 7687 _.extend( this.filters, this.options.filters ); 7688 7689 // Build `<option>` elements. 7690 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 7691 return { 7692 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 7693 priority: filter.priority || 50 7694 }; 7695 }, this ).sortBy('priority').pluck('el').value() ); 7696 7697 this.listenTo( this.model, 'change', this.select ); 7698 this.select(); 7699 }, 7700 7701 /** 7702 * @abstract 7703 */ 7704 createFilters: function() { 7705 this.filters = {}; 7706 }, 7707 7708 /** 7709 * When the selected filter changes, update the Attachment Query properties to match. 7710 */ 7711 change: function() { 7712 var filter = this.filters[ this.el.value ]; 7713 if ( filter ) { 7714 this.model.set( filter.props ); 7715 } 7716 }, 7717 7718 select: function() { 7719 var model = this.model, 7720 value = 'all', 7721 props = model.toJSON(); 7722 7723 _.find( this.filters, function( filter, id ) { 7724 var equal = _.all( filter.props, function( prop, key ) { 7725 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); 7726 }); 7727 7728 if ( equal ) { 7729 return value = id; 7730 } 7731 }); 7732 7733 this.$el.val( value ); 7734 } 7735 }); 7736 7737 module.exports = AttachmentFilters; 7738 7739 7740 /***/ }), 7741 /* 79 */ 7742 /***/ (function(module, exports) { 7743 7744 var l10n = wp.media.view.l10n, 7745 DateFilter; 7746 7747 /** 7748 * A filter dropdown for month/dates. 7749 * 7750 * @memberOf wp.media.view.AttachmentFilters 7751 * 7752 * @class 7753 * @augments wp.media.view.AttachmentFilters 7754 * @augments wp.media.View 7755 * @augments wp.Backbone.View 7756 * @augments Backbone.View 7757 */ 7758 DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{ 7759 id: 'media-attachment-date-filters', 7760 7761 createFilters: function() { 7762 var filters = {}; 7763 _.each( wp.media.view.settings.months || {}, function( value, index ) { 7764 filters[ index ] = { 7765 text: value.text, 7766 props: { 7767 year: value.year, 7768 monthnum: value.month 7769 } 7770 }; 7771 }); 7772 filters.all = { 7773 text: l10n.allDates, 7774 props: { 7775 monthnum: false, 7776 year: false 7777 }, 7778 priority: 10 7779 }; 7780 this.filters = filters; 7781 } 7782 }); 7783 7784 module.exports = DateFilter; 7785 7786 7787 /***/ }), 7788 /* 80 */ 7789 /***/ (function(module, exports) { 7790 7791 var l10n = wp.media.view.l10n, 7792 Uploaded; 7793 7794 /** 7795 * wp.media.view.AttachmentFilters.Uploaded 7796 * 7797 * @memberOf wp.media.view.AttachmentFilters 7798 * 7799 * @class 7800 * @augments wp.media.view.AttachmentFilters 7801 * @augments wp.media.View 7802 * @augments wp.Backbone.View 7803 * @augments Backbone.View 7804 */ 7805 Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{ 7806 createFilters: function() { 7807 var type = this.model.get('type'), 7808 types = wp.media.view.settings.mimeTypes, 7809 uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0, 7810 text; 7811 7812 if ( types && type ) { 7813 text = types[ type ]; 7814 } 7815 7816 this.filters = { 7817 all: { 7818 text: text || l10n.allMediaItems, 7819 props: { 7820 uploadedTo: null, 7821 orderby: 'date', 7822 order: 'DESC', 7823 author: null 7824 }, 7825 priority: 10 7826 }, 7827 7828 uploaded: { 7829 text: l10n.uploadedToThisPost, 7830 props: { 7831 uploadedTo: wp.media.view.settings.post.id, 7832 orderby: 'menuOrder', 7833 order: 'ASC', 7834 author: null 7835 }, 7836 priority: 20 7837 }, 7838 7839 unattached: { 7840 text: l10n.unattached, 7841 props: { 7842 uploadedTo: 0, 7843 orderby: 'menuOrder', 7844 order: 'ASC', 7845 author: null 7846 }, 7847 priority: 50 7848 } 7849 }; 7850 7851 if ( uid ) { 7852 this.filters.mine = { 7853 text: l10n.mine, 7854 props: { 7855 orderby: 'date', 7856 order: 'DESC', 7857 author: uid 7858 }, 7859 priority: 50 7860 }; 7861 } 7862 } 7863 }); 7864 7865 module.exports = Uploaded; 7866 7867 7868 /***/ }), 7869 /* 81 */ 7870 /***/ (function(module, exports) { 7871 7872 var l10n = wp.media.view.l10n, 7873 All; 7874 7875 /** 7876 * wp.media.view.AttachmentFilters.All 7877 * 7878 * @memberOf wp.media.view.AttachmentFilters 7879 * 7880 * @class 7881 * @augments wp.media.view.AttachmentFilters 7882 * @augments wp.media.View 7883 * @augments wp.Backbone.View 7884 * @augments Backbone.View 7885 */ 7886 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{ 7887 createFilters: function() { 7888 var filters = {}, 7889 uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0; 7890 7891 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 7892 filters[ key ] = { 7893 text: text, 7894 props: { 7895 status: null, 7896 type: key, 7897 uploadedTo: null, 7898 orderby: 'date', 7899 order: 'DESC', 7900 author: null 7901 } 7902 }; 7903 }); 7904 7905 filters.all = { 7906 text: l10n.allMediaItems, 7907 props: { 7908 status: null, 7909 type: null, 7910 uploadedTo: null, 7911 orderby: 'date', 7912 order: 'DESC', 7913 author: null 7914 }, 7915 priority: 10 7916 }; 7917 7918 if ( wp.media.view.settings.post.id ) { 7919 filters.uploaded = { 7920 text: l10n.uploadedToThisPost, 7921 props: { 7922 status: null, 7923 type: null, 7924 uploadedTo: wp.media.view.settings.post.id, 7925 orderby: 'menuOrder', 7926 order: 'ASC', 7927 author: null 7928 }, 7929 priority: 20 7930 }; 7931 } 7932 7933 filters.unattached = { 7934 text: l10n.unattached, 7935 props: { 7936 status: null, 7937 uploadedTo: 0, 7938 type: null, 7939 orderby: 'menuOrder', 7940 order: 'ASC', 7941 author: null 7942 }, 7943 priority: 50 7944 }; 7945 7946 if ( uid ) { 7947 filters.mine = { 7948 text: l10n.mine, 7949 props: { 7950 status: null, 7951 type: null, 7952 uploadedTo: null, 7953 orderby: 'date', 7954 order: 'DESC', 7955 author: uid 7956 }, 7957 priority: 50 7958 }; 7959 } 7960 7961 if ( wp.media.view.settings.mediaTrash && 7962 this.controller.isModeActive( 'grid' ) ) { 7963 7964 filters.trash = { 7965 text: l10n.trash, 7966 props: { 7967 uploadedTo: null, 7968 status: 'trash', 7969 type: null, 7970 orderby: 'date', 7971 order: 'DESC', 7972 author: null 7973 }, 7974 priority: 50 7975 }; 7976 } 7977 7978 this.filters = filters; 7979 } 7980 }); 7981 7982 module.exports = All; 7983 7984 7985 /***/ }), 7986 /* 82 */ 7987 /***/ (function(module, exports) { 7988 7989 var View = wp.media.View, 7990 mediaTrash = wp.media.view.settings.mediaTrash, 7991 l10n = wp.media.view.l10n, 7992 $ = jQuery, 7993 AttachmentsBrowser; 7994 7995 /** 7996 * wp.media.view.AttachmentsBrowser 7997 * 7998 * @memberOf wp.media.view 7999 * 8000 * @class 8001 * @augments wp.media.View 8002 * @augments wp.Backbone.View 8003 * @augments Backbone.View 8004 * 8005 * @param {object} [options] The options hash passed to the view. 8006 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar. 8007 * Accepts 'uploaded' and 'all'. 8008 * @param {boolean} [options.search=true] Whether to show the search interface in the 8009 * browser's toolbar. 8010 * @param {boolean} [options.date=true] Whether to show the date filter in the 8011 * browser's toolbar. 8012 * @param {boolean} [options.display=false] Whether to show the attachments display settings 8013 * view in the sidebar. 8014 * @param {boolean|string} [options.sidebar=true] Whether to create a sidebar for the browser. 8015 * Accepts true, false, and 'errors'. 8016 */ 8017 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{ 8018 tagName: 'div', 8019 className: 'attachments-browser', 8020 8021 initialize: function() { 8022 _.defaults( this.options, { 8023 filters: false, 8024 search: true, 8025 date: true, 8026 display: false, 8027 sidebar: true, 8028 AttachmentView: wp.media.view.Attachment.Library 8029 }); 8030 8031 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this ); 8032 this.controller.on( 'edit:selection', this.editSelection ); 8033 8034 // In the Media Library, the sidebar is used to display errors before the attachments grid. 8035 if ( this.options.sidebar && 'errors' === this.options.sidebar ) { 8036 this.createSidebar(); 8037 } 8038 8039 /* 8040 * In the grid mode (the Media Library), place the Inline Uploader before 8041 * other sections so that the visual order and the DOM order match. This way, 8042 * the Inline Uploader in the Media Library is right after the "Add New" 8043 * button, see ticket #37188. 8044 */ 8045 if ( this.controller.isModeActive( 'grid' ) ) { 8046 this.createUploader(); 8047 8048 /* 8049 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library 8050 * and also for other things, for example the "Drag and drop to reorder" and 8051 * "Suggested dimensions" info in the media modal. 8052 */ 8053 this.createToolbar(); 8054 } else { 8055 this.createToolbar(); 8056 this.createUploader(); 8057 } 8058 8059 8060 // Add a heading before the attachments list. 8061 this.createAttachmentsHeading(); 8062 8063 // Create the list of attachments. 8064 this.createAttachments(); 8065 8066 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. 8067 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { 8068 this.createSidebar(); 8069 } 8070 8071 this.updateContent(); 8072 8073 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 8074 this.$el.addClass( 'hide-sidebar' ); 8075 8076 if ( 'errors' === this.options.sidebar ) { 8077 this.$el.addClass( 'sidebar-for-errors' ); 8078 } 8079 } 8080 8081 this.collection.on( 'add remove reset', this.updateContent, this ); 8082 8083 // The non-cached or cached attachments query has completed. 8084 this.collection.on( 'attachments:received', this.announceSearchResults, this ); 8085 }, 8086 8087 /** 8088 * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate 8089 * the number of search results to screen reader users. This function is 8090 * debounced because the collection updates multiple times. 8091 * 8092 * @since 5.3.0 8093 * 8094 * @return {void} 8095 */ 8096 announceSearchResults: _.debounce( function() { 8097 var count; 8098 8099 if ( this.collection.mirroring.args.s ) { 8100 count = this.collection.length; 8101 8102 if ( 0 === count ) { 8103 wp.a11y.speak( l10n.noMediaTryNewSearch ); 8104 return; 8105 } 8106 8107 if ( this.collection.hasMore() ) { 8108 wp.a11y.speak( l10n.mediaFoundHasMoreResults.replace( '%d', count ) ); 8109 return; 8110 } 8111 8112 wp.a11y.speak( l10n.mediaFound.replace( '%d', count ) ); 8113 } 8114 }, 200 ), 8115 8116 editSelection: function( modal ) { 8117 // When editing a selection, move focus to the "Go to library" button. 8118 modal.$( '.media-button-backToLibrary' ).focus(); 8119 }, 8120 8121 /** 8122 * @return {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining. 8123 */ 8124 dispose: function() { 8125 this.options.selection.off( null, null, this ); 8126 View.prototype.dispose.apply( this, arguments ); 8127 return this; 8128 }, 8129 8130 createToolbar: function() { 8131 var LibraryViewSwitcher, Filters, toolbarOptions, 8132 showFilterByType = -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ); 8133 8134 toolbarOptions = { 8135 controller: this.controller 8136 }; 8137 8138 if ( this.controller.isModeActive( 'grid' ) ) { 8139 toolbarOptions.className = 'media-toolbar wp-filter'; 8140 } 8141 8142 /** 8143 * @member {wp.media.view.Toolbar} 8144 */ 8145 this.toolbar = new wp.media.view.Toolbar( toolbarOptions ); 8146 8147 this.views.add( this.toolbar ); 8148 8149 this.toolbar.set( 'spinner', new wp.media.view.Spinner({ 8150 priority: -20 8151 }) ); 8152 8153 if ( showFilterByType || this.options.date ) { 8154 /* 8155 * Create a h2 heading before the select elements that filter attachments. 8156 * This heading is visible in the modal and visually hidden in the grid. 8157 */ 8158 this.toolbar.set( 'filters-heading', new wp.media.view.Heading( { 8159 priority: -100, 8160 text: l10n.filterAttachments, 8161 level: 'h2', 8162 className: 'media-attachments-filter-heading' 8163 }).render() ); 8164 } 8165 8166 if ( showFilterByType ) { 8167 // "Filters" is a <select>, a visually hidden label element needs to be rendered before. 8168 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ 8169 value: l10n.filterByType, 8170 attributes: { 8171 'for': 'media-attachment-filters' 8172 }, 8173 priority: -80 8174 }).render() ); 8175 8176 if ( 'uploaded' === this.options.filters ) { 8177 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({ 8178 controller: this.controller, 8179 model: this.collection.props, 8180 priority: -80 8181 }).render() ); 8182 } else { 8183 Filters = new wp.media.view.AttachmentFilters.All({ 8184 controller: this.controller, 8185 model: this.collection.props, 8186 priority: -80 8187 }); 8188 8189 this.toolbar.set( 'filters', Filters.render() ); 8190 } 8191 } 8192 8193 /* 8194 * Feels odd to bring the global media library switcher into the Attachment browser view. 8195 * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 8196 * which the controller can tap into and add this view? 8197 */ 8198 if ( this.controller.isModeActive( 'grid' ) ) { 8199 LibraryViewSwitcher = View.extend({ 8200 className: 'view-switch media-grid-view-switch', 8201 template: wp.template( 'media-library-view-switcher') 8202 }); 8203 8204 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 8205 controller: this.controller, 8206 priority: -90 8207 }).render() ); 8208 8209 // DateFilter is a <select>, a visually hidden label element needs to be rendered before. 8210 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 8211 value: l10n.filterByDate, 8212 attributes: { 8213 'for': 'media-attachment-date-filters' 8214 }, 8215 priority: -75 8216 }).render() ); 8217 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 8218 controller: this.controller, 8219 model: this.collection.props, 8220 priority: -75 8221 }).render() ); 8222 8223 // BulkSelection is a <div> with subviews, including screen reader text. 8224 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 8225 text: l10n.bulkSelect, 8226 controller: this.controller, 8227 priority: -70 8228 }).render() ); 8229 8230 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 8231 filters: Filters, 8232 style: 'primary', 8233 disabled: true, 8234 text: mediaTrash ? l10n.trashSelected : l10n.deletePermanently, 8235 controller: this.controller, 8236 priority: -80, 8237 click: function() { 8238 var changed = [], removed = [], 8239 selection = this.controller.state().get( 'selection' ), 8240 library = this.controller.state().get( 'library' ); 8241 8242 if ( ! selection.length ) { 8243 return; 8244 } 8245 8246 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 8247 return; 8248 } 8249 8250 if ( mediaTrash && 8251 'trash' !== selection.at( 0 ).get( 'status' ) && 8252 ! window.confirm( l10n.warnBulkTrash ) ) { 8253 8254 return; 8255 } 8256 8257 selection.each( function( model ) { 8258 if ( ! model.get( 'nonces' )['delete'] ) { 8259 removed.push( model ); 8260 return; 8261 } 8262 8263 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 8264 model.set( 'status', 'inherit' ); 8265 changed.push( model.save() ); 8266 removed.push( model ); 8267 } else if ( mediaTrash ) { 8268 model.set( 'status', 'trash' ); 8269 changed.push( model.save() ); 8270 removed.push( model ); 8271 } else { 8272 model.destroy({wait: true}); 8273 } 8274 } ); 8275 8276 if ( changed.length ) { 8277 selection.remove( removed ); 8278 8279 $.when.apply( null, changed ).then( _.bind( function() { 8280 library._requery( true ); 8281 this.controller.trigger( 'selection:action:done' ); 8282 }, this ) ); 8283 } else { 8284 this.controller.trigger( 'selection:action:done' ); 8285 } 8286 } 8287 }).render() ); 8288 8289 if ( mediaTrash ) { 8290 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 8291 filters: Filters, 8292 style: 'link button-link-delete', 8293 disabled: true, 8294 text: l10n.deletePermanently, 8295 controller: this.controller, 8296 priority: -55, 8297 click: function() { 8298 var removed = [], 8299 destroy = [], 8300 selection = this.controller.state().get( 'selection' ); 8301 8302 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 8303 return; 8304 } 8305 8306 selection.each( function( model ) { 8307 if ( ! model.get( 'nonces' )['delete'] ) { 8308 removed.push( model ); 8309 return; 8310 } 8311 8312 destroy.push( model ); 8313 } ); 8314 8315 if ( removed.length ) { 8316 selection.remove( removed ); 8317 } 8318 8319 if ( destroy.length ) { 8320 $.when.apply( null, destroy.map( function (item) { 8321 return item.destroy(); 8322 } ) ).then( _.bind( function() { 8323 this.controller.trigger( 'selection:action:done' ); 8324 }, this ) ); 8325 } 8326 } 8327 }).render() ); 8328 } 8329 8330 } else if ( this.options.date ) { 8331 // DateFilter is a <select>, a visually hidden label element needs to be rendered before. 8332 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 8333 value: l10n.filterByDate, 8334 attributes: { 8335 'for': 'media-attachment-date-filters' 8336 }, 8337 priority: -75 8338 }).render() ); 8339 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 8340 controller: this.controller, 8341 model: this.collection.props, 8342 priority: -75 8343 }).render() ); 8344 } 8345 8346 if ( this.options.search ) { 8347 // Search is an input, a visually hidden label element needs to be rendered before. 8348 this.toolbar.set( 'searchLabel', new wp.media.view.Label({ 8349 value: l10n.searchLabel, 8350 className: 'media-search-input-label', 8351 attributes: { 8352 'for': 'media-search-input' 8353 }, 8354 priority: 60 8355 }).render() ); 8356 this.toolbar.set( 'search', new wp.media.view.Search({ 8357 controller: this.controller, 8358 model: this.collection.props, 8359 priority: 60 8360 }).render() ); 8361 } 8362 8363 if ( this.options.dragInfo ) { 8364 this.toolbar.set( 'dragInfo', new View({ 8365 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0], 8366 priority: -40 8367 }) ); 8368 } 8369 8370 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 8371 this.toolbar.set( 'suggestedDimensions', new View({ 8372 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0], 8373 priority: -40 8374 }) ); 8375 } 8376 }, 8377 8378 updateContent: function() { 8379 var view = this, 8380 noItemsView; 8381 8382 if ( this.controller.isModeActive( 'grid' ) ) { 8383 noItemsView = view.attachmentsNoResults; 8384 } else { 8385 noItemsView = view.uploader; 8386 } 8387 8388 if ( ! this.collection.length ) { 8389 this.toolbar.get( 'spinner' ).show(); 8390 this.dfd = this.collection.more().done( function() { 8391 if ( ! view.collection.length ) { 8392 noItemsView.$el.removeClass( 'hidden' ); 8393 } else { 8394 noItemsView.$el.addClass( 'hidden' ); 8395 } 8396 view.toolbar.get( 'spinner' ).hide(); 8397 } ); 8398 } else { 8399 noItemsView.$el.addClass( 'hidden' ); 8400 view.toolbar.get( 'spinner' ).hide(); 8401 } 8402 }, 8403 8404 createUploader: function() { 8405 this.uploader = new wp.media.view.UploaderInline({ 8406 controller: this.controller, 8407 status: false, 8408 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 8409 canClose: this.controller.isModeActive( 'grid' ) 8410 }); 8411 8412 this.uploader.$el.addClass( 'hidden' ); 8413 this.views.add( this.uploader ); 8414 }, 8415 8416 toggleUploader: function() { 8417 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 8418 this.uploader.show(); 8419 } else { 8420 this.uploader.hide(); 8421 } 8422 }, 8423 8424 createAttachments: function() { 8425 this.attachments = new wp.media.view.Attachments({ 8426 controller: this.controller, 8427 collection: this.collection, 8428 selection: this.options.selection, 8429 model: this.model, 8430 sortable: this.options.sortable, 8431 scrollElement: this.options.scrollElement, 8432 idealColumnWidth: this.options.idealColumnWidth, 8433 8434 // The single `Attachment` view to be used in the `Attachments` view. 8435 AttachmentView: this.options.AttachmentView 8436 }); 8437 8438 // Add keydown listener to the instance of the Attachments view. 8439 this.controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) ); 8440 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) ); 8441 8442 this.views.add( this.attachments ); 8443 8444 8445 if ( this.controller.isModeActive( 'grid' ) ) { 8446 this.attachmentsNoResults = new View({ 8447 controller: this.controller, 8448 tagName: 'p' 8449 }); 8450 8451 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 8452 this.attachmentsNoResults.$el.html( l10n.noMedia ); 8453 8454 this.views.add( this.attachmentsNoResults ); 8455 } 8456 }, 8457 8458 createAttachmentsHeading: function() { 8459 this.attachmentsHeading = new wp.media.view.Heading( { 8460 text: l10n.attachmentsList, 8461 level: 'h2', 8462 className: 'media-views-heading screen-reader-text' 8463 } ); 8464 this.views.add( this.attachmentsHeading ); 8465 }, 8466 8467 createSidebar: function() { 8468 var options = this.options, 8469 selection = options.selection, 8470 sidebar = this.sidebar = new wp.media.view.Sidebar({ 8471 controller: this.controller 8472 }); 8473 8474 this.views.add( sidebar ); 8475 8476 if ( this.controller.uploader ) { 8477 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({ 8478 controller: this.controller, 8479 priority: 40 8480 }) ); 8481 } 8482 8483 selection.on( 'selection:single', this.createSingle, this ); 8484 selection.on( 'selection:unsingle', this.disposeSingle, this ); 8485 8486 if ( selection.single() ) { 8487 this.createSingle(); 8488 } 8489 }, 8490 8491 createSingle: function() { 8492 var sidebar = this.sidebar, 8493 single = this.options.selection.single(); 8494 8495 sidebar.set( 'details', new wp.media.view.Attachment.Details({ 8496 controller: this.controller, 8497 model: single, 8498 priority: 80 8499 }) ); 8500 8501 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({ 8502 controller: this.controller, 8503 model: single, 8504 priority: 120 8505 }) ); 8506 8507 if ( this.options.display ) { 8508 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({ 8509 controller: this.controller, 8510 model: this.model.display( single ), 8511 attachment: single, 8512 priority: 160, 8513 userSettings: this.model.get('displayUserSettings') 8514 }) ); 8515 } 8516 8517 // Show the sidebar on mobile. 8518 if ( this.model.id === 'insert' ) { 8519 sidebar.$el.addClass( 'visible' ); 8520 } 8521 }, 8522 8523 disposeSingle: function() { 8524 var sidebar = this.sidebar; 8525 sidebar.unset('details'); 8526 sidebar.unset('compat'); 8527 sidebar.unset('display'); 8528 // Hide the sidebar on mobile. 8529 sidebar.$el.removeClass( 'visible' ); 8530 } 8531 }); 8532 8533 module.exports = AttachmentsBrowser; 8534 8535 8536 /***/ }), 8537 /* 83 */ 8538 /***/ (function(module, exports) { 8539 8540 var _n = wp.i18n._n, 8541 sprintf = wp.i18n.sprintf, 8542 Selection; 8543 8544 /** 8545 * wp.media.view.Selection 8546 * 8547 * @memberOf wp.media.view 8548 * 8549 * @class 8550 * @augments wp.media.View 8551 * @augments wp.Backbone.View 8552 * @augments Backbone.View 8553 */ 8554 Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{ 8555 tagName: 'div', 8556 className: 'media-selection', 8557 template: wp.template('media-selection'), 8558 8559 events: { 8560 'click .edit-selection': 'edit', 8561 'click .clear-selection': 'clear' 8562 }, 8563 8564 initialize: function() { 8565 _.defaults( this.options, { 8566 editable: false, 8567 clearable: true 8568 }); 8569 8570 /** 8571 * @member {wp.media.view.Attachments.Selection} 8572 */ 8573 this.attachments = new wp.media.view.Attachments.Selection({ 8574 controller: this.controller, 8575 collection: this.collection, 8576 selection: this.collection, 8577 model: new Backbone.Model() 8578 }); 8579 8580 this.views.set( '.selection-view', this.attachments ); 8581 this.collection.on( 'add remove reset', this.refresh, this ); 8582 this.controller.on( 'content:activate', this.refresh, this ); 8583 }, 8584 8585 ready: function() { 8586 this.refresh(); 8587 }, 8588 8589 refresh: function() { 8590 // If the selection hasn't been rendered, bail. 8591 if ( ! this.$el.children().length ) { 8592 return; 8593 } 8594 8595 var collection = this.collection, 8596 editing = 'edit-selection' === this.controller.content.mode(); 8597 8598 // If nothing is selected, display nothing. 8599 this.$el.toggleClass( 'empty', ! collection.length ); 8600 this.$el.toggleClass( 'one', 1 === collection.length ); 8601 this.$el.toggleClass( 'editing', editing ); 8602 8603 this.$( '.count' ).text( 8604 /* translators: %s: Number of selected media attachments. */ 8605 sprintf( _n( '%s item selected', '%s items selected', collection.length ), collection.length ) 8606 ); 8607 }, 8608 8609 edit: function( event ) { 8610 event.preventDefault(); 8611 if ( this.options.editable ) { 8612 this.options.editable.call( this, this.collection ); 8613 } 8614 }, 8615 8616 clear: function( event ) { 8617 event.preventDefault(); 8618 this.collection.reset(); 8619 8620 // Move focus to the modal. 8621 this.controller.modal.focusManager.focus(); 8622 } 8623 }); 8624 8625 module.exports = Selection; 8626 8627 8628 /***/ }), 8629 /* 84 */ 8630 /***/ (function(module, exports) { 8631 8632 /** 8633 * wp.media.view.Attachment.Selection 8634 * 8635 * @memberOf wp.media.view.Attachment 8636 * 8637 * @class 8638 * @augments wp.media.view.Attachment 8639 * @augments wp.media.View 8640 * @augments wp.Backbone.View 8641 * @augments Backbone.View 8642 */ 8643 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{ 8644 className: 'attachment selection', 8645 8646 // On click, just select the model, instead of removing the model from 8647 // the selection. 8648 toggleSelection: function() { 8649 this.options.selection.single( this.model ); 8650 } 8651 }); 8652 8653 module.exports = Selection; 8654 8655 8656 /***/ }), 8657 /* 85 */ 8658 /***/ (function(module, exports) { 8659 8660 var Attachments = wp.media.view.Attachments, 8661 Selection; 8662 8663 /** 8664 * wp.media.view.Attachments.Selection 8665 * 8666 * @memberOf wp.media.view.Attachments 8667 * 8668 * @class 8669 * @augments wp.media.view.Attachments 8670 * @augments wp.media.View 8671 * @augments wp.Backbone.View 8672 * @augments Backbone.View 8673 */ 8674 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{ 8675 events: {}, 8676 initialize: function() { 8677 _.defaults( this.options, { 8678 sortable: false, 8679 resize: false, 8680 8681 // The single `Attachment` view to be used in the `Attachments` view. 8682 AttachmentView: wp.media.view.Attachment.Selection 8683 }); 8684 // Call 'initialize' directly on the parent class. 8685 return Attachments.prototype.initialize.apply( this, arguments ); 8686 } 8687 }); 8688 8689 module.exports = Selection; 8690 8691 8692 /***/ }), 8693 /* 86 */ 8694 /***/ (function(module, exports) { 8695 8696 /** 8697 * wp.media.view.Attachment.EditSelection 8698 * 8699 * @memberOf wp.media.view.Attachment 8700 * 8701 * @class 8702 * @augments wp.media.view.Attachment.Selection 8703 * @augments wp.media.view.Attachment 8704 * @augments wp.media.View 8705 * @augments wp.Backbone.View 8706 * @augments Backbone.View 8707 */ 8708 var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{ 8709 buttons: { 8710 close: true 8711 } 8712 }); 8713 8714 module.exports = EditSelection; 8715 8716 8717 /***/ }), 8718 /* 87 */ 8719 /***/ (function(module, exports) { 8720 8721 var View = wp.media.View, 8722 $ = Backbone.$, 8723 Settings; 8724 8725 /** 8726 * wp.media.view.Settings 8727 * 8728 * @memberOf wp.media.view 8729 * 8730 * @class 8731 * @augments wp.media.View 8732 * @augments wp.Backbone.View 8733 * @augments Backbone.View 8734 */ 8735 Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 8736 events: { 8737 'click button': 'updateHandler', 8738 'change input': 'updateHandler', 8739 'change select': 'updateHandler', 8740 'change textarea': 'updateHandler' 8741 }, 8742 8743 initialize: function() { 8744 this.model = this.model || new Backbone.Model(); 8745 this.listenTo( this.model, 'change', this.updateChanges ); 8746 }, 8747 8748 prepare: function() { 8749 return _.defaults({ 8750 model: this.model.toJSON() 8751 }, this.options ); 8752 }, 8753 /** 8754 * @return {wp.media.view.Settings} Returns itself to allow chaining. 8755