Changeset 31494
- Timestamp:
- 02/22/2015 08:47:01 AM (10 years ago)
- Location:
- trunk/src/wp-includes/js/media
- Files:
-
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/js/media/controllers/edit-attachment-metadata.js
r31492 r31494 10 10 * @augments Backbone.Model 11 11 */ 12 var State = require( './state.js' ),12 var State = wp.media.controller.State, 13 13 l10n = wp.media.view.l10n, 14 14 EditAttachmentMetadata; -
trunk/src/wp-includes/js/media/grid.js
r31492 r31494 11 11 * @augments Backbone.Model 12 12 */ 13 var State = require( './state.js' ),13 var State = wp.media.controller.State, 14 14 l10n = wp.media.view.l10n, 15 15 EditAttachmentMetadata; … … 30 30 module.exports = EditAttachmentMetadata; 31 31 32 },{"./state.js":6}],2:[function(require,module,exports){ 33 /*globals wp */ 34 35 /** 36 * wp.media.controller.EditImage 37 * 38 * A state for editing (cropping, etc.) an image. 39 * 40 * @class 41 * @augments wp.media.controller.State 42 * @augments Backbone.Model 43 * 44 * @param {object} attributes The attributes hash passed to the state. 45 * @param {wp.media.model.Attachment} attributes.model The attachment. 46 * @param {string} [attributes.id=edit-image] Unique identifier. 47 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 48 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 49 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 50 * @param {string} [attributes.menu=false] Initial mode for the menu region. 51 * @param {string} [attributes.url] Unused. @todo Consider removal. 52 */ 53 var State = require( './state.js' ), 54 ToolbarView = require( '../views/toolbar.js' ), 55 l10n = wp.media.view.l10n, 56 EditImage; 57 58 EditImage = State.extend({ 59 defaults: { 60 id: 'edit-image', 61 title: l10n.editImage, 62 menu: false, 63 toolbar: 'edit-image', 64 content: 'edit-image', 65 url: '' 66 }, 67 68 /** 69 * @since 3.9.0 70 */ 71 activate: function() { 72 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 73 }, 74 75 /** 76 * @since 3.9.0 77 */ 78 deactivate: function() { 79 this.stopListening( this.frame ); 80 }, 81 82 /** 83 * @since 3.9.0 84 */ 85 toolbar: function() { 86 var frame = this.frame, 87 lastState = frame.lastState(), 88 previous = lastState && lastState.id; 89 90 frame.toolbar.set( new ToolbarView({ 91 controller: frame, 92 items: { 93 back: { 94 style: 'primary', 95 text: l10n.back, 96 priority: 20, 97 click: function() { 98 if ( previous ) { 99 frame.setState( previous ); 100 } else { 101 frame.close(); 102 } 103 } 104 } 105 } 106 }) ); 107 } 108 }); 109 110 module.exports = EditImage; 111 112 },{"../views/toolbar.js":44,"./state.js":6}],3:[function(require,module,exports){ 113 /*globals wp, _, Backbone */ 114 115 /** 116 * wp.media.controller.Library 117 * 118 * A state for choosing an attachment or group of attachments from the media library. 119 * 120 * @class 121 * @augments wp.media.controller.State 122 * @augments Backbone.Model 123 * @mixes media.selectionSync 124 * 125 * @param {object} [attributes] The attributes hash passed to the state. 126 * @param {string} [attributes.id=library] Unique identifier. 127 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 128 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 129 * If one is not supplied, a collection of all attachments will be created. 130 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 131 * If the 'selection' attribute is a plain JS object, 132 * a Selection will be created using its values as the selection instance's `props` model. 133 * Otherwise, it will copy the library's `props` model. 134 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 135 * @param {string} [attributes.content=upload] Initial mode for the content region. 136 * Overridden by persistent user setting if 'contentUserSetting' is true. 137 * @param {string} [attributes.menu=default] Initial mode for the menu region. 138 * @param {string} [attributes.router=browse] Initial mode for the router region. 139 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 140 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 141 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 142 * Accepts 'all', 'uploaded', or 'unattached'. 143 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 144 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 145 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 146 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 147 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 148 */ 149 var selectionSync = require( '../utils/selection-sync.js' ), 150 State = require( './state.js' ), 151 l10n = wp.media.view.l10n, 152 getUserSetting = window.getUserSetting, 153 setUserSetting = window.setUserSetting, 154 Library; 155 156 Library = State.extend({ 157 defaults: { 158 id: 'library', 159 title: l10n.mediaLibraryTitle, 160 multiple: false, 161 content: 'upload', 162 menu: 'default', 163 router: 'browse', 164 toolbar: 'select', 165 searchable: true, 166 filterable: false, 167 sortable: true, 168 autoSelect: true, 169 describe: false, 170 contentUserSetting: true, 171 syncSelection: true 172 }, 173 174 /** 175 * If a library isn't provided, query all media items. 176 * If a selection instance isn't provided, create one. 177 * 178 * @since 3.5.0 179 */ 180 initialize: function() { 181 var selection = this.get('selection'), 182 props; 183 184 if ( ! this.get('library') ) { 185 this.set( 'library', wp.media.query() ); 186 } 187 188 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 189 props = selection; 190 191 if ( ! props ) { 192 props = this.get('library').props.toJSON(); 193 props = _.omit( props, 'orderby', 'query' ); 194 } 195 196 this.set( 'selection', new wp.media.model.Selection( null, { 197 multiple: this.get('multiple'), 198 props: props 199 }) ); 200 } 201 202 this.resetDisplays(); 203 }, 204 205 /** 206 * @since 3.5.0 207 */ 208 activate: function() { 209 this.syncSelection(); 210 211 wp.Uploader.queue.on( 'add', this.uploading, this ); 212 213 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 214 215 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 216 this.frame.on( 'content:activate', this.saveContentMode, this ); 217 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 218 } 219 }, 220 221 /** 222 * @since 3.5.0 223 */ 224 deactivate: function() { 225 this.recordSelection(); 226 227 this.frame.off( 'content:activate', this.saveContentMode, this ); 228 229 // Unbind all event handlers that use this state as the context 230 // from the selection. 231 this.get('selection').off( null, null, this ); 232 233 wp.Uploader.queue.off( null, null, this ); 234 }, 235 236 /** 237 * Reset the library to its initial state. 238 * 239 * @since 3.5.0 240 */ 241 reset: function() { 242 this.get('selection').reset(); 243 this.resetDisplays(); 244 this.refreshContent(); 245 }, 246 247 /** 248 * Reset the attachment display settings defaults to the site options. 249 * 250 * If site options don't define them, fall back to a persistent user setting. 251 * 252 * @since 3.5.0 253 */ 254 resetDisplays: function() { 255 var defaultProps = wp.media.view.settings.defaultProps; 256 this._displays = []; 257 this._defaultDisplaySettings = { 258 align: defaultProps.align || getUserSetting( 'align', 'none' ), 259 size: defaultProps.size || getUserSetting( 'imgsize', 'medium' ), 260 link: defaultProps.link || getUserSetting( 'urlbutton', 'file' ) 261 }; 262 }, 263 264 /** 265 * Create a model to represent display settings (alignment, etc.) for an attachment. 266 * 267 * @since 3.5.0 268 * 269 * @param {wp.media.model.Attachment} attachment 270 * @returns {Backbone.Model} 271 */ 272 display: function( attachment ) { 273 var displays = this._displays; 274 275 if ( ! displays[ attachment.cid ] ) { 276 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 277 } 278 return displays[ attachment.cid ]; 279 }, 280 281 /** 282 * Given an attachment, create attachment display settings properties. 283 * 284 * @since 3.6.0 285 * 286 * @param {wp.media.model.Attachment} attachment 287 * @returns {Object} 288 */ 289 defaultDisplaySettings: function( attachment ) { 290 var settings = this._defaultDisplaySettings; 291 if ( settings.canEmbed = this.canEmbed( attachment ) ) { 292 settings.link = 'embed'; 293 } 294 return settings; 295 }, 296 297 /** 298 * Whether an attachment can be embedded (audio or video). 299 * 300 * @since 3.6.0 301 * 302 * @param {wp.media.model.Attachment} attachment 303 * @returns {Boolean} 304 */ 305 canEmbed: function( attachment ) { 306 // If uploading, we know the filename but not the mime type. 307 if ( ! attachment.get('uploading') ) { 308 var type = attachment.get('type'); 309 if ( type !== 'audio' && type !== 'video' ) { 310 return false; 311 } 312 } 313 314 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 315 }, 316 317 318 /** 319 * If the state is active, no items are selected, and the current 320 * content mode is not an option in the state's router (provided 321 * the state has a router), reset the content mode to the default. 322 * 323 * @since 3.5.0 324 */ 325 refreshContent: function() { 326 var selection = this.get('selection'), 327 frame = this.frame, 328 router = frame.router.get(), 329 mode = frame.content.mode(); 330 331 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 332 this.frame.content.render( this.get('content') ); 333 } 334 }, 335 336 /** 337 * Callback handler when an attachment is uploaded. 338 * 339 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 340 * 341 * Adds any uploading attachments to the selection. 342 * 343 * If the state only supports one attachment to be selected and multiple 344 * attachments are uploaded, the last attachment in the upload queue will 345 * be selected. 346 * 347 * @since 3.5.0 348 * 349 * @param {wp.media.model.Attachment} attachment 350 */ 351 uploading: function( attachment ) { 352 var content = this.frame.content; 353 354 if ( 'upload' === content.mode() ) { 355 this.frame.content.mode('browse'); 356 } 357 358 if ( this.get( 'autoSelect' ) ) { 359 this.get('selection').add( attachment ); 360 this.frame.trigger( 'library:selection:add' ); 361 } 362 }, 363 364 /** 365 * Persist the mode of the content region as a user setting. 366 * 367 * @since 3.5.0 368 */ 369 saveContentMode: function() { 370 if ( 'browse' !== this.get('router') ) { 371 return; 372 } 373 374 var mode = this.frame.content.mode(), 375 view = this.frame.router.get(); 376 377 if ( view && view.get( mode ) ) { 378 setUserSetting( 'libraryContent', mode ); 379 } 380 } 381 }); 382 383 // Make selectionSync available on any Media Library state. 384 _.extend( Library.prototype, selectionSync ); 385 386 module.exports = Library; 387 388 },{"../utils/selection-sync.js":9,"./state.js":6}],4:[function(require,module,exports){ 389 /*globals Backbone, _ */ 390 391 /** 392 * wp.media.controller.Region 393 * 394 * A region is a persistent application layout area. 395 * 396 * A region assumes one mode at any time, and can be switched to another. 397 * 398 * When mode changes, events are triggered on the region's parent view. 399 * The parent view will listen to specific events and fill the region with an 400 * appropriate view depending on mode. For example, a frame listens for the 401 * 'browse' mode t be activated on the 'content' view and then fills the region 402 * with an AttachmentsBrowser view. 403 * 404 * @class 405 * 406 * @param {object} options Options hash for the region. 407 * @param {string} options.id Unique identifier for the region. 408 * @param {Backbone.View} options.view A parent view the region exists within. 409 * @param {string} options.selector jQuery selector for the region within the parent view. 410 */ 411 var Region = function( options ) { 412 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 413 }; 414 415 // Use Backbone's self-propagating `extend` inheritance method. 416 Region.extend = Backbone.Model.extend; 417 418 _.extend( Region.prototype, { 419 /** 420 * Activate a mode. 421 * 422 * @since 3.5.0 423 * 424 * @param {string} mode 425 * 426 * @fires this.view#{this.id}:activate:{this._mode} 427 * @fires this.view#{this.id}:activate 428 * @fires this.view#{this.id}:deactivate:{this._mode} 429 * @fires this.view#{this.id}:deactivate 430 * 431 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 432 */ 433 mode: function( mode ) { 434 if ( ! mode ) { 435 return this._mode; 436 } 437 // Bail if we're trying to change to the current mode. 438 if ( mode === this._mode ) { 439 return this; 440 } 441 442 /** 443 * Region mode deactivation event. 444 * 445 * @event this.view#{this.id}:deactivate:{this._mode} 446 * @event this.view#{this.id}:deactivate 447 */ 448 this.trigger('deactivate'); 449 450 this._mode = mode; 451 this.render( mode ); 452 453 /** 454 * Region mode activation event. 455 * 456 * @event this.view#{this.id}:activate:{this._mode} 457 * @event this.view#{this.id}:activate 458 */ 459 this.trigger('activate'); 460 return this; 461 }, 462 /** 463 * Render a mode. 464 * 465 * @since 3.5.0 466 * 467 * @param {string} mode 468 * 469 * @fires this.view#{this.id}:create:{this._mode} 470 * @fires this.view#{this.id}:create 471 * @fires this.view#{this.id}:render:{this._mode} 472 * @fires this.view#{this.id}:render 473 * 474 * @returns {wp.media.controller.Region} Returns itself to allow chaining 475 */ 476 render: function( mode ) { 477 // If the mode isn't active, activate it. 478 if ( mode && mode !== this._mode ) { 479 return this.mode( mode ); 480 } 481 482 var set = { view: null }, 483 view; 484 485 /** 486 * Create region view event. 487 * 488 * Region view creation takes place in an event callback on the frame. 489 * 490 * @event this.view#{this.id}:create:{this._mode} 491 * @event this.view#{this.id}:create 492 */ 493 this.trigger( 'create', set ); 494 view = set.view; 495 496 /** 497 * Render region view event. 498 * 499 * Region view creation takes place in an event callback on the frame. 500 * 501 * @event this.view#{this.id}:create:{this._mode} 502 * @event this.view#{this.id}:create 503 */ 504 this.trigger( 'render', view ); 505 if ( view ) { 506 this.set( view ); 507 } 508 return this; 509 }, 510 511 /** 512 * Get the region's view. 513 * 514 * @since 3.5.0 515 * 516 * @returns {wp.media.View} 517 */ 518 get: function() { 519 return this.view.views.first( this.selector ); 520 }, 521 522 /** 523 * Set the region's view as a subview of the frame. 524 * 525 * @since 3.5.0 526 * 527 * @param {Array|Object} views 528 * @param {Object} [options={}] 529 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 530 */ 531 set: function( views, options ) { 532 if ( options ) { 533 options.add = false; 534 } 535 return this.view.views.set( this.selector, views, options ); 536 }, 537 538 /** 539 * Trigger regional view events on the frame. 540 * 541 * @since 3.5.0 542 * 543 * @param {string} event 544 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 545 */ 546 trigger: function( event ) { 547 var base, args; 548 549 if ( ! this._mode ) { 550 return; 551 } 552 553 args = _.toArray( arguments ); 554 base = this.id + ':' + event; 555 556 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 557 args[0] = base + ':' + this._mode; 558 this.view.trigger.apply( this.view, args ); 559 560 // Trigger `{this.id}:{event}` event on the frame. 561 args[0] = base; 562 this.view.trigger.apply( this.view, args ); 563 return this; 564 } 565 }); 566 567 module.exports = Region; 568 569 },{}],5:[function(require,module,exports){ 570 /*globals _, Backbone */ 571 572 /** 573 * wp.media.controller.StateMachine 574 * 575 * A state machine keeps track of state. It is in one state at a time, 576 * and can change from one state to another. 577 * 578 * States are stored as models in a Backbone collection. 579 * 580 * @since 3.5.0 581 * 582 * @class 583 * @augments Backbone.Model 584 * @mixin 585 * @mixes Backbone.Events 586 * 587 * @param {Array} states 588 */ 589 var StateMachine = function( states ) { 590 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 591 this.states = new Backbone.Collection( states ); 592 }; 593 594 // Use Backbone's self-propagating `extend` inheritance method. 595 StateMachine.extend = Backbone.Model.extend; 596 597 _.extend( StateMachine.prototype, Backbone.Events, { 598 /** 599 * Fetch a state. 600 * 601 * If no `id` is provided, returns the active state. 602 * 603 * Implicitly creates states. 604 * 605 * Ensure that the `states` collection exists so the `StateMachine` 606 * can be used as a mixin. 607 * 608 * @since 3.5.0 609 * 610 * @param {string} id 611 * @returns {wp.media.controller.State} Returns a State model 612 * from the StateMachine collection 613 */ 614 state: function( id ) { 615 this.states = this.states || new Backbone.Collection(); 616 617 // Default to the active state. 618 id = id || this._state; 619 620 if ( id && ! this.states.get( id ) ) { 621 this.states.add({ id: id }); 622 } 623 return this.states.get( id ); 624 }, 625 626 /** 627 * Sets the active state. 628 * 629 * Bail if we're trying to select the current state, if we haven't 630 * created the `states` collection, or are trying to select a state 631 * that does not exist. 632 * 633 * @since 3.5.0 634 * 635 * @param {string} id 636 * 637 * @fires wp.media.controller.State#deactivate 638 * @fires wp.media.controller.State#activate 639 * 640 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 641 */ 642 setState: function( id ) { 643 var previous = this.state(); 644 645 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 646 return this; 647 } 648 649 if ( previous ) { 650 previous.trigger('deactivate'); 651 this._lastState = previous.id; 652 } 653 654 this._state = id; 655 this.state().trigger('activate'); 656 657 return this; 658 }, 659 660 /** 661 * Returns the previous active state. 662 * 663 * Call the `state()` method with no parameters to retrieve the current 664 * active state. 665 * 666 * @since 3.5.0 667 * 668 * @returns {wp.media.controller.State} Returns a State model 669 * from the StateMachine collection 670 */ 671 lastState: function() { 672 if ( this._lastState ) { 673 return this.state( this._lastState ); 674 } 675 } 676 }); 677 678 // Map all event binding and triggering on a StateMachine to its `states` collection. 679 _.each([ 'on', 'off', 'trigger' ], function( method ) { 680 /** 681 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 682 */ 683 StateMachine.prototype[ method ] = function() { 684 // Ensure that the `states` collection exists so the `StateMachine` 685 // can be used as a mixin. 686 this.states = this.states || new Backbone.Collection(); 687 // Forward the method to the `states` collection. 688 this.states[ method ].apply( this.states, arguments ); 689 return this; 690 }; 691 }); 692 693 module.exports = StateMachine; 694 695 },{}],6:[function(require,module,exports){ 696 /*globals _, Backbone */ 697 698 /** 699 * wp.media.controller.State 700 * 701 * A state is a step in a workflow that when set will trigger the controllers 702 * for the regions to be updated as specified in the frame. 703 * 704 * A state has an event-driven lifecycle: 705 * 706 * 'ready' triggers when a state is added to a state machine's collection. 707 * 'activate' triggers when a state is activated by a state machine. 708 * 'deactivate' triggers when a state is deactivated by a state machine. 709 * 'reset' is not triggered automatically. It should be invoked by the 710 * proper controller to reset the state to its default. 711 * 712 * @class 713 * @augments Backbone.Model 714 */ 715 var State = Backbone.Model.extend({ 716 /** 717 * Constructor. 718 * 719 * @since 3.5.0 720 */ 721 constructor: function() { 722 this.on( 'activate', this._preActivate, this ); 723 this.on( 'activate', this.activate, this ); 724 this.on( 'activate', this._postActivate, this ); 725 this.on( 'deactivate', this._deactivate, this ); 726 this.on( 'deactivate', this.deactivate, this ); 727 this.on( 'reset', this.reset, this ); 728 this.on( 'ready', this._ready, this ); 729 this.on( 'ready', this.ready, this ); 730 /** 731 * Call parent constructor with passed arguments 732 */ 733 Backbone.Model.apply( this, arguments ); 734 this.on( 'change:menu', this._updateMenu, this ); 735 }, 736 /** 737 * Ready event callback. 738 * 739 * @abstract 740 * @since 3.5.0 741 */ 742 ready: function() {}, 743 744 /** 745 * Activate event callback. 746 * 747 * @abstract 748 * @since 3.5.0 749 */ 750 activate: function() {}, 751 752 /** 753 * Deactivate event callback. 754 * 755 * @abstract 756 * @since 3.5.0 757 */ 758 deactivate: function() {}, 759 760 /** 761 * Reset event callback. 762 * 763 * @abstract 764 * @since 3.5.0 765 */ 766 reset: function() {}, 767 768 /** 769 * @access private 770 * @since 3.5.0 771 */ 772 _ready: function() { 773 this._updateMenu(); 774 }, 775 776 /** 777 * @access private 778 * @since 3.5.0 779 */ 780 _preActivate: function() { 781 this.active = true; 782 }, 783 784 /** 785 * @access private 786 * @since 3.5.0 787 */ 788 _postActivate: function() { 789 this.on( 'change:menu', this._menu, this ); 790 this.on( 'change:titleMode', this._title, this ); 791 this.on( 'change:content', this._content, this ); 792 this.on( 'change:toolbar', this._toolbar, this ); 793 794 this.frame.on( 'title:render:default', this._renderTitle, this ); 795 796 this._title(); 797 this._menu(); 798 this._toolbar(); 799 this._content(); 800 this._router(); 801 }, 802 803 /** 804 * @access private 805 * @since 3.5.0 806 */ 807 _deactivate: function() { 808 this.active = false; 809 810 this.frame.off( 'title:render:default', this._renderTitle, this ); 811 812 this.off( 'change:menu', this._menu, this ); 813 this.off( 'change:titleMode', this._title, this ); 814 this.off( 'change:content', this._content, this ); 815 this.off( 'change:toolbar', this._toolbar, this ); 816 }, 817 818 /** 819 * @access private 820 * @since 3.5.0 821 */ 822 _title: function() { 823 this.frame.title.render( this.get('titleMode') || 'default' ); 824 }, 825 826 /** 827 * @access private 828 * @since 3.5.0 829 */ 830 _renderTitle: function( view ) { 831 view.$el.text( this.get('title') || '' ); 832 }, 833 834 /** 835 * @access private 836 * @since 3.5.0 837 */ 838 _router: function() { 839 var router = this.frame.router, 840 mode = this.get('router'), 841 view; 842 843 this.frame.$el.toggleClass( 'hide-router', ! mode ); 844 if ( ! mode ) { 845 return; 846 } 847 848 this.frame.router.render( mode ); 849 850 view = router.get(); 851 if ( view && view.select ) { 852 view.select( this.frame.content.mode() ); 853 } 854 }, 855 856 /** 857 * @access private 858 * @since 3.5.0 859 */ 860 _menu: function() { 861 var menu = this.frame.menu, 862 mode = this.get('menu'), 863 view; 864 865 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 866 if ( ! mode ) { 867 return; 868 } 869 870 menu.mode( mode ); 871 872 view = menu.get(); 873 if ( view && view.select ) { 874 view.select( this.id ); 875 } 876 }, 877 878 /** 879 * @access private 880 * @since 3.5.0 881 */ 882 _updateMenu: function() { 883 var previous = this.previous('menu'), 884 menu = this.get('menu'); 885 886 if ( previous ) { 887 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 888 } 889 890 if ( menu ) { 891 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 892 } 893 }, 894 895 /** 896 * Create a view in the media menu for the state. 897 * 898 * @access private 899 * @since 3.5.0 900 * 901 * @param {media.view.Menu} view The menu view. 902 */ 903 _renderMenu: function( view ) { 904 var menuItem = this.get('menuItem'), 905 title = this.get('title'), 906 priority = this.get('priority'); 907 908 if ( ! menuItem && title ) { 909 menuItem = { text: title }; 910 911 if ( priority ) { 912 menuItem.priority = priority; 913 } 914 } 915 916 if ( ! menuItem ) { 917 return; 918 } 919 920 view.set( this.id, menuItem ); 921 } 922 }); 923 924 _.each(['toolbar','content'], function( region ) { 925 /** 926 * @access private 927 */ 928 State.prototype[ '_' + region ] = function() { 929 var mode = this.get( region ); 930 if ( mode ) { 931 this.frame[ region ].render( mode ); 932 } 933 }; 934 }); 935 936 module.exports = State; 937 938 },{}],7:[function(require,module,exports){ 32 },{}],2:[function(require,module,exports){ 939 33 /*globals wp */ 940 34 … … 951 45 media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-selected-permanently.js' ); 952 46 953 },{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js": 8,"./views/attachment/details-two-column.js":16,"./views/button/delete-selected-permanently.js":22,"./views/button/delete-selected.js":23,"./views/button/select-mode-toggle.js":24,"./views/edit-image-details.js":25,"./views/frame/edit-attachments.js":28,"./views/frame/manage.js":29}],8:[function(require,module,exports){47 },{"./controllers/edit-attachment-metadata.js":1,"./routers/manage.js":3,"./views/attachment/details-two-column.js":4,"./views/button/delete-selected-permanently.js":5,"./views/button/delete-selected.js":6,"./views/button/select-mode-toggle.js":7,"./views/edit-image-details.js":8,"./views/frame/edit-attachments.js":9,"./views/frame/manage.js":10}],3:[function(require,module,exports){ 954 48 /*globals wp, Backbone */ 955 49 … … 1001 95 module.exports = Router; 1002 96 1003 },{}],9:[function(require,module,exports){ 1004 /*globals _ */ 1005 1006 /** 1007 * wp.media.selectionSync 1008 * 1009 * Sync an attachments selection in a state with another state. 1010 * 1011 * Allows for selecting multiple images in the Insert Media workflow, and then 1012 * switching to the Insert Gallery workflow while preserving the attachments selection. 1013 * 1014 * @mixin 1015 */ 1016 var selectionSync = { 1017 /** 1018 * @since 3.5.0 1019 */ 1020 syncSelection: function() { 1021 var selection = this.get('selection'), 1022 manager = this.frame._selection; 1023 1024 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 1025 return; 1026 } 1027 1028 // If the selection supports multiple items, validate the stored 1029 // attachments based on the new selection's conditions. Record 1030 // the attachments that are not included; we'll maintain a 1031 // reference to those. Other attachments are considered in flux. 1032 if ( selection.multiple ) { 1033 selection.reset( [], { silent: true }); 1034 selection.validateAll( manager.attachments ); 1035 manager.difference = _.difference( manager.attachments.models, selection.models ); 1036 } 1037 1038 // Sync the selection's single item with the master. 1039 selection.single( manager.single ); 1040 }, 1041 1042 /** 1043 * Record the currently active attachments, which is a combination 1044 * of the selection's attachments and the set of selected 1045 * attachments that this specific selection considered invalid. 1046 * Reset the difference and record the single attachment. 1047 * 1048 * @since 3.5.0 1049 */ 1050 recordSelection: function() { 1051 var selection = this.get('selection'), 1052 manager = this.frame._selection; 1053 1054 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 1055 return; 1056 } 1057 1058 if ( selection.multiple ) { 1059 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 1060 manager.difference = []; 1061 } else { 1062 manager.attachments.add( selection.toArray() ); 1063 } 1064 1065 manager.single = selection._single; 1066 } 1067 }; 1068 1069 module.exports = selectionSync; 1070 1071 },{}],10:[function(require,module,exports){ 1072 /*globals _ */ 1073 1074 /** 1075 * wp.media.view.AttachmentCompat 1076 * 1077 * A view to display fields added via the `attachment_fields_to_edit` filter. 1078 * 1079 * @class 1080 * @augments wp.media.View 1081 * @augments wp.Backbone.View 1082 * @augments Backbone.View 1083 */ 1084 var View = require( './view.js' ), 1085 AttachmentCompat; 1086 1087 AttachmentCompat = View.extend({ 1088 tagName: 'form', 1089 className: 'compat-item', 1090 1091 events: { 1092 'submit': 'preventDefault', 1093 'change input': 'save', 1094 'change select': 'save', 1095 'change textarea': 'save' 1096 }, 1097 1098 initialize: function() { 1099 this.listenTo( this.model, 'change:compat', this.render ); 1100 }, 1101 /** 1102 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 1103 */ 1104 dispose: function() { 1105 if ( this.$(':focus').length ) { 1106 this.save(); 1107 } 1108 /** 1109 * call 'dispose' directly on the parent class 1110 */ 1111 return View.prototype.dispose.apply( this, arguments ); 1112 }, 1113 /** 1114 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 1115 */ 1116 render: function() { 1117 var compat = this.model.get('compat'); 1118 if ( ! compat || ! compat.item ) { 1119 return; 1120 } 1121 1122 this.views.detach(); 1123 this.$el.html( compat.item ); 1124 this.views.render(); 1125 return this; 1126 }, 1127 /** 1128 * @param {Object} event 1129 */ 1130 preventDefault: function( event ) { 1131 event.preventDefault(); 1132 }, 1133 /** 1134 * @param {Object} event 1135 */ 1136 save: function( event ) { 1137 var data = {}; 1138 1139 if ( event ) { 1140 event.preventDefault(); 1141 } 1142 1143 _.each( this.$el.serializeArray(), function( pair ) { 1144 data[ pair.name ] = pair.value; 1145 }); 1146 1147 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 1148 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 1149 }, 1150 1151 postSave: function() { 1152 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 1153 } 1154 }); 1155 1156 module.exports = AttachmentCompat; 1157 1158 },{"./view.js":49}],11:[function(require,module,exports){ 1159 /*globals _, jQuery */ 1160 1161 /** 1162 * wp.media.view.AttachmentFilters 1163 * 1164 * @class 1165 * @augments wp.media.View 1166 * @augments wp.Backbone.View 1167 * @augments Backbone.View 1168 */ 1169 var View = require( './view.js' ), 1170 $ = jQuery, 1171 AttachmentFilters; 1172 1173 AttachmentFilters = View.extend({ 1174 tagName: 'select', 1175 className: 'attachment-filters', 1176 id: 'media-attachment-filters', 1177 1178 events: { 1179 change: 'change' 1180 }, 1181 1182 keys: [], 1183 1184 initialize: function() { 1185 this.createFilters(); 1186 _.extend( this.filters, this.options.filters ); 1187 1188 // Build `<option>` elements. 1189 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 1190 return { 1191 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 1192 priority: filter.priority || 50 1193 }; 1194 }, this ).sortBy('priority').pluck('el').value() ); 1195 1196 this.listenTo( this.model, 'change', this.select ); 1197 this.select(); 1198 }, 1199 1200 /** 1201 * @abstract 1202 */ 1203 createFilters: function() { 1204 this.filters = {}; 1205 }, 1206 1207 /** 1208 * When the selected filter changes, update the Attachment Query properties to match. 1209 */ 1210 change: function() { 1211 var filter = this.filters[ this.el.value ]; 1212 if ( filter ) { 1213 this.model.set( filter.props ); 1214 } 1215 }, 1216 1217 select: function() { 1218 var model = this.model, 1219 value = 'all', 1220 props = model.toJSON(); 1221 1222 _.find( this.filters, function( filter, id ) { 1223 var equal = _.all( filter.props, function( prop, key ) { 1224 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); 1225 }); 1226 1227 if ( equal ) { 1228 return value = id; 1229 } 1230 }); 1231 1232 this.$el.val( value ); 1233 } 1234 }); 1235 1236 module.exports = AttachmentFilters; 1237 1238 },{"./view.js":49}],12:[function(require,module,exports){ 1239 /*globals wp */ 1240 1241 /** 1242 * wp.media.view.AttachmentFilters.All 1243 * 1244 * @class 1245 * @augments wp.media.view.AttachmentFilters 1246 * @augments wp.media.View 1247 * @augments wp.Backbone.View 1248 * @augments Backbone.View 1249 */ 1250 var AttachmentFilters = require( '../attachment-filters.js' ), 1251 l10n = wp.media.view.l10n, 1252 All; 1253 1254 All = AttachmentFilters.extend({ 1255 createFilters: function() { 1256 var filters = {}; 1257 1258 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 1259 filters[ key ] = { 1260 text: text, 1261 props: { 1262 status: null, 1263 type: key, 1264 uploadedTo: null, 1265 orderby: 'date', 1266 order: 'DESC' 1267 } 1268 }; 1269 }); 1270 1271 filters.all = { 1272 text: l10n.allMediaItems, 1273 props: { 1274 status: null, 1275 type: null, 1276 uploadedTo: null, 1277 orderby: 'date', 1278 order: 'DESC' 1279 }, 1280 priority: 10 1281 }; 1282 1283 if ( wp.media.view.settings.post.id ) { 1284 filters.uploaded = { 1285 text: l10n.uploadedToThisPost, 1286 props: { 1287 status: null, 1288 type: null, 1289 uploadedTo: wp.media.view.settings.post.id, 1290 orderby: 'menuOrder', 1291 order: 'ASC' 1292 }, 1293 priority: 20 1294 }; 1295 } 1296 1297 filters.unattached = { 1298 text: l10n.unattached, 1299 props: { 1300 status: null, 1301 uploadedTo: 0, 1302 type: null, 1303 orderby: 'menuOrder', 1304 order: 'ASC' 1305 }, 1306 priority: 50 1307 }; 1308 1309 if ( wp.media.view.settings.mediaTrash && 1310 this.controller.isModeActive( 'grid' ) ) { 1311 1312 filters.trash = { 1313 text: l10n.trash, 1314 props: { 1315 uploadedTo: null, 1316 status: 'trash', 1317 type: null, 1318 orderby: 'date', 1319 order: 'DESC' 1320 }, 1321 priority: 50 1322 }; 1323 } 1324 1325 this.filters = filters; 1326 } 1327 }); 1328 1329 module.exports = All; 1330 1331 },{"../attachment-filters.js":11}],13:[function(require,module,exports){ 1332 /*globals wp, _ */ 1333 1334 /** 1335 * A filter dropdown for month/dates. 1336 * 1337 * @class 1338 * @augments wp.media.view.AttachmentFilters 1339 * @augments wp.media.View 1340 * @augments wp.Backbone.View 1341 * @augments Backbone.View 1342 */ 1343 var AttachmentFilters = require( '../attachment-filters.js' ), 1344 l10n = wp.media.view.l10n, 1345 DateFilter; 1346 1347 DateFilter = AttachmentFilters.extend({ 1348 id: 'media-attachment-date-filters', 1349 1350 createFilters: function() { 1351 var filters = {}; 1352 _.each( wp.media.view.settings.months || {}, function( value, index ) { 1353 filters[ index ] = { 1354 text: value.text, 1355 props: { 1356 year: value.year, 1357 monthnum: value.month 1358 } 1359 }; 1360 }); 1361 filters.all = { 1362 text: l10n.allDates, 1363 props: { 1364 monthnum: false, 1365 year: false 1366 }, 1367 priority: 10 1368 }; 1369 this.filters = filters; 1370 } 1371 }); 1372 1373 module.exports = DateFilter; 1374 1375 },{"../attachment-filters.js":11}],14:[function(require,module,exports){ 1376 /*globals wp */ 1377 1378 /** 1379 * wp.media.view.AttachmentFilters.Uploaded 1380 * 1381 * @class 1382 * @augments wp.media.view.AttachmentFilters 1383 * @augments wp.media.View 1384 * @augments wp.Backbone.View 1385 * @augments Backbone.View 1386 */ 1387 var AttachmentFilters = require( '../attachment-filters.js' ), 1388 l10n = wp.media.view.l10n, 1389 Uploaded; 1390 1391 Uploaded = AttachmentFilters.extend({ 1392 createFilters: function() { 1393 var type = this.model.get('type'), 1394 types = wp.media.view.settings.mimeTypes, 1395 text; 1396 1397 if ( types && type ) { 1398 text = types[ type ]; 1399 } 1400 1401 this.filters = { 1402 all: { 1403 text: text || l10n.allMediaItems, 1404 props: { 1405 uploadedTo: null, 1406 orderby: 'date', 1407 order: 'DESC' 1408 }, 1409 priority: 10 1410 }, 1411 1412 uploaded: { 1413 text: l10n.uploadedToThisPost, 1414 props: { 1415 uploadedTo: wp.media.view.settings.post.id, 1416 orderby: 'menuOrder', 1417 order: 'ASC' 1418 }, 1419 priority: 20 1420 }, 1421 1422 unattached: { 1423 text: l10n.unattached, 1424 props: { 1425 uploadedTo: 0, 1426 orderby: 'menuOrder', 1427 order: 'ASC' 1428 }, 1429 priority: 50 1430 } 1431 }; 1432 } 1433 }); 1434 1435 module.exports = Uploaded; 1436 1437 },{"../attachment-filters.js":11}],15:[function(require,module,exports){ 1438 /*globals wp, _, jQuery */ 1439 1440 /** 1441 * wp.media.view.Attachment 1442 * 1443 * @class 1444 * @augments wp.media.View 1445 * @augments wp.Backbone.View 1446 * @augments Backbone.View 1447 */ 1448 var View = require( './view.js' ), 1449 $ = jQuery, 1450 Attachment; 1451 1452 Attachment = View.extend({ 1453 tagName: 'li', 1454 className: 'attachment', 1455 template: wp.template('attachment'), 1456 1457 attributes: function() { 1458 return { 1459 'tabIndex': 0, 1460 'role': 'checkbox', 1461 'aria-label': this.model.get( 'title' ), 1462 'aria-checked': false, 1463 'data-id': this.model.get( 'id' ) 1464 }; 1465 }, 1466 1467 events: { 1468 'click .js--select-attachment': 'toggleSelectionHandler', 1469 'change [data-setting]': 'updateSetting', 1470 'change [data-setting] input': 'updateSetting', 1471 'change [data-setting] select': 'updateSetting', 1472 'change [data-setting] textarea': 'updateSetting', 1473 'click .close': 'removeFromLibrary', 1474 'click .check': 'checkClickHandler', 1475 'click a': 'preventDefault', 1476 'keydown .close': 'removeFromLibrary', 1477 'keydown': 'toggleSelectionHandler' 1478 }, 1479 1480 buttons: {}, 1481 1482 initialize: function() { 1483 var selection = this.options.selection, 1484 options = _.defaults( this.options, { 1485 rerenderOnModelChange: true 1486 } ); 1487 1488 if ( options.rerenderOnModelChange ) { 1489 this.listenTo( this.model, 'change', this.render ); 1490 } else { 1491 this.listenTo( this.model, 'change:percent', this.progress ); 1492 } 1493 this.listenTo( this.model, 'change:title', this._syncTitle ); 1494 this.listenTo( this.model, 'change:caption', this._syncCaption ); 1495 this.listenTo( this.model, 'change:artist', this._syncArtist ); 1496 this.listenTo( this.model, 'change:album', this._syncAlbum ); 1497 1498 // Update the selection. 1499 this.listenTo( this.model, 'add', this.select ); 1500 this.listenTo( this.model, 'remove', this.deselect ); 1501 if ( selection ) { 1502 selection.on( 'reset', this.updateSelect, this ); 1503 // Update the model's details view. 1504 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 1505 this.details( this.model, this.controller.state().get('selection') ); 1506 } 1507 1508 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 1509 }, 1510 /** 1511 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 1512 */ 1513 dispose: function() { 1514 var selection = this.options.selection; 1515 1516 // Make sure all settings are saved before removing the view. 1517 this.updateAll(); 1518 1519 if ( selection ) { 1520 selection.off( null, null, this ); 1521 } 1522 /** 1523 * call 'dispose' directly on the parent class 1524 */ 1525 View.prototype.dispose.apply( this, arguments ); 1526 return this; 1527 }, 1528 /** 1529 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 1530 */ 1531 render: function() { 1532 var options = _.defaults( this.model.toJSON(), { 1533 orientation: 'landscape', 1534 uploading: false, 1535 type: '', 1536 subtype: '', 1537 icon: '', 1538 filename: '', 1539 caption: '', 1540 title: '', 1541 dateFormatted: '', 1542 width: '', 1543 height: '', 1544 compat: false, 1545 alt: '', 1546 description: '' 1547 }, this.options ); 1548 1549 options.buttons = this.buttons; 1550 options.describe = this.controller.state().get('describe'); 1551 1552 if ( 'image' === options.type ) { 1553 options.size = this.imageSize(); 1554 } 1555 1556 options.can = {}; 1557 if ( options.nonces ) { 1558 options.can.remove = !! options.nonces['delete']; 1559 options.can.save = !! options.nonces.update; 1560 } 1561 1562 if ( this.controller.state().get('allowLocalEdits') ) { 1563 options.allowLocalEdits = true; 1564 } 1565 1566 if ( options.uploading && ! options.percent ) { 1567 options.percent = 0; 1568 } 1569 1570 this.views.detach(); 1571 this.$el.html( this.template( options ) ); 1572 1573 this.$el.toggleClass( 'uploading', options.uploading ); 1574 1575 if ( options.uploading ) { 1576 this.$bar = this.$('.media-progress-bar div'); 1577 } else { 1578 delete this.$bar; 1579 } 1580 1581 // Check if the model is selected. 1582 this.updateSelect(); 1583 1584 // Update the save status. 1585 this.updateSave(); 1586 1587 this.views.render(); 1588 1589 return this; 1590 }, 1591 1592 progress: function() { 1593 if ( this.$bar && this.$bar.length ) { 1594 this.$bar.width( this.model.get('percent') + '%' ); 1595 } 1596 }, 1597 1598 /** 1599 * @param {Object} event 1600 */ 1601 toggleSelectionHandler: function( event ) { 1602 var method; 1603 1604 // Don't do anything inside inputs. 1605 if ( 'INPUT' === event.target.nodeName ) { 1606 return; 1607 } 1608 1609 // Catch arrow events 1610 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 1611 this.controller.trigger( 'attachment:keydown:arrow', event ); 1612 return; 1613 } 1614 1615 // Catch enter and space events 1616 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 1617 return; 1618 } 1619 1620 event.preventDefault(); 1621 1622 // In the grid view, bubble up an edit:attachment event to the controller. 1623 if ( this.controller.isModeActive( 'grid' ) ) { 1624 if ( this.controller.isModeActive( 'edit' ) ) { 1625 // Pass the current target to restore focus when closing 1626 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 1627 return; 1628 } 1629 1630 if ( this.controller.isModeActive( 'select' ) ) { 1631 method = 'toggle'; 1632 } 1633 } 1634 1635 if ( event.shiftKey ) { 1636 method = 'between'; 1637 } else if ( event.ctrlKey || event.metaKey ) { 1638 method = 'toggle'; 1639 } 1640 1641 this.toggleSelection({ 1642 method: method 1643 }); 1644 1645 this.controller.trigger( 'selection:toggle' ); 1646 }, 1647 /** 1648 * @param {Object} options 1649 */ 1650 toggleSelection: function( options ) { 1651 var collection = this.collection, 1652 selection = this.options.selection, 1653 model = this.model, 1654 method = options && options.method, 1655 single, models, singleIndex, modelIndex; 1656 1657 if ( ! selection ) { 1658 return; 1659 } 1660 1661 single = selection.single(); 1662 method = _.isUndefined( method ) ? selection.multiple : method; 1663 1664 // If the `method` is set to `between`, select all models that 1665 // exist between the current and the selected model. 1666 if ( 'between' === method && single && selection.multiple ) { 1667 // If the models are the same, short-circuit. 1668 if ( single === model ) { 1669 return; 1670 } 1671 1672 singleIndex = collection.indexOf( single ); 1673 modelIndex = collection.indexOf( this.model ); 1674 1675 if ( singleIndex < modelIndex ) { 1676 models = collection.models.slice( singleIndex, modelIndex + 1 ); 1677 } else { 1678 models = collection.models.slice( modelIndex, singleIndex + 1 ); 1679 } 1680 1681 selection.add( models ); 1682 selection.single( model ); 1683 return; 1684 1685 // If the `method` is set to `toggle`, just flip the selection 1686 // status, regardless of whether the model is the single model. 1687 } else if ( 'toggle' === method ) { 1688 selection[ this.selected() ? 'remove' : 'add' ]( model ); 1689 selection.single( model ); 1690 return; 1691 } else if ( 'add' === method ) { 1692 selection.add( model ); 1693 selection.single( model ); 1694 return; 1695 } 1696 1697 // Fixes bug that loses focus when selecting a featured image 1698 if ( ! method ) { 1699 method = 'add'; 1700 } 1701 1702 if ( method !== 'add' ) { 1703 method = 'reset'; 1704 } 1705 1706 if ( this.selected() ) { 1707 // If the model is the single model, remove it. 1708 // If it is not the same as the single model, 1709 // it now becomes the single model. 1710 selection[ single === model ? 'remove' : 'single' ]( model ); 1711 } else { 1712 // If the model is not selected, run the `method` on the 1713 // selection. By default, we `reset` the selection, but the 1714 // `method` can be set to `add` the model to the selection. 1715 selection[ method ]( model ); 1716 selection.single( model ); 1717 } 1718 }, 1719 1720 updateSelect: function() { 1721 this[ this.selected() ? 'select' : 'deselect' ](); 1722 }, 1723 /** 1724 * @returns {unresolved|Boolean} 1725 */ 1726 selected: function() { 1727 var selection = this.options.selection; 1728 if ( selection ) { 1729 return !! selection.get( this.model.cid ); 1730 } 1731 }, 1732 /** 1733 * @param {Backbone.Model} model 1734 * @param {Backbone.Collection} collection 1735 */ 1736 select: function( model, collection ) { 1737 var selection = this.options.selection, 1738 controller = this.controller; 1739 1740 // Check if a selection exists and if it's the collection provided. 1741 // If they're not the same collection, bail; we're in another 1742 // selection's event loop. 1743 if ( ! selection || ( collection && collection !== selection ) ) { 1744 return; 1745 } 1746 1747 // Bail if the model is already selected. 1748 if ( this.$el.hasClass( 'selected' ) ) { 1749 return; 1750 } 1751 1752 // Add 'selected' class to model, set aria-checked to true. 1753 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 1754 // Make the checkbox tabable, except in media grid (bulk select mode). 1755 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 1756 this.$( '.check' ).attr( 'tabindex', '0' ); 1757 } 1758 }, 1759 /** 1760 * @param {Backbone.Model} model 1761 * @param {Backbone.Collection} collection 1762 */ 1763 deselect: function( model, collection ) { 1764 var selection = this.options.selection; 1765 1766 // Check if a selection exists and if it's the collection provided. 1767 // If they're not the same collection, bail; we're in another 1768 // selection's event loop. 1769 if ( ! selection || ( collection && collection !== selection ) ) { 1770 return; 1771 } 1772 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 1773 .find( '.check' ).attr( 'tabindex', '-1' ); 1774 }, 1775 /** 1776 * @param {Backbone.Model} model 1777 * @param {Backbone.Collection} collection 1778 */ 1779 details: function( model, collection ) { 1780 var selection = this.options.selection, 1781 details; 1782 1783 if ( selection !== collection ) { 1784 return; 1785 } 1786 1787 details = selection.single(); 1788 this.$el.toggleClass( 'details', details === this.model ); 1789 }, 1790 /** 1791 * @param {Object} event 1792 */ 1793 preventDefault: function( event ) { 1794 event.preventDefault(); 1795 }, 1796 /** 1797 * @param {string} size 1798 * @returns {Object} 1799 */ 1800 imageSize: function( size ) { 1801 var sizes = this.model.get('sizes'), matched = false; 1802 1803 size = size || 'medium'; 1804 1805 // Use the provided image size if possible. 1806 if ( sizes ) { 1807 if ( sizes[ size ] ) { 1808 matched = sizes[ size ]; 1809 } else if ( sizes.large ) { 1810 matched = sizes.large; 1811 } else if ( sizes.thumbnail ) { 1812 matched = sizes.thumbnail; 1813 } else if ( sizes.full ) { 1814 matched = sizes.full; 1815 } 1816 1817 if ( matched ) { 1818 return _.clone( matched ); 1819 } 1820 } 1821 1822 return { 1823 url: this.model.get('url'), 1824 width: this.model.get('width'), 1825 height: this.model.get('height'), 1826 orientation: this.model.get('orientation') 1827 }; 1828 }, 1829 /** 1830 * @param {Object} event 1831 */ 1832 updateSetting: function( event ) { 1833 var $setting = $( event.target ).closest('[data-setting]'), 1834 setting, value; 1835 1836 if ( ! $setting.length ) { 1837 return; 1838 } 1839 1840 setting = $setting.data('setting'); 1841 value = event.target.value; 1842 1843 if ( this.model.get( setting ) !== value ) { 1844 this.save( setting, value ); 1845 } 1846 }, 1847 1848 /** 1849 * Pass all the arguments to the model's save method. 1850 * 1851 * Records the aggregate status of all save requests and updates the 1852 * view's classes accordingly. 1853 */ 1854 save: function() { 1855 var view = this, 1856 save = this._save = this._save || { status: 'ready' }, 1857 request = this.model.save.apply( this.model, arguments ), 1858 requests = save.requests ? $.when( request, save.requests ) : request; 1859 1860 // If we're waiting to remove 'Saved.', stop. 1861 if ( save.savedTimer ) { 1862 clearTimeout( save.savedTimer ); 1863 } 1864 1865 this.updateSave('waiting'); 1866 save.requests = requests; 1867 requests.always( function() { 1868 // If we've performed another request since this one, bail. 1869 if ( save.requests !== requests ) { 1870 return; 1871 } 1872 1873 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 1874 save.savedTimer = setTimeout( function() { 1875 view.updateSave('ready'); 1876 delete save.savedTimer; 1877 }, 2000 ); 1878 }); 1879 }, 1880 /** 1881 * @param {string} status 1882 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 1883 */ 1884 updateSave: function( status ) { 1885 var save = this._save = this._save || { status: 'ready' }; 1886 1887 if ( status && status !== save.status ) { 1888 this.$el.removeClass( 'save-' + save.status ); 1889 save.status = status; 1890 } 1891 1892 this.$el.addClass( 'save-' + save.status ); 1893 return this; 1894 }, 1895 1896 updateAll: function() { 1897 var $settings = this.$('[data-setting]'), 1898 model = this.model, 1899 changed; 1900 1901 changed = _.chain( $settings ).map( function( el ) { 1902 var $input = $('input, textarea, select, [value]', el ), 1903 setting, value; 1904 1905 if ( ! $input.length ) { 1906 return; 1907 } 1908 1909 setting = $(el).data('setting'); 1910 value = $input.val(); 1911 1912 // Record the value if it changed. 1913 if ( model.get( setting ) !== value ) { 1914 return [ setting, value ]; 1915 } 1916 }).compact().object().value(); 1917 1918 if ( ! _.isEmpty( changed ) ) { 1919 model.save( changed ); 1920 } 1921 }, 1922 /** 1923 * @param {Object} event 1924 */ 1925 removeFromLibrary: function( event ) { 1926 // Catch enter and space events 1927 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 1928 return; 1929 } 1930 1931 // Stop propagation so the model isn't selected. 1932 event.stopPropagation(); 1933 1934 this.collection.remove( this.model ); 1935 }, 1936 1937 /** 1938 * Add the model if it isn't in the selection, if it is in the selection, 1939 * remove it. 1940 * 1941 * @param {[type]} event [description] 1942 * @return {[type]} [description] 1943 */ 1944 checkClickHandler: function ( event ) { 1945 var selection = this.options.selection; 1946 if ( ! selection ) { 1947 return; 1948 } 1949 event.stopPropagation(); 1950 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 1951 selection.remove( this.model ); 1952 // Move focus back to the attachment tile (from the check). 1953 this.$el.focus(); 1954 } else { 1955 selection.add( this.model ); 1956 } 1957 } 1958 }); 1959 1960 // Ensure settings remain in sync between attachment views. 1961 _.each({ 1962 caption: '_syncCaption', 1963 title: '_syncTitle', 1964 artist: '_syncArtist', 1965 album: '_syncAlbum' 1966 }, function( method, setting ) { 1967 /** 1968 * @param {Backbone.Model} model 1969 * @param {string} value 1970 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 1971 */ 1972 Attachment.prototype[ method ] = function( model, value ) { 1973 var $setting = this.$('[data-setting="' + setting + '"]'); 1974 1975 if ( ! $setting.length ) { 1976 return this; 1977 } 1978 1979 // If the updated value is in sync with the value in the DOM, there 1980 // is no need to re-render. If we're currently editing the value, 1981 // it will automatically be in sync, suppressing the re-render for 1982 // the view we're editing, while updating any others. 1983 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 1984 return this; 1985 } 1986 1987 return this.render(); 1988 }; 1989 }); 1990 1991 module.exports = Attachment; 1992 1993 },{"./view.js":49}],16:[function(require,module,exports){ 97 },{}],4:[function(require,module,exports){ 1994 98 /*globals wp */ 1995 99 … … 2007 111 * @augments Backbone.View 2008 112 */ 2009 var Details = require( './details.js' ),113 var Details = wp.media.view.Attachment.Details, 2010 114 TwoColumn; 2011 115 … … 2036 140 module.exports = TwoColumn; 2037 141 2038 },{"./details.js":17}],17:[function(require,module,exports){ 2039 /*globals wp, _ */ 2040 2041 /** 2042 * wp.media.view.Attachment.Details 2043 * 2044 * @class 2045 * @augments wp.media.view.Attachment 2046 * @augments wp.media.View 2047 * @augments wp.Backbone.View 2048 * @augments Backbone.View 2049 */ 2050 var Attachment = require( '../attachment.js' ), 2051 l10n = wp.media.view.l10n, 2052 Details; 2053 2054 Details = Attachment.extend({ 2055 tagName: 'div', 2056 className: 'attachment-details', 2057 template: wp.template('attachment-details'), 2058 2059 attributes: function() { 2060 return { 2061 'tabIndex': 0, 2062 'data-id': this.model.get( 'id' ) 2063 }; 2064 }, 2065 2066 events: { 2067 'change [data-setting]': 'updateSetting', 2068 'change [data-setting] input': 'updateSetting', 2069 'change [data-setting] select': 'updateSetting', 2070 'change [data-setting] textarea': 'updateSetting', 2071 'click .delete-attachment': 'deleteAttachment', 2072 'click .trash-attachment': 'trashAttachment', 2073 'click .untrash-attachment': 'untrashAttachment', 2074 'click .edit-attachment': 'editAttachment', 2075 'click .refresh-attachment': 'refreshAttachment', 2076 'keydown': 'toggleSelectionHandler' 2077 }, 2078 2079 initialize: function() { 2080 this.options = _.defaults( this.options, { 2081 rerenderOnModelChange: false 2082 }); 2083 2084 this.on( 'ready', this.initialFocus ); 2085 // Call 'initialize' directly on the parent class. 2086 Attachment.prototype.initialize.apply( this, arguments ); 2087 }, 2088 2089 initialFocus: function() { 2090 if ( ! wp.media.isTouchDevice ) { 2091 this.$( ':input' ).eq( 0 ).focus(); 2092 } 2093 }, 2094 /** 2095 * @param {Object} event 2096 */ 2097 deleteAttachment: function( event ) { 2098 event.preventDefault(); 2099 2100 if ( window.confirm( l10n.warnDelete ) ) { 2101 this.model.destroy(); 2102 // Keep focus inside media modal 2103 // after image is deleted 2104 this.controller.modal.focusManager.focus(); 2105 } 2106 }, 2107 /** 2108 * @param {Object} event 2109 */ 2110 trashAttachment: function( event ) { 2111 var library = this.controller.library; 2112 event.preventDefault(); 2113 2114 if ( wp.media.view.settings.mediaTrash && 2115 'edit-metadata' === this.controller.content.mode() ) { 2116 2117 this.model.set( 'status', 'trash' ); 2118 this.model.save().done( function() { 2119 library._requery( true ); 2120 } ); 2121 } else { 2122 this.model.destroy(); 2123 } 2124 }, 2125 /** 2126 * @param {Object} event 2127 */ 2128 untrashAttachment: function( event ) { 2129 var library = this.controller.library; 2130 event.preventDefault(); 2131 2132 this.model.set( 'status', 'inherit' ); 2133 this.model.save().done( function() { 2134 library._requery( true ); 2135 } ); 2136 }, 2137 /** 2138 * @param {Object} event 2139 */ 2140 editAttachment: function( event ) { 2141 var editState = this.controller.states.get( 'edit-image' ); 2142 if ( window.imageEdit && editState ) { 2143 event.preventDefault(); 2144 2145 editState.set( 'image', this.model ); 2146 this.controller.setState( 'edit-image' ); 2147 } else { 2148 this.$el.addClass('needs-refresh'); 2149 } 2150 }, 2151 /** 2152 * @param {Object} event 2153 */ 2154 refreshAttachment: function( event ) { 2155 this.$el.removeClass('needs-refresh'); 2156 event.preventDefault(); 2157 this.model.fetch(); 2158 }, 2159 /** 2160 * When reverse tabbing(shift+tab) out of the right details panel, deliver 2161 * the focus to the item in the list that was being edited. 2162 * 2163 * @param {Object} event 2164 */ 2165 toggleSelectionHandler: function( event ) { 2166 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 2167 this.controller.trigger( 'attachment:details:shift-tab', event ); 2168 return false; 2169 } 2170 2171 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 2172 this.controller.trigger( 'attachment:keydown:arrow', event ); 2173 return; 2174 } 2175 } 2176 }); 2177 2178 module.exports = Details; 2179 2180 },{"../attachment.js":15}],18:[function(require,module,exports){ 2181 /** 2182 * wp.media.view.Attachment.Library 2183 * 2184 * @class 2185 * @augments wp.media.view.Attachment 2186 * @augments wp.media.View 2187 * @augments wp.Backbone.View 2188 * @augments Backbone.View 2189 */ 2190 var Attachment = require( '../attachment.js' ), 2191 Library; 2192 2193 Library = Attachment.extend({ 2194 buttons: { 2195 check: true 2196 } 2197 }); 2198 2199 module.exports = Library; 2200 2201 },{"../attachment.js":15}],19:[function(require,module,exports){ 2202 /*globals wp, _, jQuery */ 2203 2204 /** 2205 * wp.media.view.Attachments 2206 * 2207 * @class 2208 * @augments wp.media.View 2209 * @augments wp.Backbone.View 2210 * @augments Backbone.View 2211 */ 2212 var View = require( './view.js' ), 2213 Attachment = require( './attachment.js' ), 2214 $ = jQuery, 2215 Attachments; 2216 2217 Attachments = View.extend({ 2218 tagName: 'ul', 2219 className: 'attachments', 2220 2221 attributes: { 2222 tabIndex: -1 2223 }, 2224 2225 initialize: function() { 2226 this.el.id = _.uniqueId('__attachments-view-'); 2227 2228 _.defaults( this.options, { 2229 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 2230 refreshThreshold: 3, 2231 AttachmentView: Attachment, 2232 sortable: false, 2233 resize: true, 2234 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 2235 }); 2236 2237 this._viewsByCid = {}; 2238 this.$window = $( window ); 2239 this.resizeEvent = 'resize.media-modal-columns'; 2240 2241 this.collection.on( 'add', function( attachment ) { 2242 this.views.add( this.createAttachmentView( attachment ), { 2243 at: this.collection.indexOf( attachment ) 2244 }); 2245 }, this ); 2246 2247 this.collection.on( 'remove', function( attachment ) { 2248 var view = this._viewsByCid[ attachment.cid ]; 2249 delete this._viewsByCid[ attachment.cid ]; 2250 2251 if ( view ) { 2252 view.remove(); 2253 } 2254 }, this ); 2255 2256 this.collection.on( 'reset', this.render, this ); 2257 2258 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 2259 2260 // Throttle the scroll handler and bind this. 2261 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 2262 2263 this.options.scrollElement = this.options.scrollElement || this.el; 2264 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 2265 2266 this.initSortable(); 2267 2268 _.bindAll( this, 'setColumns' ); 2269 2270 if ( this.options.resize ) { 2271 this.on( 'ready', this.bindEvents ); 2272 this.controller.on( 'open', this.setColumns ); 2273 2274 // Call this.setColumns() after this view has been rendered in the DOM so 2275 // attachments get proper width applied. 2276 _.defer( this.setColumns, this ); 2277 } 2278 }, 2279 2280 bindEvents: function() { 2281 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 2282 }, 2283 2284 attachmentFocus: function() { 2285 this.$( 'li:first' ).focus(); 2286 }, 2287 2288 restoreFocus: function() { 2289 this.$( 'li.selected:first' ).focus(); 2290 }, 2291 2292 arrowEvent: function( event ) { 2293 var attachments = this.$el.children( 'li' ), 2294 perRow = this.columns, 2295 index = attachments.filter( ':focus' ).index(), 2296 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 2297 2298 if ( index === -1 ) { 2299 return; 2300 } 2301 2302 // Left arrow 2303 if ( 37 === event.keyCode ) { 2304 if ( 0 === index ) { 2305 return; 2306 } 2307 attachments.eq( index - 1 ).focus(); 2308 } 2309 2310 // Up arrow 2311 if ( 38 === event.keyCode ) { 2312 if ( 1 === row ) { 2313 return; 2314 } 2315 attachments.eq( index - perRow ).focus(); 2316 } 2317 2318 // Right arrow 2319 if ( 39 === event.keyCode ) { 2320 if ( attachments.length === index ) { 2321 return; 2322 } 2323 attachments.eq( index + 1 ).focus(); 2324 } 2325 2326 // Down arrow 2327 if ( 40 === event.keyCode ) { 2328 if ( Math.ceil( attachments.length / perRow ) === row ) { 2329 return; 2330 } 2331 attachments.eq( index + perRow ).focus(); 2332 } 2333 }, 2334 2335 dispose: function() { 2336 this.collection.props.off( null, null, this ); 2337 if ( this.options.resize ) { 2338 this.$window.off( this.resizeEvent ); 2339 } 2340 2341 /** 2342 * call 'dispose' directly on the parent class 2343 */ 2344 View.prototype.dispose.apply( this, arguments ); 2345 }, 2346 2347 setColumns: function() { 2348 var prev = this.columns, 2349 width = this.$el.width(); 2350 2351 if ( width ) { 2352 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 2353 2354 if ( ! prev || prev !== this.columns ) { 2355 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 2356 } 2357 } 2358 }, 2359 2360 initSortable: function() { 2361 var collection = this.collection; 2362 2363 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 2364 return; 2365 } 2366 2367 this.$el.sortable( _.extend({ 2368 // If the `collection` has a `comparator`, disable sorting. 2369 disabled: !! collection.comparator, 2370 2371 // Change the position of the attachment as soon as the 2372 // mouse pointer overlaps a thumbnail. 2373 tolerance: 'pointer', 2374 2375 // Record the initial `index` of the dragged model. 2376 start: function( event, ui ) { 2377 ui.item.data('sortableIndexStart', ui.item.index()); 2378 }, 2379 2380 // Update the model's index in the collection. 2381 // Do so silently, as the view is already accurate. 2382 update: function( event, ui ) { 2383 var model = collection.at( ui.item.data('sortableIndexStart') ), 2384 comparator = collection.comparator; 2385 2386 // Temporarily disable the comparator to prevent `add` 2387 // from re-sorting. 2388 delete collection.comparator; 2389 2390 // Silently shift the model to its new index. 2391 collection.remove( model, { 2392 silent: true 2393 }); 2394 collection.add( model, { 2395 silent: true, 2396 at: ui.item.index() 2397 }); 2398 2399 // Restore the comparator. 2400 collection.comparator = comparator; 2401 2402 // Fire the `reset` event to ensure other collections sync. 2403 collection.trigger( 'reset', collection ); 2404 2405 // If the collection is sorted by menu order, 2406 // update the menu order. 2407 collection.saveMenuOrder(); 2408 } 2409 }, this.options.sortable ) ); 2410 2411 // If the `orderby` property is changed on the `collection`, 2412 // check to see if we have a `comparator`. If so, disable sorting. 2413 collection.props.on( 'change:orderby', function() { 2414 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 2415 }, this ); 2416 2417 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 2418 this.refreshSortable(); 2419 }, 2420 2421 refreshSortable: function() { 2422 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 2423 return; 2424 } 2425 2426 // If the `collection` has a `comparator`, disable sorting. 2427 var collection = this.collection, 2428 orderby = collection.props.get('orderby'), 2429 enabled = 'menuOrder' === orderby || ! collection.comparator; 2430 2431 this.$el.sortable( 'option', 'disabled', ! enabled ); 2432 }, 2433 2434 /** 2435 * @param {wp.media.model.Attachment} attachment 2436 * @returns {wp.media.View} 2437 */ 2438 createAttachmentView: function( attachment ) { 2439 var view = new this.options.AttachmentView({ 2440 controller: this.controller, 2441 model: attachment, 2442 collection: this.collection, 2443 selection: this.options.selection 2444 }); 2445 2446 return this._viewsByCid[ attachment.cid ] = view; 2447 }, 2448 2449 prepare: function() { 2450 // Create all of the Attachment views, and replace 2451 // the list in a single DOM operation. 2452 if ( this.collection.length ) { 2453 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 2454 2455 // If there are no elements, clear the views and load some. 2456 } else { 2457 this.views.unset(); 2458 this.collection.more().done( this.scroll ); 2459 } 2460 }, 2461 2462 ready: function() { 2463 // Trigger the scroll event to check if we're within the 2464 // threshold to query for additional attachments. 2465 this.scroll(); 2466 }, 2467 2468 scroll: function() { 2469 var view = this, 2470 el = this.options.scrollElement, 2471 scrollTop = el.scrollTop, 2472 toolbar; 2473 2474 // The scroll event occurs on the document, but the element 2475 // that should be checked is the document body. 2476 if ( el === document ) { 2477 el = document.body; 2478 scrollTop = $(document).scrollTop(); 2479 } 2480 2481 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 2482 return; 2483 } 2484 2485 toolbar = this.views.parent.toolbar; 2486 2487 // Show the spinner only if we are close to the bottom. 2488 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 2489 toolbar.get('spinner').show(); 2490 } 2491 2492 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 2493 this.collection.more().done(function() { 2494 view.scroll(); 2495 toolbar.get('spinner').hide(); 2496 }); 2497 } 2498 } 2499 }); 2500 2501 module.exports = Attachments; 2502 2503 },{"./attachment.js":15,"./view.js":49}],20:[function(require,module,exports){ 2504 /*globals wp, _, jQuery */ 2505 2506 /** 2507 * wp.media.view.AttachmentsBrowser 2508 * 2509 * @class 2510 * @augments wp.media.View 2511 * @augments wp.Backbone.View 2512 * @augments Backbone.View 2513 * 2514 * @param {object} options 2515 * @param {object} [options.filters=false] Which filters to show in the browser's toolbar. 2516 * Accepts 'uploaded' and 'all'. 2517 * @param {object} [options.search=true] Whether to show the search interface in the 2518 * browser's toolbar. 2519 * @param {object} [options.date=true] Whether to show the date filter in the 2520 * browser's toolbar. 2521 * @param {object} [options.display=false] Whether to show the attachments display settings 2522 * view in the sidebar. 2523 * @param {bool|string} [options.sidebar=true] Whether to create a sidebar for the browser. 2524 * Accepts true, false, and 'errors'. 2525 */ 2526 var View = require( '../view.js' ), 2527 Library = require( '../attachment/library.js' ), 2528 Toolbar = require( '../toolbar.js' ), 2529 Spinner = require( '../spinner.js' ), 2530 Search = require( '../search.js' ), 2531 Label = require( '../label.js' ), 2532 Uploaded = require( '../attachment-filters/uploaded.js' ), 2533 All = require( '../attachment-filters/all.js' ), 2534 DateFilter = require( '../attachment-filters/date.js' ), 2535 UploaderInline = require( '../uploader/inline.js' ), 2536 Attachments = require( '../attachments.js' ), 2537 Sidebar = require( '../sidebar.js' ), 2538 UploaderStatus = require( '../uploader/status.js' ), 2539 Details = require( '../attachment/details.js' ), 2540 AttachmentCompat = require( '../attachment-compat.js' ), 2541 AttachmentDisplay = require( '../settings/attachment-display.js' ), 2542 mediaTrash = wp.media.view.settings.mediaTrash, 2543 l10n = wp.media.view.l10n, 2544 $ = jQuery, 2545 AttachmentsBrowser; 2546 2547 AttachmentsBrowser = View.extend({ 2548 tagName: 'div', 2549 className: 'attachments-browser', 2550 2551 initialize: function() { 2552 _.defaults( this.options, { 2553 filters: false, 2554 search: true, 2555 date: true, 2556 display: false, 2557 sidebar: true, 2558 AttachmentView: Library 2559 }); 2560 2561 this.listenTo( this.controller, 'toggle:upload:attachment', _.bind( this.toggleUploader, this ) ); 2562 this.controller.on( 'edit:selection', this.editSelection ); 2563 this.createToolbar(); 2564 if ( this.options.sidebar ) { 2565 this.createSidebar(); 2566 } 2567 this.createUploader(); 2568 this.createAttachments(); 2569 this.updateContent(); 2570 2571 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 2572 this.$el.addClass( 'hide-sidebar' ); 2573 2574 if ( 'errors' === this.options.sidebar ) { 2575 this.$el.addClass( 'sidebar-for-errors' ); 2576 } 2577 } 2578 2579 this.collection.on( 'add remove reset', this.updateContent, this ); 2580 }, 2581 2582 editSelection: function( modal ) { 2583 modal.$( '.media-button-backToLibrary' ).focus(); 2584 }, 2585 2586 /** 2587 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining 2588 */ 2589 dispose: function() { 2590 this.options.selection.off( null, null, this ); 2591 View.prototype.dispose.apply( this, arguments ); 2592 return this; 2593 }, 2594 2595 createToolbar: function() { 2596 var LibraryViewSwitcher, Filters, toolbarOptions; 2597 2598 toolbarOptions = { 2599 controller: this.controller 2600 }; 2601 2602 if ( this.controller.isModeActive( 'grid' ) ) { 2603 toolbarOptions.className = 'media-toolbar wp-filter'; 2604 } 2605 2606 /** 2607 * @member {wp.media.view.Toolbar} 2608 */ 2609 this.toolbar = new Toolbar( toolbarOptions ); 2610 2611 this.views.add( this.toolbar ); 2612 2613 this.toolbar.set( 'spinner', new Spinner({ 2614 priority: -60 2615 }) ); 2616 2617 if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) { 2618 // "Filters" will return a <select>, need to render 2619 // screen reader text before 2620 this.toolbar.set( 'filtersLabel', new Label({ 2621 value: l10n.filterByType, 2622 attributes: { 2623 'for': 'media-attachment-filters' 2624 }, 2625 priority: -80 2626 }).render() ); 2627 2628 if ( 'uploaded' === this.options.filters ) { 2629 this.toolbar.set( 'filters', new Uploaded({ 2630 controller: this.controller, 2631 model: this.collection.props, 2632 priority: -80 2633 }).render() ); 2634 } else { 2635 Filters = new All({ 2636 controller: this.controller, 2637 model: this.collection.props, 2638 priority: -80 2639 }); 2640 2641 this.toolbar.set( 'filters', Filters.render() ); 2642 } 2643 } 2644 2645 // Feels odd to bring the global media library switcher into the Attachment 2646 // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 2647 // which the controller can tap into and add this view? 2648 if ( this.controller.isModeActive( 'grid' ) ) { 2649 LibraryViewSwitcher = View.extend({ 2650 className: 'view-switch media-grid-view-switch', 2651 template: wp.template( 'media-library-view-switcher') 2652 }); 2653 2654 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 2655 controller: this.controller, 2656 priority: -90 2657 }).render() ); 2658 2659 // DateFilter is a <select>, screen reader text needs to be rendered before 2660 this.toolbar.set( 'dateFilterLabel', new Label({ 2661 value: l10n.filterByDate, 2662 attributes: { 2663 'for': 'media-attachment-date-filters' 2664 }, 2665 priority: -75 2666 }).render() ); 2667 this.toolbar.set( 'dateFilter', new DateFilter({ 2668 controller: this.controller, 2669 model: this.collection.props, 2670 priority: -75 2671 }).render() ); 2672 2673 // BulkSelection is a <div> with subviews, including screen reader text 2674 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 2675 text: l10n.bulkSelect, 2676 controller: this.controller, 2677 priority: -70 2678 }).render() ); 2679 2680 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 2681 filters: Filters, 2682 style: 'primary', 2683 disabled: true, 2684 text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected, 2685 controller: this.controller, 2686 priority: -60, 2687 click: function() { 2688 var changed = [], removed = [], 2689 selection = this.controller.state().get( 'selection' ), 2690 library = this.controller.state().get( 'library' ); 2691 2692 if ( ! selection.length ) { 2693 return; 2694 } 2695 2696 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 2697 return; 2698 } 2699 2700 if ( mediaTrash && 2701 'trash' !== selection.at( 0 ).get( 'status' ) && 2702 ! window.confirm( l10n.warnBulkTrash ) ) { 2703 2704 return; 2705 } 2706 2707 selection.each( function( model ) { 2708 if ( ! model.get( 'nonces' )['delete'] ) { 2709 removed.push( model ); 2710 return; 2711 } 2712 2713 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 2714 model.set( 'status', 'inherit' ); 2715 changed.push( model.save() ); 2716 removed.push( model ); 2717 } else if ( mediaTrash ) { 2718 model.set( 'status', 'trash' ); 2719 changed.push( model.save() ); 2720 removed.push( model ); 2721 } else { 2722 model.destroy({wait: true}); 2723 } 2724 } ); 2725 2726 if ( changed.length ) { 2727 selection.remove( removed ); 2728 2729 $.when.apply( null, changed ).then( _.bind( function() { 2730 library._requery( true ); 2731 this.controller.trigger( 'selection:action:done' ); 2732 }, this ) ); 2733 } else { 2734 this.controller.trigger( 'selection:action:done' ); 2735 } 2736 } 2737 }).render() ); 2738 2739 if ( mediaTrash ) { 2740 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 2741 filters: Filters, 2742 style: 'primary', 2743 disabled: true, 2744 text: l10n.deleteSelected, 2745 controller: this.controller, 2746 priority: -55, 2747 click: function() { 2748 var removed = [], selection = this.controller.state().get( 'selection' ); 2749 2750 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 2751 return; 2752 } 2753 2754 selection.each( function( model ) { 2755 if ( ! model.get( 'nonces' )['delete'] ) { 2756 removed.push( model ); 2757 return; 2758 } 2759 2760 model.destroy(); 2761 } ); 2762 2763 selection.remove( removed ); 2764 this.controller.trigger( 'selection:action:done' ); 2765 } 2766 }).render() ); 2767 } 2768 2769 } else if ( this.options.date ) { 2770 // DateFilter is a <select>, screen reader text needs to be rendered before 2771 this.toolbar.set( 'dateFilterLabel', new Label({ 2772 value: l10n.filterByDate, 2773 attributes: { 2774 'for': 'media-attachment-date-filters' 2775 }, 2776 priority: -75 2777 }).render() ); 2778 this.toolbar.set( 'dateFilter', new DateFilter({ 2779 controller: this.controller, 2780 model: this.collection.props, 2781 priority: -75 2782 }).render() ); 2783 } 2784 2785 if ( this.options.search ) { 2786 // Search is an input, screen reader text needs to be rendered before 2787 this.toolbar.set( 'searchLabel', new Label({ 2788 value: l10n.searchMediaLabel, 2789 attributes: { 2790 'for': 'media-search-input' 2791 }, 2792 priority: 60 2793 }).render() ); 2794 this.toolbar.set( 'search', new Search({ 2795 controller: this.controller, 2796 model: this.collection.props, 2797 priority: 60 2798 }).render() ); 2799 } 2800 2801 if ( this.options.dragInfo ) { 2802 this.toolbar.set( 'dragInfo', new View({ 2803 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0], 2804 priority: -40 2805 }) ); 2806 } 2807 2808 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 2809 this.toolbar.set( 'suggestedDimensions', new View({ 2810 el: $( '<div class="instructions">' + l10n.suggestedDimensions + ' ' + this.options.suggestedWidth + ' × ' + this.options.suggestedHeight + '</div>' )[0], 2811 priority: -40 2812 }) ); 2813 } 2814 }, 2815 2816 updateContent: function() { 2817 var view = this, 2818 noItemsView; 2819 2820 if ( this.controller.isModeActive( 'grid' ) ) { 2821 noItemsView = view.attachmentsNoResults; 2822 } else { 2823 noItemsView = view.uploader; 2824 } 2825 2826 if ( ! this.collection.length ) { 2827 this.toolbar.get( 'spinner' ).show(); 2828 this.dfd = this.collection.more().done( function() { 2829 if ( ! view.collection.length ) { 2830 noItemsView.$el.removeClass( 'hidden' ); 2831 } else { 2832 noItemsView.$el.addClass( 'hidden' ); 2833 } 2834 view.toolbar.get( 'spinner' ).hide(); 2835 } ); 2836 } else { 2837 noItemsView.$el.addClass( 'hidden' ); 2838 view.toolbar.get( 'spinner' ).hide(); 2839 } 2840 }, 2841 2842 createUploader: function() { 2843 this.uploader = new UploaderInline({ 2844 controller: this.controller, 2845 status: false, 2846 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 2847 canClose: this.controller.isModeActive( 'grid' ) 2848 }); 2849 2850 this.uploader.hide(); 2851 this.views.add( this.uploader ); 2852 }, 2853 2854 toggleUploader: function() { 2855 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 2856 this.uploader.show(); 2857 } else { 2858 this.uploader.hide(); 2859 } 2860 }, 2861 2862 createAttachments: function() { 2863 this.attachments = new Attachments({ 2864 controller: this.controller, 2865 collection: this.collection, 2866 selection: this.options.selection, 2867 model: this.model, 2868 sortable: this.options.sortable, 2869 scrollElement: this.options.scrollElement, 2870 idealColumnWidth: this.options.idealColumnWidth, 2871 2872 // The single `Attachment` view to be used in the `Attachments` view. 2873 AttachmentView: this.options.AttachmentView 2874 }); 2875 2876 // Add keydown listener to the instance of the Attachments view 2877 this.attachments.listenTo( this.controller, 'attachment:keydown:arrow', this.attachments.arrowEvent ); 2878 this.attachments.listenTo( this.controller, 'attachment:details:shift-tab', this.attachments.restoreFocus ); 2879 2880 this.views.add( this.attachments ); 2881 2882 2883 if ( this.controller.isModeActive( 'grid' ) ) { 2884 this.attachmentsNoResults = new View({ 2885 controller: this.controller, 2886 tagName: 'p' 2887 }); 2888 2889 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 2890 this.attachmentsNoResults.$el.html( l10n.noMedia ); 2891 2892 this.views.add( this.attachmentsNoResults ); 2893 } 2894 }, 2895 2896 createSidebar: function() { 2897 var options = this.options, 2898 selection = options.selection, 2899 sidebar = this.sidebar = new Sidebar({ 2900 controller: this.controller 2901 }); 2902 2903 this.views.add( sidebar ); 2904 2905 if ( this.controller.uploader ) { 2906 sidebar.set( 'uploads', new UploaderStatus({ 2907 controller: this.controller, 2908 priority: 40 2909 }) ); 2910 } 2911 2912 selection.on( 'selection:single', this.createSingle, this ); 2913 selection.on( 'selection:unsingle', this.disposeSingle, this ); 2914 2915 if ( selection.single() ) { 2916 this.createSingle(); 2917 } 2918 }, 2919 2920 createSingle: function() { 2921 var sidebar = this.sidebar, 2922 single = this.options.selection.single(); 2923 2924 sidebar.set( 'details', new Details({ 2925 controller: this.controller, 2926 model: single, 2927 priority: 80 2928 }) ); 2929 2930 sidebar.set( 'compat', new AttachmentCompat({ 2931 controller: this.controller, 2932 model: single, 2933 priority: 120 2934 }) ); 2935 2936 if ( this.options.display ) { 2937 sidebar.set( 'display', new AttachmentDisplay({ 2938 controller: this.controller, 2939 model: this.model.display( single ), 2940 attachment: single, 2941 priority: 160, 2942 userSettings: this.model.get('displayUserSettings') 2943 }) ); 2944 } 2945 2946 // Show the sidebar on mobile 2947 if ( this.model.id === 'insert' ) { 2948 sidebar.$el.addClass( 'visible' ); 2949 } 2950 }, 2951 2952 disposeSingle: function() { 2953 var sidebar = this.sidebar; 2954 sidebar.unset('details'); 2955 sidebar.unset('compat'); 2956 sidebar.unset('display'); 2957 // Hide the sidebar on mobile 2958 sidebar.$el.removeClass( 'visible' ); 2959 } 2960 }); 2961 2962 module.exports = AttachmentsBrowser; 2963 2964 },{"../attachment-compat.js":10,"../attachment-filters/all.js":12,"../attachment-filters/date.js":13,"../attachment-filters/uploaded.js":14,"../attachment/details.js":17,"../attachment/library.js":18,"../attachments.js":19,"../label.js":31,"../search.js":39,"../settings/attachment-display.js":41,"../sidebar.js":42,"../spinner.js":43,"../toolbar.js":44,"../uploader/inline.js":45,"../uploader/status.js":47,"../view.js":49}],21:[function(require,module,exports){ 2965 /*globals _, Backbone */ 2966 2967 /** 2968 * wp.media.view.Button 2969 * 2970 * @class 2971 * @augments wp.media.View 2972 * @augments wp.Backbone.View 2973 * @augments Backbone.View 2974 */ 2975 var View = require( './view.js' ), 2976 Button; 2977 2978 Button = View.extend({ 2979 tagName: 'a', 2980 className: 'media-button', 2981 attributes: { href: '#' }, 2982 2983 events: { 2984 'click': 'click' 2985 }, 2986 2987 defaults: { 2988 text: '', 2989 style: '', 2990 size: 'large', 2991 disabled: false 2992 }, 2993 2994 initialize: function() { 2995 /** 2996 * Create a model with the provided `defaults`. 2997 * 2998 * @member {Backbone.Model} 2999 */ 3000 this.model = new Backbone.Model( this.defaults ); 3001 3002 // If any of the `options` have a key from `defaults`, apply its 3003 // value to the `model` and remove it from the `options object. 3004 _.each( this.defaults, function( def, key ) { 3005 var value = this.options[ key ]; 3006 if ( _.isUndefined( value ) ) { 3007 return; 3008 } 3009 3010 this.model.set( key, value ); 3011 delete this.options[ key ]; 3012 }, this ); 3013 3014 this.listenTo( this.model, 'change', this.render ); 3015 }, 3016 /** 3017 * @returns {wp.media.view.Button} Returns itself to allow chaining 3018 */ 3019 render: function() { 3020 var classes = [ 'button', this.className ], 3021 model = this.model.toJSON(); 3022 3023 if ( model.style ) { 3024 classes.push( 'button-' + model.style ); 3025 } 3026 3027 if ( model.size ) { 3028 classes.push( 'button-' + model.size ); 3029 } 3030 3031 classes = _.uniq( classes.concat( this.options.classes ) ); 3032 this.el.className = classes.join(' '); 3033 3034 this.$el.attr( 'disabled', model.disabled ); 3035 this.$el.text( this.model.get('text') ); 3036 3037 return this; 3038 }, 3039 /** 3040 * @param {Object} event 3041 */ 3042 click: function( event ) { 3043 if ( '#' === this.attributes.href ) { 3044 event.preventDefault(); 3045 } 3046 3047 if ( this.options.click && ! this.model.get('disabled') ) { 3048 this.options.click.apply( this, arguments ); 3049 } 3050 } 3051 }); 3052 3053 module.exports = Button; 3054 3055 },{"./view.js":49}],22:[function(require,module,exports){ 142 },{}],5:[function(require,module,exports){ 3056 143 /** 3057 144 * wp.media.view.DeleteSelectedPermanentlyButton … … 3066 153 * @augments Backbone.View 3067 154 */ 3068 var Button = require( '../button.js' ),155 var Button = wp.media.view.Button, 3069 156 DeleteSelected = require( './delete-selected.js' ), 3070 157 DeleteSelectedPermanently; … … 3100 187 module.exports = DeleteSelectedPermanently; 3101 188 3102 },{". ./button.js":21,"./delete-selected.js":23}],23:[function(require,module,exports){189 },{"./delete-selected.js":6}],6:[function(require,module,exports){ 3103 190 /*globals wp */ 3104 191 … … 3114 201 * @augments Backbone.View 3115 202 */ 3116 var Button = require( '../button.js' ),203 var Button = wp.media.view.Button, 3117 204 l10n = wp.media.view.l10n, 3118 205 DeleteSelected; … … 3155 242 module.exports = DeleteSelected; 3156 243 3157 },{ "../button.js":21}],24:[function(require,module,exports){244 },{}],7:[function(require,module,exports){ 3158 245 /*globals wp */ 3159 246 … … 3167 254 * @augments Backbone.View 3168 255 */ 3169 var Button = require( '../button.js' ),256 var Button = wp.media.view.Button, 3170 257 l10n = wp.media.view.l10n, 3171 258 SelectModeToggle; … … 3221 308 module.exports = SelectModeToggle; 3222 309 3223 },{ "../button.js":21}],25:[function(require,module,exports){310 },{}],8:[function(require,module,exports){ 3224 311 /*globals wp, _ */ 3225 312 … … 3228 315 * 3229 316 * @class 3230 * @augments wp.media.view.EditImage .Details317 * @augments wp.media.view.EditImage 3231 318 * @augments wp.media.View 3232 319 * @augments wp.Backbone.View 3233 320 * @augments Backbone.View 3234 321 */ 3235 var View = require( './view.js' ),322 var View = wp.media.View, 3236 323 EditImage = wp.media.view.EditImage, 3237 324 Details; … … 3258 345 module.exports = Details; 3259 346 3260 },{"./view.js":49}],26:[function(require,module,exports){ 3261 /** 3262 * wp.media.view.FocusManager 3263 * 3264 * @class 3265 * @augments wp.media.View 3266 * @augments wp.Backbone.View 3267 * @augments Backbone.View 3268 */ 3269 var View = require( './view.js' ), 3270 FocusManager; 3271 3272 FocusManager = View.extend({ 3273 3274 events: { 3275 'keydown': 'constrainTabbing' 3276 }, 3277 3278 focus: function() { // Reset focus on first left menu item 3279 this.$('.media-menu-item').first().focus(); 3280 }, 3281 /** 3282 * @param {Object} event 3283 */ 3284 constrainTabbing: function( event ) { 3285 var tabbables; 3286 3287 // Look for the tab key. 3288 if ( 9 !== event.keyCode ) { 3289 return; 3290 } 3291 3292 // Skip the file input added by Plupload. 3293 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 3294 3295 // Keep tab focus within media modal while it's open 3296 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 3297 tabbables.first().focus(); 3298 return false; 3299 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 3300 tabbables.last().focus(); 3301 return false; 3302 } 3303 } 3304 3305 }); 3306 3307 module.exports = FocusManager; 3308 3309 },{"./view.js":49}],27:[function(require,module,exports){ 3310 /*globals _, Backbone */ 3311 3312 /** 3313 * wp.media.view.Frame 3314 * 3315 * A frame is a composite view consisting of one or more regions and one or more 3316 * states. 3317 * 3318 * @see wp.media.controller.State 3319 * @see wp.media.controller.Region 3320 * 3321 * @class 3322 * @augments wp.media.View 3323 * @augments wp.Backbone.View 3324 * @augments Backbone.View 3325 * @mixes wp.media.controller.StateMachine 3326 */ 3327 var StateMachine = require( '../controllers/state-machine.js' ), 3328 State = require( '../controllers/state.js' ), 3329 Region = require( '../controllers/region.js' ), 3330 View = require( './view.js' ), 3331 Frame; 3332 3333 Frame = View.extend({ 3334 initialize: function() { 3335 _.defaults( this.options, { 3336 mode: [ 'select' ] 3337 }); 3338 this._createRegions(); 3339 this._createStates(); 3340 this._createModes(); 3341 }, 3342 3343 _createRegions: function() { 3344 // Clone the regions array. 3345 this.regions = this.regions ? this.regions.slice() : []; 3346 3347 // Initialize regions. 3348 _.each( this.regions, function( region ) { 3349 this[ region ] = new Region({ 3350 view: this, 3351 id: region, 3352 selector: '.media-frame-' + region 3353 }); 3354 }, this ); 3355 }, 3356 /** 3357 * Create the frame's states. 3358 * 3359 * @see wp.media.controller.State 3360 * @see wp.media.controller.StateMachine 3361 * 3362 * @fires wp.media.controller.State#ready 3363 */ 3364 _createStates: function() { 3365 // Create the default `states` collection. 3366 this.states = new Backbone.Collection( null, { 3367 model: State 3368 }); 3369 3370 // Ensure states have a reference to the frame. 3371 this.states.on( 'add', function( model ) { 3372 model.frame = this; 3373 model.trigger('ready'); 3374 }, this ); 3375 3376 if ( this.options.states ) { 3377 this.states.add( this.options.states ); 3378 } 3379 }, 3380 3381 /** 3382 * A frame can be in a mode or multiple modes at one time. 3383 * 3384 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 3385 */ 3386 _createModes: function() { 3387 // Store active "modes" that the frame is in. Unrelated to region modes. 3388 this.activeModes = new Backbone.Collection(); 3389 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 3390 3391 _.each( this.options.mode, function( mode ) { 3392 this.activateMode( mode ); 3393 }, this ); 3394 }, 3395 /** 3396 * Reset all states on the frame to their defaults. 3397 * 3398 * @returns {wp.media.view.Frame} Returns itself to allow chaining 3399 */ 3400 reset: function() { 3401 this.states.invoke( 'trigger', 'reset' ); 3402 return this; 3403 }, 3404 /** 3405 * Map activeMode collection events to the frame. 3406 */ 3407 triggerModeEvents: function( model, collection, options ) { 3408 var collectionEvent, 3409 modeEventMap = { 3410 add: 'activate', 3411 remove: 'deactivate' 3412 }, 3413 eventToTrigger; 3414 // Probably a better way to do this. 3415 _.each( options, function( value, key ) { 3416 if ( value ) { 3417 collectionEvent = key; 3418 } 3419 } ); 3420 3421 if ( ! _.has( modeEventMap, collectionEvent ) ) { 3422 return; 3423 } 3424 3425 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 3426 this.trigger( eventToTrigger ); 3427 }, 3428 /** 3429 * Activate a mode on the frame. 3430 * 3431 * @param string mode Mode ID. 3432 * @returns {this} Returns itself to allow chaining. 3433 */ 3434 activateMode: function( mode ) { 3435 // Bail if the mode is already active. 3436 if ( this.isModeActive( mode ) ) { 3437 return; 3438 } 3439 this.activeModes.add( [ { id: mode } ] ); 3440 // Add a CSS class to the frame so elements can be styled for the mode. 3441 this.$el.addClass( 'mode-' + mode ); 3442 3443 return this; 3444 }, 3445 /** 3446 * Deactivate a mode on the frame. 3447 * 3448 * @param string mode Mode ID. 3449 * @returns {this} Returns itself to allow chaining. 3450 */ 3451 deactivateMode: function( mode ) { 3452 // Bail if the mode isn't active. 3453 if ( ! this.isModeActive( mode ) ) { 3454 return this; 3455 } 3456 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 3457 this.$el.removeClass( 'mode-' + mode ); 3458 /** 3459 * Frame mode deactivation event. 3460 * 3461 * @event this#{mode}:deactivate 3462 */ 3463 this.trigger( mode + ':deactivate' ); 3464 3465 return this; 3466 }, 3467 /** 3468 * Check if a mode is enabled on the frame. 3469 * 3470 * @param string mode Mode ID. 3471 * @return bool 3472 */ 3473 isModeActive: function( mode ) { 3474 return Boolean( this.activeModes.where( { id: mode } ).length ); 3475 } 3476 }); 3477 3478 // Make the `Frame` a `StateMachine`. 3479 _.extend( Frame.prototype, StateMachine.prototype ); 3480 3481 module.exports = Frame; 3482 3483 },{"../controllers/region.js":4,"../controllers/state-machine.js":5,"../controllers/state.js":6,"./view.js":49}],28:[function(require,module,exports){ 347 },{}],9:[function(require,module,exports){ 3484 348 /*globals wp, _, jQuery */ 3485 349 … … 3500 364 * @mixes wp.media.controller.StateMachine 3501 365 */ 3502 var Frame = require( '../frame.js' ), 3503 MediaFrame = require( '../media-frame.js' ), 3504 Modal = require( '../modal.js' ), 366 var Frame = wp.media.view.Frame, 367 MediaFrame = wp.media.view.MediaFrame, 368 Modal = wp.media.view.Modal, 369 AttachmentCompat = wp.media.view.AttachmentCompat, 370 EditImageController = wp.media.controller.EditImage, 371 3505 372 EditAttachmentMetadata = require( '../../controllers/edit-attachment-metadata.js' ), 3506 373 TwoColumn = require( '../attachment/details-two-column.js' ), 3507 AttachmentCompat = require( '../attachment-compat.js' ),3508 EditImageController = require( '../../controllers/edit-image.js' ),3509 374 DetailsView = require( '../edit-image-details.js' ), 375 3510 376 $ = jQuery, 3511 377 EditAttachments; … … 3731 597 module.exports = EditAttachments; 3732 598 3733 },{"../../controllers/edit-attachment-metadata.js":1,"../ ../controllers/edit-image.js":2,"../attachment-compat.js":10,"../attachment/details-two-column.js":16,"../edit-image-details.js":25,"../frame.js":27,"../media-frame.js":32,"../modal.js":35}],29:[function(require,module,exports){599 },{"../../controllers/edit-attachment-metadata.js":1,"../attachment/details-two-column.js":4,"../edit-image-details.js":8}],10:[function(require,module,exports){ 3734 600 /*globals wp, _, jQuery, Backbone */ 3735 601 … … 3749 615 * @mixes wp.media.controller.StateMachine 3750 616 */ 3751 var MediaFrame = require( '../media-frame.js' ), 3752 UploaderWindow = require( '../uploader/window.js' ), 3753 AttachmentsBrowser = require( '../attachments/browser.js' ), 617 var MediaFrame = wp.media.view.MediaFrame, 618 UploaderWindow = wp.media.view.UploaderWindow, 619 AttachmentsBrowser = wp.media.view.AttachmentsBrowser, 620 Library = wp.media.controller.Library, 621 3754 622 Router = require( '../../routers/manage.js' ), 3755 Library = require( '../../controllers/library.js' ), 623 3756 624 $ = jQuery, 3757 625 Manage; … … 3980 848 module.exports = Manage; 3981 849 3982 },{"../../controllers/library.js":3,"../../routers/manage.js":8,"../attachments/browser.js":20,"../media-frame.js":32,"../uploader/window.js":48}],30:[function(require,module,exports){ 3983 /** 3984 * wp.media.view.Iframe 3985 * 3986 * @class 3987 * @augments wp.media.View 3988 * @augments wp.Backbone.View 3989 * @augments Backbone.View 3990 */ 3991 var View = require( './view.js' ), 3992 Iframe; 3993 3994 Iframe = View.extend({ 3995 className: 'media-iframe', 3996 /** 3997 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 3998 */ 3999 render: function() { 4000 this.views.detach(); 4001 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 4002 this.views.render(); 4003 return this; 4004 } 4005 }); 4006 4007 module.exports = Iframe; 4008 4009 },{"./view.js":49}],31:[function(require,module,exports){ 4010 /** 4011 * wp.media.view.Label 4012 * 4013 * @class 4014 * @augments wp.media.View 4015 * @augments wp.Backbone.View 4016 * @augments Backbone.View 4017 */ 4018 var View = require( './view.js' ), 4019 Label; 4020 4021 Label = View.extend({ 4022 tagName: 'label', 4023 className: 'screen-reader-text', 4024 4025 initialize: function() { 4026 this.value = this.options.value; 4027 }, 4028 4029 render: function() { 4030 this.$el.html( this.value ); 4031 4032 return this; 4033 } 4034 }); 4035 4036 module.exports = Label; 4037 4038 },{"./view.js":49}],32:[function(require,module,exports){ 4039 /*globals wp, _, jQuery */ 4040 4041 /** 4042 * wp.media.view.MediaFrame 4043 * 4044 * The frame used to create the media modal. 4045 * 4046 * @class 4047 * @augments wp.media.view.Frame 4048 * @augments wp.media.View 4049 * @augments wp.Backbone.View 4050 * @augments Backbone.View 4051 * @mixes wp.media.controller.StateMachine 4052 */ 4053 var View = require( './view.js' ), 4054 Frame = require( './frame.js' ), 4055 Modal = require( './modal.js' ), 4056 UploaderWindow = require( './uploader/window.js' ), 4057 Menu = require( './menu.js' ), 4058 Toolbar = require( './toolbar.js' ), 4059 Router = require( './router.js' ), 4060 Iframe = require( './iframe.js' ), 4061 $ = jQuery, 4062 MediaFrame; 4063 4064 MediaFrame = Frame.extend({ 4065 className: 'media-frame', 4066 template: wp.template('media-frame'), 4067 regions: ['menu','title','content','toolbar','router'], 4068 4069 events: { 4070 'click div.media-frame-title h1': 'toggleMenu' 4071 }, 4072 4073 /** 4074 * @global wp.Uploader 4075 */ 4076 initialize: function() { 4077 Frame.prototype.initialize.apply( this, arguments ); 4078 4079 _.defaults( this.options, { 4080 title: '', 4081 modal: true, 4082 uploader: true 4083 }); 4084 4085 // Ensure core UI is enabled. 4086 this.$el.addClass('wp-core-ui'); 4087 4088 // Initialize modal container view. 4089 if ( this.options.modal ) { 4090 this.modal = new Modal({ 4091 controller: this, 4092 title: this.options.title 4093 }); 4094 4095 this.modal.content( this ); 4096 } 4097 4098 // Force the uploader off if the upload limit has been exceeded or 4099 // if the browser isn't supported. 4100 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 4101 this.options.uploader = false; 4102 } 4103 4104 // Initialize window-wide uploader. 4105 if ( this.options.uploader ) { 4106 this.uploader = new UploaderWindow({ 4107 controller: this, 4108 uploader: { 4109 dropzone: this.modal ? this.modal.$el : this.$el, 4110 container: this.$el 4111 } 4112 }); 4113 this.views.set( '.media-frame-uploader', this.uploader ); 4114 } 4115 4116 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 4117 4118 // Bind default title creation. 4119 this.on( 'title:create:default', this.createTitle, this ); 4120 this.title.mode('default'); 4121 4122 this.on( 'title:render', function( view ) { 4123 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 4124 }); 4125 4126 // Bind default menu. 4127 this.on( 'menu:create:default', this.createMenu, this ); 4128 }, 4129 /** 4130 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 4131 */ 4132 render: function() { 4133 // Activate the default state if no active state exists. 4134 if ( ! this.state() && this.options.state ) { 4135 this.setState( this.options.state ); 4136 } 4137 /** 4138 * call 'render' directly on the parent class 4139 */ 4140 return Frame.prototype.render.apply( this, arguments ); 4141 }, 4142 /** 4143 * @param {Object} title 4144 * @this wp.media.controller.Region 4145 */ 4146 createTitle: function( title ) { 4147 title.view = new View({ 4148 controller: this, 4149 tagName: 'h1' 4150 }); 4151 }, 4152 /** 4153 * @param {Object} menu 4154 * @this wp.media.controller.Region 4155 */ 4156 createMenu: function( menu ) { 4157 menu.view = new Menu({ 4158 controller: this 4159 }); 4160 }, 4161 4162 toggleMenu: function() { 4163 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 4164 }, 4165 4166 /** 4167 * @param {Object} toolbar 4168 * @this wp.media.controller.Region 4169 */ 4170 createToolbar: function( toolbar ) { 4171 toolbar.view = new Toolbar({ 4172 controller: this 4173 }); 4174 }, 4175 /** 4176 * @param {Object} router 4177 * @this wp.media.controller.Region 4178 */ 4179 createRouter: function( router ) { 4180 router.view = new Router({ 4181 controller: this 4182 }); 4183 }, 4184 /** 4185 * @param {Object} options 4186 */ 4187 createIframeStates: function( options ) { 4188 var settings = wp.media.view.settings, 4189 tabs = settings.tabs, 4190 tabUrl = settings.tabUrl, 4191 $postId; 4192 4193 if ( ! tabs || ! tabUrl ) { 4194 return; 4195 } 4196 4197 // Add the post ID to the tab URL if it exists. 4198 $postId = $('#post_ID'); 4199 if ( $postId.length ) { 4200 tabUrl += '&post_id=' + $postId.val(); 4201 } 4202 4203 // Generate the tab states. 4204 _.each( tabs, function( title, id ) { 4205 this.state( 'iframe:' + id ).set( _.defaults({ 4206 tab: id, 4207 src: tabUrl + '&tab=' + id, 4208 title: title, 4209 content: 'iframe', 4210 menu: 'default' 4211 }, options ) ); 4212 }, this ); 4213 4214 this.on( 'content:create:iframe', this.iframeContent, this ); 4215 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 4216 this.on( 'menu:render:default', this.iframeMenu, this ); 4217 this.on( 'open', this.hijackThickbox, this ); 4218 this.on( 'close', this.restoreThickbox, this ); 4219 }, 4220 4221 /** 4222 * @param {Object} content 4223 * @this wp.media.controller.Region 4224 */ 4225 iframeContent: function( content ) { 4226 this.$el.addClass('hide-toolbar'); 4227 content.view = new Iframe({ 4228 controller: this 4229 }); 4230 }, 4231 4232 iframeContentCleanup: function() { 4233 this.$el.removeClass('hide-toolbar'); 4234 }, 4235 4236 iframeMenu: function( view ) { 4237 var views = {}; 4238 4239 if ( ! view ) { 4240 return; 4241 } 4242 4243 _.each( wp.media.view.settings.tabs, function( title, id ) { 4244 views[ 'iframe:' + id ] = { 4245 text: this.state( 'iframe:' + id ).get('title'), 4246 priority: 200 4247 }; 4248 }, this ); 4249 4250 view.set( views ); 4251 }, 4252 4253 hijackThickbox: function() { 4254 var frame = this; 4255 4256 if ( ! window.tb_remove || this._tb_remove ) { 4257 return; 4258 } 4259 4260 this._tb_remove = window.tb_remove; 4261 window.tb_remove = function() { 4262 frame.close(); 4263 frame.reset(); 4264 frame.setState( frame.options.state ); 4265 frame._tb_remove.call( window ); 4266 }; 4267 }, 4268 4269 restoreThickbox: function() { 4270 if ( ! this._tb_remove ) { 4271 return; 4272 } 4273 4274 window.tb_remove = this._tb_remove; 4275 delete this._tb_remove; 4276 } 4277 }); 4278 4279 // Map some of the modal's methods to the frame. 4280 _.each(['open','close','attach','detach','escape'], function( method ) { 4281 /** 4282 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 4283 */ 4284 MediaFrame.prototype[ method ] = function() { 4285 if ( this.modal ) { 4286 this.modal[ method ].apply( this.modal, arguments ); 4287 } 4288 return this; 4289 }; 4290 }); 4291 4292 module.exports = MediaFrame; 4293 4294 },{"./frame.js":27,"./iframe.js":30,"./menu.js":34,"./modal.js":35,"./router.js":38,"./toolbar.js":44,"./uploader/window.js":48,"./view.js":49}],33:[function(require,module,exports){ 4295 /*globals jQuery */ 4296 4297 /** 4298 * wp.media.view.MenuItem 4299 * 4300 * @class 4301 * @augments wp.media.View 4302 * @augments wp.Backbone.View 4303 * @augments Backbone.View 4304 */ 4305 var View = require( './view.js' ), 4306 $ = jQuery, 4307 MenuItem; 4308 4309 MenuItem = View.extend({ 4310 tagName: 'a', 4311 className: 'media-menu-item', 4312 4313 attributes: { 4314 href: '#' 4315 }, 4316 4317 events: { 4318 'click': '_click' 4319 }, 4320 /** 4321 * @param {Object} event 4322 */ 4323 _click: function( event ) { 4324 var clickOverride = this.options.click; 4325 4326 if ( event ) { 4327 event.preventDefault(); 4328 } 4329 4330 if ( clickOverride ) { 4331 clickOverride.call( this ); 4332 } else { 4333 this.click(); 4334 } 4335 4336 // When selecting a tab along the left side, 4337 // focus should be transferred into the main panel 4338 if ( ! wp.media.isTouchDevice ) { 4339 $('.media-frame-content input').first().focus(); 4340 } 4341 }, 4342 4343 click: function() { 4344 var state = this.options.state; 4345 4346 if ( state ) { 4347 this.controller.setState( state ); 4348 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 4349 } 4350 }, 4351 /** 4352 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 4353 */ 4354 render: function() { 4355 var options = this.options; 4356 4357 if ( options.text ) { 4358 this.$el.text( options.text ); 4359 } else if ( options.html ) { 4360 this.$el.html( options.html ); 4361 } 4362 4363 return this; 4364 } 4365 }); 4366 4367 module.exports = MenuItem; 4368 4369 },{"./view.js":49}],34:[function(require,module,exports){ 4370 /** 4371 * wp.media.view.Menu 4372 * 4373 * @class 4374 * @augments wp.media.view.PriorityList 4375 * @augments wp.media.View 4376 * @augments wp.Backbone.View 4377 * @augments Backbone.View 4378 */ 4379 var MenuItem = require( './menu-item.js' ), 4380 PriorityList = require( './priority-list.js' ), 4381 Menu; 4382 4383 Menu = PriorityList.extend({ 4384 tagName: 'div', 4385 className: 'media-menu', 4386 property: 'state', 4387 ItemView: MenuItem, 4388 region: 'menu', 4389 4390 /* TODO: alternatively hide on any click anywhere 4391 events: { 4392 'click': 'click' 4393 }, 4394 4395 click: function() { 4396 this.$el.removeClass( 'visible' ); 4397 }, 4398 */ 4399 4400 /** 4401 * @param {Object} options 4402 * @param {string} id 4403 * @returns {wp.media.View} 4404 */ 4405 toView: function( options, id ) { 4406 options = options || {}; 4407 options[ this.property ] = options[ this.property ] || id; 4408 return new this.ItemView( options ).render(); 4409 }, 4410 4411 ready: function() { 4412 /** 4413 * call 'ready' directly on the parent class 4414 */ 4415 PriorityList.prototype.ready.apply( this, arguments ); 4416 this.visibility(); 4417 }, 4418 4419 set: function() { 4420 /** 4421 * call 'set' directly on the parent class 4422 */ 4423 PriorityList.prototype.set.apply( this, arguments ); 4424 this.visibility(); 4425 }, 4426 4427 unset: function() { 4428 /** 4429 * call 'unset' directly on the parent class 4430 */ 4431 PriorityList.prototype.unset.apply( this, arguments ); 4432 this.visibility(); 4433 }, 4434 4435 visibility: function() { 4436 var region = this.region, 4437 view = this.controller[ region ].get(), 4438 views = this.views.get(), 4439 hide = ! views || views.length < 2; 4440 4441 if ( this === view ) { 4442 this.controller.$el.toggleClass( 'hide-' + region, hide ); 4443 } 4444 }, 4445 /** 4446 * @param {string} id 4447 */ 4448 select: function( id ) { 4449 var view = this.get( id ); 4450 4451 if ( ! view ) { 4452 return; 4453 } 4454 4455 this.deselect(); 4456 view.$el.addClass('active'); 4457 }, 4458 4459 deselect: function() { 4460 this.$el.children().removeClass('active'); 4461 }, 4462 4463 hide: function( id ) { 4464 var view = this.get( id ); 4465 4466 if ( ! view ) { 4467 return; 4468 } 4469 4470 view.$el.addClass('hidden'); 4471 }, 4472 4473 show: function( id ) { 4474 var view = this.get( id ); 4475 4476 if ( ! view ) { 4477 return; 4478 } 4479 4480 view.$el.removeClass('hidden'); 4481 } 4482 }); 4483 4484 module.exports = Menu; 4485 4486 },{"./menu-item.js":33,"./priority-list.js":36}],35:[function(require,module,exports){ 4487 /*globals wp, _, jQuery */ 4488 4489 /** 4490 * wp.media.view.Modal 4491 * 4492 * A modal view, which the media modal uses as its default container. 4493 * 4494 * @class 4495 * @augments wp.media.View 4496 * @augments wp.Backbone.View 4497 * @augments Backbone.View 4498 */ 4499 var View = require( './view.js' ), 4500 FocusManager = require( './focus-manager.js' ), 4501 $ = jQuery, 4502 Modal; 4503 4504 Modal = View.extend({ 4505 tagName: 'div', 4506 template: wp.template('media-modal'), 4507 4508 attributes: { 4509 tabindex: 0 4510 }, 4511 4512 events: { 4513 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 4514 'keydown': 'keydown' 4515 }, 4516 4517 initialize: function() { 4518 _.defaults( this.options, { 4519 container: document.body, 4520 title: '', 4521 propagate: true, 4522 freeze: true 4523 }); 4524 4525 this.focusManager = new FocusManager({ 4526 el: this.el 4527 }); 4528 }, 4529 /** 4530 * @returns {Object} 4531 */ 4532 prepare: function() { 4533 return { 4534 title: this.options.title 4535 }; 4536 }, 4537 4538 /** 4539 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4540 */ 4541 attach: function() { 4542 if ( this.views.attached ) { 4543 return this; 4544 } 4545 4546 if ( ! this.views.rendered ) { 4547 this.render(); 4548 } 4549 4550 this.$el.appendTo( this.options.container ); 4551 4552 // Manually mark the view as attached and trigger ready. 4553 this.views.attached = true; 4554 this.views.ready(); 4555 4556 return this.propagate('attach'); 4557 }, 4558 4559 /** 4560 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4561 */ 4562 detach: function() { 4563 if ( this.$el.is(':visible') ) { 4564 this.close(); 4565 } 4566 4567 this.$el.detach(); 4568 this.views.attached = false; 4569 return this.propagate('detach'); 4570 }, 4571 4572 /** 4573 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4574 */ 4575 open: function() { 4576 var $el = this.$el, 4577 options = this.options, 4578 mceEditor; 4579 4580 if ( $el.is(':visible') ) { 4581 return this; 4582 } 4583 4584 if ( ! this.views.attached ) { 4585 this.attach(); 4586 } 4587 4588 // If the `freeze` option is set, record the window's scroll position. 4589 if ( options.freeze ) { 4590 this._freeze = { 4591 scrollTop: $( window ).scrollTop() 4592 }; 4593 } 4594 4595 // Disable page scrolling. 4596 $( 'body' ).addClass( 'modal-open' ); 4597 4598 $el.show(); 4599 4600 // Try to close the onscreen keyboard 4601 if ( 'ontouchend' in document ) { 4602 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 4603 mceEditor.iframeElement.focus(); 4604 mceEditor.iframeElement.blur(); 4605 4606 setTimeout( function() { 4607 mceEditor.iframeElement.blur(); 4608 }, 100 ); 4609 } 4610 } 4611 4612 this.$el.focus(); 4613 4614 return this.propagate('open'); 4615 }, 4616 4617 /** 4618 * @param {Object} options 4619 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4620 */ 4621 close: function( options ) { 4622 var freeze = this._freeze; 4623 4624 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 4625 return this; 4626 } 4627 4628 // Enable page scrolling. 4629 $( 'body' ).removeClass( 'modal-open' ); 4630 4631 // Hide modal and remove restricted media modal tab focus once it's closed 4632 this.$el.hide().undelegate( 'keydown' ); 4633 4634 // Put focus back in useful location once modal is closed 4635 $('#wpbody-content').focus(); 4636 4637 this.propagate('close'); 4638 4639 // If the `freeze` option is set, restore the container's scroll position. 4640 if ( freeze ) { 4641 $( window ).scrollTop( freeze.scrollTop ); 4642 } 4643 4644 if ( options && options.escape ) { 4645 this.propagate('escape'); 4646 } 4647 4648 return this; 4649 }, 4650 /** 4651 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4652 */ 4653 escape: function() { 4654 return this.close({ escape: true }); 4655 }, 4656 /** 4657 * @param {Object} event 4658 */ 4659 escapeHandler: function( event ) { 4660 event.preventDefault(); 4661 this.escape(); 4662 }, 4663 4664 /** 4665 * @param {Array|Object} content Views to register to '.media-modal-content' 4666 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4667 */ 4668 content: function( content ) { 4669 this.views.set( '.media-modal-content', content ); 4670 return this; 4671 }, 4672 4673 /** 4674 * Triggers a modal event and if the `propagate` option is set, 4675 * forwards events to the modal's controller. 4676 * 4677 * @param {string} id 4678 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4679 */ 4680 propagate: function( id ) { 4681 this.trigger( id ); 4682 4683 if ( this.options.propagate ) { 4684 this.controller.trigger( id ); 4685 } 4686 4687 return this; 4688 }, 4689 /** 4690 * @param {Object} event 4691 */ 4692 keydown: function( event ) { 4693 // Close the modal when escape is pressed. 4694 if ( 27 === event.which && this.$el.is(':visible') ) { 4695 this.escape(); 4696 event.stopImmediatePropagation(); 4697 } 4698 } 4699 }); 4700 4701 module.exports = Modal; 4702 4703 },{"./focus-manager.js":26,"./view.js":49}],36:[function(require,module,exports){ 4704 /*globals _, Backbone */ 4705 4706 /** 4707 * wp.media.view.PriorityList 4708 * 4709 * @class 4710 * @augments wp.media.View 4711 * @augments wp.Backbone.View 4712 * @augments Backbone.View 4713 */ 4714 var View = require( './view.js' ), 4715 PriorityList; 4716 4717 PriorityList = View.extend({ 4718 tagName: 'div', 4719 4720 initialize: function() { 4721 this._views = {}; 4722 4723 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 4724 delete this.options.views; 4725 4726 if ( ! this.options.silent ) { 4727 this.render(); 4728 } 4729 }, 4730 /** 4731 * @param {string} id 4732 * @param {wp.media.View|Object} view 4733 * @param {Object} options 4734 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 4735 */ 4736 set: function( id, view, options ) { 4737 var priority, views, index; 4738 4739 options = options || {}; 4740 4741 // Accept an object with an `id` : `view` mapping. 4742 if ( _.isObject( id ) ) { 4743 _.each( id, function( view, id ) { 4744 this.set( id, view ); 4745 }, this ); 4746 return this; 4747 } 4748 4749 if ( ! (view instanceof Backbone.View) ) { 4750 view = this.toView( view, id, options ); 4751 } 4752 view.controller = view.controller || this.controller; 4753 4754 this.unset( id ); 4755 4756 priority = view.options.priority || 10; 4757 views = this.views.get() || []; 4758 4759 _.find( views, function( existing, i ) { 4760 if ( existing.options.priority > priority ) { 4761 index = i; 4762 return true; 4763 } 4764 }); 4765 4766 this._views[ id ] = view; 4767 this.views.add( view, { 4768 at: _.isNumber( index ) ? index : views.length || 0 4769 }); 4770 4771 return this; 4772 }, 4773 /** 4774 * @param {string} id 4775 * @returns {wp.media.View} 4776 */ 4777 get: function( id ) { 4778 return this._views[ id ]; 4779 }, 4780 /** 4781 * @param {string} id 4782 * @returns {wp.media.view.PriorityList} 4783 */ 4784 unset: function( id ) { 4785 var view = this.get( id ); 4786 4787 if ( view ) { 4788 view.remove(); 4789 } 4790 4791 delete this._views[ id ]; 4792 return this; 4793 }, 4794 /** 4795 * @param {Object} options 4796 * @returns {wp.media.View} 4797 */ 4798 toView: function( options ) { 4799 return new View( options ); 4800 } 4801 }); 4802 4803 module.exports = PriorityList; 4804 4805 },{"./view.js":49}],37:[function(require,module,exports){ 4806 /** 4807 * wp.media.view.RouterItem 4808 * 4809 * @class 4810 * @augments wp.media.view.MenuItem 4811 * @augments wp.media.View 4812 * @augments wp.Backbone.View 4813 * @augments Backbone.View 4814 */ 4815 var MenuItem = require( './menu-item.js' ), 4816 RouterItem; 4817 4818 RouterItem = MenuItem.extend({ 4819 /** 4820 * On click handler to activate the content region's corresponding mode. 4821 */ 4822 click: function() { 4823 var contentMode = this.options.contentMode; 4824 if ( contentMode ) { 4825 this.controller.content.mode( contentMode ); 4826 } 4827 } 4828 }); 4829 4830 module.exports = RouterItem; 4831 4832 },{"./menu-item.js":33}],38:[function(require,module,exports){ 4833 /** 4834 * wp.media.view.Router 4835 * 4836 * @class 4837 * @augments wp.media.view.Menu 4838 * @augments wp.media.view.PriorityList 4839 * @augments wp.media.View 4840 * @augments wp.Backbone.View 4841 * @augments Backbone.View 4842 */ 4843 var Menu = require( './menu.js' ), 4844 RouterItem = require( './router-item.js' ), 4845 Router; 4846 4847 Router = Menu.extend({ 4848 tagName: 'div', 4849 className: 'media-router', 4850 property: 'contentMode', 4851 ItemView: RouterItem, 4852 region: 'router', 4853 4854 initialize: function() { 4855 this.controller.on( 'content:render', this.update, this ); 4856 // Call 'initialize' directly on the parent class. 4857 Menu.prototype.initialize.apply( this, arguments ); 4858 }, 4859 4860 update: function() { 4861 var mode = this.controller.content.mode(); 4862 if ( mode ) { 4863 this.select( mode ); 4864 } 4865 } 4866 }); 4867 4868 module.exports = Router; 4869 4870 },{"./menu.js":34,"./router-item.js":37}],39:[function(require,module,exports){ 4871 /*globals wp */ 4872 4873 /** 4874 * wp.media.view.Search 4875 * 4876 * @class 4877 * @augments wp.media.View 4878 * @augments wp.Backbone.View 4879 * @augments Backbone.View 4880 */ 4881 var View = require( './view.js' ), 4882 l10n = wp.media.view.l10n, 4883 Search; 4884 4885 Search = View.extend({ 4886 tagName: 'input', 4887 className: 'search', 4888 id: 'media-search-input', 4889 4890 attributes: { 4891 type: 'search', 4892 placeholder: l10n.search 4893 }, 4894 4895 events: { 4896 'input': 'search', 4897 'keyup': 'search', 4898 'change': 'search', 4899 'search': 'search' 4900 }, 4901 4902 /** 4903 * @returns {wp.media.view.Search} Returns itself to allow chaining 4904 */ 4905 render: function() { 4906 this.el.value = this.model.escape('search'); 4907 return this; 4908 }, 4909 4910 search: function( event ) { 4911 if ( event.target.value ) { 4912 this.model.set( 'search', event.target.value ); 4913 } else { 4914 this.model.unset('search'); 4915 } 4916 } 4917 }); 4918 4919 module.exports = Search; 4920 4921 },{"./view.js":49}],40:[function(require,module,exports){ 4922 /*globals _, jQuery, Backbone */ 4923 4924 /** 4925 * wp.media.view.Settings 4926 * 4927 * @class 4928 * @augments wp.media.View 4929 * @augments wp.Backbone.View 4930 * @augments Backbone.View 4931 */ 4932 var View = require( './view.js' ), 4933 $ = jQuery, 4934 Settings; 4935 4936 Settings = View.extend({ 4937 events: { 4938 'click button': 'updateHandler', 4939 'change input': 'updateHandler', 4940 'change select': 'updateHandler', 4941 'change textarea': 'updateHandler' 4942 }, 4943 4944 initialize: function() { 4945 this.model = this.model || new Backbone.Model(); 4946 this.listenTo( this.model, 'change', this.updateChanges ); 4947 }, 4948 4949 prepare: function() { 4950 return _.defaults({ 4951 model: this.model.toJSON() 4952 }, this.options ); 4953 }, 4954 /** 4955 * @returns {wp.media.view.Settings} Returns itself to allow chaining 4956 */ 4957 render: function() { 4958 View.prototype.render.apply( this, arguments ); 4959 // Select the correct values. 4960 _( this.model.attributes ).chain().keys().each( this.update, this ); 4961 return this; 4962 }, 4963 /** 4964 * @param {string} key 4965 */ 4966 update: function( key ) { 4967 var value = this.model.get( key ), 4968 $setting = this.$('[data-setting="' + key + '"]'), 4969 $buttons, $value; 4970 4971 // Bail if we didn't find a matching setting. 4972 if ( ! $setting.length ) { 4973 return; 4974 } 4975 4976 // Attempt to determine how the setting is rendered and update 4977 // the selected value. 4978 4979 // Handle dropdowns. 4980 if ( $setting.is('select') ) { 4981 $value = $setting.find('[value="' + value + '"]'); 4982 4983 if ( $value.length ) { 4984 $setting.find('option').prop( 'selected', false ); 4985 $value.prop( 'selected', true ); 4986 } else { 4987 // If we can't find the desired value, record what *is* selected. 4988 this.model.set( key, $setting.find(':selected').val() ); 4989 } 4990 4991 // Handle button groups. 4992 } else if ( $setting.hasClass('button-group') ) { 4993 $buttons = $setting.find('button').removeClass('active'); 4994 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 4995 4996 // Handle text inputs and textareas. 4997 } else if ( $setting.is('input[type="text"], textarea') ) { 4998 if ( ! $setting.is(':focus') ) { 4999 $setting.val( value ); 5000 } 5001 // Handle checkboxes. 5002 } else if ( $setting.is('input[type="checkbox"]') ) { 5003 $setting.prop( 'checked', !! value && 'false' !== value ); 5004 } 5005 }, 5006 /** 5007 * @param {Object} event 5008 */ 5009 updateHandler: function( event ) { 5010 var $setting = $( event.target ).closest('[data-setting]'), 5011 value = event.target.value, 5012 userSetting; 5013 5014 event.preventDefault(); 5015 5016 if ( ! $setting.length ) { 5017 return; 5018 } 5019 5020 // Use the correct value for checkboxes. 5021 if ( $setting.is('input[type="checkbox"]') ) { 5022 value = $setting[0].checked; 5023 } 5024 5025 // Update the corresponding setting. 5026 this.model.set( $setting.data('setting'), value ); 5027 5028 // If the setting has a corresponding user setting, 5029 // update that as well. 5030 if ( userSetting = $setting.data('userSetting') ) { 5031 window.setUserSetting( userSetting, value ); 5032 } 5033 }, 5034 5035 updateChanges: function( model ) { 5036 if ( model.hasChanged() ) { 5037 _( model.changed ).chain().keys().each( this.update, this ); 5038 } 5039 } 5040 }); 5041 5042 module.exports = Settings; 5043 5044 },{"./view.js":49}],41:[function(require,module,exports){ 5045 /*globals wp, _ */ 5046 5047 /** 5048 * wp.media.view.Settings.AttachmentDisplay 5049 * 5050 * @class 5051 * @augments wp.media.view.Settings 5052 * @augments wp.media.View 5053 * @augments wp.Backbone.View 5054 * @augments Backbone.View 5055 */ 5056 var Settings = require( '../settings.js' ), 5057 AttachmentDisplay; 5058 5059 AttachmentDisplay = Settings.extend({ 5060 className: 'attachment-display-settings', 5061 template: wp.template('attachment-display-settings'), 5062 5063 initialize: function() { 5064 var attachment = this.options.attachment; 5065 5066 _.defaults( this.options, { 5067 userSettings: false 5068 }); 5069 // Call 'initialize' directly on the parent class. 5070 Settings.prototype.initialize.apply( this, arguments ); 5071 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 5072 5073 if ( attachment ) { 5074 attachment.on( 'change:uploading', this.render, this ); 5075 } 5076 }, 5077 5078 dispose: function() { 5079 var attachment = this.options.attachment; 5080 if ( attachment ) { 5081 attachment.off( null, null, this ); 5082 } 5083 /** 5084 * call 'dispose' directly on the parent class 5085 */ 5086 Settings.prototype.dispose.apply( this, arguments ); 5087 }, 5088 /** 5089 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 5090 */ 5091 render: function() { 5092 var attachment = this.options.attachment; 5093 if ( attachment ) { 5094 _.extend( this.options, { 5095 sizes: attachment.get('sizes'), 5096 type: attachment.get('type') 5097 }); 5098 } 5099 /** 5100 * call 'render' directly on the parent class 5101 */ 5102 Settings.prototype.render.call( this ); 5103 this.updateLinkTo(); 5104 return this; 5105 }, 5106 5107 updateLinkTo: function() { 5108 var linkTo = this.model.get('link'), 5109 $input = this.$('.link-to-custom'), 5110 attachment = this.options.attachment; 5111 5112 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 5113 $input.addClass( 'hidden' ); 5114 return; 5115 } 5116 5117 if ( attachment ) { 5118 if ( 'post' === linkTo ) { 5119 $input.val( attachment.get('link') ); 5120 } else if ( 'file' === linkTo ) { 5121 $input.val( attachment.get('url') ); 5122 } else if ( ! this.model.get('linkUrl') ) { 5123 $input.val('http://'); 5124 } 5125 5126 $input.prop( 'readonly', 'custom' !== linkTo ); 5127 } 5128 5129 $input.removeClass( 'hidden' ); 5130 5131 // If the input is visible, focus and select its contents. 5132 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 5133 $input.focus()[0].select(); 5134 } 5135 } 5136 }); 5137 5138 module.exports = AttachmentDisplay; 5139 5140 },{"../settings.js":40}],42:[function(require,module,exports){ 5141 /** 5142 * wp.media.view.Sidebar 5143 * 5144 * @class 5145 * @augments wp.media.view.PriorityList 5146 * @augments wp.media.View 5147 * @augments wp.Backbone.View 5148 * @augments Backbone.View 5149 */ 5150 var PriorityList = require( './priority-list.js' ), 5151 Sidebar; 5152 5153 Sidebar = PriorityList.extend({ 5154 className: 'media-sidebar' 5155 }); 5156 5157 module.exports = Sidebar; 5158 5159 },{"./priority-list.js":36}],43:[function(require,module,exports){ 5160 /*globals _ */ 5161 5162 /** 5163 * wp.media.view.Spinner 5164 * 5165 * @class 5166 * @augments wp.media.View 5167 * @augments wp.Backbone.View 5168 * @augments Backbone.View 5169 */ 5170 var View = require( './view.js' ), 5171 Spinner; 5172 5173 Spinner = View.extend({ 5174 tagName: 'span', 5175 className: 'spinner', 5176 spinnerTimeout: false, 5177 delay: 400, 5178 5179 show: function() { 5180 if ( ! this.spinnerTimeout ) { 5181 this.spinnerTimeout = _.delay(function( $el ) { 5182 $el.show(); 5183 }, this.delay, this.$el ); 5184 } 5185 5186 return this; 5187 }, 5188 5189 hide: function() { 5190 this.$el.hide(); 5191 this.spinnerTimeout = clearTimeout( this.spinnerTimeout ); 5192 5193 return this; 5194 } 5195 }); 5196 5197 module.exports = Spinner; 5198 5199 },{"./view.js":49}],44:[function(require,module,exports){ 5200 /*globals _, Backbone */ 5201 5202 /** 5203 * wp.media.view.Toolbar 5204 * 5205 * A toolbar which consists of a primary and a secondary section. Each sections 5206 * can be filled with views. 5207 * 5208 * @class 5209 * @augments wp.media.View 5210 * @augments wp.Backbone.View 5211 * @augments Backbone.View 5212 */ 5213 var View = require( './view.js' ), 5214 Button = require( './button.js' ), 5215 PriorityList = require( './priority-list.js' ), 5216 Toolbar; 5217 5218 Toolbar = View.extend({ 5219 tagName: 'div', 5220 className: 'media-toolbar', 5221 5222 initialize: function() { 5223 var state = this.controller.state(), 5224 selection = this.selection = state.get('selection'), 5225 library = this.library = state.get('library'); 5226 5227 this._views = {}; 5228 5229 // The toolbar is composed of two `PriorityList` views. 5230 this.primary = new PriorityList(); 5231 this.secondary = new PriorityList(); 5232 this.primary.$el.addClass('media-toolbar-primary search-form'); 5233 this.secondary.$el.addClass('media-toolbar-secondary'); 5234 5235 this.views.set([ this.secondary, this.primary ]); 5236 5237 if ( this.options.items ) { 5238 this.set( this.options.items, { silent: true }); 5239 } 5240 5241 if ( ! this.options.silent ) { 5242 this.render(); 5243 } 5244 5245 if ( selection ) { 5246 selection.on( 'add remove reset', this.refresh, this ); 5247 } 5248 5249 if ( library ) { 5250 library.on( 'add remove reset', this.refresh, this ); 5251 } 5252 }, 5253 /** 5254 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 5255 */ 5256 dispose: function() { 5257 if ( this.selection ) { 5258 this.selection.off( null, null, this ); 5259 } 5260 5261 if ( this.library ) { 5262 this.library.off( null, null, this ); 5263 } 5264 /** 5265 * call 'dispose' directly on the parent class 5266 */ 5267 return View.prototype.dispose.apply( this, arguments ); 5268 }, 5269 5270 ready: function() { 5271 this.refresh(); 5272 }, 5273 5274 /** 5275 * @param {string} id 5276 * @param {Backbone.View|Object} view 5277 * @param {Object} [options={}] 5278 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5279 */ 5280 set: function( id, view, options ) { 5281 var list; 5282 options = options || {}; 5283 5284 // Accept an object with an `id` : `view` mapping. 5285 if ( _.isObject( id ) ) { 5286 _.each( id, function( view, id ) { 5287 this.set( id, view, { silent: true }); 5288 }, this ); 5289 5290 } else { 5291 if ( ! ( view instanceof Backbone.View ) ) { 5292 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 5293 view = new Button( view ).render(); 5294 } 5295 5296 view.controller = view.controller || this.controller; 5297 5298 this._views[ id ] = view; 5299 5300 list = view.options.priority < 0 ? 'secondary' : 'primary'; 5301 this[ list ].set( id, view, options ); 5302 } 5303 5304 if ( ! options.silent ) { 5305 this.refresh(); 5306 } 5307 5308 return this; 5309 }, 5310 /** 5311 * @param {string} id 5312 * @returns {wp.media.view.Button} 5313 */ 5314 get: function( id ) { 5315 return this._views[ id ]; 5316 }, 5317 /** 5318 * @param {string} id 5319 * @param {Object} options 5320 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5321 */ 5322 unset: function( id, options ) { 5323 delete this._views[ id ]; 5324 this.primary.unset( id, options ); 5325 this.secondary.unset( id, options ); 5326 5327 if ( ! options || ! options.silent ) { 5328 this.refresh(); 5329 } 5330 return this; 5331 }, 5332 5333 refresh: function() { 5334 var state = this.controller.state(), 5335 library = state.get('library'), 5336 selection = state.get('selection'); 5337 5338 _.each( this._views, function( button ) { 5339 if ( ! button.model || ! button.options || ! button.options.requires ) { 5340 return; 5341 } 5342 5343 var requires = button.options.requires, 5344 disabled = false; 5345 5346 // Prevent insertion of attachments if any of them are still uploading 5347 disabled = _.some( selection.models, function( attachment ) { 5348 return attachment.get('uploading') === true; 5349 }); 5350 5351 if ( requires.selection && selection && ! selection.length ) { 5352 disabled = true; 5353 } else if ( requires.library && library && ! library.length ) { 5354 disabled = true; 5355 } 5356 button.model.set( 'disabled', disabled ); 5357 }); 5358 } 5359 }); 5360 5361 module.exports = Toolbar; 5362 5363 },{"./button.js":21,"./priority-list.js":36,"./view.js":49}],45:[function(require,module,exports){ 5364 /*globals wp, _ */ 5365 5366 /** 5367 * wp.media.view.UploaderInline 5368 * 5369 * The inline uploader that shows up in the 'Upload Files' tab. 5370 * 5371 * @class 5372 * @augments wp.media.View 5373 * @augments wp.Backbone.View 5374 * @augments Backbone.View 5375 */ 5376 var View = require( '../view.js' ), 5377 UploaderStatus = require( './status.js' ), 5378 UploaderInline; 5379 5380 UploaderInline = View.extend({ 5381 tagName: 'div', 5382 className: 'uploader-inline', 5383 template: wp.template('uploader-inline'), 5384 5385 events: { 5386 'click .close': 'hide' 5387 }, 5388 5389 initialize: function() { 5390 _.defaults( this.options, { 5391 message: '', 5392 status: true, 5393 canClose: false 5394 }); 5395 5396 if ( ! this.options.$browser && this.controller.uploader ) { 5397 this.options.$browser = this.controller.uploader.$browser; 5398 } 5399 5400 if ( _.isUndefined( this.options.postId ) ) { 5401 this.options.postId = wp.media.view.settings.post.id; 5402 } 5403 5404 if ( this.options.status ) { 5405 this.views.set( '.upload-inline-status', new UploaderStatus({ 5406 controller: this.controller 5407 }) ); 5408 } 5409 }, 5410 5411 prepare: function() { 5412 var suggestedWidth = this.controller.state().get('suggestedWidth'), 5413 suggestedHeight = this.controller.state().get('suggestedHeight'), 5414 data = {}; 5415 5416 data.message = this.options.message; 5417 data.canClose = this.options.canClose; 5418 5419 if ( suggestedWidth && suggestedHeight ) { 5420 data.suggestedWidth = suggestedWidth; 5421 data.suggestedHeight = suggestedHeight; 5422 } 5423 5424 return data; 5425 }, 5426 /** 5427 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 5428 */ 5429 dispose: function() { 5430 if ( this.disposing ) { 5431 /** 5432 * call 'dispose' directly on the parent class 5433 */ 5434 return View.prototype.dispose.apply( this, arguments ); 5435 } 5436 5437 // Run remove on `dispose`, so we can be sure to refresh the 5438 // uploader with a view-less DOM. Track whether we're disposing 5439 // so we don't trigger an infinite loop. 5440 this.disposing = true; 5441 return this.remove(); 5442 }, 5443 /** 5444 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 5445 */ 5446 remove: function() { 5447 /** 5448 * call 'remove' directly on the parent class 5449 */ 5450 var result = View.prototype.remove.apply( this, arguments ); 5451 5452 _.defer( _.bind( this.refresh, this ) ); 5453 return result; 5454 }, 5455 5456 refresh: function() { 5457 var uploader = this.controller.uploader; 5458 5459 if ( uploader ) { 5460 uploader.refresh(); 5461 } 5462 }, 5463 /** 5464 * @returns {wp.media.view.UploaderInline} 5465 */ 5466 ready: function() { 5467 var $browser = this.options.$browser, 5468 $placeholder; 5469 5470 if ( this.controller.uploader ) { 5471 $placeholder = this.$('.browser'); 5472 5473 // Check if we've already replaced the placeholder. 5474 if ( $placeholder[0] === $browser[0] ) { 5475 return; 5476 } 5477 5478 $browser.detach().text( $placeholder.text() ); 5479 $browser[0].className = $placeholder[0].className; 5480 $placeholder.replaceWith( $browser.show() ); 5481 } 5482 5483 this.refresh(); 5484 return this; 5485 }, 5486 show: function() { 5487 this.$el.removeClass( 'hidden' ); 5488 }, 5489 hide: function() { 5490 this.$el.addClass( 'hidden' ); 5491 } 5492 5493 }); 5494 5495 module.exports = UploaderInline; 5496 5497 },{"../view.js":49,"./status.js":47}],46:[function(require,module,exports){ 5498 /*globals wp */ 5499 5500 /** 5501 * wp.media.view.UploaderStatusError 5502 * 5503 * @class 5504 * @augments wp.media.View 5505 * @augments wp.Backbone.View 5506 * @augments Backbone.View 5507 */ 5508 var View = require( '../view.js' ), 5509 UploaderStatusError; 5510 5511 UploaderStatusError = View.extend({ 5512 className: 'upload-error', 5513 template: wp.template('uploader-status-error') 5514 }); 5515 5516 module.exports = UploaderStatusError; 5517 5518 },{"../view.js":49}],47:[function(require,module,exports){ 5519 /*globals wp, _ */ 5520 5521 /** 5522 * wp.media.view.UploaderStatus 5523 * 5524 * An uploader status for on-going uploads. 5525 * 5526 * @class 5527 * @augments wp.media.View 5528 * @augments wp.Backbone.View 5529 * @augments Backbone.View 5530 */ 5531 var View = require( '../view.js' ), 5532 UploaderStatusError = require( './status-error.js' ), 5533 UploaderStatus; 5534 5535 UploaderStatus = View.extend({ 5536 className: 'media-uploader-status', 5537 template: wp.template('uploader-status'), 5538 5539 events: { 5540 'click .upload-dismiss-errors': 'dismiss' 5541 }, 5542 5543 initialize: function() { 5544 this.queue = wp.Uploader.queue; 5545 this.queue.on( 'add remove reset', this.visibility, this ); 5546 this.queue.on( 'add remove reset change:percent', this.progress, this ); 5547 this.queue.on( 'add remove reset change:uploading', this.info, this ); 5548 5549 this.errors = wp.Uploader.errors; 5550 this.errors.reset(); 5551 this.errors.on( 'add remove reset', this.visibility, this ); 5552 this.errors.on( 'add', this.error, this ); 5553 }, 5554 /** 5555 * @global wp.Uploader 5556 * @returns {wp.media.view.UploaderStatus} 5557 */ 5558 dispose: function() { 5559 wp.Uploader.queue.off( null, null, this ); 5560 /** 5561 * call 'dispose' directly on the parent class 5562 */ 5563 View.prototype.dispose.apply( this, arguments ); 5564 return this; 5565 }, 5566 5567 visibility: function() { 5568 this.$el.toggleClass( 'uploading', !! this.queue.length ); 5569 this.$el.toggleClass( 'errors', !! this.errors.length ); 5570 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 5571 }, 5572 5573 ready: function() { 5574 _.each({ 5575 '$bar': '.media-progress-bar div', 5576 '$index': '.upload-index', 5577 '$total': '.upload-total', 5578 '$filename': '.upload-filename' 5579 }, function( selector, key ) { 5580 this[ key ] = this.$( selector ); 5581 }, this ); 5582 5583 this.visibility(); 5584 this.progress(); 5585 this.info(); 5586 }, 5587 5588 progress: function() { 5589 var queue = this.queue, 5590 $bar = this.$bar; 5591 5592 if ( ! $bar || ! queue.length ) { 5593 return; 5594 } 5595 5596 $bar.width( ( queue.reduce( function( memo, attachment ) { 5597 if ( ! attachment.get('uploading') ) { 5598 return memo + 100; 5599 } 5600 5601 var percent = attachment.get('percent'); 5602 return memo + ( _.isNumber( percent ) ? percent : 100 ); 5603 }, 0 ) / queue.length ) + '%' ); 5604 }, 5605 5606 info: function() { 5607 var queue = this.queue, 5608 index = 0, active; 5609 5610 if ( ! queue.length ) { 5611 return; 5612 } 5613 5614 active = this.queue.find( function( attachment, i ) { 5615 index = i; 5616 return attachment.get('uploading'); 5617 }); 5618 5619 this.$index.text( index + 1 ); 5620 this.$total.text( queue.length ); 5621 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 5622 }, 5623 /** 5624 * @param {string} filename 5625 * @returns {string} 5626 */ 5627 filename: function( filename ) { 5628 return wp.media.truncate( _.escape( filename ), 24 ); 5629 }, 5630 /** 5631 * @param {Backbone.Model} error 5632 */ 5633 error: function( error ) { 5634 this.views.add( '.upload-errors', new UploaderStatusError({ 5635 filename: this.filename( error.get('file').name ), 5636 message: error.get('message') 5637 }), { at: 0 }); 5638 }, 5639 5640 /** 5641 * @global wp.Uploader 5642 * 5643 * @param {Object} event 5644 */ 5645 dismiss: function( event ) { 5646 var errors = this.views.get('.upload-errors'); 5647 5648 event.preventDefault(); 5649 5650 if ( errors ) { 5651 _.invoke( errors, 'remove' ); 5652 } 5653 wp.Uploader.errors.reset(); 5654 } 5655 }); 5656 5657 module.exports = UploaderStatus; 5658 5659 },{"../view.js":49,"./status-error.js":46}],48:[function(require,module,exports){ 5660 /*globals wp, _, jQuery */ 5661 5662 /** 5663 * wp.media.view.UploaderWindow 5664 * 5665 * An uploader window that allows for dragging and dropping media. 5666 * 5667 * @class 5668 * @augments wp.media.View 5669 * @augments wp.Backbone.View 5670 * @augments Backbone.View 5671 * 5672 * @param {object} [options] Options hash passed to the view. 5673 * @param {object} [options.uploader] Uploader properties. 5674 * @param {jQuery} [options.uploader.browser] 5675 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 5676 * @param {object} [options.uploader.params] 5677 */ 5678 var View = require( '../view.js' ), 5679 $ = jQuery, 5680 UploaderWindow; 5681 5682 UploaderWindow = View.extend({ 5683 tagName: 'div', 5684 className: 'uploader-window', 5685 template: wp.template('uploader-window'), 5686 5687 initialize: function() { 5688 var uploader; 5689 5690 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 5691 5692 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 5693 dropzone: this.$el, 5694 browser: this.$browser, 5695 params: {} 5696 }); 5697 5698 // Ensure the dropzone is a jQuery collection. 5699 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 5700 uploader.dropzone = $( uploader.dropzone ); 5701 } 5702 5703 this.controller.on( 'activate', this.refresh, this ); 5704 5705 this.controller.on( 'detach', function() { 5706 this.$browser.remove(); 5707 }, this ); 5708 }, 5709 5710 refresh: function() { 5711 if ( this.uploader ) { 5712 this.uploader.refresh(); 5713 } 5714 }, 5715 5716 ready: function() { 5717 var postId = wp.media.view.settings.post.id, 5718 dropzone; 5719 5720 // If the uploader already exists, bail. 5721 if ( this.uploader ) { 5722 return; 5723 } 5724 5725 if ( postId ) { 5726 this.options.uploader.params.post_id = postId; 5727 } 5728 this.uploader = new wp.Uploader( this.options.uploader ); 5729 5730 dropzone = this.uploader.dropzone; 5731 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 5732 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 5733 5734 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 5735 }, 5736 5737 _ready: function() { 5738 this.controller.trigger( 'uploader:ready' ); 5739 }, 5740 5741 show: function() { 5742 var $el = this.$el.show(); 5743 5744 // Ensure that the animation is triggered by waiting until 5745 // the transparent element is painted into the DOM. 5746 _.defer( function() { 5747 $el.css({ opacity: 1 }); 5748 }); 5749 }, 5750 5751 hide: function() { 5752 var $el = this.$el.css({ opacity: 0 }); 5753 5754 wp.media.transition( $el ).done( function() { 5755 // Transition end events are subject to race conditions. 5756 // Make sure that the value is set as intended. 5757 if ( '0' === $el.css('opacity') ) { 5758 $el.hide(); 5759 } 5760 }); 5761 5762 // https://core.trac.wordpress.org/ticket/27341 5763 _.delay( function() { 5764 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 5765 $el.hide(); 5766 } 5767 }, 500 ); 5768 } 5769 }); 5770 5771 module.exports = UploaderWindow; 5772 5773 },{"../view.js":49}],49:[function(require,module,exports){ 5774 /*globals wp */ 5775 5776 /** 5777 * wp.media.View 5778 * 5779 * The base view class for media. 5780 * 5781 * Undelegating events, removing events from the model, and 5782 * removing events from the controller mirror the code for 5783 * `Backbone.View.dispose` in Backbone 0.9.8 development. 5784 * 5785 * This behavior has since been removed, and should not be used 5786 * outside of the media manager. 5787 * 5788 * @class 5789 * @augments wp.Backbone.View 5790 * @augments Backbone.View 5791 */ 5792 var View = wp.Backbone.View.extend({ 5793 constructor: function( options ) { 5794 if ( options && options.controller ) { 5795 this.controller = options.controller; 5796 } 5797 wp.Backbone.View.apply( this, arguments ); 5798 }, 5799 /** 5800 * @todo The internal comment mentions this might have been a stop-gap 5801 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 5802 * care of this in Backbone.View now. 5803 * 5804 * @returns {wp.media.View} Returns itself to allow chaining 5805 */ 5806 dispose: function() { 5807 // Undelegating events, removing events from the model, and 5808 // removing events from the controller mirror the code for 5809 // `Backbone.View.dispose` in Backbone 0.9.8 development. 5810 this.undelegateEvents(); 5811 5812 if ( this.model && this.model.off ) { 5813 this.model.off( null, null, this ); 5814 } 5815 5816 if ( this.collection && this.collection.off ) { 5817 this.collection.off( null, null, this ); 5818 } 5819 5820 // Unbind controller events. 5821 if ( this.controller && this.controller.off ) { 5822 this.controller.off( null, null, this ); 5823 } 5824 5825 return this; 5826 }, 5827 /** 5828 * @returns {wp.media.View} Returns itself to allow chaining 5829 */ 5830 remove: function() { 5831 this.dispose(); 5832 /** 5833 * call 'remove' directly on the parent class 5834 */ 5835 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 5836 } 5837 }); 5838 5839 module.exports = View; 5840 5841 },{}]},{},[7]); 850 },{"../../routers/manage.js":3}]},{},[2]); -
trunk/src/wp-includes/js/media/views.js
r31492 r31494 2182 2182 2183 2183 media.View = require( './views/view.js' ); 2184 media.view.Frame = require( './views/ view.js' );2184 media.view.Frame = require( './views/frame.js' ); 2185 2185 media.view.MediaFrame = require( './views/media-frame.js' ); 2186 2186 media.view.MediaFrame.Select = require( './views/frame/select.js' ); … … 2236 2236 media.view.Spinner = require( './views/spinner.js' ); 2237 2237 2238 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame /image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){2238 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/edit-image.js":4,"./controllers/embed.js":5,"./controllers/featured-image.js":6,"./controllers/gallery-add.js":7,"./controllers/gallery-edit.js":8,"./controllers/image-details.js":9,"./controllers/library.js":10,"./controllers/media-library.js":11,"./controllers/region.js":12,"./controllers/replace-image.js":13,"./controllers/state-machine.js":14,"./controllers/state.js":15,"./utils/selection-sync.js":16,"./views/attachment-compat.js":18,"./views/attachment-filters.js":19,"./views/attachment-filters/all.js":20,"./views/attachment-filters/date.js":21,"./views/attachment-filters/uploaded.js":22,"./views/attachment.js":23,"./views/attachment/details.js":24,"./views/attachment/edit-library.js":25,"./views/attachment/edit-selection.js":26,"./views/attachment/library.js":27,"./views/attachment/selection.js":28,"./views/attachments.js":29,"./views/attachments/browser.js":30,"./views/attachments/selection.js":31,"./views/button-group.js":32,"./views/button.js":33,"./views/cropper.js":34,"./views/edit-image.js":35,"./views/embed.js":36,"./views/embed/image.js":37,"./views/embed/link.js":38,"./views/embed/url.js":39,"./views/focus-manager.js":40,"./views/frame.js":41,"./views/frame/image-details.js":42,"./views/frame/post.js":43,"./views/frame/select.js":44,"./views/iframe.js":45,"./views/image-details.js":46,"./views/label.js":47,"./views/media-frame.js":48,"./views/menu-item.js":49,"./views/menu.js":50,"./views/modal.js":51,"./views/priority-list.js":52,"./views/router-item.js":53,"./views/router.js":54,"./views/search.js":55,"./views/selection.js":56,"./views/settings.js":57,"./views/settings/attachment-display.js":58,"./views/settings/gallery.js":59,"./views/settings/playlist.js":60,"./views/sidebar.js":61,"./views/spinner.js":62,"./views/toolbar.js":63,"./views/toolbar/embed.js":64,"./views/toolbar/select.js":65,"./views/uploader/editor.js":66,"./views/uploader/inline.js":67,"./views/uploader/status-error.js":68,"./views/uploader/status.js":69,"./views/uploader/window.js":70,"./views/view.js":71}],18:[function(require,module,exports){ 2239 2239 /*globals _ */ 2240 2240 -
trunk/src/wp-includes/js/media/views.manifest.js
r31491 r31494 93 93 94 94 media.View = require( './views/view.js' ); 95 media.view.Frame = require( './views/ view.js' );95 media.view.Frame = require( './views/frame.js' ); 96 96 media.view.MediaFrame = require( './views/media-frame.js' ); 97 97 media.view.MediaFrame.Select = require( './views/frame/select.js' ); -
trunk/src/wp-includes/js/media/views/attachment/details-two-column.js
r31492 r31494 14 14 * @augments Backbone.View 15 15 */ 16 var Details = require( './details.js' ),16 var Details = wp.media.view.Attachment.Details, 17 17 TwoColumn; 18 18 -
trunk/src/wp-includes/js/media/views/button/delete-selected-permanently.js
r31492 r31494 11 11 * @augments Backbone.View 12 12 */ 13 var Button = require( '../button.js' ),13 var Button = wp.media.view.Button, 14 14 DeleteSelected = require( './delete-selected.js' ), 15 15 DeleteSelectedPermanently; -
trunk/src/wp-includes/js/media/views/button/delete-selected.js
r31492 r31494 12 12 * @augments Backbone.View 13 13 */ 14 var Button = require( '../button.js' ),14 var Button = wp.media.view.Button, 15 15 l10n = wp.media.view.l10n, 16 16 DeleteSelected; -
trunk/src/wp-includes/js/media/views/button/select-mode-toggle.js
r31492 r31494 10 10 * @augments Backbone.View 11 11 */ 12 var Button = require( '../button.js' ),12 var Button = wp.media.view.Button, 13 13 l10n = wp.media.view.l10n, 14 14 SelectModeToggle; -
trunk/src/wp-includes/js/media/views/edit-image-details.js
r31492 r31494 5 5 * 6 6 * @class 7 * @augments wp.media.view.EditImage .Details7 * @augments wp.media.view.EditImage 8 8 * @augments wp.media.View 9 9 * @augments wp.Backbone.View 10 10 * @augments Backbone.View 11 11 */ 12 var View = require( './view.js' ),12 var View = wp.media.View, 13 13 EditImage = wp.media.view.EditImage, 14 14 Details; -
trunk/src/wp-includes/js/media/views/frame/edit-attachments.js
r31492 r31494 17 17 * @mixes wp.media.controller.StateMachine 18 18 */ 19 var Frame = require( '../frame.js' ), 20 MediaFrame = require( '../media-frame.js' ), 21 Modal = require( '../modal.js' ), 19 var Frame = wp.media.view.Frame, 20 MediaFrame = wp.media.view.MediaFrame, 21 Modal = wp.media.view.Modal, 22 AttachmentCompat = wp.media.view.AttachmentCompat, 23 EditImageController = wp.media.controller.EditImage, 24 22 25 EditAttachmentMetadata = require( '../../controllers/edit-attachment-metadata.js' ), 23 26 TwoColumn = require( '../attachment/details-two-column.js' ), 24 AttachmentCompat = require( '../attachment-compat.js' ),25 EditImageController = require( '../../controllers/edit-image.js' ),26 27 DetailsView = require( '../edit-image-details.js' ), 28 27 29 $ = jQuery, 28 30 EditAttachments; -
trunk/src/wp-includes/js/media/views/frame/manage.js
r31491 r31494 16 16 * @mixes wp.media.controller.StateMachine 17 17 */ 18 var MediaFrame = require( '../media-frame.js' ), 19 UploaderWindow = require( '../uploader/window.js' ), 20 AttachmentsBrowser = require( '../attachments/browser.js' ), 18 var MediaFrame = wp.media.view.MediaFrame, 19 UploaderWindow = wp.media.view.UploaderWindow, 20 AttachmentsBrowser = wp.media.view.AttachmentsBrowser, 21 Library = wp.media.controller.Library, 22 21 23 Router = require( '../../routers/manage.js' ), 22 Library = require( '../../controllers/library.js' ), 24 23 25 $ = jQuery, 24 26 Manage;
Note: See TracChangeset
for help on using the changeset viewer.