[ 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' ).trigger( '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 // Pause current audio/video even after closing the modal. 4536 $( '.mejs-pause button' ).trigger( 'click' ); 4537 4538 // Enable page scrolling. 4539 $( 'body' ).removeClass( 'modal-open' ); 4540 4541 // Hide modal and remove restricted media modal tab focus once it's closed. 4542 this.$el.hide().off( 'keydown' ); 4543 4544 /* 4545 * Make visible again to assistive technologies all body children that 4546 * have been made hidden when the modal opened. 4547 */ 4548 this.focusManager.removeAriaHiddenFromBodyChildren(); 4549 4550 // Move focus back in useful location once modal is closed. 4551 if ( null !== this.clickedOpenerEl ) { 4552 // Move focus back to the element that opened the modal. 4553 this.clickedOpenerEl.focus(); 4554 } else { 4555 // Fallback to the admin page main element. 4556 $( '#wpbody-content' ) 4557 .attr( 'tabindex', '-1' ) 4558 .trigger( 'focus' ); 4559 } 4560 4561 this.propagate('close'); 4562 4563 if ( options && options.escape ) { 4564 this.propagate('escape'); 4565 } 4566 4567 return this; 4568 }, 4569 /** 4570 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4571 */ 4572 escape: function() { 4573 return this.close({ escape: true }); 4574 }, 4575 /** 4576 * @param {Object} event 4577 */ 4578 escapeHandler: function( event ) { 4579 event.preventDefault(); 4580 this.escape(); 4581 }, 4582 4583 /** 4584 * @param {Array|Object} content Views to register to '.media-modal-content' 4585 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4586 */ 4587 content: function( content ) { 4588 this.views.set( '.media-modal-content', content ); 4589 return this; 4590 }, 4591 4592 /** 4593 * Triggers a modal event and if the `propagate` option is set, 4594 * forwards events to the modal's controller. 4595 * 4596 * @param {string} id 4597 * @return {wp.media.view.Modal} Returns itself to allow chaining. 4598 */ 4599 propagate: function( id ) { 4600 this.trigger( id ); 4601 4602 if ( this.options.propagate ) { 4603 this.controller.trigger( id ); 4604 } 4605 4606 return this; 4607 }, 4608 /** 4609 * @param {Object} event 4610 */ 4611 keydown: function( event ) { 4612 // Close the modal when escape is pressed. 4613 if ( 27 === event.which && this.$el.is(':visible') ) { 4614 this.escape(); 4615 event.stopImmediatePropagation(); 4616 } 4617 } 4618 }); 4619 4620 module.exports = Modal; 4621 4622 4623 /***/ }), 4624 /* 56 */ 4625 /***/ (function(module, exports) { 4626 4627 var $ = jQuery; 4628 4629 /** 4630 * wp.media.view.FocusManager 4631 * 4632 * @memberOf wp.media.view 4633 * 4634 * @class 4635 * @augments wp.media.View 4636 * @augments wp.Backbone.View 4637 * @augments Backbone.View 4638 */ 4639 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 4640 4641 events: { 4642 'keydown': 'focusManagementMode' 4643 }, 4644 4645 /** 4646 * Initializes the Focus Manager. 4647 * 4648 * @param {Object} options The Focus Manager options. 4649 * 4650 * @since 5.3.0 4651 * 4652 * @return {void} 4653 */ 4654 initialize: function( options ) { 4655 this.mode = options.mode || 'constrainTabbing'; 4656 this.tabsAutomaticActivation = options.tabsAutomaticActivation || false; 4657 }, 4658 4659 /** 4660 * Determines which focus management mode to use. 4661 * 4662 * @since 5.3.0 4663 * 4664 * @param {Object} event jQuery event object. 4665 * 4666 * @return {void} 4667 */ 4668 focusManagementMode: function( event ) { 4669 if ( this.mode === 'constrainTabbing' ) { 4670 this.constrainTabbing( event ); 4671 } 4672 4673 if ( this.mode === 'tabsNavigation' ) { 4674 this.tabsNavigation( event ); 4675 } 4676 }, 4677 4678 /** 4679 * Gets all the tabbable elements. 4680 * 4681 * @since 5.3.0 4682 * 4683 * @return {Object} A jQuery collection of tabbable elements. 4684 */ 4685 getTabbables: function() { 4686 // Skip the file input added by Plupload. 4687 return this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4688 }, 4689 4690 /** 4691 * Moves focus to the modal dialog. 4692 * 4693 * @since 3.5.0 4694 * 4695 * @return {void} 4696 */ 4697 focus: function() { 4698 this.$( '.media-modal' ).trigger( 'focus' ); 4699 }, 4700 4701 /** 4702 * Constrains navigation with the Tab key within the media view element. 4703 * 4704 * @since 4.0.0 4705 * 4706 * @param {Object} event A keydown jQuery event. 4707 * 4708 * @return {void} 4709 */ 4710 constrainTabbing: function( event ) { 4711 var tabbables; 4712 4713 // Look for the tab key. 4714 if ( 9 !== event.keyCode ) { 4715 return; 4716 } 4717 4718 tabbables = this.getTabbables(); 4719 4720 // Keep tab focus within media modal while it's open. 4721 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4722 tabbables.first().focus(); 4723 return false; 4724 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4725 tabbables.last().focus(); 4726 return false; 4727 } 4728 }, 4729 4730 /** 4731 * Hides from assistive technologies all the body children. 4732 * 4733 * Sets an `aria-hidden="true"` attribute on all the body children except 4734 * the provided element and other elements that should not be hidden. 4735 * 4736 * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy 4737 * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"` 4738 * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside 4739 * of the modal dialog and get hidden from assistive technologies. 4740 * 4741 * @since 5.2.3 4742 * 4743 * @param {Object} visibleElement The jQuery object representing the element that should not be hidden. 4744 * 4745 * @return {void} 4746 */ 4747 setAriaHiddenOnBodyChildren: function( visibleElement ) { 4748 var bodyChildren, 4749 self = this; 4750 4751 if ( this.isBodyAriaHidden ) { 4752 return; 4753 } 4754 4755 // Get all the body children. 4756 bodyChildren = document.body.children; 4757 4758 // Loop through the body children and hide the ones that should be hidden. 4759 _.each( bodyChildren, function( element ) { 4760 // Don't hide the modal element. 4761 if ( element === visibleElement[0] ) { 4762 return; 4763 } 4764 4765 // Determine the body children to hide. 4766 if ( self.elementShouldBeHidden( element ) ) { 4767 element.setAttribute( 'aria-hidden', 'true' ); 4768 // Store the hidden elements. 4769 self.ariaHiddenElements.push( element ); 4770 } 4771 } ); 4772 4773 this.isBodyAriaHidden = true; 4774 }, 4775 4776 /** 4777 * Unhides from assistive technologies all the body children. 4778 * 4779 * Makes visible again to assistive technologies all the body children 4780 * previously hidden and stored in this.ariaHiddenElements. 4781 * 4782 * @since 5.2.3 4783 * 4784 * @return {void} 4785 */ 4786 removeAriaHiddenFromBodyChildren: function() { 4787 _.each( this.ariaHiddenElements, function( element ) { 4788 element.removeAttribute( 'aria-hidden' ); 4789 } ); 4790 4791 this.ariaHiddenElements = []; 4792 this.isBodyAriaHidden = false; 4793 }, 4794 4795 /** 4796 * Determines if the passed element should not be hidden from assistive technologies. 4797 * 4798 * @since 5.2.3 4799 * 4800 * @param {Object} element The DOM element that should be checked. 4801 * 4802 * @return {boolean} Whether the element should not be hidden from assistive technologies. 4803 */ 4804 elementShouldBeHidden: function( element ) { 4805 var role = element.getAttribute( 'role' ), 4806 liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ]; 4807 4808 /* 4809 * Don't hide scripts, elements that already have `aria-hidden`, and 4810 * ARIA live regions. 4811 */ 4812 return ! ( 4813 element.tagName === 'SCRIPT' || 4814 element.hasAttribute( 'aria-hidden' ) || 4815 element.hasAttribute( 'aria-live' ) || 4816 liveRegionsRoles.indexOf( role ) !== -1 4817 ); 4818 }, 4819 4820 /** 4821 * Whether the body children are hidden from assistive technologies. 4822 * 4823 * @since 5.2.3 4824 */ 4825 isBodyAriaHidden: false, 4826 4827 /** 4828 * Stores an array of DOM elements that should be hidden from assistive 4829 * technologies, for example when the media modal dialog opens. 4830 * 4831 * @since 5.2.3 4832 */ 4833 ariaHiddenElements: [], 4834 4835 /** 4836 * Holds the jQuery collection of ARIA tabs. 4837 * 4838 * @since 5.3.0 4839 */ 4840 tabs: $(), 4841 4842 /** 4843 * Sets up tabs in an ARIA tabbed interface. 4844 * 4845 * @since 5.3.0 4846 * 4847 * @param {Object} event jQuery event object. 4848 * 4849 * @return {void} 4850 */ 4851 setupAriaTabs: function() { 4852 this.tabs = this.$( '[role="tab"]' ); 4853 4854 // Set up initial attributes. 4855 this.tabs.attr( { 4856 'aria-selected': 'false', 4857 tabIndex: '-1' 4858 } ); 4859 4860 // Set up attributes on the initially active tab. 4861 this.tabs.filter( '.active' ) 4862 .removeAttr( 'tabindex' ) 4863 .attr( 'aria-selected', 'true' ); 4864 }, 4865 4866 /** 4867 * Enables arrows navigation within the ARIA tabbed interface. 4868 * 4869 * @since 5.3.0 4870 * 4871 * @param {Object} event jQuery event object. 4872 * 4873 * @return {void} 4874 */ 4875 tabsNavigation: function( event ) { 4876 var orientation = 'horizontal', 4877 keys = [ 32, 35, 36, 37, 38, 39, 40 ]; 4878 4879 // Return if not Spacebar, End, Home, or Arrow keys. 4880 if ( keys.indexOf( event.which ) === -1 ) { 4881 return; 4882 } 4883 4884 // Determine navigation direction. 4885 if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) { 4886 orientation = 'vertical'; 4887 } 4888 4889 // Make Up and Down arrow keys do nothing with horizontal tabs. 4890 if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { 4891 return; 4892 } 4893 4894 // Make Left and Right arrow keys do nothing with vertical tabs. 4895 if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { 4896 return; 4897 } 4898 4899 this.switchTabs( event, this.tabs ); 4900 }, 4901 4902 /** 4903 * Switches tabs in the ARIA tabbed interface. 4904 * 4905 * @since 5.3.0 4906 * 4907 * @param {Object} event jQuery event object. 4908 * 4909 * @return {void} 4910 */ 4911 switchTabs: function( event ) { 4912 var key = event.which, 4913 index = this.tabs.index( $( event.target ) ), 4914 newIndex; 4915 4916 switch ( key ) { 4917 // Space bar: Activate current targeted tab. 4918 case 32: { 4919 this.activateTab( this.tabs[ index ] ); 4920 break; 4921 } 4922 // End key: Activate last tab. 4923 case 35: { 4924 event.preventDefault(); 4925 this.activateTab( this.tabs[ this.tabs.length - 1 ] ); 4926 break; 4927 } 4928 // Home key: Activate first tab. 4929 case 36: { 4930 event.preventDefault(); 4931 this.activateTab( this.tabs[ 0 ] ); 4932 break; 4933 } 4934 // Left and up keys: Activate previous tab. 4935 case 37: 4936 case 38: { 4937 event.preventDefault(); 4938 newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1; 4939 this.activateTab( this.tabs[ newIndex ] ); 4940 break; 4941 } 4942 // Right and down keys: Activate next tab. 4943 case 39: 4944 case 40: { 4945 event.preventDefault(); 4946 newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1; 4947 this.activateTab( this.tabs[ newIndex ] ); 4948 break; 4949 } 4950 } 4951 }, 4952 4953 /** 4954 * Sets a single tab to be focusable and semantically selected. 4955 * 4956 * @since 5.3.0 4957 * 4958 * @param {Object} tab The tab DOM element. 4959 * 4960 * @return {void} 4961 */ 4962 activateTab: function( tab ) { 4963 if ( ! tab ) { 4964 return; 4965 } 4966 4967 // The tab is a DOM element: no need for jQuery methods. 4968 tab.focus(); 4969 4970 // Handle automatic activation. 4971 if ( this.tabsAutomaticActivation ) { 4972 tab.removeAttribute( 'tabindex' ); 4973 tab.setAttribute( 'aria-selected', 'true' ); 4974 tab.click(); 4975 4976 return; 4977 } 4978 4979 // Handle manual activation. 4980 $( tab ).on( 'click', function() { 4981 tab.removeAttribute( 'tabindex' ); 4982 tab.setAttribute( 'aria-selected', 'true' ); 4983 } ); 4984 } 4985 }); 4986 4987 module.exports = FocusManager; 4988 4989 4990 /***/ }), 4991 /* 57 */ 4992 /***/ (function(module, exports) { 4993 4994 var $ = jQuery, 4995 UploaderWindow; 4996 4997 /** 4998 * wp.media.view.UploaderWindow 4999 * 5000 * An uploader window that allows for dragging and dropping media. 5001 * 5002 * @memberOf wp.media.view 5003 * 5004 * @class 5005 * @augments wp.media.View 5006 * @augments wp.Backbone.View 5007 * @augments Backbone.View 5008 * 5009 * @param {object} [options] Options hash passed to the view. 5010 * @param {object} [options.uploader] Uploader properties. 5011 * @param {jQuery} [options.uploader.browser] 5012 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 5013 * @param {object} [options.uploader.params] 5014 */ 5015 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{ 5016 tagName: 'div', 5017 className: 'uploader-window', 5018 template: wp.template('uploader-window'), 5019 5020 initialize: function() { 5021 var uploader; 5022 5023 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 5024 5025 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 5026 dropzone: this.$el, 5027 browser: this.$browser, 5028 params: {} 5029 }); 5030 5031 // Ensure the dropzone is a jQuery collection. 5032 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 5033 uploader.dropzone = $( uploader.dropzone ); 5034 } 5035 5036 this.controller.on( 'activate', this.refresh, this ); 5037 5038 this.controller.on( 'detach', function() { 5039 this.$browser.remove(); 5040 }, this ); 5041 }, 5042 5043 refresh: function() { 5044 if ( this.uploader ) { 5045 this.uploader.refresh(); 5046 } 5047 }, 5048 5049 ready: function() { 5050 var postId = wp.media.view.settings.post.id, 5051 dropzone; 5052 5053 // If the uploader already exists, bail. 5054 if ( this.uploader ) { 5055 return; 5056 } 5057 5058 if ( postId ) { 5059 this.options.uploader.params.post_id = postId; 5060 } 5061 this.uploader = new wp.Uploader( this.options.uploader ); 5062 5063 dropzone = this.uploader.dropzone; 5064 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 5065 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 5066 5067 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 5068 }, 5069 5070 _ready: function() { 5071 this.controller.trigger( 'uploader:ready' ); 5072 }, 5073 5074 show: function() { 5075 var $el = this.$el.show(); 5076 5077 // Ensure that the animation is triggered by waiting until 5078 // the transparent element is painted into the DOM. 5079 _.defer( function() { 5080 $el.css({ opacity: 1 }); 5081 }); 5082 }, 5083 5084 hide: function() { 5085 var $el = this.$el.css({ opacity: 0 }); 5086 5087 wp.media.transition( $el ).done( function() { 5088 // Transition end events are subject to race conditions. 5089 // Make sure that the value is set as intended. 5090 if ( '0' === $el.css('opacity') ) { 5091 $el.hide(); 5092 } 5093 }); 5094 5095 // https://core.trac.wordpress.org/ticket/27341 5096 _.delay( function() { 5097 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 5098 $el.hide(); 5099 } 5100 }, 500 ); 5101 } 5102 }); 5103 5104 module.exports = UploaderWindow; 5105 5106 5107 /***/ }), 5108 /* 58 */ 5109 /***/ (function(module, exports) { 5110 5111 var View = wp.media.View, 5112 l10n = wp.media.view.l10n, 5113 $ = jQuery, 5114 EditorUploader; 5115 5116 /** 5117 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 5118 * and relays drag'n'dropped files to a media workflow. 5119 * 5120 * wp.media.view.EditorUploader 5121 * 5122 * @memberOf wp.media.view 5123 * 5124 * @class 5125 * @augments wp.media.View 5126 * @augments wp.Backbone.View 5127 * @augments Backbone.View 5128 */ 5129 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{ 5130 tagName: 'div', 5131 className: 'uploader-editor', 5132 template: wp.template( 'uploader-editor' ), 5133 5134 localDrag: false, 5135 overContainer: false, 5136 overDropzone: false, 5137 draggingFile: null, 5138 5139 /** 5140 * Bind drag'n'drop events to callbacks. 5141 */ 5142 initialize: function() { 5143 this.initialized = false; 5144 5145 // Bail if not enabled or UA does not support drag'n'drop or File API. 5146 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 5147 return this; 5148 } 5149 5150 this.$document = $(document); 5151 this.dropzones = []; 5152 this.files = []; 5153 5154 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 5155 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 5156 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 5157 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 5158 5159 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 5160 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 5161 5162 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 5163 this.localDrag = event.type === 'dragstart'; 5164 5165 if ( event.type === 'drop' ) { 5166 this.containerDragleave(); 5167 } 5168 }, this ) ); 5169 5170 this.initialized = true; 5171 return this; 5172 }, 5173 5174 /** 5175 * Check browser support for drag'n'drop. 5176 * 5177 * @return {boolean} 5178 */ 5179 browserSupport: function() { 5180 var supports = false, div = document.createElement('div'); 5181 5182 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 5183 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 5184 return supports; 5185 }, 5186 5187 isDraggingFile: function( event ) { 5188 if ( this.draggingFile !== null ) { 5189 return this.draggingFile; 5190 } 5191 5192 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 5193 return false; 5194 } 5195 5196 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 5197 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 5198 5199 return this.draggingFile; 5200 }, 5201 5202 refresh: function( e ) { 5203 var dropzone_id; 5204 for ( dropzone_id in this.dropzones ) { 5205 // Hide the dropzones only if dragging has left the screen. 5206 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 5207 } 5208 5209 if ( ! _.isUndefined( e ) ) { 5210 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 5211 } 5212 5213 if ( ! this.overContainer && ! this.overDropzone ) { 5214 this.draggingFile = null; 5215 } 5216 5217 return this; 5218 }, 5219 5220 render: function() { 5221 if ( ! this.initialized ) { 5222 return this; 5223 } 5224 5225 View.prototype.render.apply( this, arguments ); 5226 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 5227 return this; 5228 }, 5229 5230 attach: function( index, editor ) { 5231 // Attach a dropzone to an editor. 5232 var dropzone = this.$el.clone(); 5233 this.dropzones.push( dropzone ); 5234 $( editor ).append( dropzone ); 5235 return this; 5236 }, 5237 5238 /** 5239 * When a file is dropped on the editor uploader, open up an editor media workflow 5240 * and upload the file immediately. 5241 * 5242 * @param {jQuery.Event} event The 'drop' event. 5243 */ 5244 drop: function( event ) { 5245 var $wrap, uploadView; 5246 5247 this.containerDragleave( event ); 5248 this.dropzoneDragleave( event ); 5249 5250 this.files = event.originalEvent.dataTransfer.files; 5251 if ( this.files.length < 1 ) { 5252 return; 5253 } 5254 5255 // Set the active editor to the drop target. 5256 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 5257 if ( $wrap.length > 0 && $wrap[0].id ) { 5258 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 5259 } 5260 5261 if ( ! this.workflow ) { 5262 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 5263 frame: 'post', 5264 state: 'insert', 5265 title: l10n.addMedia, 5266 multiple: true 5267 }); 5268 5269 uploadView = this.workflow.uploader; 5270 5271 if ( uploadView.uploader && uploadView.uploader.ready ) { 5272 this.addFiles.apply( this ); 5273 } else { 5274 this.workflow.on( 'uploader:ready', this.addFiles, this ); 5275 } 5276 } else { 5277 this.workflow.state().reset(); 5278 this.addFiles.apply( this ); 5279 this.workflow.open(); 5280 } 5281 5282 return false; 5283 }, 5284 5285 /** 5286 * Add the files to the uploader. 5287 */ 5288 addFiles: function() { 5289 if ( this.files.length ) { 5290 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 5291 this.files = []; 5292 } 5293 return this; 5294 }, 5295 5296 containerDragover: function( event ) { 5297 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 5298 return; 5299 } 5300 5301 this.overContainer = true; 5302 this.refresh(); 5303 }, 5304 5305 containerDragleave: function() { 5306 this.overContainer = false; 5307 5308 // Throttle dragleave because it's called when bouncing from some elements to others. 5309 _.delay( _.bind( this.refresh, this ), 50 ); 5310 }, 5311 5312 dropzoneDragover: function( event ) { 5313 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 5314 return; 5315 } 5316 5317 this.overDropzone = true; 5318 this.refresh( event ); 5319 return false; 5320 }, 5321 5322 dropzoneDragleave: function( e ) { 5323 this.overDropzone = false; 5324 _.delay( _.bind( this.refresh, this, e ), 50 ); 5325 }, 5326 5327 click: function( e ) { 5328 // In the rare case where the dropzone gets stuck, hide it on click. 5329 this.containerDragleave( e ); 5330 this.dropzoneDragleave( e ); 5331 this.localDrag = false; 5332 } 5333 }); 5334 5335 module.exports = EditorUploader; 5336 5337 5338 /***/ }), 5339 /* 59 */ 5340 /***/ (function(module, exports) { 5341 5342 var View = wp.media.View, 5343 UploaderInline; 5344 5345 /** 5346 * wp.media.view.UploaderInline 5347 * 5348 * The inline uploader that shows up in the 'Upload Files' tab. 5349 * 5350 * @memberOf wp.media.view 5351 * 5352 * @class 5353 * @augments wp.media.View 5354 * @augments wp.Backbone.View 5355 * @augments Backbone.View 5356 */ 5357 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 5358 tagName: 'div', 5359 className: 'uploader-inline', 5360 template: wp.template('uploader-inline'), 5361 5362 events: { 5363 'click .close': 'hide' 5364 }, 5365 5366 initialize: function() { 5367 _.defaults( this.options, { 5368 message: '', 5369 status: true, 5370 canClose: false 5371 }); 5372 5373 if ( ! this.options.$browser && this.controller.uploader ) { 5374 this.options.$browser = this.controller.uploader.$browser; 5375 } 5376 5377 if ( _.isUndefined( this.options.postId ) ) { 5378 this.options.postId = wp.media.view.settings.post.id; 5379 } 5380 5381 if ( this.options.status ) { 5382 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 5383 controller: this.controller 5384 }) ); 5385 } 5386 }, 5387 5388 prepare: function() { 5389 var suggestedWidth = this.controller.state().get('suggestedWidth'), 5390 suggestedHeight = this.controller.state().get('suggestedHeight'), 5391 data = {}; 5392 5393 data.message = this.options.message; 5394 data.canClose = this.options.canClose; 5395 5396 if ( suggestedWidth && suggestedHeight ) { 5397 data.suggestedWidth = suggestedWidth; 5398 data.suggestedHeight = suggestedHeight; 5399 } 5400 5401 return data; 5402 }, 5403 /** 5404 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 5405 */ 5406 dispose: function() { 5407 if ( this.disposing ) { 5408 /** 5409 * call 'dispose' directly on the parent class 5410 */ 5411 return View.prototype.dispose.apply( this, arguments ); 5412 } 5413 5414 /* 5415 * Run remove on `dispose`, so we can be sure to refresh the 5416 * uploader with a view-less DOM. Track whether we're disposing 5417 * so we don't trigger an infinite loop. 5418 */ 5419 this.disposing = true; 5420 return this.remove(); 5421 }, 5422 /** 5423 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 5424 */ 5425 remove: function() { 5426 /** 5427 * call 'remove' directly on the parent class 5428 */ 5429 var result = View.prototype.remove.apply( this, arguments ); 5430 5431 _.defer( _.bind( this.refresh, this ) ); 5432 return result; 5433 }, 5434 5435 refresh: function() { 5436 var uploader = this.controller.uploader; 5437 5438 if ( uploader ) { 5439 uploader.refresh(); 5440 } 5441 }, 5442 /** 5443 * @return {wp.media.view.UploaderInline} 5444 */ 5445 ready: function() { 5446 var $browser = this.options.$browser, 5447 $placeholder; 5448 5449 if ( this.controller.uploader ) { 5450 $placeholder = this.$('.browser'); 5451 5452 // Check if we've already replaced the placeholder. 5453 if ( $placeholder[0] === $browser[0] ) { 5454 return; 5455 } 5456 5457 $browser.detach().text( $placeholder.text() ); 5458 $browser[0].className = $placeholder[0].className; 5459 $browser[0].setAttribute( 'aria-labelledby', $browser[0].id + ' ' + $placeholder[0].getAttribute('aria-labelledby') ); 5460 $placeholder.replaceWith( $browser.show() ); 5461 } 5462 5463 this.refresh(); 5464 return this; 5465 }, 5466 show: function() { 5467 this.$el.removeClass( 'hidden' ); 5468 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 5469 this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' ); 5470 } 5471 }, 5472 hide: function() { 5473 this.$el.addClass( 'hidden' ); 5474 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 5475 this.controller.$uploaderToggler 5476 .attr( 'aria-expanded', 'false' ) 5477 // Move focus back to the toggle button when closing the uploader. 5478 .trigger( 'focus' ); 5479 } 5480 } 5481 5482 }); 5483 5484 module.exports = UploaderInline; 5485 5486 5487 /***/ }), 5488 /* 60 */ 5489 /***/ (function(module, exports) { 5490 5491 var View = wp.media.View, 5492 UploaderStatus; 5493 5494 /** 5495 * wp.media.view.UploaderStatus 5496 * 5497 * An uploader status for on-going uploads. 5498 * 5499 * @memberOf wp.media.view 5500 * 5501 * @class 5502 * @augments wp.media.View 5503 * @augments wp.Backbone.View 5504 * @augments Backbone.View 5505 */ 5506 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{ 5507 className: 'media-uploader-status', 5508 template: wp.template('uploader-status'), 5509 5510 events: { 5511 'click .upload-dismiss-errors': 'dismiss' 5512 }, 5513 5514 initialize: function() { 5515 this.queue = wp.Uploader.queue; 5516 this.queue.on( 'add remove reset', this.visibility, this ); 5517 this.queue.on( 'add remove reset change:percent', this.progress, this ); 5518 this.queue.on( 'add remove reset change:uploading', this.info, this ); 5519 5520 this.errors = wp.Uploader.errors; 5521 this.errors.reset(); 5522 this.errors.on( 'add remove reset', this.visibility, this ); 5523 this.errors.on( 'add', this.error, this ); 5524 }, 5525 /** 5526 * @return {wp.media.view.UploaderStatus} 5527 */ 5528 dispose: function() { 5529 wp.Uploader.queue.off( null, null, this ); 5530 /** 5531 * call 'dispose' directly on the parent class 5532 */ 5533 View.prototype.dispose.apply( this, arguments ); 5534 return this; 5535 }, 5536 5537 visibility: function() { 5538 this.$el.toggleClass( 'uploading', !! this.queue.length ); 5539 this.$el.toggleClass( 'errors', !! this.errors.length ); 5540 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 5541 }, 5542 5543 ready: function() { 5544 _.each({ 5545 '$bar': '.media-progress-bar div', 5546 '$index': '.upload-index', 5547 '$total': '.upload-total', 5548 '$filename': '.upload-filename' 5549 }, function( selector, key ) { 5550 this[ key ] = this.$( selector ); 5551 }, this ); 5552 5553 this.visibility(); 5554 this.progress(); 5555 this.info(); 5556 }, 5557 5558 progress: function() { 5559 var queue = this.queue, 5560 $bar = this.$bar; 5561 5562 if ( ! $bar || ! queue.length ) { 5563 return; 5564 } 5565 5566 $bar.width( ( queue.reduce( function( memo, attachment ) { 5567 if ( ! attachment.get('uploading') ) { 5568 return memo + 100; 5569 } 5570 5571 var percent = attachment.get('percent'); 5572 return memo + ( _.isNumber( percent ) ? percent : 100 ); 5573 }, 0 ) / queue.length ) + '%' ); 5574 }, 5575 5576 info: function() { 5577 var queue = this.queue, 5578 index = 0, active; 5579 5580 if ( ! queue.length ) { 5581 return; 5582 } 5583 5584 active = this.queue.find( function( attachment, i ) { 5585 index = i; 5586 return attachment.get('uploading'); 5587 }); 5588 5589 this.$index.text( index + 1 ); 5590 this.$total.text( queue.length ); 5591 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 5592 }, 5593 /** 5594 * @param {string} filename 5595 * @return {string} 5596 */ 5597 filename: function( filename ) { 5598 return _.escape( filename ); 5599 }, 5600 /** 5601 * @param {Backbone.Model} error 5602 */ 5603 error: function( error ) { 5604 var statusError = new wp.media.view.UploaderStatusError( { 5605 filename: this.filename( error.get( 'file' ).name ), 5606 message: error.get( 'message' ) 5607 } ); 5608 5609 var buttonClose = this.$el.find( 'button' ); 5610 5611 // Can show additional info here while retrying to create image sub-sizes. 5612 this.views.add( '.upload-errors', statusError, { at: 0 } ); 5613 _.delay( function() { 5614 buttonClose.trigger( 'focus' ); 5615 wp.a11y.speak( error.get( 'message' ), 'assertive' ); 5616 }, 1000 ); 5617 }, 5618 5619 dismiss: function() { 5620 var errors = this.views.get('.upload-errors'); 5621 5622 if ( errors ) { 5623 _.invoke( errors, 'remove' ); 5624 } 5625 wp.Uploader.errors.reset(); 5626 // Move focus to the modal after the dismiss button gets removed from the DOM. 5627 if ( this.controller.modal ) { 5628 this.controller.modal.focusManager.focus(); 5629 } 5630 } 5631 }); 5632 5633 module.exports = UploaderStatus; 5634 5635 5636 /***/ }), 5637 /* 61 */ 5638 /***/ (function(module, exports) { 5639 5640 /** 5641 * wp.media.view.UploaderStatusError 5642 * 5643 * @memberOf wp.media.view 5644 * 5645 * @class 5646 * @augments wp.media.View 5647 * @augments wp.Backbone.View 5648 * @augments Backbone.View 5649 */ 5650 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{ 5651 className: 'upload-error', 5652 template: wp.template('uploader-status-error') 5653 }); 5654 5655 module.exports = UploaderStatusError; 5656 5657 5658 /***/ }), 5659 /* 62 */ 5660 /***/ (function(module, exports) { 5661 5662 var View = wp.media.View, 5663 Toolbar; 5664 5665 /** 5666 * wp.media.view.Toolbar 5667 * 5668 * A toolbar which consists of a primary and a secondary section. Each sections 5669 * can be filled with views. 5670 * 5671 * @memberOf wp.media.view 5672 * 5673 * @class 5674 * @augments wp.media.View 5675 * @augments wp.Backbone.View 5676 * @augments Backbone.View 5677 */ 5678 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ 5679 tagName: 'div', 5680 className: 'media-toolbar', 5681 5682 initialize: function() { 5683 var state = this.controller.state(), 5684 selection = this.selection = state.get('selection'), 5685 library = this.library = state.get('library'); 5686 5687 this._views = {}; 5688 5689 // The toolbar is composed of two `PriorityList` views. 5690 this.primary = new wp.media.view.PriorityList(); 5691 this.secondary = new wp.media.view.PriorityList(); 5692 this.primary.$el.addClass('media-toolbar-primary search-form'); 5693 this.secondary.$el.addClass('media-toolbar-secondary'); 5694 5695 this.views.set([ this.secondary, this.primary ]); 5696 5697 if ( this.options.items ) { 5698 this.set( this.options.items, { silent: true }); 5699 } 5700 5701 if ( ! this.options.silent ) { 5702 this.render(); 5703 } 5704 5705 if ( selection ) { 5706 selection.on( 'add remove reset', this.refresh, this ); 5707 } 5708 5709 if ( library ) { 5710 library.on( 'add remove reset', this.refresh, this ); 5711 } 5712 }, 5713 /** 5714 * @return {wp.media.view.Toolbar} Returns itsef to allow chaining 5715 */ 5716 dispose: function() { 5717 if ( this.selection ) { 5718 this.selection.off( null, null, this ); 5719 } 5720 5721 if ( this.library ) { 5722 this.library.off( null, null, this ); 5723 } 5724 /** 5725 * call 'dispose' directly on the parent class 5726 */ 5727 return View.prototype.dispose.apply( this, arguments ); 5728 }, 5729 5730 ready: function() { 5731 this.refresh(); 5732 }, 5733 5734 /** 5735 * @param {string} id 5736 * @param {Backbone.View|Object} view 5737 * @param {Object} [options={}] 5738 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 5739 */ 5740 set: function( id, view, options ) { 5741 var list; 5742 options = options || {}; 5743 5744 // Accept an object with an `id` : `view` mapping. 5745 if ( _.isObject( id ) ) { 5746 _.each( id, function( view, id ) { 5747 this.set( id, view, { silent: true }); 5748 }, this ); 5749 5750 } else { 5751 if ( ! ( view instanceof Backbone.View ) ) { 5752 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 5753 view = new wp.media.view.Button( view ).render(); 5754 } 5755 5756 view.controller = view.controller || this.controller; 5757 5758 this._views[ id ] = view; 5759 5760 list = view.options.priority < 0 ? 'secondary' : 'primary'; 5761 this[ list ].set( id, view, options ); 5762 } 5763 5764 if ( ! options.silent ) { 5765 this.refresh(); 5766 } 5767 5768 return this; 5769 }, 5770 /** 5771 * @param {string} id 5772 * @return {wp.media.view.Button} 5773 */ 5774 get: function( id ) { 5775 return this._views[ id ]; 5776 }, 5777 /** 5778 * @param {string} id 5779 * @param {Object} options 5780 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 5781 */ 5782 unset: function( id, options ) { 5783 delete this._views[ id ]; 5784 this.primary.unset( id, options ); 5785 this.secondary.unset( id, options ); 5786 5787 if ( ! options || ! options.silent ) { 5788 this.refresh(); 5789 } 5790 return this; 5791 }, 5792 5793 refresh: function() { 5794 var state = this.controller.state(), 5795 library = state.get('library'), 5796 selection = state.get('selection'); 5797 5798 _.each( this._views, function( button ) { 5799 if ( ! button.model || ! button.options || ! button.options.requires ) { 5800 return; 5801 } 5802 5803 var requires = button.options.requires, 5804 disabled = false; 5805 5806 // Prevent insertion of attachments if any of them are still uploading. 5807 if ( selection && selection.models ) { 5808 disabled = _.some( selection.models, function( attachment ) { 5809 return attachment.get('uploading') === true; 5810 }); 5811 } 5812 5813 if ( requires.selection && selection && ! selection.length ) { 5814 disabled = true; 5815 } else if ( requires.library && library && ! library.length ) { 5816 disabled = true; 5817 } 5818 button.model.set( 'disabled', disabled ); 5819 }); 5820 } 5821 }); 5822 5823 module.exports = Toolbar; 5824 5825 5826 /***/ }), 5827 /* 63 */ 5828 /***/ (function(module, exports) { 5829 5830 var Toolbar = wp.media.view.Toolbar, 5831 l10n = wp.media.view.l10n, 5832 Select; 5833 5834 /** 5835 * wp.media.view.Toolbar.Select 5836 * 5837 * @memberOf wp.media.view.Toolbar 5838 * 5839 * @class 5840 * @augments wp.media.view.Toolbar 5841 * @augments wp.media.View 5842 * @augments wp.Backbone.View 5843 * @augments Backbone.View 5844 */ 5845 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{ 5846 initialize: function() { 5847 var options = this.options; 5848 5849 _.bindAll( this, 'clickSelect' ); 5850 5851 _.defaults( options, { 5852 event: 'select', 5853 state: false, 5854 reset: true, 5855 close: true, 5856 text: l10n.select, 5857 5858 // Does the button rely on the selection? 5859 requires: { 5860 selection: true 5861 } 5862 }); 5863 5864 options.items = _.defaults( options.items || {}, { 5865 select: { 5866 style: 'primary', 5867 text: options.text, 5868 priority: 80, 5869 click: this.clickSelect, 5870 requires: options.requires 5871 } 5872 }); 5873 // Call 'initialize' directly on the parent class. 5874 Toolbar.prototype.initialize.apply( this, arguments ); 5875 }, 5876 5877 clickSelect: function() { 5878 var options = this.options, 5879 controller = this.controller; 5880 5881 if ( options.close ) { 5882 controller.close(); 5883 } 5884 5885 if ( options.event ) { 5886 controller.state().trigger( options.event ); 5887 } 5888 5889 if ( options.state ) { 5890 controller.setState( options.state ); 5891 } 5892 5893 if ( options.reset ) { 5894 controller.reset(); 5895 } 5896 } 5897 }); 5898 5899 module.exports = Select; 5900 5901 5902 /***/ }), 5903 /* 64 */ 5904 /***/ (function(module, exports) { 5905 5906 var Select = wp.media.view.Toolbar.Select, 5907 l10n = wp.media.view.l10n, 5908 Embed; 5909 5910 /** 5911 * wp.media.view.Toolbar.Embed 5912 * 5913 * @memberOf wp.media.view.Toolbar 5914 * 5915 * @class 5916 * @augments wp.media.view.Toolbar.Select 5917 * @augments wp.media.view.Toolbar 5918 * @augments wp.media.View 5919 * @augments wp.Backbone.View 5920 * @augments Backbone.View 5921 */ 5922 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{ 5923 initialize: function() { 5924 _.defaults( this.options, { 5925 text: l10n.insertIntoPost, 5926 requires: false 5927 }); 5928 // Call 'initialize' directly on the parent class. 5929 Select.prototype.initialize.apply( this, arguments ); 5930 }, 5931 5932 refresh: function() { 5933 var url = this.controller.state().props.get('url'); 5934 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 5935 /** 5936 * call 'refresh' directly on the parent class 5937 */ 5938 Select.prototype.refresh.apply( this, arguments ); 5939 } 5940 }); 5941 5942 module.exports = Embed; 5943 5944 5945 /***/ }), 5946 /* 65 */ 5947 /***/ (function(module, exports) { 5948 5949 /** 5950 * wp.media.view.Button 5951 * 5952 * @memberOf wp.media.view 5953 * 5954 * @class 5955 * @augments wp.media.View 5956 * @augments wp.Backbone.View 5957 * @augments Backbone.View 5958 */ 5959 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{ 5960 tagName: 'button', 5961 className: 'media-button', 5962 attributes: { type: 'button' }, 5963 5964 events: { 5965 'click': 'click' 5966 }, 5967 5968 defaults: { 5969 text: '', 5970 style: '', 5971 size: 'large', 5972 disabled: false 5973 }, 5974 5975 initialize: function() { 5976 /** 5977 * Create a model with the provided `defaults`. 5978 * 5979 * @member {Backbone.Model} 5980 */ 5981 this.model = new Backbone.Model( this.defaults ); 5982 5983 // If any of the `options` have a key from `defaults`, apply its 5984 // value to the `model` and remove it from the `options object. 5985 _.each( this.defaults, function( def, key ) { 5986 var value = this.options[ key ]; 5987 if ( _.isUndefined( value ) ) { 5988 return; 5989 } 5990 5991 this.model.set( key, value ); 5992 delete this.options[ key ]; 5993 }, this ); 5994 5995 this.listenTo( this.model, 'change', this.render ); 5996 }, 5997 /** 5998 * @return {wp.media.view.Button} Returns itself to allow chaining. 5999 */ 6000 render: function() { 6001 var classes = [ 'button', this.className ], 6002 model = this.model.toJSON(); 6003 6004 if ( model.style ) { 6005 classes.push( 'button-' + model.style ); 6006 } 6007 6008 if ( model.size ) { 6009 classes.push( 'button-' + model.size ); 6010 } 6011 6012 classes = _.uniq( classes.concat( this.options.classes ) ); 6013 this.el.className = classes.join(' '); 6014 6015 this.$el.attr( 'disabled', model.disabled ); 6016 this.$el.text( this.model.get('text') ); 6017 6018 return this; 6019 }, 6020 /** 6021 * @param {Object} event 6022 */ 6023 click: function( event ) { 6024 if ( '#' === this.attributes.href ) { 6025 event.preventDefault(); 6026 } 6027 6028 if ( this.options.click && ! this.model.get('disabled') ) { 6029 this.options.click.apply( this, arguments ); 6030 } 6031 } 6032 }); 6033 6034 module.exports = Button; 6035 6036 6037 /***/ }), 6038 /* 66 */ 6039 /***/ (function(module, exports) { 6040 6041 var $ = Backbone.$, 6042 ButtonGroup; 6043 6044 /** 6045 * wp.media.view.ButtonGroup 6046 * 6047 * @memberOf wp.media.view 6048 * 6049 * @class 6050 * @augments wp.media.View 6051 * @augments wp.Backbone.View 6052 * @augments Backbone.View 6053 */ 6054 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{ 6055 tagName: 'div', 6056 className: 'button-group button-large media-button-group', 6057 6058 initialize: function() { 6059 /** 6060 * @member {wp.media.view.Button[]} 6061 */ 6062 this.buttons = _.map( this.options.buttons || [], function( button ) { 6063 if ( button instanceof Backbone.View ) { 6064 return button; 6065 } else { 6066 return new wp.media.view.Button( button ).render(); 6067 } 6068 }); 6069 6070 delete this.options.buttons; 6071 6072 if ( this.options.classes ) { 6073 this.$el.addClass( this.options.classes ); 6074 } 6075 }, 6076 6077 /** 6078 * @return {wp.media.view.ButtonGroup} 6079 */ 6080 render: function() { 6081 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 6082 return this; 6083 } 6084 }); 6085 6086 module.exports = ButtonGroup; 6087 6088 6089 /***/ }), 6090 /* 67 */ 6091 /***/ (function(module, exports) { 6092 6093 /** 6094 * wp.media.view.PriorityList 6095 * 6096 * @memberOf wp.media.view 6097 * 6098 * @class 6099 * @augments wp.media.View 6100 * @augments wp.Backbone.View 6101 * @augments Backbone.View 6102 */ 6103 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{ 6104 tagName: 'div', 6105 6106 initialize: function() { 6107 this._views = {}; 6108 6109 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6110 delete this.options.views; 6111 6112 if ( ! this.options.silent ) { 6113 this.render(); 6114 } 6115 }, 6116 /** 6117 * @param {string} id 6118 * @param {wp.media.View|Object} view 6119 * @param {Object} options 6120 * @return {wp.media.view.PriorityList} Returns itself to allow chaining. 6121 */ 6122 set: function( id, view, options ) { 6123 var priority, views, index; 6124 6125 options = options || {}; 6126 6127 // Accept an object with an `id` : `view` mapping. 6128 if ( _.isObject( id ) ) { 6129 _.each( id, function( view, id ) { 6130 this.set( id, view ); 6131 }, this ); 6132 return this; 6133 } 6134 6135 if ( ! (view instanceof Backbone.View) ) { 6136 view = this.toView( view, id, options ); 6137 } 6138 view.controller = view.controller || this.controller; 6139 6140 this.unset( id ); 6141 6142 priority = view.options.priority || 10; 6143 views = this.views.get() || []; 6144 6145 _.find( views, function( existing, i ) { 6146 if ( existing.options.priority > priority ) { 6147 index = i; 6148 return true; 6149 } 6150 }); 6151 6152 this._views[ id ] = view; 6153 this.views.add( view, { 6154 at: _.isNumber( index ) ? index : views.length || 0 6155 }); 6156 6157 return this; 6158 }, 6159 /** 6160 * @param {string} id 6161 * @return {wp.media.View} 6162 */ 6163 get: function( id ) { 6164 return this._views[ id ]; 6165 }, 6166 /** 6167 * @param {string} id 6168 * @return {wp.media.view.PriorityList} 6169 */ 6170 unset: function( id ) { 6171 var view = this.get( id ); 6172 6173 if ( view ) { 6174 view.remove(); 6175 } 6176 6177 delete this._views[ id ]; 6178 return this; 6179 }, 6180 /** 6181 * @param {Object} options 6182 * @return {wp.media.View} 6183 */ 6184 toView: function( options ) { 6185 return new wp.media.View( options ); 6186 } 6187 }); 6188 6189 module.exports = PriorityList; 6190 6191 6192 /***/ }), 6193 /* 68 */ 6194 /***/ (function(module, exports) { 6195 6196 var MenuItem; 6197 6198 /** 6199 * wp.media.view.MenuItem 6200 * 6201 * @memberOf wp.media.view 6202 * 6203 * @class 6204 * @augments wp.media.View 6205 * @augments wp.Backbone.View 6206 * @augments Backbone.View 6207 */ 6208 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{ 6209 tagName: 'button', 6210 className: 'media-menu-item', 6211 6212 attributes: { 6213 type: 'button', 6214 role: 'tab' 6215 }, 6216 6217 events: { 6218 'click': '_click' 6219 }, 6220 6221 /** 6222 * Allows to override the click event. 6223 */ 6224 _click: function() { 6225 var clickOverride = this.options.click; 6226 6227 if ( clickOverride ) { 6228 clickOverride.call( this ); 6229 } else { 6230 this.click(); 6231 } 6232 }, 6233 6234 click: function() { 6235 var state = this.options.state; 6236 6237 if ( state ) { 6238 this.controller.setState( state ); 6239 // Toggle the menu visibility in the responsive view. 6240 this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below. 6241 } 6242 }, 6243 6244 /** 6245 * @return {wp.media.view.MenuItem} returns itself to allow chaining. 6246 */ 6247 render: function() { 6248 var options = this.options, 6249 menuProperty = options.state || options.contentMode; 6250 6251 if ( options.text ) { 6252 this.$el.text( options.text ); 6253 } else if ( options.html ) { 6254 this.$el.html( options.html ); 6255 } 6256 6257 // Set the menu item ID based on the frame state associated to the menu item. 6258 this.$el.attr( 'id', 'menu-item-' + menuProperty ); 6259 6260 return this; 6261 } 6262 }); 6263 6264 module.exports = MenuItem; 6265 6266 6267 /***/ }), 6268 /* 69 */ 6269 /***/ (function(module, exports) { 6270 6271 var MenuItem = wp.media.view.MenuItem, 6272 PriorityList = wp.media.view.PriorityList, 6273 Menu; 6274 6275 /** 6276 * wp.media.view.Menu 6277 * 6278 * @memberOf wp.media.view 6279 * 6280 * @class 6281 * @augments wp.media.view.PriorityList 6282 * @augments wp.media.View 6283 * @augments wp.Backbone.View 6284 * @augments Backbone.View 6285 */ 6286 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{ 6287 tagName: 'div', 6288 className: 'media-menu', 6289 property: 'state', 6290 ItemView: MenuItem, 6291 region: 'menu', 6292 6293 attributes: { 6294 role: 'tablist', 6295 'aria-orientation': 'horizontal' 6296 }, 6297 6298 initialize: function() { 6299 this._views = {}; 6300 6301 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6302 delete this.options.views; 6303 6304 if ( ! this.options.silent ) { 6305 this.render(); 6306 } 6307 6308 // Initialize the Focus Manager. 6309 this.focusManager = new wp.media.view.FocusManager( { 6310 el: this.el, 6311 mode: 'tabsNavigation' 6312 } ); 6313 6314 // The menu is always rendered and can be visible or hidden on some frames. 6315 this.isVisible = true; 6316 }, 6317 6318 /** 6319 * @param {Object} options 6320 * @param {string} id 6321 * @return {wp.media.View} 6322 */ 6323 toView: function( options, id ) { 6324 options = options || {}; 6325 options[ this.property ] = options[ this.property ] || id; 6326 return new this.ItemView( options ).render(); 6327 }, 6328 6329 ready: function() { 6330 /** 6331 * call 'ready' directly on the parent class 6332 */ 6333 PriorityList.prototype.ready.apply( this, arguments ); 6334 this.visibility(); 6335 6336 // Set up aria tabs initial attributes. 6337 this.focusManager.setupAriaTabs(); 6338 }, 6339 6340 set: function() { 6341 /** 6342 * call 'set' directly on the parent class 6343 */ 6344 PriorityList.prototype.set.apply( this, arguments ); 6345 this.visibility(); 6346 }, 6347 6348 unset: function() { 6349 /** 6350 * call 'unset' directly on the parent class 6351 */ 6352 PriorityList.prototype.unset.apply( this, arguments ); 6353 this.visibility(); 6354 }, 6355 6356 visibility: function() { 6357 var region = this.region, 6358 view = this.controller[ region ].get(), 6359 views = this.views.get(), 6360 hide = ! views || views.length < 2; 6361 6362 if ( this === view ) { 6363 // Flag this menu as hidden or visible. 6364 this.isVisible = ! hide; 6365 // Set or remove a CSS class to hide the menu. 6366 this.controller.$el.toggleClass( 'hide-' + region, hide ); 6367 } 6368 }, 6369 /** 6370 * @param {string} id 6371 */ 6372 select: function( id ) { 6373 var view = this.get( id ); 6374 6375 if ( ! view ) { 6376 return; 6377 } 6378 6379 this.deselect(); 6380 view.$el.addClass('active'); 6381 6382 // Set up again the aria tabs initial attributes after the menu updates. 6383 this.focusManager.setupAriaTabs(); 6384 }, 6385 6386 deselect: function() { 6387 this.$el.children().removeClass('active'); 6388 }, 6389 6390 hide: function( id ) { 6391 var view = this.get( id ); 6392 6393 if ( ! view ) { 6394 return; 6395 } 6396 6397 view.$el.addClass('hidden'); 6398 }, 6399 6400 show: function( id ) { 6401 var view = this.get( id ); 6402 6403 if ( ! view ) { 6404 return; 6405 } 6406 6407 view.$el.removeClass('hidden'); 6408 } 6409 }); 6410 6411 module.exports = Menu; 6412 6413 6414 /***/ }), 6415 /* 70 */ 6416 /***/ (function(module, exports) { 6417 6418 /** 6419 * wp.media.view.RouterItem 6420 * 6421 * @memberOf wp.media.view 6422 * 6423 * @class 6424 * @augments wp.media.view.MenuItem 6425 * @augments wp.media.View 6426 * @augments wp.Backbone.View 6427 * @augments Backbone.View 6428 */ 6429 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{ 6430 /** 6431 * On click handler to activate the content region's corresponding mode. 6432 */ 6433 click: function() { 6434 var contentMode = this.options.contentMode; 6435 if ( contentMode ) { 6436 this.controller.content.mode( contentMode ); 6437 } 6438 } 6439 }); 6440 6441 module.exports = RouterItem; 6442 6443 6444 /***/ }), 6445 /* 71 */ 6446 /***/ (function(module, exports) { 6447 6448 var Menu = wp.media.view.Menu, 6449 Router; 6450 6451 /** 6452 * wp.media.view.Router 6453 * 6454 * @memberOf wp.media.view 6455 * 6456 * @class 6457 * @augments wp.media.view.Menu 6458 * @augments wp.media.view.PriorityList 6459 * @augments wp.media.View 6460 * @augments wp.Backbone.View 6461 * @augments Backbone.View 6462 */ 6463 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{ 6464 tagName: 'div', 6465 className: 'media-router', 6466 property: 'contentMode', 6467 ItemView: wp.media.view.RouterItem, 6468 region: 'router', 6469 6470 attributes: { 6471 role: 'tablist', 6472 'aria-orientation': 'horizontal' 6473 }, 6474 6475 initialize: function() { 6476 this.controller.on( 'content:render', this.update, this ); 6477 // Call 'initialize' directly on the parent class. 6478 Menu.prototype.initialize.apply( this, arguments ); 6479 }, 6480 6481 update: function() { 6482 var mode = this.controller.content.mode(); 6483 if ( mode ) { 6484 this.select( mode ); 6485 } 6486 } 6487 }); 6488 6489 module.exports = Router; 6490 6491 6492 /***/ }), 6493 /* 72 */ 6494 /***/ (function(module, exports) { 6495 6496 /** 6497 * wp.media.view.Sidebar 6498 * 6499 * @memberOf wp.media.view 6500 * 6501 * @class 6502 * @augments wp.media.view.PriorityList 6503 * @augments wp.media.View 6504 * @augments wp.Backbone.View 6505 * @augments Backbone.View 6506 */ 6507 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{ 6508 className: 'media-sidebar' 6509 }); 6510 6511 module.exports = Sidebar; 6512 6513 6514 /***/ }), 6515 /* 73 */ 6516 /***/ (function(module, exports) { 6517 6518 var View = wp.media.View, 6519 $ = jQuery, 6520 Attachment; 6521 6522 /** 6523 * wp.media.view.Attachment 6524 * 6525 * @memberOf wp.media.view 6526 * 6527 * @class 6528 * @augments wp.media.View 6529 * @augments wp.Backbone.View 6530 * @augments Backbone.View 6531 */ 6532 Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{ 6533 tagName: 'li', 6534 className: 'attachment', 6535 template: wp.template('attachment'), 6536 6537 attributes: function() { 6538 return { 6539 'tabIndex': 0, 6540 'role': 'checkbox', 6541 'aria-label': this.model.get( 'title' ), 6542 'aria-checked': false, 6543 'data-id': this.model.get( 'id' ) 6544 }; 6545 }, 6546 6547 events: { 6548 'click': 'toggleSelectionHandler', 6549 'change [data-setting]': 'updateSetting', 6550 'change [data-setting] input': 'updateSetting', 6551 'change [data-setting] select': 'updateSetting', 6552 'change [data-setting] textarea': 'updateSetting', 6553 'click .attachment-close': 'removeFromLibrary', 6554 'click .check': 'checkClickHandler', 6555 'keydown': 'toggleSelectionHandler' 6556 }, 6557 6558 buttons: {}, 6559 6560 initialize: function() { 6561 var selection = this.options.selection, 6562 options = _.defaults( this.options, { 6563 rerenderOnModelChange: true 6564 } ); 6565 6566 if ( options.rerenderOnModelChange ) { 6567 this.listenTo( this.model, 'change', this.render ); 6568 } else { 6569 this.listenTo( this.model, 'change:percent', this.progress ); 6570 } 6571 this.listenTo( this.model, 'change:title', this._syncTitle ); 6572 this.listenTo( this.model, 'change:caption', this._syncCaption ); 6573 this.listenTo( this.model, 'change:artist', this._syncArtist ); 6574 this.listenTo( this.model, 'change:album', this._syncAlbum ); 6575 6576 // Update the selection. 6577 this.listenTo( this.model, 'add', this.select ); 6578 this.listenTo( this.model, 'remove', this.deselect ); 6579 if ( selection ) { 6580 selection.on( 'reset', this.updateSelect, this ); 6581 // Update the model's details view. 6582 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 6583 this.details( this.model, this.controller.state().get('selection') ); 6584 } 6585 6586 this.listenTo( this.controller.states, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 6587 }, 6588 /** 6589 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 6590 */ 6591 dispose: function() { 6592 var selection = this.options.selection; 6593 6594 // Make sure all settings are saved before removing the view. 6595 this.updateAll(); 6596 6597 if ( selection ) { 6598 selection.off( null, null, this ); 6599 } 6600 /** 6601 * call 'dispose' directly on the parent class 6602 */ 6603 View.prototype.dispose.apply( this, arguments ); 6604 return this; 6605 }, 6606 /** 6607 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 6608 */ 6609 render: function() { 6610 var options = _.defaults( this.model.toJSON(), { 6611 orientation: 'landscape', 6612 uploading: false, 6613 type: '', 6614 subtype: '', 6615 icon: '', 6616 filename: '', 6617 caption: '', 6618 title: '', 6619 dateFormatted: '', 6620 width: '', 6621 height: '', 6622 compat: false, 6623 alt: '', 6624 description: '' 6625 }, this.options ); 6626 6627 options.buttons = this.buttons; 6628 options.describe = this.controller.state().get('describe'); 6629 6630 if ( 'image' === options.type ) { 6631 options.size = this.imageSize(); 6632 } 6633 6634 options.can = {}; 6635 if ( options.nonces ) { 6636 options.can.remove = !! options.nonces['delete']; 6637 options.can.save = !! options.nonces.update; 6638 } 6639 6640 if ( this.controller.state().get('allowLocalEdits') ) { 6641 options.allowLocalEdits = true; 6642 } 6643 6644 if ( options.uploading && ! options.percent ) { 6645 options.percent = 0; 6646 } 6647 6648 this.views.detach(); 6649 this.$el.html( this.template( options ) ); 6650 6651 this.$el.toggleClass( 'uploading', options.uploading ); 6652 6653 if ( options.uploading ) { 6654 this.$bar = this.$('.media-progress-bar div'); 6655 } else { 6656 delete this.$bar; 6657 } 6658 6659 // Check if the model is selected. 6660 this.updateSelect(); 6661 6662 // Update the save status. 6663 this.updateSave(); 6664 6665 this.views.render(); 6666 6667 return this; 6668 }, 6669 6670 progress: function() { 6671 if ( this.$bar && this.$bar.length ) { 6672 this.$bar.width( this.model.get('percent') + '%' ); 6673 } 6674 }, 6675 6676 /** 6677 * @param {Object} event 6678 */ 6679 toggleSelectionHandler: function( event ) { 6680 var method; 6681 6682 // Don't do anything inside inputs and on the attachment check and remove buttons. 6683 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 6684 return; 6685 } 6686 6687 // Catch arrow events. 6688 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 6689 this.controller.trigger( 'attachment:keydown:arrow', event ); 6690 return; 6691 } 6692 6693 // Catch enter and space events. 6694 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6695 return; 6696 } 6697 6698 event.preventDefault(); 6699 6700 // In the grid view, bubble up an edit:attachment event to the controller. 6701 if ( this.controller.isModeActive( 'grid' ) ) { 6702 if ( this.controller.isModeActive( 'edit' ) ) { 6703 // Pass the current target to restore focus when closing. 6704 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 6705 return; 6706 } 6707 6708 if ( this.controller.isModeActive( 'select' ) ) { 6709 method = 'toggle'; 6710 } 6711 } 6712 6713 if ( event.shiftKey ) { 6714 method = 'between'; 6715 } else if ( event.ctrlKey || event.metaKey ) { 6716 method = 'toggle'; 6717 } 6718 6719 this.toggleSelection({ 6720 method: method 6721 }); 6722 6723 this.controller.trigger( 'selection:toggle' ); 6724 }, 6725 /** 6726 * @param {Object} options 6727 */ 6728 toggleSelection: function( options ) { 6729 var collection = this.collection, 6730 selection = this.options.selection, 6731 model = this.model, 6732 method = options && options.method, 6733 single, models, singleIndex, modelIndex; 6734 6735 if ( ! selection ) { 6736 return; 6737 } 6738 6739 single = selection.single(); 6740 method = _.isUndefined( method ) ? selection.multiple : method; 6741 6742 // If the `method` is set to `between`, select all models that 6743 // exist between the current and the selected model. 6744 if ( 'between' === method && single && selection.multiple ) { 6745 // If the models are the same, short-circuit. 6746 if ( single === model ) { 6747 return; 6748 } 6749 6750 singleIndex = collection.indexOf( single ); 6751 modelIndex = collection.indexOf( this.model ); 6752 6753 if ( singleIndex < modelIndex ) { 6754 models = collection.models.slice( singleIndex, modelIndex + 1 ); 6755 } else { 6756 models = collection.models.slice( modelIndex, singleIndex + 1 ); 6757 } 6758 6759 selection.add( models ); 6760 selection.single( model ); 6761 return; 6762 6763 // If the `method` is set to `toggle`, just flip the selection 6764 // status, regardless of whether the model is the single model. 6765 } else if ( 'toggle' === method ) { 6766 selection[ this.selected() ? 'remove' : 'add' ]( model ); 6767 selection.single( model ); 6768 return; 6769 } else if ( 'add' === method ) { 6770 selection.add( model ); 6771 selection.single( model ); 6772 return; 6773 } 6774 6775 // Fixes bug that loses focus when selecting a featured image. 6776 if ( ! method ) { 6777 method = 'add'; 6778 } 6779 6780 if ( method !== 'add' ) { 6781 method = 'reset'; 6782 } 6783 6784 if ( this.selected() ) { 6785 /* 6786 * If the model is the single model, remove it. 6787 * If it is not the same as the single model, 6788 * it now becomes the single model. 6789 */ 6790 selection[ single === model ? 'remove' : 'single' ]( model ); 6791 } else { 6792 /* 6793 * If the model is not selected, run the `method` on the 6794 * selection. By default, we `reset` the selection, but the 6795 * `method` can be set to `add` the model to the selection. 6796 */ 6797 selection[ method ]( model ); 6798 selection.single( model ); 6799 } 6800 }, 6801 6802 updateSelect: function() { 6803 this[ this.selected() ? 'select' : 'deselect' ](); 6804 }, 6805 /** 6806 * @return {unresolved|boolean} 6807 */ 6808 selected: function() { 6809 var selection = this.options.selection; 6810 if ( selection ) { 6811 return !! selection.get( this.model.cid ); 6812 } 6813 }, 6814 /** 6815 * @param {Backbone.Model} model 6816 * @param {Backbone.Collection} collection 6817 */ 6818 select: function( model, collection ) { 6819 var selection = this.options.selection, 6820 controller = this.controller; 6821 6822 /* 6823 * Check if a selection exists and if it's the collection provided. 6824 * If they're not the same collection, bail; we're in another 6825 * selection's event loop. 6826 */ 6827 if ( ! selection || ( collection && collection !== selection ) ) { 6828 return; 6829 } 6830 6831 // Bail if the model is already selected. 6832 if ( this.$el.hasClass( 'selected' ) ) { 6833 return; 6834 } 6835 6836 // Add 'selected' class to model, set aria-checked to true. 6837 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 6838 // Make the checkbox tabable, except in media grid (bulk select mode). 6839 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 6840 this.$( '.check' ).attr( 'tabindex', '0' ); 6841 } 6842 }, 6843 /** 6844 * @param {Backbone.Model} model 6845 * @param {Backbone.Collection} collection 6846 */ 6847 deselect: function( model, collection ) { 6848 var selection = this.options.selection; 6849 6850 /* 6851 * Check if a selection exists and if it's the collection provided. 6852 * If they're not the same collection, bail; we're in another 6853 * selection's event loop. 6854 */ 6855 if ( ! selection || ( collection && collection !== selection ) ) { 6856 return; 6857 } 6858 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 6859 .find( '.check' ).attr( 'tabindex', '-1' ); 6860 }, 6861 /** 6862 * @param {Backbone.Model} model 6863 * @param {Backbone.Collection} collection 6864 */ 6865 details: function( model, collection ) { 6866 var selection = this.options.selection, 6867 details; 6868 6869 if ( selection !== collection ) { 6870 return; 6871 } 6872 6873 details = selection.single(); 6874 this.$el.toggleClass( 'details', details === this.model ); 6875 }, 6876 /** 6877 * @param {string} size 6878 * @return {Object} 6879 */ 6880 imageSize: function( size ) { 6881 var sizes = this.model.get('sizes'), matched = false; 6882 6883 size = size || 'medium'; 6884 6885 // Use the provided image size if possible. 6886 if ( sizes ) { 6887 if ( sizes[ size ] ) { 6888 matched = sizes[ size ]; 6889 } else if ( sizes.large ) { 6890 matched = sizes.large; 6891 } else if ( sizes.thumbnail ) { 6892 matched = sizes.thumbnail; 6893 } else if ( sizes.full ) { 6894 matched = sizes.full; 6895 } 6896 6897 if ( matched ) { 6898 return _.clone( matched ); 6899 } 6900 } 6901 6902 return { 6903 url: this.model.get('url'), 6904 width: this.model.get('width'), 6905 height: this.model.get('height'), 6906 orientation: this.model.get('orientation') 6907 }; 6908 }, 6909 /** 6910 * @param {Object} event 6911 */ 6912 updateSetting: function( event ) { 6913 var $setting = $( event.target ).closest('[data-setting]'), 6914 setting, value; 6915 6916 if ( ! $setting.length ) { 6917 return; 6918 } 6919 6920 setting = $setting.data('setting'); 6921 value = event.target.value; 6922 6923 if ( this.model.get( setting ) !== value ) { 6924 this.save( setting, value ); 6925 } 6926 }, 6927 6928 /** 6929 * Pass all the arguments to the model's save method. 6930 * 6931 * Records the aggregate status of all save requests and updates the 6932 * view's classes accordingly. 6933 */ 6934 save: function() { 6935 var view = this, 6936 save = this._save = this._save || { status: 'ready' }, 6937 request = this.model.save.apply( this.model, arguments ), 6938 requests = save.requests ? $.when( request, save.requests ) : request; 6939 6940 // If we're waiting to remove 'Saved.', stop. 6941 if ( save.savedTimer ) { 6942 clearTimeout( save.savedTimer ); 6943 } 6944 6945 this.updateSave('waiting'); 6946 save.requests = requests; 6947 requests.always( function() { 6948 // If we've performed another request since this one, bail. 6949 if ( save.requests !== requests ) { 6950 return; 6951 } 6952 6953 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 6954 save.savedTimer = setTimeout( function() { 6955 view.updateSave('ready'); 6956 delete save.savedTimer; 6957 }, 2000 ); 6958 }); 6959 }, 6960 /** 6961 * @param {string} status 6962 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 6963 */ 6964 updateSave: function( status ) { 6965 var save = this._save = this._save || { status: 'ready' }; 6966 6967 if ( status && status !== save.status ) { 6968 this.$el.removeClass( 'save-' + save.status ); 6969 save.status = status; 6970 } 6971 6972 this.$el.addClass( 'save-' + save.status ); 6973 return this; 6974 }, 6975 6976 updateAll: function() { 6977 var $settings = this.$('[data-setting]'), 6978 model = this.model, 6979 changed; 6980 6981 changed = _.chain( $settings ).map( function( el ) { 6982 var $input = $('input, textarea, select, [value]', el ), 6983 setting, value; 6984 6985 if ( ! $input.length ) { 6986 return; 6987 } 6988 6989 setting = $(el).data('setting'); 6990 value = $input.val(); 6991 6992 // Record the value if it changed. 6993 if ( model.get( setting ) !== value ) { 6994 return [ setting, value ]; 6995 } 6996 }).compact().object().value(); 6997 6998 if ( ! _.isEmpty( changed ) ) { 6999 model.save( changed ); 7000 } 7001 }, 7002 /** 7003 * @param {Object} event 7004 */ 7005 removeFromLibrary: function( event ) { 7006 // Catch enter and space events. 7007 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 7008 return; 7009 } 7010 7011 // Stop propagation so the model isn't selected. 7012 event.stopPropagation(); 7013 7014 this.collection.remove( this.model ); 7015 }, 7016 7017 /** 7018 * Add the model if it isn't in the selection, if it is in the selection, 7019 * remove it. 7020 * 7021 * @param {[type]} event [description] 7022 * @return {[type]} [description] 7023 */ 7024 checkClickHandler: function ( event ) { 7025 var selection = this.options.selection; 7026 if ( ! selection ) { 7027 return; 7028 } 7029 event.stopPropagation(); 7030 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 7031 selection.remove( this.model ); 7032 // Move focus back to the attachment tile (from the check). 7033 this.$el.focus(); 7034 } else { 7035 selection.add( this.model ); 7036 } 7037 7038 // Trigger an action button update. 7039 this.controller.trigger( 'selection:toggle' ); 7040 } 7041 }); 7042 7043 // Ensure settings remain in sync between attachment views. 7044 _.each({ 7045 caption: '_syncCaption', 7046 title: '_syncTitle', 7047 artist: '_syncArtist', 7048 album: '_syncAlbum' 7049 }, function( method, setting ) { 7050 /** 7051 * @function _syncCaption 7052 * @memberOf wp.media.view.Attachment 7053 * @instance 7054 * 7055 * @param {Backbone.Model} model 7056 * @param {string} value 7057 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7058 */ 7059 /** 7060 * @function _syncTitle 7061 * @memberOf wp.media.view.Attachment 7062 * @instance 7063 * 7064 * @param {Backbone.Model} model 7065 * @param {string} value 7066 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7067 */ 7068 /** 7069 * @function _syncArtist 7070 * @memberOf wp.media.view.Attachment 7071 * @instance 7072 * 7073 * @param {Backbone.Model} model 7074 * @param {string} value 7075 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7076 */ 7077 /** 7078 * @function _syncAlbum 7079 * @memberOf wp.media.view.Attachment 7080 * @instance 7081 * 7082 * @param {Backbone.Model} model 7083 * @param {string} value 7084 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 7085 */ 7086 Attachment.prototype[ method ] = function( model, value ) { 7087 var $setting = this.$('[data-setting="' + setting + '"]'); 7088 7089 if ( ! $setting.length ) { 7090 return this; 7091 } 7092 7093 /* 7094 * If the updated value is in sync with the value in the DOM, there 7095 * is no need to re-render. If we're currently editing the value, 7096 * it will automatically be in sync, suppressing the re-render for 7097 * the view we're editing, while updating any others. 7098 */ 7099 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 7100 return this; 7101 } 7102 7103 return this.render(); 7104 }; 7105 }); 7106 7107 module.exports = Attachment; 7108 7109 7110 /***/ }), 7111 /* 74 */ 7112 /***/ (function(module, exports) { 7113 7114 /** 7115 * wp.media.view.Attachment.Library 7116 * 7117 * @memberOf wp.media.view.Attachment 7118 * 7119 * @class 7120 * @augments wp.media.view.Attachment 7121 * @augments wp.media.View 7122 * @augments wp.Backbone.View 7123 * @augments Backbone.View 7124 */ 7125 var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{ 7126 buttons: { 7127 check: true 7128 } 7129 }); 7130 7131 module.exports = Library; 7132 7133 7134 /***/ }), 7135 /* 75 */ 7136 /***/ (function(module, exports) { 7137 7138 /** 7139 * wp.media.view.Attachment.EditLibrary 7140 * 7141 * @memberOf wp.media.view.Attachment 7142 * 7143 * @class 7144 * @augments wp.media.view.Attachment 7145 * @augments wp.media.View 7146 * @augments wp.Backbone.View 7147 * @augments Backbone.View 7148 */ 7149 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{ 7150 buttons: { 7151 close: true 7152 } 7153 }); 7154 7155 module.exports = EditLibrary; 7156 7157 7158 /***/ }), 7159 /* 76 */ 7160 /***/ (function(module, exports) { 7161 7162 var View = wp.media.View, 7163 $ = jQuery, 7164 Attachments; 7165 7166 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 7167 tagName: 'ul', 7168 className: 'attachments', 7169 7170 attributes: { 7171 tabIndex: -1 7172 }, 7173 7174 /** 7175 * Represents the overview of attachments in the Media Library. 7176 * 7177 * The constructor binds events to the collection this view represents when 7178 * adding or removing attachments or resetting the entire collection. 7179 * 7180 * @since 3.5.0 7181 * 7182 * @constructs 7183 * @memberof wp.media.view 7184 * 7185 * @augments wp.media.View 7186 * 7187 * @listens collection:add 7188 * @listens collection:remove 7189 * @listens collection:reset 7190 * @listens controller:library:selection:add 7191 * @listens scrollElement:scroll 7192 * @listens this:ready 7193 * @listens controller:open 7194 */ 7195 initialize: function() { 7196 this.el.id = _.uniqueId('__attachments-view-'); 7197 7198 /** 7199 * @param refreshSensitivity The time in milliseconds to throttle the scroll 7200 * handler. 7201 * @param refreshThreshold The amount of pixels that should be scrolled before 7202 * loading more attachments from the server. 7203 * @param AttachmentView The view class to be used for models in the 7204 * collection. 7205 * @param sortable A jQuery sortable options object 7206 * ( http://api.jqueryui.com/sortable/ ). 7207 * @param resize A boolean indicating whether or not to listen to 7208 * resize events. 7209 * @param idealColumnWidth The width in pixels which a column should have when 7210 * calculating the total number of columns. 7211 */ 7212 _.defaults( this.options, { 7213 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 7214 refreshThreshold: 3, 7215 AttachmentView: wp.media.view.Attachment, 7216 sortable: false, 7217 resize: true, 7218 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 7219 }); 7220 7221 this._viewsByCid = {}; 7222 this.$window = $( window ); 7223 this.resizeEvent = 'resize.media-modal-columns'; 7224 7225 this.collection.on( 'add', function( attachment ) { 7226 this.views.add( this.createAttachmentView( attachment ), { 7227 at: this.collection.indexOf( attachment ) 7228 }); 7229 }, this ); 7230 7231 /* 7232 * Find the view to be removed, delete it and call the remove function to clear 7233 * any set event handlers. 7234 */ 7235 this.collection.on( 'remove', function( attachment ) { 7236 var view = this._viewsByCid[ attachment.cid ]; 7237 delete this._viewsByCid[ attachment.cid ]; 7238 7239 if ( view ) { 7240 view.remove(); 7241 } 7242 }, this ); 7243 7244 this.collection.on( 'reset', this.render, this ); 7245 7246 this.controller.on( 'library:selection:add', this.attachmentFocus, this ); 7247 7248 // Throttle the scroll handler and bind this. 7249 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 7250 7251 this.options.scrollElement = this.options.scrollElement || this.el; 7252 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 7253 7254 this.initSortable(); 7255 7256 _.bindAll( this, 'setColumns' ); 7257 7258 if ( this.options.resize ) { 7259 this.on( 'ready', this.bindEvents ); 7260 this.controller.on( 'open', this.setColumns ); 7261 7262 /* 7263 * Call this.setColumns() after this view has been rendered in the 7264 * DOM so attachments get proper width applied. 7265 */ 7266 _.defer( this.setColumns, this ); 7267 } 7268 }, 7269 7270 /** 7271 * Listens to the resizeEvent on the window. 7272 * 7273 * Adjusts the amount of columns accordingly. First removes any existing event 7274 * handlers to prevent duplicate listeners. 7275 * 7276 * @since 4.0.0 7277 * 7278 * @listens window:resize 7279 * 7280 * @return {void} 7281 */ 7282 bindEvents: function() { 7283 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 7284 }, 7285 7286 /** 7287 * Focuses the first item in the collection. 7288 * 7289 * @since 4.0.0 7290 * 7291 * @return {void} 7292 */ 7293 attachmentFocus: function() { 7294 /* 7295 * @todo When uploading new attachments, this tries to move focus to 7296 * the attachments grid. Actually, a progress bar gets initially displayed 7297 * and then updated when uploading completes, so focus is lost. 7298 * Additionally: this view is used for both the attachments list and 7299 * the list of selected attachments in the bottom media toolbar. Thus, when 7300 * uploading attachments, it is called twice and returns two different `this`. 7301 * `this.columns` is truthy within the modal. 7302 */ 7303 if ( this.columns ) { 7304 // Move focus to the grid list within the modal. 7305 this.$el.focus(); 7306 } 7307 }, 7308 7309 /** 7310 * Restores focus to the selected item in the collection. 7311 * 7312 * Moves focus back to the first selected attachment in the grid. Used when 7313 * tabbing backwards from the attachment details sidebar. 7314 * See media.view.AttachmentsBrowser. 7315 * 7316 * @since 4.0.0 7317 * 7318 * @return {void} 7319 */ 7320 restoreFocus: function() { 7321 this.$( 'li.selected:first' ).focus(); 7322 }, 7323 7324 /** 7325 * Handles events for arrow key presses. 7326 * 7327 * Focuses the attachment in the direction of the used arrow key if it exists. 7328 * 7329 * @since 4.0.0 7330 * 7331 * @param {KeyboardEvent} event The keyboard event that triggered this function. 7332 * 7333 * @return {void} 7334 */ 7335 arrowEvent: function( event ) { 7336 var attachments = this.$el.children( 'li' ), 7337 perRow = this.columns, 7338 index = attachments.filter( ':focus' ).index(), 7339 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 7340 7341 if ( index === -1 ) { 7342 return; 7343 } 7344 7345 // Left arrow = 37. 7346 if ( 37 === event.keyCode ) { 7347 if ( 0 === index ) { 7348 return; 7349 } 7350 attachments.eq( index - 1 ).focus(); 7351 } 7352 7353 // Up arrow = 38. 7354 if ( 38 === event.keyCode ) { 7355 if ( 1 === row ) { 7356 return; 7357 } 7358 attachments.eq( index - perRow ).focus(); 7359 } 7360 7361 // Right arrow = 39. 7362 if ( 39 === event.keyCode ) { 7363 if ( attachments.length === index ) { 7364 return; 7365 } 7366 attachments.eq( index + 1 ).focus(); 7367 } 7368 7369 // Down arrow = 40. 7370 if ( 40 === event.keyCode ) { 7371 if ( Math.ceil( attachments.length / perRow ) === row ) { 7372 return; 7373 } 7374 attachments.eq( index + perRow ).focus(); 7375 } 7376 }, 7377 7378 /** 7379 * Clears any set event handlers. 7380 * 7381 * @since 3.5.0 7382 * 7383 * @return {void} 7384 */ 7385 dispose: function() { 7386 this.collection.props.off( null, null, this ); 7387 if ( this.options.resize ) { 7388 this.$window.off( this.resizeEvent ); 7389 } 7390 7391 // Call 'dispose' directly on the parent class. 7392 View.prototype.dispose.apply( this, arguments ); 7393 }, 7394 7395 /** 7396 * Calculates the amount of columns. 7397 * 7398 * Calculates the amount of columns and sets it on the data-columns attribute 7399 * of .media-frame-content. 7400 * 7401 * @since 4.0.0 7402 * 7403 * @return {void} 7404 */ 7405 setColumns: function() { 7406 var prev = this.columns, 7407 width = this.$el.width(); 7408 7409 if ( width ) { 7410 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 7411 7412 if ( ! prev || prev !== this.columns ) { 7413 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 7414 } 7415 } 7416 }, 7417 7418 /** 7419 * Initializes jQuery sortable on the attachment list. 7420 * 7421 * Fails gracefully if jQuery sortable doesn't exist or isn't passed 7422 * in the options. 7423 * 7424 * @since 3.5.0 7425 * 7426 * @fires collection:reset 7427 * 7428 * @return {void} 7429 */ 7430 initSortable: function() { 7431 var collection = this.collection; 7432 7433 if ( ! this.options.sortable || ! $.fn.sortable ) { 7434 return; 7435 } 7436 7437 this.$el.sortable( _.extend({ 7438 // If the `collection` has a `comparator`, disable sorting. 7439 disabled: !! collection.comparator, 7440 7441 /* 7442 * Change the position of the attachment as soon as the mouse pointer 7443 * overlaps a thumbnail. 7444 */ 7445 tolerance: 'pointer', 7446 7447 // Record the initial `index` of the dragged model. 7448 start: function( event, ui ) { 7449 ui.item.data('sortableIndexStart', ui.item.index()); 7450 }, 7451 7452 /* 7453 * Update the model's index in the collection. Do so silently, as the view 7454 * is already accurate. 7455 */ 7456 update: function( event, ui ) { 7457 var model = collection.at( ui.item.data('sortableIndexStart') ), 7458 comparator = collection.comparator; 7459 7460 // Temporarily disable the comparator to prevent `add` 7461 // from re-sorting. 7462 delete collection.comparator; 7463 7464 // Silently shift the model to its new index. 7465 collection.remove( model, { 7466 silent: true 7467 }); 7468 collection.add( model, { 7469 silent: true, 7470 at: ui.item.index() 7471 }); 7472 7473 // Restore the comparator. 7474 collection.comparator = comparator; 7475 7476 // Fire the `reset` event to ensure other collections sync. 7477 collection.trigger( 'reset', collection ); 7478 7479 // If the collection is sorted by menu order, update the menu order. 7480 collection.saveMenuOrder(); 7481 } 7482 }, this.options.sortable ) ); 7483 7484 /* 7485 * If the `orderby` property is changed on the `collection`, 7486 * check to see if we have a `comparator`. If so, disable sorting. 7487 */ 7488 collection.props.on( 'change:orderby', function() { 7489 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 7490 }, this ); 7491 7492 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 7493 this.refreshSortable(); 7494 }, 7495 7496 /** 7497 * Disables jQuery sortable if collection has a comparator or collection.orderby 7498 * equals menuOrder. 7499 * 7500 * @since 3.5.0 7501 * 7502 * @return {void} 7503 */ 7504 refreshSortable: function() { 7505 if ( ! this.options.sortable || ! $.fn.sortable ) { 7506 return; 7507 } 7508 7509 var collection = this.collection, 7510 orderby = collection.props.get('orderby'), 7511 enabled = 'menuOrder' === orderby || ! collection.comparator; 7512 7513 this.$el.sortable( 'option', 'disabled', ! enabled ); 7514 }, 7515 7516 /** 7517 * Creates a new view for an attachment and adds it to _viewsByCid. 7518 * 7519 * @since 3.5.0 7520 * 7521 * @param {wp.media.model.Attachment} attachment 7522 * 7523 * @return {wp.media.View} The created view. 7524 */ 7525 createAttachmentView: function( attachment ) { 7526 var view = new this.options.AttachmentView({ 7527 controller: this.controller, 7528 model: attachment, 7529 collection: this.collection, 7530 selection: this.options.selection 7531 }); 7532 7533 return this._viewsByCid[ attachment.cid ] = view; 7534 }, 7535 7536 /** 7537 * Prepares view for display. 7538 * 7539 * Creates views for every attachment in collection if the collection is not 7540 * empty, otherwise clears all views and loads more attachments. 7541 * 7542 * @since 3.5.0 7543 * 7544 * @return {void} 7545 */ 7546 prepare: function() { 7547 if ( this.collection.length ) { 7548 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 7549 } else { 7550 this.views.unset(); 7551 this.collection.more().done( this.scroll ); 7552 } 7553 }, 7554 7555 /** 7556 * Triggers the scroll function to check if we should query for additional 7557 * attachments right away. 7558 * 7559 * @since 3.5.0 7560 * 7561 * @return {void} 7562 */ 7563 ready: function() { 7564 this.scroll(); 7565 }, 7566 7567 /** 7568 * Handles scroll events. 7569 * 7570 * Shows the spinner if we're close to the bottom. Loads more attachments from 7571 * server if we're {refreshThreshold} times away from the bottom. 7572 * 7573 * @since 3.5.0 7574 * 7575 * @return {void} 7576 */ 7577 scroll: function() { 7578 var view = this, 7579 el = this.options.scrollElement, 7580 scrollTop = el.scrollTop, 7581 toolbar; 7582 7583 /* 7584 * The scroll event occurs on the document, but the element that should be 7585 * checked is the document body. 7586 */ 7587 if ( el === document ) { 7588 el = document.body; 7589 scrollTop = $(document).scrollTop(); 7590 } 7591 7592 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 7593 return; 7594 } 7595 7596 toolbar = this.views.parent.toolbar; 7597 7598 // Show the spinner only if we are close to the bottom. 7599 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 7600 toolbar.get('spinner').show(); 7601 } 7602 7603 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 7604 this.collection.more().done(function() { 7605 view.scroll(); 7606 toolbar.get('spinner').hide(); 7607 }); 7608 } 7609 } 7610 }); 7611 7612 module.exports = Attachments; 7613 7614 7615 /***/ }), 7616 /* 77 */ 7617 /***/ (function(module, exports) { 7618 7619 var Search; 7620 7621 /** 7622 * wp.media.view.Search 7623 * 7624 * @memberOf wp.media.view 7625 * 7626 * @class 7627 * @augments wp.media.View 7628 * @augments wp.Backbone.View 7629 * @augments Backbone.View 7630 */ 7631 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{ 7632 tagName: 'input', 7633 className: 'search', 7634 id: 'media-search-input', 7635 7636 attributes: { 7637 type: 'search' 7638 }, 7639 7640 events: { 7641 'input': 'search' 7642 }, 7643 7644 /** 7645 * @return {wp.media.view.Search} Returns itself to allow chaining. 7646 */ 7647 render: function() { 7648 this.el.value = this.model.escape('search'); 7649 return this; 7650 }, 7651 7652 search: _.debounce( function( event ) { 7653 var searchTerm = event.target.value.trim(); 7654 7655 // Trigger the search only after 2 ASCII characters. 7656 if ( searchTerm && searchTerm.length > 1 ) { 7657 this.model.set( 'search', searchTerm ); 7658 } else { 7659 this.model.unset( 'search' ); 7660 } 7661 }, 500 ) 7662 }); 7663 7664 module.exports = Search; 7665 7666 7667 /***/ }), 7668 /* 78 */ 7669 /***/ (function(module, exports) { 7670 7671 var $ = jQuery, 7672 AttachmentFilters; 7673 7674 /** 7675 * wp.media.view.AttachmentFilters 7676 * 7677 * @memberOf wp.media.view 7678 * 7679 * @class 7680 * @augments wp.media.View 7681 * @augments wp.Backbone.View 7682 * @augments Backbone.View 7683 */ 7684 AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{ 7685 tagName: 'select', 7686 className: 'attachment-filters', 7687 id: 'media-attachment-filters', 7688 7689 events: { 7690 change: 'change' 7691 }, 7692 7693 keys: [], 7694 7695 initialize: function() { 7696 this.createFilters(); 7697 _.extend( this.filters, this.options.filters ); 7698 7699 // Build `<option>` elements. 7700 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 7701 return { 7702 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 7703 priority: filter.priority || 50 7704 }; 7705 }, this ).sortBy('priority').pluck('el').value() ); 7706 7707 this.listenTo( this.model, 'change', this.select ); 7708 this.select(); 7709 }, 7710 7711 /** 7712 * @abstract 7713 */ 7714 createFilters: function() { 7715 this.filters = {}; 7716 }, 7717 7718 /** 7719 * When the selected filter changes, update the Attachment Query properties to match. 7720 */ 7721 change: function() { 7722 var filter = this.filters[ this.el.value ]; 7723 if ( filter ) { 7724 this.model.set( filter.props ); 7725 } 7726 }, 7727 7728 select: function() { 7729 var model = this.model, 7730 value = 'all', 7731 props = model.toJSON(); 7732 7733 _.find( this.filters, function( filter, id ) { 7734 var equal = _.all( filter.props, function( prop, key ) { 7735 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); 7736 }); 7737 7738 if ( equal ) { 7739 return value = id; 7740 } 7741 }); 7742 7743 this.$el.val( value ); 7744 } 7745 }); 7746 7747 module.exports = AttachmentFilters; 7748 7749 7750 /***/ }), 7751 /* 79 */ 7752 /***/ (function(module, exports) { 7753 7754 var l10n = wp.media.view.l10n, 7755 DateFilter; 7756 7757 /** 7758 * A filter dropdown for month/dates. 7759 * 7760 * @memberOf wp.media.view.AttachmentFilters 7761 * 7762 * @class 7763 * @augments wp.media.view.AttachmentFilters 7764 * @augments wp.media.View 7765 * @augments wp.Backbone.View 7766 * @augments Backbone.View 7767 */ 7768 DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{ 7769 id: 'media-attachment-date-filters', 7770 7771 createFilters: function() { 7772 var filters = {}; 7773 _.each( wp.media.view.settings.months || {}, function( value, index ) { 7774 filters[ index ] = { 7775 text: value.text, 7776 props: { 7777 year: value.year, 7778 monthnum: value.month 7779 } 7780 }; 7781 }); 7782 filters.all = { 7783 text: l10n.allDates, 7784 props: { 7785 monthnum: false, 7786 year: false 7787 }, 7788 priority: 10 7789 }; 7790 this.filters = filters; 7791 } 7792 }); 7793 7794 module.exports = DateFilter; 7795 7796 7797 /***/ }), 7798 /* 80 */ 7799 /***/ (function(module, exports) { 7800 7801 var l10n = wp.media.view.l10n, 7802 Uploaded; 7803 7804 /** 7805 * wp.media.view.AttachmentFilters.Uploaded 7806 * 7807 * @memberOf wp.media.view.AttachmentFilters 7808 * 7809 * @class 7810 * @augments wp.media.view.AttachmentFilters 7811 * @augments wp.media.View 7812 * @augments wp.Backbone.View 7813 * @augments Backbone.View 7814 */ 7815 Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{ 7816 createFilters: function() { 7817 var type = this.model.get('type'), 7818 types = wp.media.view.settings.mimeTypes, 7819 uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0, 7820 text; 7821 7822 if ( types && type ) { 7823 text = types[ type ]; 7824 } 7825 7826 this.filters = { 7827 all: { 7828 text: text || l10n.allMediaItems, 7829 props: { 7830 uploadedTo: null, 7831 orderby: 'date', 7832 order: 'DESC', 7833 author: null 7834 }, 7835 priority: 10 7836 }, 7837 7838 uploaded: { 7839 text: l10n.uploadedToThisPost, 7840 props: { 7841 uploadedTo: wp.media.view.settings.post.id, 7842 orderby: 'menuOrder', 7843 order: 'ASC', 7844 author: null 7845 }, 7846 priority: 20 7847 }, 7848 7849 unattached: { 7850 text: l10n.unattached, 7851 props: { 7852 uploadedTo: 0, 7853 orderby: 'menuOrder', 7854 order: 'ASC', 7855 author: null 7856 }, 7857 priority: 50 7858 } 7859 }; 7860 7861 if ( uid ) { 7862 this.filters.mine = { 7863 text: l10n.mine, 7864 props: { 7865 orderby: 'date', 7866 order: 'DESC', 7867 author: uid 7868 }, 7869 priority: 50 7870 }; 7871 } 7872 } 7873 }); 7874 7875 module.exports = Uploaded; 7876 7877 7878 /***/ }), 7879 /* 81 */ 7880 /***/ (function(module, exports) { 7881 7882 var l10n = wp.media.view.l10n, 7883 All; 7884 7885 /** 7886 * wp.media.view.AttachmentFilters.All 7887 * 7888 * @memberOf wp.media.view.AttachmentFilters 7889 * 7890 * @class 7891 * @augments wp.media.view.AttachmentFilters 7892 * @augments wp.media.View 7893 * @augments wp.Backbone.View 7894 * @augments Backbone.View 7895 */ 7896 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{ 7897 createFilters: function() { 7898 var filters = {}, 7899 uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0; 7900 7901 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 7902 filters[ key ] = { 7903 text: text, 7904 props: { 7905 status: null, 7906 type: key, 7907 uploadedTo: null, 7908 orderby: 'date', 7909 order: 'DESC', 7910 author: null 7911 } 7912 }; 7913 }); 7914 7915 filters.all = { 7916 text: l10n.allMediaItems, 7917 props: { 7918 status: null, 7919 type: null, 7920 uploadedTo: null, 7921 orderby: 'date', 7922 order: 'DESC', 7923 author: null 7924 }, 7925 priority: 10 7926 }; 7927 7928 if ( wp.media.view.settings.post.id ) { 7929 filters.uploaded = { 7930 text: l10n.uploadedToThisPost, 7931 props: { 7932 status: null, 7933 type: null, 7934 uploadedTo: wp.media.view.settings.post.id, 7935 orderby: 'menuOrder', 7936 order: 'ASC', 7937 author: null 7938 }, 7939 priority: 20 7940 }; 7941 } 7942 7943 filters.unattached = { 7944 text: l10n.unattached, 7945 props: { 7946 status: null, 7947 uploadedTo: 0, 7948 type: null, 7949 orderby: 'menuOrder', 7950 order: 'ASC', 7951 author: null 7952 }, 7953 priority: 50 7954 }; 7955 7956 if ( uid ) { 7957 filters.mine = { 7958 text: l10n.mine, 7959 props: { 7960 status: null, 7961 type: null, 7962 uploadedTo: null, 7963 orderby: 'date', 7964 order: 'DESC', 7965 author: uid 7966 }, 7967 priority: 50 7968 }; 7969 } 7970 7971 if ( wp.media.view.settings.mediaTrash && 7972 this.controller.isModeActive( 'grid' ) ) { 7973 7974 filters.trash = { 7975 text: l10n.trash, 7976 props: { 7977 uploadedTo: null, 7978 status: 'trash', 7979 type: null, 7980 orderby: 'date', 7981 order: 'DESC', 7982 author: null 7983 }, 7984 priority: 50 7985 }; 7986 } 7987 7988 this.filters = filters; 7989 } 7990 }); 7991 7992 module.exports = All; 7993 7994 7995 /***/ }), 7996 /* 82 */ 7997 /***/ (function(module, exports) { 7998 7999 var View = wp.media.View, 8000 mediaTrash = wp.media.view.settings.mediaTrash, 8001 l10n = wp.media.view.l10n, 8002 $ = jQuery, 8003 AttachmentsBrowser; 8004 8005 /** 8006 * wp.media.view.AttachmentsBrowser 8007 * 8008 * @memberOf wp.media.view 8009 * 8010 * @class 8011 * @augments wp.media.View 8012 * @augments wp.Backbone.View 8013 * @augments Backbone.View 8014 * 8015 * @param {object} [options] The options hash passed to the view. 8016 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar. 8017 * Accepts 'uploaded' and 'all'. 8018 * @param {boolean} [options.search=true] Whether to show the search interface in the 8019 * browser's toolbar. 8020 * @param {boolean} [options.date=true] Whether to show the date filter in the 8021 * browser's toolbar. 8022 * @param {boolean} [options.display=false] Whether to show the attachments display settings 8023 * view in the sidebar. 8024 * @param {boolean|string} [options.sidebar=true] Whether to create a sidebar for the browser. 8025 * Accepts true, false, and 'errors'. 8026 */ 8027 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{ 8028 tagName: 'div', 8029 className: 'attachments-browser', 8030 8031 initialize: function() { 8032 _.defaults( this.options, { 8033 filters: false, 8034 search: true, 8035 date: true, 8036 display: false, 8037 sidebar: true, 8038 AttachmentView: wp.media.view.Attachment.Library 8039 }); 8040 8041 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this ); 8042 this.controller.on( 'edit:selection', this.editSelection ); 8043 8044 // In the Media Library, the sidebar is used to display errors before the attachments grid. 8045 if ( this.options.sidebar && 'errors' === this.options.sidebar ) { 8046 this.createSidebar(); 8047 } 8048 8049 /* 8050 * In the grid mode (the Media Library), place the Inline Uploader before 8051 * other sections so that the visual order and the DOM order match. This way, 8052 * the Inline Uploader in the Media Library is right after the "Add New" 8053 * button, see ticket #37188. 8054 */ 8055 if ( this.controller.isModeActive( 'grid' ) ) { 8056 this.createUploader(); 8057 8058 /* 8059 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library 8060 * and also for other things, for example the "Drag and drop to reorder" and 8061 * "Suggested dimensions" info in the media modal. 8062 */ 8063 this.createToolbar(); 8064 } else { 8065 this.createToolbar(); 8066 this.createUploader(); 8067 } 8068 8069 8070 // Add a heading before the attachments list. 8071 this.createAttachmentsHeading(); 8072 8073 // Create the list of attachments. 8074 this.createAttachments(); 8075 8076 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. 8077 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { 8078 this.createSidebar(); 8079 } 8080 8081 this.updateContent(); 8082 8083 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 8084 this.$el.addClass( 'hide-sidebar' ); 8085 8086 if ( 'errors' === this.options.sidebar ) { 8087 this.$el.addClass( 'sidebar-for-errors' ); 8088 } 8089 } 8090 8091 this.collection.on( 'add remove reset', this.updateContent, this ); 8092 8093 // The non-cached or cached attachments query has completed. 8094 this.collection.on( 'attachments:received', this.announceSearchResults, this ); 8095 }, 8096 8097 /** 8098 * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate 8099 * the number of search results to screen reader users. This function is 8100 * debounced because the collection updates multiple times. 8101 * 8102 * @since 5.3.0 8103 * 8104 * @return {void} 8105 */ 8106 announceSearchResults: _.debounce( function() { 8107 var count; 8108 8109 if ( this.collection.mirroring.args.s ) { 8110 count = this.collection.length; 8111 8112 if ( 0 === count ) { 8113 wp.a11y.speak( l10n.noMediaTryNewSearch ); 8114 return; 8115 } 8116 8117 if ( this.collection.hasMore() ) { 8118 wp.a11y.speak( l10n.mediaFoundHasMoreResults.replace( '%d', count ) ); 8119 return; 8120 } 8121 8122 wp.a11y.speak( l10n.mediaFound.replace( '%d', count ) ); 8123 } 8124 }, 200 ), 8125 8126 editSelection: function( modal ) { 8127 // When editing a selection, move focus to the "Go to library" button. 8128 modal.$( '.media-button-backToLibrary' ).focus(); 8129 }, 8130 8131 /** 8132 * @return {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining. 8133 */ 8134 dispose: function() { 8135 this.options.selection.off( null, null, this ); 8136 View.prototype.dispose.apply( this, arguments ); 8137 return this; 8138 }, 8139 8140 createToolbar: function() { 8141 var LibraryViewSwitcher, Filters, toolbarOptions, 8142 showFilterByType = -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ); 8143 8144 toolbarOptions = { 8145 controller: this.controller 8146 }; 8147 8148 if ( this.controller.isModeActive( 'grid' ) ) { 8149 toolbarOptions.className = 'media-toolbar wp-filter'; 8150 } 8151 8152 /** 8153 * @member {wp.media.view.Toolbar} 8154 */ 8155 this.toolbar = new wp.media.view.Toolbar( toolbarOptions ); 8156 8157 this.views.add( this.toolbar ); 8158 8159 this.toolbar.set( 'spinner', new wp.media.view.Spinner({ 8160 priority: -20 8161 }) ); 8162 8163 if ( showFilterByType || this.options.date ) { 8164 /* 8165 * Create a h2 heading before the select elements that filter attachments. 8166 * This heading is visible in the modal and visually hidden in the grid. 8167 */ 8168 this.toolbar.set( 'filters-heading', new wp.media.view.Heading( { 8169 priority: -100, 8170 text: l10n.filterAttachments, 8171 level: 'h2', 8172 className: 'media-attachments-filter-heading' 8173 }).render() ); 8174 } 8175 8176 if ( showFilterByType ) { 8177 // "Filters" is a <select>, a visually hidden label element needs to be rendered before. 8178 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ 8179 value: l10n.filterByType, 8180 attributes: { 8181 'for': 'media-attachment-filters' 8182 }, 8183 priority: -80 8184 }).render() ); 8185 8186 if ( 'uploaded' === this.options.filters ) { 8187 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({ 8188 controller: this.controller, 8189 model: this.collection.props, 8190 priority: -80 8191 }).render() ); 8192 } else { 8193 Filters = new wp.media.view.AttachmentFilters.All({ 8194 controller: this.controller, 8195 model: this.collection.props, 8196 priority: -80 8197 }); 8198 8199 this.toolbar.set( 'filters', Filters.render() ); 8200 } 8201 } 8202 8203 /* 8204 * Feels odd to bring the global media library switcher into the Attachment browser view. 8205 * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 8206 * which the controller can tap into and add this view? 8207 */ 8208 if ( this.controller.isModeActive( 'grid' ) ) { 8209 LibraryViewSwitcher = View.extend({ 8210 className: 'view-switch media-grid-view-switch', 8211 template: wp.template( 'media-library-view-switcher') 8212 }); 8213 8214 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 8215 controller: this.controller, 8216 priority: -90 8217 }).render() ); 8218 8219 // DateFilter is a <select>, a visually hidden label element needs to be rendered before. 8220 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 8221 value: l10n.filterByDate, 8222 attributes: { 8223 'for': 'media-attachment-date-filters' 8224 }, 8225 priority: -75 8226 }).render() ); 8227 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 8228 controller: this.controller, 8229 model: this.collection.props, 8230 priority: -75 8231 }).render() ); 8232 8233 // BulkSelection is a <div> with subviews, including screen reader text. 8234 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 8235 text: l10n.bulkSelect, 8236 controller: this.controller, 8237 priority: -70 8238 }).render() ); 8239 8240 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 8241 filters: Filters, 8242 style: 'primary', 8243 disabled: true, 8244 text: mediaTrash ? l10n.trashSelected : l10n.deletePermanently, 8245 controller: this.controller, 8246 priority: -80, 8247 click: function() { 8248 var changed = [], removed = [], 8249 selection = this.controller.state().get( 'selection' ), 8250 library = this.controller.state().get( 'library' ); 8251 8252 if ( ! selection.length ) { 8253 return; 8254 } 8255 8256 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 8257 return; 8258 } 8259 8260 if ( mediaTrash && 8261 'trash' !== selection.at( 0 ).get( 'status' ) && 8262 ! window.confirm( l10n.warnBulkTrash ) ) { 8263 8264 return; 8265 } 8266 8267 selection.each( function( model ) { 8268 if ( ! model.get( 'nonces' )['delete'] ) { 8269 removed.push( model ); 8270 return; 8271 } 8272 8273 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 8274 model.set( 'status', 'inherit' ); 8275 changed.push( model.save() ); 8276 removed.push( model ); 8277 } else if ( mediaTrash ) { 8278 model.set( 'status', 'trash' ); 8279 changed.push( model.save() ); 8280 removed.push( model ); 8281 } else { 8282 model.destroy({wait: true}); 8283 } 8284 } ); 8285 8286 if ( changed.length ) { 8287 selection.remove( removed ); 8288 8289 $.when.apply( null, changed ).then( _.bind( function() { 8290 library._requery( true ); 8291 this.controller.trigger( 'selection:action:done' ); 8292 }, this ) ); 8293 } else { 8294 this.controller.trigger( 'selection:action:done' ); 8295 } 8296 } 8297 }).render() ); 8298 8299 if ( mediaTrash ) { 8300 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 8301 filters: Filters, 8302 style: 'link button-link-delete', 8303 disabled: true, 8304 text: l10n.deletePermanently, 8305 controller: this.controller, 8306 priority: -55, 8307 click: function() { 8308 var removed = [], 8309 destroy = [], 8310 selection = this.controller.state().get( 'selection' ); 8311 8312 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 8313 return; 8314 } 8315 8316 selection.each( function( model ) { 8317 if ( ! model.get( 'nonces' )['delete'] ) { 8318 removed.push( model ); 8319 return; 8320 } 8321 8322 destroy.push( model ); 8323 } ); 8324 8325 if ( removed.length ) { 8326 selection.remove( removed ); 8327 } 8328 8329 if ( destroy.length ) { 8330 $.when.apply( null, destroy.map( function (item) { 8331 return item.destroy(); 8332 } ) ).then( _.bind( function() { 8333 this.controller.trigger( 'selection:action:done' ); 8334 }, this ) ); 8335 } 8336 } 8337 }).render() ); 8338 } 8339 8340 } else if ( this.options.date ) { 8341 // DateFilter is a <select>, a visually hidden label element needs to be rendered before. 8342 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 8343 value: l10n.filterByDate, 8344 attributes: { 8345 'for': 'media-attachment-date-filters' 8346 }, 8347 priority: -75 8348 }).render() ); 8349 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 8350 controller: this.controller, 8351 model: this.collection.props, 8352 priority: -75 8353 }).render() ); 8354 } 8355 8356 if ( this.options.search ) { 8357 // Search is an input, a visually hidden label element needs to be rendered before. 8358 this.toolbar.set( 'searchLabel', new wp.media.view.Label({ 8359 value: l10n.searchLabel, 8360 className: 'media-search-input-label', 8361 attributes: { 8362 'for': 'media-search-input' 8363 }, 8364 priority: 60 8365 }).render() ); 8366 this.toolbar.set( 'search', new wp.media.view.Search({ 8367 controller: this.controller, 8368 model: this.collection.props, 8369 priority: 60 8370 }).render() ); 8371 } 8372 8373 if ( this.options.dragInfo ) { 8374 this.toolbar.set( 'dragInfo', new View({ 8375 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0], 8376 priority: -40 8377 }) ); 8378 } 8379 8380 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 8381 this.toolbar.set( 'suggestedDimensions', new View({ 8382 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0], 8383 priority: -40 8384 }) ); 8385 } 8386 }, 8387 8388 updateContent: function() { 8389 var view = this, 8390 noItemsView; 8391 8392 if ( this.controller.isModeActive( 'grid' ) ) { 8393 noItemsView = view.attachmentsNoResults; 8394 } else { 8395 noItemsView = view.uploader; 8396 } 8397 8398 if ( ! this.collection.length ) { 8399 this.toolbar.get( 'spinner' ).show(); 8400 this.dfd = this.collection.more().done( function() { 8401 if ( ! view.collection.length ) { 8402 noItemsView.$el.removeClass( 'hidden' ); 8403 } else { 8404 noItemsView.$el.addClass( 'hidden' ); 8405 } 8406 view.toolbar.get( 'spinner' ).hide(); 8407 } ); 8408 } else { 8409 noItemsView.$el.addClass( 'hidden' ); 8410 view.toolbar.get( 'spinner' ).hide(); 8411 } 8412 }, 8413 8414 createUploader: function() { 8415 this.uploader = new wp.media.view.UploaderInline({ 8416 controller: this.controller, 8417 status: false, 8418 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 8419 canClose: this.controller.isModeActive( 'grid' ) 8420 }); 8421 8422 this.uploader.$el.addClass( 'hidden' ); 8423 this.views.add( this.uploader ); 8424 }, 8425 8426 toggleUploader: function() { 8427 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 8428 this.uploader.show(); 8429 } else { 8430 this.uploader.hide(); 8431 } 8432 }, 8433 8434 createAttachments: function() { 8435 this.attachments = new wp.media.view.Attachments({ 8436 controller: this.controller, 8437 collection: this.collection, 8438 selection: this.options.selection, 8439 model: this.model, 8440 sortable: this.options.sortable, 8441 scrollElement: this.options.scrollElement, 8442 idealColumnWidth: this.options.idealColumnWidth, 8443 8444 // The single `Attachment` view to be used in the `Attachments` view. 8445 AttachmentView: this.options.AttachmentView 8446 }); 8447 8448 // Add keydown listener to the instance of the Attachments view. 8449 this.controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) ); 8450 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) ); 8451 8452 this.views.add( this.attachments ); 8453 8454 8455 if ( this.controller.isModeActive( 'grid' ) ) { 8456 this.attachmentsNoResults = new View({ 8457 controller: this.controller, 8458 tagName: 'p' 8459 }); 8460 8461 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 8462 this.attachmentsNoResults.$el.html( l10n.noMedia ); 8463 8464 this.views.add( this.attachmentsNoResults ); 8465 } 8466 }, 8467 8468 createAttachmentsHeading: function() { 8469 this.attachmentsHeading = new wp.media.view.Heading( { 8470 text: l10n.attachmentsList, 8471 level: 'h2', 8472 className: 'media-views-heading screen-reader-text' 8473 } ); 8474 this.views.add( this.attachmentsHeading ); 8475 }, 8476 8477 createSidebar: function() { 8478 var options = this.options, 8479 selection = options.selection, 8480 sidebar = this.sidebar = new wp.media.view.Sidebar({ 8481 controller: this.controller 8482 }); 8483 8484 this.views.add( sidebar ); 8485 8486 if ( this.controller.uploader ) { 8487 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({ 8488 controller: this.controller, 8489 priority: 40 8490 }) ); 8491 } 8492 8493 selection.on( 'selection:single', this.createSingle, this ); 8494 selection.on( 'selection:unsingle', this.disposeSingle, this ); 8495 8496 if ( selection.single() ) { 8497 this.createSingle(); 8498 } 8499 }, 8500 8501 createSingle: function() { 8502 var sidebar = this.sidebar, 8503 single = this.options.selection.single(); 8504 8505 sidebar.set( 'details', new wp.media.view.Attachment.Details({ 8506 controller: this.controller, 8507 model: single, 8508 priority: 80 8509 }) ); 8510 8511 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({ 8512 controller: this.controller, 8513 model: single, 8514 priority: 120 8515 }) ); 8516 8517 if ( this.options.display ) { 8518 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({ 8519 controller: this.controller, 8520 model: this.model.display( single ), 8521 attachment: single, 8522 priority: 160, 8523 userSettings: this.model.get('displayUserSettings') 8524 }) ); 8525 } 8526 8527 // Show the sidebar on mobile. 8528 if ( this.model.id === 'insert' ) { 8529 sidebar.$el.addClass( 'visible' ); 8530 } 8531 }, 8532 8533 disposeSingle: function() { 8534 var sidebar = this.sidebar; 8535 sidebar.unset('details'); 8536 sidebar.unset('compat'); 8537 sidebar.unset('display'); 8538 // Hide the sidebar on mobile. 8539 sidebar.$el.removeClass( 'visible' ); 8540 } 8541 }); 8542 8543 module.exports = AttachmentsBrowser; 8544 8545 8546 /***/ }), 8547 /* 83 */ 8548 /***/ (function(module, exports) { 8549 8550 var _n = wp.i18n._n, 8551 sprintf = wp.i18n.sprintf, 8552 Selection; 8553 8554 /** 8555 * wp.media.view.Selection 8556 * 8557 * @memberOf wp.media.view 8558 * 8559 * @class 8560 * @augments wp.media.View 8561 * @augments wp.Backbone.View 8562 * @augments Backbone.View 8563 */ 8564 Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{ 8565 tagName: 'div', 8566 className: 'media-selection', 8567 template: wp.template('media-selection'), 8568 8569 events: { 8570 'click .edit-selection': 'edit', 8571 'click .clear-selection': 'clear' 8572 }, 8573 8574 initialize: function() { 8575 _.defaults( this.options, { 8576 editable: false, 8577 clearable: true 8578 }); 8579 8580 /** 8581 * @member {wp.media.view.Attachments.Selection} 8582 */ 8583 this.attachments = new wp.media.view.Attachments.Selection({ 8584 controller: this.controller, 8585 collection: this.collection, 8586 selection: this.collection, 8587 model: new Backbone.Model() 8588 }); 8589 8590 this.views.set( '.selection-view', this.attachments ); 8591 this.collection.on( 'add remove reset', this.refresh, this ); 8592 this.controller.on( 'content:activate', this.refresh, this ); 8593 }, 8594 8595 ready: function() { 8596 this.refresh(); 8597 }, 8598 8599 refresh: function() { 8600 // If the selection hasn't been rendered, bail. 8601 if ( ! this.$el.children().length ) { 8602 return; 8603 } 8604 8605 var collection = this.collection, 8606 editing = 'edit-selection' === this.controller.content.mode(); 8607 8608 // If nothing is selected, display nothing. 8609 this.$el.toggleClass( 'empty', ! collection.length ); 8610 this.$el.toggleClass( 'one', 1 === collection.length ); 8611 this.$el.toggleClass( 'editing', editing ); 8612 8613 this.$( '.count' ).text( 8614 /* translators: %s: Number of selected media attachments. */ 8615 sprintf( _n( '%s item selected', '%s items selected', collection.length ), collection.length ) 8616 ); 8617 }, 8618 8619 edit: function( event ) { 8620 event.preventDefault(); 8621 if ( this.options.editable ) { 8622 this.options.editable.call( this, this.collection ); 8623 } 8624 }, 8625 8626 clear: function( event ) { 8627 event.preventDefault(); 8628 this.collection.reset(); 8629 8630 // Move focus to the modal. 8631 this.controller.modal.focusManager.focus(); 8632 } 8633 }); 8634 8635 module.exports = Selection; 8636 8637 8638 /***/ }), 8639 /* 84 */ 8640 /***/ (function(module, exports) { 8641 8642 /** 8643 * wp.media.view.Attachment.Selection 8644 * 8645 * @memberOf wp.media.view.Attachment 8646 * 8647 * @class 8648 * @augments wp.media.view.Attachment 8649 * @augments wp.media.View 8650 * @augments wp.Backbone.View 8651 * @augments Backbone.View 8652 */ 8653 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{ 8654 className: 'attachment selection', 8655 8656 // On click, just select the model, instead of removing the model from 8657 // the selection. 8658 toggleSelection: function() { 8659 this.options.selection.single( this.model ); 8660 } 8661 }); 8662 8663 module.exports = Selection; 8664 8665 8666 /***/ }), 8667 /* 85 */ 8668 /***/ (function(module, exports) { 8669 8670 var Attachments = wp.media.view.Attachments, 8671 Selection; 8672 8673 /** 8674 * wp.media.view.Attachments.Selection 8675 * 8676 * @memberOf wp.media.view.Attachments 8677 * 8678 * @class 8679 * @augments wp.media.view.Attachments 8680 * @augments wp.media.View 8681 * @augments wp.Backbone.View 8682 * @augments Backbone.View 8683 */ 8684 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{ 8685 events: {}, 8686 initialize: function() { 8687 _.defaults( this.options, { 8688 sortable: false, 8689 resize: false, 8690 8691 // The single `Attachment` view to be used in the `Attachments` view. 8692 AttachmentView: wp.media.view.Attachment.Selection 8693 }); 8694 // Call 'initialize' directly on the parent class. 8695 return Attachments.prototype.initialize.apply( this, arguments ); 8696 } 8697 }); 8698 8699 module.exports = Selection; 8700 8701 8702 /***/ }), 8703 /* 86 */ 8704 /***/ (function(module, exports) { 8705 8706 /** 8707 * wp.media.view.Attachment.EditSelection 8708 * 8709 * @memberOf wp.media.view.Attachment 8710 * 8711 * @class 8712 * @augments wp.media.view.Attachment.Selection 8713 * @augments wp.media.view.Attachment 8714 * @augments wp.media.View 8715 * @augments wp.Backbone.View