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