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