| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /******/ (() => { // webpackBootstrap 2 /******/ var __webpack_modules__ = ({ 3 4 /***/ 7145 5 (module) { 6 7 var Selection = wp.media.model.Selection, 8 Library = wp.media.controller.Library, 9 CollectionAdd; 10 11 /** 12 * wp.media.controller.CollectionAdd 13 * 14 * A state for adding attachments to a collection (e.g. video playlist). 15 * 16 * @memberOf wp.media.controller 17 * 18 * @class 19 * @augments wp.media.controller.Library 20 * @augments wp.media.controller.State 21 * @augments Backbone.Model 22 * 23 * @param {Object} [attributes] The attributes hash passed to the state. 24 * @param {string} [attributes.id=library] Unique identifier. 25 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 26 * @param {boolean|string} [attributes.multiple=add] Whether multi-select is enabled. Accepts 'add' or true. 27 * When set to true, requires Shift or Cmd/Ctrl to select multiple items. 28 * When set to 'add', allows selecting multiple items by clicking thumbnails. 29 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 30 * If one is not supplied, a collection of attachments of the specified type will be created. 31 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 32 * Accepts 'all', 'uploaded', or 'unattached'. 33 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 34 * @param {string} [attributes.content=upload] Initial mode for the content region. 35 * Overridden by persistent user setting if 'contentUserSetting' is true. 36 * @param {string} [attributes.router=browse] Initial mode for the router region. 37 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 38 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 39 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 40 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 41 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 42 * @param {number} [attributes.priority=100] The priority for the state link in the media menu. 43 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 44 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 45 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 46 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 47 */ 48 CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{ 49 defaults: _.defaults( { 50 // Selection defaults. @see media.model.Selection 51 multiple: 'add', 52 // Attachments browser defaults. @see media.view.AttachmentsBrowser 53 filterable: 'uploaded', 54 55 priority: 100, 56 syncSelection: false 57 }, Library.prototype.defaults ), 58 59 /** 60 * Initializes the CollectionAdd controller. 61 * 62 * @since 3.9.0 63 */ 64 initialize: function() { 65 var collectionType = this.get('collectionType'); 66 67 if ( 'video' === this.get( 'type' ) ) { 68 collectionType = 'video-' + collectionType; 69 } 70 71 this.set( 'id', collectionType + '-library' ); 72 this.set( 'toolbar', collectionType + '-add' ); 73 this.set( 'menu', collectionType ); 74 75 // If we haven't been provided a `library`, create a `Selection`. 76 if ( ! this.get('library') ) { 77 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 78 } 79 Library.prototype.initialize.apply( this, arguments ); 80 }, 81 82 /** 83 * Activates the CollectionAdd controller. 84 * 85 * @since 3.9.0 86 */ 87 activate: function() { 88 var library = this.get('library'), 89 editLibrary = this.get('editLibrary'), 90 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 91 92 if ( editLibrary && editLibrary !== edit ) { 93 library.unobserve( editLibrary ); 94 } 95 96 // Accepts attachments that exist in the original library and 97 // that do not exist in gallery's library. 98 library.validator = function( attachment ) { 99 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 100 }; 101 102 /* 103 * Reset the library to ensure that all attachments are re-added 104 * to the collection. Do so silently, as calling `observe` will 105 * trigger the `reset` event. 106 */ 107 library.reset( library.mirroring.models, { silent: true }); 108 library.observe( edit ); 109 this.set('editLibrary', edit); 110 111 Library.prototype.activate.apply( this, arguments ); 112 } 113 }); 114 115 module.exports = CollectionAdd; 116 117 118 /***/ }, 119 120 /***/ 8612 121 (module) { 122 123 var Library = wp.media.controller.Library, 124 l10n = wp.media.view.l10n, 125 $ = jQuery, 126 CollectionEdit; 127 128 /** 129 * wp.media.controller.CollectionEdit 130 * 131 * A state for editing a collection, which is used by audio and video playlists, 132 * and can be used for other collections. 133 * 134 * @memberOf wp.media.controller 135 * 136 * @class 137 * @augments wp.media.controller.Library 138 * @augments wp.media.controller.State 139 * @augments Backbone.Model 140 * 141 * @param {Object} [attributes] The attributes hash passed to the state. 142 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 143 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 144 * If one is not supplied, an empty media.model.Selection collection is created. 145 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 146 * @param {string} [attributes.content=browse] Initial mode for the content region. 147 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 148 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 149 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 150 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 151 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 152 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 153 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 154 * @param {number} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 155 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 156 * @param {number} [attributes.priority=60] The priority for the state link in the media menu. 157 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 158 * Defaults to false for this state, because the library passed in *is* the selection. 159 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 160 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 161 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 162 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 163 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 164 */ 165 CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{ 166 defaults: { 167 multiple: false, 168 sortable: true, 169 date: false, 170 searchable: false, 171 content: 'browse', 172 describe: true, 173 dragInfo: true, 174 idealColumnWidth: 170, 175 editing: false, 176 priority: 60, 177 SettingsView: false, 178 syncSelection: false 179 }, 180 181 /** 182 * Initializes the CollectionEdit controller. 183 * 184 * @since 3.9.0 185 */ 186 initialize: function() { 187 var collectionType = this.get('collectionType'); 188 189 if ( 'video' === this.get( 'type' ) ) { 190 collectionType = 'video-' + collectionType; 191 } 192 193 this.set( 'id', collectionType + '-edit' ); 194 this.set( 'toolbar', collectionType + '-edit' ); 195 196 // If we haven't been provided a `library`, create a `Selection`. 197 if ( ! this.get('library') ) { 198 this.set( 'library', new wp.media.model.Selection() ); 199 } 200 // The single `Attachment` view to be used in the `Attachments` view. 201 if ( ! this.get('AttachmentView') ) { 202 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 203 } 204 Library.prototype.initialize.apply( this, arguments ); 205 }, 206 207 /** 208 * Activates the CollectionEdit controller. 209 * 210 * @since 3.9.0 211 */ 212 activate: function() { 213 var library = this.get('library'); 214 215 // Limit the library to images only. 216 library.props.set( 'type', this.get( 'type' ) ); 217 218 // Watch for uploaded attachments. 219 this.get('library').observe( wp.Uploader.queue ); 220 221 this.frame.on( 'content:render:browse', this.renderSettings, this ); 222 223 Library.prototype.activate.apply( this, arguments ); 224 }, 225 226 /** 227 * Deactivates the CollectionEdit controller. 228 * 229 * @since 3.9.0 230 */ 231 deactivate: function() { 232 // Stop watching for uploaded attachments. 233 this.get('library').unobserve( wp.Uploader.queue ); 234 235 this.frame.off( 'content:render:browse', this.renderSettings, this ); 236 237 Library.prototype.deactivate.apply( this, arguments ); 238 }, 239 240 /** 241 * Render the collection embed settings view in the browser sidebar. 242 * 243 * @todo This is against the pattern elsewhere in media. Typically the frame 244 * is responsible for adding region mode callbacks. Explain. 245 * 246 * @since 3.9.0 247 * 248 * @param {wp.media.view.attachmentsBrowser} attachmentsBrowserView The attachments browser view. 249 */ 250 renderSettings: function( attachmentsBrowserView ) { 251 var library = this.get('library'), 252 collectionType = this.get('collectionType'), 253 dragInfoText = this.get('dragInfoText'), 254 SettingsView = this.get('SettingsView'), 255 obj = {}; 256 257 if ( ! library || ! attachmentsBrowserView ) { 258 return; 259 } 260 261 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 262 263 obj[ collectionType ] = new SettingsView({ 264 controller: this, 265 model: library[ collectionType ], 266 priority: 40 267 }); 268 269 attachmentsBrowserView.sidebar.set( obj ); 270 271 if ( dragInfoText ) { 272 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 273 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 274 priority: -40 275 }) ); 276 } 277 278 // Add the 'Reverse order' button to the toolbar. 279 attachmentsBrowserView.toolbar.set( 'reverse', { 280 text: l10n.reverseOrder, 281 priority: 80, 282 283 click: function() { 284 library.reset( library.toArray().reverse() ); 285 } 286 }); 287 } 288 }); 289 290 module.exports = CollectionEdit; 291 292 293 /***/ }, 294 295 /***/ 5422 296 (module) { 297 298 var l10n = wp.media.view.l10n, 299 Cropper; 300 301 /** 302 * wp.media.controller.Cropper 303 * 304 * A class for cropping an image when called from the header media customization panel. 305 * 306 * @memberOf wp.media.controller 307 * 308 * @class 309 * @augments wp.media.controller.State 310 * @augments Backbone.Model 311 */ 312 Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{ 313 defaults: { 314 id: 'cropper', 315 title: l10n.cropImage, 316 // Region mode defaults. 317 toolbar: 'crop', 318 content: 'crop', 319 router: false, 320 canSkipCrop: false, 321 322 // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state. 323 doCropArgs: {} 324 }, 325 326 /** 327 * Shows the crop image window when called from the Add new image button. 328 * 329 * @since 4.2.0 330 * 331 * @return {void} 332 */ 333 activate: function() { 334 this.frame.on( 'content:create:crop', this.createCropContent, this ); 335 this.frame.on( 'close', this.removeCropper, this ); 336 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 337 }, 338 339 /** 340 * Changes the state of the toolbar window to browse mode. 341 * 342 * @since 4.2.0 343 * 344 * @return {void} 345 */ 346 deactivate: function() { 347 this.frame.toolbar.mode('browse'); 348 }, 349 350 /** 351 * Creates the crop image window. 352 * 353 * Initialized when clicking on the Select and Crop button. 354 * 355 * @since 4.2.0 356 * 357 * @fires crop window 358 * 359 * @return {void} 360 */ 361 createCropContent: function() { 362 this.cropperView = new wp.media.view.Cropper({ 363 controller: this, 364 attachment: this.get('selection').first() 365 }); 366 this.cropperView.on('image-loaded', this.createCropToolbar, this); 367 this.frame.content.set(this.cropperView); 368 369 }, 370 371 /** 372 * Removes the image selection and closes the cropping window. 373 * 374 * @since 4.2.0 375 * 376 * @return {void} 377 */ 378 removeCropper: function() { 379 this.imgSelect.cancelSelection(); 380 this.imgSelect.setOptions({remove: true}); 381 this.imgSelect.update(); 382 this.cropperView.remove(); 383 }, 384 385 /** 386 * Checks if cropping can be skipped and creates crop toolbar accordingly. 387 * 388 * @since 4.2.0 389 * 390 * @return {void} 391 */ 392 createCropToolbar: function() { 393 var canSkipCrop, hasRequiredAspectRatio, suggestedCropSize, toolbarOptions; 394 395 suggestedCropSize = this.get( 'suggestedCropSize' ); 396 hasRequiredAspectRatio = this.get( 'hasRequiredAspectRatio' ); 397 canSkipCrop = this.get( 'canSkipCrop' ) || false; 398 399 toolbarOptions = { 400 controller: this.frame, 401 items: { 402 insert: { 403 style: 'primary', 404 text: l10n.cropImage, 405 priority: 80, 406 requires: { library: false, selection: false }, 407 408 click: function() { 409 var controller = this.controller, 410 selection; 411 412 selection = controller.state().get('selection').first(); 413 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 414 415 this.$el.text(l10n.cropping); 416 this.$el.prop( 'disabled', true ); 417 418 controller.state().doCrop( selection ).done( function( croppedImage ) { 419 controller.trigger('cropped', croppedImage ); 420 controller.close(); 421 }).fail( function() { 422 controller.trigger('content:error:crop'); 423 }); 424 } 425 } 426 } 427 }; 428 429 if ( canSkipCrop || hasRequiredAspectRatio ) { 430 _.extend( toolbarOptions.items, { 431 skip: { 432 style: 'secondary', 433 text: l10n.skipCropping, 434 priority: 70, 435 requires: { library: false, selection: false }, 436 click: function() { 437 var controller = this.controller, 438 selection = controller.state().get( 'selection' ).first(); 439 440 controller.state().cropperView.remove(); 441 442 // Apply the suggested crop size. 443 if ( hasRequiredAspectRatio && !canSkipCrop ) { 444 selection.set({cropDetails: suggestedCropSize}); 445 controller.state().doCrop( selection ).done( function( croppedImage ) { 446 controller.trigger( 'cropped', croppedImage ); 447 controller.close(); 448 }).fail( function() { 449 controller.trigger( 'content:error:crop' ); 450 }); 451 return; 452 } 453 454 // Skip the cropping process. 455 controller.trigger( 'skippedcrop', selection ); 456 controller.close(); 457 } 458 } 459 }); 460 } 461 462 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 463 }, 464 465 /** 466 * Creates an object with the image attachment and crop properties. 467 * 468 * @since 4.2.0 469 * 470 * @param {wp.media.model.Attachment} attachment The image attachment. 471 * @return {$.promise} A jQuery promise with the custom header crop details. 472 */ 473 doCrop: function( attachment ) { 474 return wp.ajax.post( 'custom-header-crop', _.extend( 475 {}, 476 this.defaults.doCropArgs, 477 { 478 nonce: attachment.get( 'nonces' ).edit, 479 id: attachment.get( 'id' ), 480 cropDetails: attachment.get( 'cropDetails' ) 481 } 482 ) ); 483 } 484 }); 485 486 module.exports = Cropper; 487 488 489 /***/ }, 490 491 /***/ 9660 492 (module) { 493 494 var Controller = wp.media.controller, 495 CustomizeImageCropper; 496 497 /** 498 * A state for cropping an image in the customizer. 499 * 500 * @since 4.3.0 501 * 502 * @constructs wp.media.controller.CustomizeImageCropper 503 * @memberOf wp.media.controller 504 * @augments wp.media.controller.CustomizeImageCropper.Cropper 505 * @inheritDoc 506 */ 507 CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{ 508 /** 509 * Posts the crop details to the admin. 510 * 511 * Uses crop measurements when flexible in both directions. 512 * Constrains flexible side based on image ratio and size of the fixed side. 513 * 514 * @since 4.3.0 515 * 516 * @param {Object} attachment The attachment to crop. 517 * 518 * @return {$.promise} A jQuery promise that represents the crop image request. 519 */ 520 doCrop: function( attachment ) { 521 var cropDetails = attachment.get( 'cropDetails' ), 522 control = this.get( 'control' ), 523 ratio = cropDetails.width / cropDetails.height; 524 525 // Use crop measurements when flexible in both directions. 526 if ( control.params.flex_width && control.params.flex_height ) { 527 cropDetails.dst_width = cropDetails.width; 528 cropDetails.dst_height = cropDetails.height; 529 530 // Constrain flexible side based on image ratio and size of the fixed side. 531 } else { 532 cropDetails.dst_width = control.params.flex_width ? control.params.height * ratio : control.params.width; 533 cropDetails.dst_height = control.params.flex_height ? control.params.width / ratio : control.params.height; 534 } 535 536 return wp.ajax.post( 'crop-image', { 537 wp_customize: 'on', 538 nonce: attachment.get( 'nonces' ).edit, 539 id: attachment.get( 'id' ), 540 context: control.id, 541 cropDetails: cropDetails 542 } ); 543 } 544 }); 545 546 module.exports = CustomizeImageCropper; 547 548 549 /***/ }, 550 551 /***/ 5663 552 (module) { 553 554 var l10n = wp.media.view.l10n, 555 EditImage; 556 557 /** 558 * wp.media.controller.EditImage 559 * 560 * A state for editing (cropping, etc.) an image. 561 * 562 * @memberOf wp.media.controller 563 * 564 * @class 565 * @augments wp.media.controller.State 566 * @augments Backbone.Model 567 * 568 * @param {Object} attributes The attributes hash passed to the state. 569 * @param {wp.media.model.Attachment} attributes.model The attachment. 570 * @param {string} [attributes.id=edit-image] Unique identifier. 571 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 572 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 573 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 574 * @param {string} [attributes.menu=false] Initial mode for the menu region. 575 * @param {string} [attributes.url] Unused. @todo Consider removal. 576 */ 577 EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{ 578 defaults: { 579 id: 'edit-image', 580 title: l10n.editImage, 581 menu: false, 582 toolbar: 'edit-image', 583 content: 'edit-image', 584 url: '' 585 }, 586 587 /** 588 * Activates a frame for editing a featured image. 589 * 590 * @since 3.9.0 591 * 592 * @return {void} 593 */ 594 activate: function() { 595 this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) ); 596 }, 597 598 /** 599 * Deactivates a frame for editing a featured image. 600 * 601 * @since 3.9.0 602 * 603 * @return {void} 604 */ 605 deactivate: function() { 606 this.frame.off( 'toolbar:render:edit-image' ); 607 }, 608 609 /** 610 * Adds a toolbar with a back button. 611 * 612 * When the back button is pressed it checks whether there is a previous state. 613 * In case there is a previous state it sets that previous state otherwise it 614 * closes the frame. 615 * 616 * @since 3.9.0 617 * 618 * @return {void} 619 */ 620 toolbar: function() { 621 var frame = this.frame, 622 lastState = frame.lastState(), 623 previous = lastState && lastState.id; 624 625 frame.toolbar.set( new wp.media.view.Toolbar({ 626 controller: frame, 627 items: { 628 back: { 629 style: 'primary', 630 text: l10n.back, 631 priority: 20, 632 click: function() { 633 if ( previous ) { 634 frame.setState( previous ); 635 } else { 636 frame.close(); 637 } 638 } 639 } 640 } 641 }) ); 642 } 643 }); 644 645 module.exports = EditImage; 646 647 648 /***/ }, 649 650 /***/ 4910 651 (module) { 652 653 var l10n = wp.media.view.l10n, 654 $ = Backbone.$, 655 Embed; 656 657 /** 658 * wp.media.controller.Embed 659 * 660 * A state for embedding media from a URL. 661 * 662 * @memberOf wp.media.controller 663 * 664 * @class 665 * @augments wp.media.controller.State 666 * @augments Backbone.Model 667 * 668 * @param {Object} attributes The attributes hash passed to the state. 669 * @param {string} [attributes.id=embed] Unique identifier. 670 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 671 * @param {string} [attributes.content=embed] Initial mode for the content region. 672 * @param {string} [attributes.menu=default] Initial mode for the menu region. 673 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 674 * @param {string} [attributes.menu=false] Initial mode for the menu region. 675 * @param {number} [attributes.priority=120] The priority for the state link in the media menu. 676 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 677 * @param {string} [attributes.url] The embed URL. 678 * @param {Object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 679 */ 680 Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{ 681 defaults: { 682 id: 'embed', 683 title: l10n.insertFromUrlTitle, 684 content: 'embed', 685 menu: 'default', 686 toolbar: 'main-embed', 687 priority: 120, 688 type: 'link', 689 url: '', 690 metadata: {} 691 }, 692 693 // The amount of time used when debouncing the scan. 694 sensitivity: 400, 695 696 initialize: function(options) { 697 this.metadata = options.metadata; 698 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 699 this.props = new Backbone.Model( this.metadata || { url: '' }); 700 this.props.on( 'change:url', this.debouncedScan, this ); 701 this.props.on( 'change:url', this.refresh, this ); 702 this.on( 'scan', this.scanImage, this ); 703 }, 704 705 /** 706 * Trigger a scan of the embedded URL's content for metadata required to embed. 707 * 708 * @fires wp.media.controller.Embed#scan 709 */ 710 scan: function() { 711 var scanners, 712 embed = this, 713 attributes = { 714 type: 'link', 715 scanners: [] 716 }; 717 718 /* 719 * Scan is triggered with the list of `attributes` to set on the 720 * state, useful for the 'type' attribute and 'scanners' attribute, 721 * an array of promise objects for asynchronous scan operations. 722 */ 723 if ( this.props.get('url') ) { 724 this.trigger( 'scan', attributes ); 725 } 726 727 if ( attributes.scanners.length ) { 728 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 729 scanners.always( function() { 730 if ( embed.get('scanners') === scanners ) { 731 embed.set( 'loading', false ); 732 } 733 }); 734 } else { 735 attributes.scanners = null; 736 } 737 738 attributes.loading = !! attributes.scanners; 739 this.set( attributes ); 740 }, 741 /** 742 * Try scanning the embed as an image to discover its dimensions. 743 * 744 * @param {Object} attributes The attributes for the embed scan. 745 */ 746 scanImage: function( attributes ) { 747 var frame = this.frame, 748 state = this, 749 url = this.props.get('url'), 750 image = new Image(), 751 deferred = $.Deferred(); 752 753 attributes.scanners.push( deferred.promise() ); 754 755 // Try to load the image and find its width/height. 756 image.onload = function() { 757 deferred.resolve(); 758 759 if ( state !== frame.state() || url !== state.props.get('url') ) { 760 return; 761 } 762 763 state.set({ 764 type: 'image' 765 }); 766 767 state.props.set({ 768 width: image.width, 769 height: image.height 770 }); 771 }; 772 773 image.onerror = deferred.reject; 774 image.src = url; 775 }, 776 777 refresh: function() { 778 this.frame.toolbar.get().refresh(); 779 }, 780 781 reset: function() { 782 this.props.clear().set({ url: '' }); 783 784 if ( this.active ) { 785 this.refresh(); 786 } 787 } 788 }); 789 790 module.exports = Embed; 791 792 793 /***/ }, 794 795 /***/ 1169 796 (module) { 797 798 var Attachment = wp.media.model.Attachment, 799 Library = wp.media.controller.Library, 800 l10n = wp.media.view.l10n, 801 FeaturedImage; 802 803 /** 804 * wp.media.controller.FeaturedImage 805 * 806 * A state for selecting a featured image for a post. 807 * 808 * @memberOf wp.media.controller 809 * 810 * @class 811 * @augments wp.media.controller.Library 812 * @augments wp.media.controller.State 813 * @augments Backbone.Model 814 * 815 * @param {Object} [attributes] The attributes hash passed to the state. 816 * @param {string} [attributes.id=featured-image] Unique identifier. 817 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 818 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 819 * If one is not supplied, a collection of all images will be created. 820 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 821 * @param {string} [attributes.content=upload] Initial mode for the content region. 822 * Overridden by persistent user setting if 'contentUserSetting' is true. 823 * @param {string} [attributes.menu=default] Initial mode for the menu region. 824 * @param {string} [attributes.router=browse] Initial mode for the router region. 825 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 826 * @param {number} [attributes.priority=60] The priority for the state link in the media menu. 827 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 828 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 829 * Accepts 'all', 'uploaded', or 'unattached'. 830 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 831 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 832 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 833 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 834 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 835 */ 836 FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{ 837 defaults: _.defaults({ 838 id: 'featured-image', 839 title: l10n.setFeaturedImageTitle, 840 multiple: false, 841 filterable: 'uploaded', 842 toolbar: 'featured-image', 843 priority: 60, 844 syncSelection: true 845 }, Library.prototype.defaults ), 846 847 /** 848 * Initializes the FeaturedImage controller. 849 * 850 * @since 3.5.0 851 */ 852 initialize: function() { 853 var library, comparator; 854 855 // If we haven't been provided a `library`, create a `Selection`. 856 if ( ! this.get('library') ) { 857 this.set( 'library', wp.media.query({ type: 'image' }) ); 858 } 859 860 Library.prototype.initialize.apply( this, arguments ); 861 862 library = this.get('library'); 863 comparator = library.comparator; 864 865 // Overload the library's comparator to push items that are not in 866 // the mirrored query to the front of the aggregate collection. 867 library.comparator = function( a, b ) { 868 var aInQuery = !! this.mirroring.get( a.cid ), 869 bInQuery = !! this.mirroring.get( b.cid ); 870 871 if ( ! aInQuery && bInQuery ) { 872 return -1; 873 } else if ( aInQuery && ! bInQuery ) { 874 return 1; 875 } else { 876 return comparator.apply( this, arguments ); 877 } 878 }; 879 880 // Add all items in the selection to the library, so any featured 881 // images that are not initially loaded still appear. 882 library.observe( this.get('selection') ); 883 }, 884 885 /** 886 * Activates the FeaturedImage controller. 887 * 888 * @since 3.5.0 889 */ 890 activate: function() { 891 this.frame.on( 'open', this.updateSelection, this ); 892 893 Library.prototype.activate.apply( this, arguments ); 894 }, 895 896 /** 897 * Deactivates the FeaturedImage controller. 898 * 899 * @since 3.5.0 900 */ 901 deactivate: function() { 902 this.frame.off( 'open', this.updateSelection, this ); 903 904 Library.prototype.deactivate.apply( this, arguments ); 905 }, 906 907 /** 908 * Updates the selection to match the current featured image. 909 * 910 * @since 3.5.0 911 */ 912 updateSelection: function() { 913 var selection = this.get('selection'), 914 id = wp.media.view.settings.post.featuredImageId, 915 attachment; 916 917 if ( '' !== id && -1 !== id ) { 918 attachment = Attachment.get( id ); 919 attachment.fetch(); 920 } 921 922 selection.reset( attachment ? [ attachment ] : [] ); 923 } 924 }); 925 926 module.exports = FeaturedImage; 927 928 929 /***/ }, 930 931 /***/ 7127 932 (module) { 933 934 var Selection = wp.media.model.Selection, 935 Library = wp.media.controller.Library, 936 l10n = wp.media.view.l10n, 937 GalleryAdd; 938 939 /** 940 * wp.media.controller.GalleryAdd 941 * 942 * A state for selecting more images to add to a gallery. 943 * 944 * @since 3.5.0 945 * 946 * @class 947 * @augments wp.media.controller.Library 948 * @augments wp.media.controller.State 949 * @augments Backbone.Model 950 * 951 * @memberof wp.media.controller 952 * 953 * @param {Object} [attributes] The attributes hash passed to the state. 954 * @param {string} [attributes.id=gallery-library] Unique identifier. 955 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 956 * @param {boolean|string} [attributes.multiple=add] Whether multi-select is enabled. Accepts 'add' or true. 957 * When set to true, requires Shift or Cmd/Ctrl to select multiple items. 958 * When set to 'add', allows selecting multiple items by clicking thumbnails. 959 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 960 * If one is not supplied, a collection of all images will be created. 961 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 962 * Accepts 'all', 'uploaded', or 'unattached'. 963 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 964 * @param {string} [attributes.content=upload] Initial mode for the content region. 965 * Overridden by persistent user setting if 'contentUserSetting' is true. 966 * @param {string} [attributes.router=browse] Initial mode for the router region. 967 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 968 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 969 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 970 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 971 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 972 * @param {number} [attributes.priority=100] The priority for the state link in the media menu. 973 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 974 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 975 */ 976 GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{ 977 defaults: _.defaults({ 978 id: 'gallery-library', 979 title: l10n.addToGalleryTitle, 980 multiple: 'add', 981 filterable: 'uploaded', 982 menu: 'gallery', 983 toolbar: 'gallery-add', 984 priority: 100, 985 syncSelection: false 986 }, Library.prototype.defaults ), 987 988 /** 989 * Initializes the library. Creates a library of images if a library isn't supplied. 990 * 991 * @since 3.5.0 992 * 993 * @return {void} 994 */ 995 initialize: function() { 996 if ( ! this.get('library') ) { 997 this.set( 'library', wp.media.query({ type: 'image' }) ); 998 } 999 1000 Library.prototype.initialize.apply( this, arguments ); 1001 }, 1002 1003 /** 1004 * Activates the library. 1005 * 1006 * Removes all event listeners if in edit mode. Creates a validator to check an attachment. 1007 * Resets library and re-enables event listeners. Activates edit mode. Calls the parent's activate method. 1008 * 1009 * @since 3.5.0 1010 * 1011 * @return {void} 1012 */ 1013 activate: function() { 1014 var library = this.get('library'), 1015 edit = this.frame.state('gallery-edit').get('library'); 1016 1017 if ( this.editLibrary && this.editLibrary !== edit ) { 1018 library.unobserve( this.editLibrary ); 1019 } 1020 1021 /* 1022 * Accept attachments that exist in the original library but 1023 * that do not exist in gallery's library yet. 1024 */ 1025 library.validator = function( attachment ) { 1026 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1027 }; 1028 1029 /* 1030 * Reset the library to ensure that all attachments are re-added 1031 * to the collection. Do so silently, as calling `observe` will 1032 * trigger the `reset` event. 1033 */ 1034 library.reset( library.mirroring.models, { silent: true }); 1035 library.observe( edit ); 1036 this.editLibrary = edit; 1037 1038 Library.prototype.activate.apply( this, arguments ); 1039 } 1040 }); 1041 1042 module.exports = GalleryAdd; 1043 1044 1045 /***/ }, 1046 1047 /***/ 2038 1048 (module) { 1049 1050 var Library = wp.media.controller.Library, 1051 l10n = wp.media.view.l10n, 1052 GalleryEdit; 1053 1054 /** 1055 * wp.media.controller.GalleryEdit 1056 * 1057 * A state for editing a gallery's images and settings. 1058 * 1059 * @since 3.5.0 1060 * 1061 * @class 1062 * @augments wp.media.controller.Library 1063 * @augments wp.media.controller.State 1064 * @augments Backbone.Model 1065 * 1066 * @memberOf wp.media.controller 1067 * 1068 * @param {Object} [attributes] The attributes hash passed to the state. 1069 * @param {string} [attributes.id=gallery-edit] Unique identifier. 1070 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 1071 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 1072 * If one is not supplied, an empty media.model.Selection collection is created. 1073 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1074 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1075 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1076 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1077 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 1078 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1079 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1080 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 1081 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1082 * @param {number} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1083 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1084 * @param {number} [attributes.priority=60] The priority for the state link in the media menu. 1085 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1086 * Defaults to false for this state, because the library passed in *is* the selection. 1087 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1088 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1089 */ 1090 GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{ 1091 defaults: { 1092 id: 'gallery-edit', 1093 title: l10n.editGalleryTitle, 1094 multiple: false, 1095 searchable: false, 1096 sortable: true, 1097 date: false, 1098 display: false, 1099 content: 'browse', 1100 toolbar: 'gallery-edit', 1101 describe: true, 1102 displaySettings: true, 1103 dragInfo: true, 1104 idealColumnWidth: 170, 1105 editing: false, 1106 priority: 60, 1107 syncSelection: false 1108 }, 1109 1110 /** 1111 * Initializes the library. 1112 * 1113 * Creates a selection if a library isn't supplied and creates an attachment 1114 * view if no attachment view is supplied. 1115 * 1116 * @since 3.5.0 1117 * 1118 * @return {void} 1119 */ 1120 initialize: function() { 1121 // If we haven't been provided a `library`, create a `Selection`. 1122 if ( ! this.get('library') ) { 1123 this.set( 'library', new wp.media.model.Selection() ); 1124 } 1125 1126 // The single `Attachment` view to be used in the `Attachments` view. 1127 if ( ! this.get('AttachmentView') ) { 1128 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1129 } 1130 1131 Library.prototype.initialize.apply( this, arguments ); 1132 }, 1133 1134 /** 1135 * Activates the library. 1136 * 1137 * Limits the library to images, watches for uploaded attachments. Watches for 1138 * the browse event on the frame and binds it to gallerySettings. 1139 * 1140 * @since 3.5.0 1141 * 1142 * @return {void} 1143 */ 1144 activate: function() { 1145 var library = this.get('library'); 1146 1147 // Limit the library to images only. 1148 library.props.set( 'type', 'image' ); 1149 1150 // Watch for uploaded attachments. 1151 this.get('library').observe( wp.Uploader.queue ); 1152 1153 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 1154 1155 Library.prototype.activate.apply( this, arguments ); 1156 }, 1157 1158 /** 1159 * Deactivates the library. 1160 * 1161 * Stops watching for uploaded attachments and browse events. 1162 * 1163 * @since 3.5.0 1164 * 1165 * @return {void} 1166 */ 1167 deactivate: function() { 1168 // Stop watching for uploaded attachments. 1169 this.get('library').unobserve( wp.Uploader.queue ); 1170 1171 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 1172 1173 Library.prototype.deactivate.apply( this, arguments ); 1174 }, 1175 1176 /** 1177 * Adds the gallery settings to the sidebar and adds a reverse button to the 1178 * toolbar. 1179 * 1180 * @since 3.5.0 1181 * 1182 * @param {wp.media.view.Frame} browser The file browser. 1183 * 1184 * @return {void} 1185 */ 1186 gallerySettings: function( browser ) { 1187 if ( ! this.get('displaySettings') ) { 1188 return; 1189 } 1190 1191 var library = this.get('library'); 1192 1193 if ( ! library || ! browser ) { 1194 return; 1195 } 1196 1197 library.gallery = library.gallery || new Backbone.Model(); 1198 1199 browser.sidebar.set({ 1200 gallery: new wp.media.view.Settings.Gallery({ 1201 controller: this, 1202 model: library.gallery, 1203 priority: 40 1204 }) 1205 }); 1206 1207 browser.toolbar.set( 'reverse', { 1208 text: l10n.reverseOrder, 1209 priority: 80, 1210 1211 click: function() { 1212 library.reset( library.toArray().reverse() ); 1213 } 1214 }); 1215 } 1216 }); 1217 1218 module.exports = GalleryEdit; 1219 1220 1221 /***/ }, 1222 1223 /***/ 705 1224 (module) { 1225 1226 var State = wp.media.controller.State, 1227 Library = wp.media.controller.Library, 1228 l10n = wp.media.view.l10n, 1229 ImageDetails; 1230 1231 /** 1232 * wp.media.controller.ImageDetails 1233 * 1234 * A state for editing the attachment display settings of an image that's been 1235 * inserted into the editor. 1236 * 1237 * @memberOf wp.media.controller 1238 * 1239 * @class 1240 * @augments wp.media.controller.State 1241 * @augments Backbone.Model 1242 * 1243 * @param {Object} [attributes] The attributes hash passed to the state. 1244 * @param {string} [attributes.id=image-details] Unique identifier. 1245 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 1246 * @param {wp.media.model.Attachment} attributes.image The image's model. 1247 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 1248 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 1249 * @param {string|false} [attributes.router=false] Initial mode for the router region. 1250 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1251 * @param {boolean} [attributes.editing=false] Unused. 1252 * @param {number} [attributes.priority=60] Unused. 1253 * 1254 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1255 * however this may not do anything. 1256 */ 1257 ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{ 1258 defaults: _.defaults({ 1259 id: 'image-details', 1260 title: l10n.imageDetailsTitle, 1261 content: 'image-details', 1262 menu: false, 1263 router: false, 1264 toolbar: 'image-details', 1265 editing: false, 1266 priority: 60 1267 }, Library.prototype.defaults ), 1268 1269 /** 1270 * Initializes the ImageDetails controller. 1271 * 1272 * @since 3.9.0 1273 * 1274 * @param {Object} options Attributes. 1275 */ 1276 initialize: function( options ) { 1277 this.image = options.image; 1278 State.prototype.initialize.apply( this, arguments ); 1279 }, 1280 1281 /** 1282 * Activates the ImageDetails controller. 1283 * 1284 * @since 3.9.0 1285 */ 1286 activate: function() { 1287 this.frame.modal.$el.addClass('image-details'); 1288 } 1289 }); 1290 1291 module.exports = ImageDetails; 1292 1293 1294 /***/ }, 1295 1296 /***/ 472 1297 (module) { 1298 1299 var l10n = wp.media.view.l10n, 1300 getUserSetting = window.getUserSetting, 1301 setUserSetting = window.setUserSetting, 1302 Library; 1303 1304 /** 1305 * wp.media.controller.Library 1306 * 1307 * A state for choosing an attachment or group of attachments from the media library. 1308 * 1309 * @memberOf wp.media.controller 1310 * 1311 * @class 1312 * @augments wp.media.controller.State 1313 * @augments Backbone.Model 1314 * @mixes media.selectionSync 1315 * 1316 * @param {Object} [attributes] The attributes hash passed to the state. 1317 * @param {string} [attributes.id=library] Unique identifier. 1318 * @param {string} [attributes.title=Media library] Title for the state. Displays in the media menu and the frame's title region. 1319 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1320 * If one is not supplied, a collection of all attachments will be created. 1321 * @param {wp.media.model.Selection|object} [attributes.selection] A collection to contain attachment selections within the state. 1322 * If the 'selection' attribute is a plain JS object, 1323 * a Selection will be created using its values as the selection instance's `props` model. 1324 * Otherwise, it will copy the library's `props` model. 1325 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1326 * @param {string} [attributes.content=upload] Initial mode for the content region. 1327 * Overridden by persistent user setting if 'contentUserSetting' is true. 1328 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1329 * @param {string} [attributes.router=browse] Initial mode for the router region. 1330 * @param {string} [attributes.toolbar=select] Initial mode for the toolbar region. 1331 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1332 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1333 * Accepts 'all', 'uploaded', or 'unattached'. 1334 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1335 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1336 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1337 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1338 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1339 */ 1340 Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{ 1341 defaults: { 1342 id: 'library', 1343 title: l10n.mediaLibraryTitle, 1344 multiple: false, 1345 content: 'upload', 1346 menu: 'default', 1347 router: 'browse', 1348 toolbar: 'select', 1349 searchable: true, 1350 filterable: false, 1351 sortable: true, 1352 autoSelect: true, 1353 describe: false, 1354 contentUserSetting: true, 1355 syncSelection: true 1356 }, 1357 1358 /** 1359 * Initializes the Library controller. 1360 * 1361 * If a library isn't provided, query all media items. 1362 * If a selection instance isn't provided, create one. 1363 * 1364 * @since 3.5.0 1365 */ 1366 initialize: function() { 1367 var selection = this.get('selection'), 1368 props; 1369 1370 if ( ! this.get('library') ) { 1371 this.set( 'library', wp.media.query() ); 1372 } 1373 1374 if ( ! ( selection instanceof wp.media.model.Selection ) ) { 1375 props = selection; 1376 1377 if ( ! props ) { 1378 props = this.get('library').props.toJSON(); 1379 props = _.omit( props, 'orderby', 'query' ); 1380 } 1381 1382 this.set( 'selection', new wp.media.model.Selection( null, { 1383 multiple: this.get('multiple'), 1384 props: props 1385 }) ); 1386 } 1387 1388 this.resetDisplays(); 1389 }, 1390 1391 /** 1392 * Activates the Library controller. 1393 * 1394 * @since 3.5.0 1395 */ 1396 activate: function() { 1397 this.syncSelection(); 1398 1399 wp.Uploader.queue.on( 'add', this.uploading, this ); 1400 1401 this.get('selection').on( 'add remove reset', this.refreshContent, this ); 1402 1403 if ( this.get( 'router' ) && this.get('contentUserSetting') ) { 1404 this.frame.on( 'content:activate', this.saveContentMode, this ); 1405 this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) ); 1406 } 1407 }, 1408 1409 /** 1410 * Deactivates the Library controller. 1411 * 1412 * @since 3.5.0 1413 */ 1414 deactivate: function() { 1415 this.recordSelection(); 1416 1417 this.frame.off( 'content:activate', this.saveContentMode, this ); 1418 1419 // Unbind all event handlers that use this state as the context 1420 // from the selection. 1421 this.get('selection').off( null, null, this ); 1422 1423 wp.Uploader.queue.off( null, null, this ); 1424 }, 1425 1426 /** 1427 * Resets the library to its initial state. 1428 * 1429 * @since 3.5.0 1430 */ 1431 reset: function() { 1432 this.get('selection').reset(); 1433 this.resetDisplays(); 1434 this.refreshContent(); 1435 }, 1436 1437 /** 1438 * Resets the attachment display settings defaults to the site options. 1439 * 1440 * If site options don't define them, fall back to a persistent user setting. 1441 * 1442 * @since 3.5.0 1443 */ 1444 resetDisplays: function() { 1445 var defaultProps = wp.media.view.settings.defaultProps; 1446 this._displays = []; 1447 this._defaultDisplaySettings = { 1448 align: getUserSetting( 'align', defaultProps.align ) || 'none', 1449 size: getUserSetting( 'imgsize', defaultProps.size ) || 'medium', 1450 link: getUserSetting( 'urlbutton', defaultProps.link ) || 'none' 1451 }; 1452 }, 1453 1454 /** 1455 * Creates a model to represent display settings (alignment, etc.) for an attachment. 1456 * 1457 * @since 3.5.0 1458 * 1459 * @param {wp.media.model.Attachment} attachment The attachment for which to create display settings. 1460 * @return {Backbone.Model} A model representing the display settings for the attachment. 1461 */ 1462 display: function( attachment ) { 1463 var displays = this._displays; 1464 1465 if ( ! displays[ attachment.cid ] ) { 1466 displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) ); 1467 } 1468 return displays[ attachment.cid ]; 1469 }, 1470 1471 /** 1472 * Given an attachment, creates attachment display settings properties. 1473 * 1474 * @since 3.6.0 1475 * 1476 * @param {wp.media.model.Attachment} attachment The attachment for which to get the default display settings. 1477 * @return {Object} The default display settings for the attachment. 1478 */ 1479 defaultDisplaySettings: function( attachment ) { 1480 var settings = _.clone( this._defaultDisplaySettings ); 1481 1482 settings.canEmbed = this.canEmbed( attachment ); 1483 if ( settings.canEmbed ) { 1484 settings.link = 'embed'; 1485 } else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) { 1486 settings.link = 'file'; 1487 } 1488 1489 return settings; 1490 }, 1491 1492 /** 1493 * Determines whether an attachment is an image. 1494 * 1495 * @since 4.4.1 1496 * 1497 * @param {wp.media.model.Attachment} attachment The attachment to check. 1498 * @return {boolean} True if the attachment is an image, false otherwise. 1499 */ 1500 isImageAttachment: function( attachment ) { 1501 // If uploading, we know the filename but not the mime type. 1502 if ( attachment.get('uploading') ) { 1503 return /\.(jpe?g|png|gif|webp|avif|heic|heif)$/i.test( attachment.get('filename') ); 1504 } 1505 1506 return attachment.get('type') === 'image'; 1507 }, 1508 1509 /** 1510 * Determines whether an attachment can be embedded (audio or video). 1511 * 1512 * @since 3.6.0 1513 * 1514 * @param {wp.media.model.Attachment} attachment The attachment to check. 1515 * @return {boolean} True if the attachment can be embedded, false otherwise. 1516 */ 1517 canEmbed: function( attachment ) { 1518 // If uploading, we know the filename but not the mime type. 1519 if ( ! attachment.get('uploading') ) { 1520 var type = attachment.get('type'); 1521 if ( type !== 'audio' && type !== 'video' ) { 1522 return false; 1523 } 1524 } 1525 1526 return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() ); 1527 }, 1528 1529 1530 /** 1531 * Resets the content mode to the default. 1532 * 1533 * If the state is active, no items are selected, and the current 1534 * content mode is not an option in the state's router (provided 1535 * the state has a router), reset the content mode to the default. 1536 * 1537 * @since 3.5.0 1538 */ 1539 refreshContent: function() { 1540 var selection = this.get('selection'), 1541 frame = this.frame, 1542 router = frame.router.get(), 1543 mode = frame.content.mode(); 1544 1545 if ( this.active && ! selection.length && router && ! router.get( mode ) ) { 1546 this.frame.content.render( this.get('content') ); 1547 } 1548 }, 1549 1550 /** 1551 * Callback handler when an attachment is uploaded. 1552 * 1553 * Switch to the Media Library if uploaded from the 'Upload Files' tab. 1554 * 1555 * Adds any uploading attachments to the selection. 1556 * 1557 * If the state only supports one attachment to be selected and multiple 1558 * attachments are uploaded, the last attachment in the upload queue will 1559 * be selected. 1560 * 1561 * @since 3.5.0 1562 * 1563 * @param {wp.media.model.Attachment} attachment The attachment that was uploaded. 1564 */ 1565 uploading: function( attachment ) { 1566 var content = this.frame.content; 1567 1568 if ( 'upload' === content.mode() ) { 1569 this.frame.content.mode('browse'); 1570 } 1571 1572 if ( this.get( 'autoSelect' ) ) { 1573 this.get('selection').add( attachment ); 1574 this.frame.trigger( 'library:selection:add' ); 1575 } 1576 }, 1577 1578 /** 1579 * Persists the mode of the content region as a user setting. 1580 * 1581 * @since 3.5.0 1582 */ 1583 saveContentMode: function() { 1584 if ( 'browse' !== this.get('router') ) { 1585 return; 1586 } 1587 1588 var mode = this.frame.content.mode(), 1589 view = this.frame.router.get(); 1590 1591 if ( view && view.get( mode ) ) { 1592 setUserSetting( 'libraryContent', mode ); 1593 } 1594 } 1595 1596 }); 1597 1598 // Make selectionSync available on any Media Library state. 1599 _.extend( Library.prototype, wp.media.selectionSync ); 1600 1601 module.exports = Library; 1602 1603 1604 /***/ }, 1605 1606 /***/ 8065 1607 (module) { 1608 1609 /** 1610 * wp.media.controller.MediaLibrary 1611 * 1612 * @memberOf wp.media.controller 1613 * 1614 * @class 1615 * @augments wp.media.controller.Library 1616 * @augments wp.media.controller.State 1617 * @augments Backbone.Model 1618 */ 1619 var Library = wp.media.controller.Library, 1620 MediaLibrary; 1621 1622 MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{ 1623 defaults: _.defaults({ 1624 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1625 filterable: 'uploaded', 1626 1627 displaySettings: false, 1628 priority: 80, 1629 syncSelection: false 1630 }, Library.prototype.defaults ), 1631 1632 /** 1633 * Initializes the MediaLibrary controller. 1634 * 1635 * @since 3.9.0 1636 * 1637 * @param {Object} options Attributes. 1638 */ 1639 initialize: function( options ) { 1640 this.media = options.media; 1641 this.type = options.type; 1642 this.set( 'library', wp.media.query({ type: this.type }) ); 1643 1644 Library.prototype.initialize.apply( this, arguments ); 1645 }, 1646 1647 /** 1648 * Activates the MediaLibrary controller. 1649 * 1650 * @since 3.9.0 1651 */ 1652 activate: function() { 1653 // @todo this should use this.frame. 1654 if ( wp.media.frame.lastMime ) { 1655 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 1656 delete wp.media.frame.lastMime; 1657 } 1658 Library.prototype.activate.apply( this, arguments ); 1659 } 1660 }); 1661 1662 module.exports = MediaLibrary; 1663 1664 1665 /***/ }, 1666 1667 /***/ 9875 1668 (module) { 1669 1670 /** 1671 * wp.media.controller.Region 1672 * 1673 * A region is a persistent application layout area. 1674 * 1675 * A region assumes one mode at any time, and can be switched to another. 1676 * 1677 * When mode changes, events are triggered on the region's parent view. 1678 * The parent view will listen to specific events and fill the region with an 1679 * appropriate view depending on mode. For example, a frame listens for the 1680 * 'browse' mode t be activated on the 'content' view and then fills the region 1681 * with an AttachmentsBrowser view. 1682 * 1683 * @memberOf wp.media.controller 1684 * 1685 * @class 1686 * 1687 * @param {Object} options Options hash for the region. 1688 * @param {string} options.id Unique identifier for the region. 1689 * @param {Backbone.View} options.view A parent view the region exists within. 1690 * @param {string} options.selector jQuery selector for the region within the parent view. 1691 */ 1692 var Region = function( options ) { 1693 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 1694 }; 1695 1696 // Use Backbone's self-propagating `extend` inheritance method. 1697 Region.extend = Backbone.Model.extend; 1698 1699 _.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{ 1700 /** 1701 * Activate a mode. 1702 * 1703 * @since 3.5.0 1704 * 1705 * @param {string} mode The mode to activate on the region. 1706 * 1707 * @fires Region#activate 1708 * @fires Region#deactivate 1709 * 1710 * @return {wp.media.controller.Region} Returns itself to allow chaining. 1711 */ 1712 mode: function( mode ) { 1713 if ( ! mode ) { 1714 return this._mode; 1715 } 1716 // Bail if we're trying to change to the current mode. 1717 if ( mode === this._mode ) { 1718 return this; 1719 } 1720 1721 /** 1722 * Region mode deactivation event. 1723 * 1724 * @event wp.media.controller.Region#deactivate 1725 */ 1726 this.trigger('deactivate'); 1727 1728 this._mode = mode; 1729 this.render( mode ); 1730 1731 /** 1732 * Region mode activation event. 1733 * 1734 * @event wp.media.controller.Region#activate 1735 */ 1736 this.trigger('activate'); 1737 return this; 1738 }, 1739 /** 1740 * Render a mode. 1741 * 1742 * @since 3.5.0 1743 * 1744 * @param {string} mode The mode to render on the region. 1745 * 1746 * @fires Region#create 1747 * @fires Region#render 1748 * 1749 * @return {wp.media.controller.Region} Returns itself to allow chaining. 1750 */ 1751 render: function( mode ) { 1752 // If the mode isn't active, activate it. 1753 if ( mode && mode !== this._mode ) { 1754 return this.mode( mode ); 1755 } 1756 1757 var set = { view: null }, 1758 view; 1759 1760 /** 1761 * Create region view event. 1762 * 1763 * Region view creation takes place in an event callback on the frame. 1764 * 1765 * @event wp.media.controller.Region#create 1766 * @type {Object} 1767 * @property {Object} view The view object for the region. 1768 */ 1769 this.trigger( 'create', set ); 1770 view = set.view; 1771 1772 /** 1773 * Render region view event. 1774 * 1775 * Region view creation takes place in an event callback on the frame. 1776 * 1777 * @event wp.media.controller.Region#render 1778 * @type {Object} 1779 */ 1780 this.trigger( 'render', view ); 1781 if ( view ) { 1782 this.set( view ); 1783 } 1784 return this; 1785 }, 1786 1787 /** 1788 * Get the region's view. 1789 * 1790 * @since 3.5.0 1791 * 1792 * @return {wp.media.View} Returns the region's view. 1793 */ 1794 get: function() { 1795 return this.view.views.first( this.selector ); 1796 }, 1797 1798 /** 1799 * Set the region's view as a subview of the frame. 1800 * 1801 * @since 3.5.0 1802 * 1803 * @param {Array|Object} views The views to set on the region. 1804 * @param {Object} [options={}] Optional. Additional options for setting the views on the region. 1805 * @return {wp.Backbone.Subviews} Subviews is returned to allow chaining. 1806 */ 1807 set: function( views, options ) { 1808 if ( options ) { 1809 options.add = false; 1810 } 1811 return this.view.views.set( this.selector, views, options ); 1812 }, 1813 1814 /** 1815 * Trigger regional view events on the frame. 1816 * 1817 * @since 3.5.0 1818 * 1819 * @param {string} event The event to trigger on the region. 1820 * @return {undefined|wp.media.controller.Region} Returns itself to allow chaining. 1821 */ 1822 trigger: function( event ) { 1823 var base, args; 1824 1825 if ( ! this._mode ) { 1826 return; 1827 } 1828 1829 args = _.toArray( arguments ); 1830 base = this.id + ':' + event; 1831 1832 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 1833 args[0] = base + ':' + this._mode; 1834 this.view.trigger.apply( this.view, args ); 1835 1836 // Trigger `{this.id}:{event}` event on the frame. 1837 args[0] = base; 1838 this.view.trigger.apply( this.view, args ); 1839 return this; 1840 } 1841 }); 1842 1843 module.exports = Region; 1844 1845 1846 /***/ }, 1847 1848 /***/ 2275 1849 (module) { 1850 1851 var Library = wp.media.controller.Library, 1852 l10n = wp.media.view.l10n, 1853 ReplaceImage; 1854 1855 /** 1856 * wp.media.controller.ReplaceImage 1857 * 1858 * A state for replacing an image. 1859 * 1860 * @memberOf wp.media.controller 1861 * 1862 * @class 1863 * @augments wp.media.controller.Library 1864 * @augments wp.media.controller.State 1865 * @augments Backbone.Model 1866 * 1867 * @param {Object} [attributes] The attributes hash passed to the state. 1868 * @param {string} [attributes.id=replace-image] Unique identifier. 1869 * @param {string} [attributes.title=Replace Image] Title for the state. Displays in the media menu and the frame's title region. 1870 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1871 * If one is not supplied, a collection of all images will be created. 1872 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1873 * @param {string} [attributes.content=upload] Initial mode for the content region. 1874 * Overridden by persistent user setting if 'contentUserSetting' is true. 1875 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1876 * @param {string} [attributes.router=browse] Initial mode for the router region. 1877 * @param {string} [attributes.toolbar=replace] Initial mode for the toolbar region. 1878 * @param {number} [attributes.priority=60] The priority for the state link in the media menu. 1879 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1880 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1881 * Accepts 'all', 'uploaded', or 'unattached'. 1882 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1883 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1884 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1885 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1886 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1887 */ 1888 ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{ 1889 defaults: _.defaults({ 1890 id: 'replace-image', 1891 title: l10n.replaceImageTitle, 1892 multiple: false, 1893 filterable: 'uploaded', 1894 toolbar: 'replace', 1895 menu: false, 1896 priority: 60, 1897 syncSelection: true 1898 }, Library.prototype.defaults ), 1899 1900 /** 1901 * Initializes the ReplaceImage controller. 1902 * 1903 * @since 3.9.0 1904 * 1905 * @param {Object} options Attributes. 1906 */ 1907 initialize: function( options ) { 1908 var library, comparator; 1909 1910 this.image = options.image; 1911 // If we haven't been provided a `library`, create a `Selection`. 1912 if ( ! this.get('library') ) { 1913 this.set( 'library', wp.media.query({ type: 'image' }) ); 1914 } 1915 1916 Library.prototype.initialize.apply( this, arguments ); 1917 1918 library = this.get('library'); 1919 comparator = library.comparator; 1920 1921 // Overload the library's comparator to push items that are not in 1922 // the mirrored query to the front of the aggregate collection. 1923 library.comparator = function( a, b ) { 1924 var aInQuery = !! this.mirroring.get( a.cid ), 1925 bInQuery = !! this.mirroring.get( b.cid ); 1926 1927 if ( ! aInQuery && bInQuery ) { 1928 return -1; 1929 } else if ( aInQuery && ! bInQuery ) { 1930 return 1; 1931 } else { 1932 return comparator.apply( this, arguments ); 1933 } 1934 }; 1935 1936 // Add all items in the selection to the library, so any featured 1937 // images that are not initially loaded still appear. 1938 library.observe( this.get('selection') ); 1939 }, 1940 1941 /** 1942 * Activates the ReplaceImage controller. 1943 * 1944 * @since 3.9.0 1945 */ 1946 activate: function() { 1947 this.frame.on( 'content:render:browse', this.updateSelection, this ); 1948 1949 Library.prototype.activate.apply( this, arguments ); 1950 }, 1951 1952 /** 1953 * Deactivates the ReplaceImage controller. 1954 * 1955 * @since 5.9.0 1956 */ 1957 deactivate: function() { 1958 this.frame.off( 'content:render:browse', this.updateSelection, this ); 1959 1960 Library.prototype.deactivate.apply( this, arguments ); 1961 }, 1962 1963 /** 1964 * Updates the selection to match the current image. 1965 * 1966 * @since 3.9.0 1967 */ 1968 updateSelection: function() { 1969 var selection = this.get('selection'), 1970 attachment = this.image.attachment; 1971 1972 selection.reset( attachment ? [ attachment ] : [] ); 1973 } 1974 }); 1975 1976 module.exports = ReplaceImage; 1977 1978 1979 /***/ }, 1980 1981 /***/ 6172 1982 (module) { 1983 1984 var Controller = wp.media.controller, 1985 SiteIconCropper; 1986 1987 /** 1988 * wp.media.controller.SiteIconCropper 1989 * 1990 * A state for cropping a Site Icon. 1991 * 1992 * @memberOf wp.media.controller 1993 * 1994 * @class 1995 * @augments wp.media.controller.Cropper 1996 * @augments wp.media.controller.State 1997 * @augments Backbone.Model 1998 */ 1999 SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{ 2000 activate: function() { 2001 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2002 this.frame.on( 'close', this.removeCropper, this ); 2003 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2004 }, 2005 2006 createCropContent: function() { 2007 this.cropperView = new wp.media.view.SiteIconCropper({ 2008 controller: this, 2009 attachment: this.get('selection').first() 2010 }); 2011 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2012 this.frame.content.set(this.cropperView); 2013 2014 }, 2015 2016 doCrop: function( attachment ) { 2017 var cropDetails = attachment.get( 'cropDetails' ), 2018 control = this.get( 'control' ); 2019 2020 cropDetails.dst_width = control.params.width; 2021 cropDetails.dst_height = control.params.height; 2022 2023 return wp.ajax.post( 'crop-image', { 2024 nonce: attachment.get( 'nonces' ).edit, 2025 id: attachment.get( 'id' ), 2026 context: 'site-icon', 2027 cropDetails: cropDetails 2028 } ); 2029 } 2030 }); 2031 2032 module.exports = SiteIconCropper; 2033 2034 2035 /***/ }, 2036 2037 /***/ 6150 2038 (module) { 2039 2040 /** 2041 * wp.media.controller.StateMachine 2042 * 2043 * A state machine keeps track of state. It is in one state at a time, 2044 * and can change from one state to another. 2045 * 2046 * States are stored as models in a Backbone collection. 2047 * 2048 * @memberOf wp.media.controller 2049 * 2050 * @since 3.5.0 2051 * 2052 * @class 2053 * @augments Backbone.Model 2054 * @mixin 2055 * @mixes Backbone.Events 2056 */ 2057 var StateMachine = function() { 2058 return { 2059 // Use Backbone's self-propagating `extend` inheritance method. 2060 extend: Backbone.Model.extend 2061 }; 2062 }; 2063 2064 _.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{ 2065 /** 2066 * Fetch a state. 2067 * 2068 * If no `id` is provided, returns the active state. 2069 * 2070 * Implicitly creates states. 2071 * 2072 * Ensure that the `states` collection exists so the `StateMachine` 2073 * can be used as a mixin. 2074 * 2075 * @since 3.5.0 2076 * 2077 * @param {string} id The ID of the state to fetch. If omitted, the active state is returned. 2078 * @return {wp.media.controller.State} Returns a State model from 2079 * the StateMachine collection. 2080 */ 2081 state: function( id ) { 2082 this.states = this.states || new Backbone.Collection(); 2083 2084 // Default to the active state. 2085 id = id || this._state; 2086 2087 if ( id && ! this.states.get( id ) ) { 2088 this.states.add({ id: id }); 2089 } 2090 return this.states.get( id ); 2091 }, 2092 2093 /** 2094 * Sets the active state. 2095 * 2096 * Bail if we're trying to select the current state, if we haven't 2097 * created the `states` collection, or are trying to select a state 2098 * that does not exist. 2099 * 2100 * @since 3.5.0 2101 * 2102 * @param {string} id The ID of the state to set as active. 2103 * 2104 * @fires wp.media.controller.State#deactivate 2105 * @fires wp.media.controller.State#activate 2106 * 2107 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 2108 */ 2109 setState: function( id ) { 2110 var previous = this.state(); 2111 2112 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 2113 return this; 2114 } 2115 2116 if ( previous ) { 2117 previous.trigger('deactivate'); 2118 this._lastState = previous.id; 2119 } 2120 2121 this._state = id; 2122 this.state().trigger('activate'); 2123 2124 return this; 2125 }, 2126 2127 /** 2128 * Returns the previous active state. 2129 * 2130 * Call the `state()` method with no parameters to retrieve the current 2131 * active state. 2132 * 2133 * @since 3.5.0 2134 * 2135 * @return {void|wp.media.controller.State} Returns a State model from 2136 * the StateMachine collection. 2137 */ 2138 lastState: function() { 2139 if ( this._lastState ) { 2140 return this.state( this._lastState ); 2141 } 2142 } 2143 }); 2144 2145 // Map all event binding and triggering on a StateMachine to its `states` collection. 2146 _.each([ 'on', 'off', 'trigger' ], function( method ) { 2147 /** 2148 * Binds an event listener to events in the StateMachine's states collection. 2149 * 2150 * @function on 2151 * @memberOf wp.media.controller.StateMachine 2152 * @instance 2153 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 2154 */ 2155 /** 2156 * Unbinds an event listener from the StateMachine's states collection. 2157 * 2158 * @function off 2159 * @memberOf wp.media.controller.StateMachine 2160 * @instance 2161 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 2162 */ 2163 /** 2164 * Triggers an event on the StateMachine's states collection. 2165 * 2166 * @function trigger 2167 * @memberOf wp.media.controller.StateMachine 2168 * @instance 2169 * @return {wp.media.controller.StateMachine} Returns itself to allow chaining. 2170 */ 2171 StateMachine.prototype[ method ] = function() { 2172 // Ensure that the `states` collection exists so the `StateMachine` 2173 // can be used as a mixin. 2174 this.states = this.states || new Backbone.Collection(); 2175 // Forward the method to the `states` collection. 2176 this.states[ method ].apply( this.states, arguments ); 2177 return this; 2178 }; 2179 }); 2180 2181 module.exports = StateMachine; 2182 2183 2184 /***/ }, 2185 2186 /***/ 5694 2187 (module) { 2188 2189 /** 2190 * wp.media.controller.State 2191 * 2192 * A state is a step in a workflow that when set will trigger the controllers 2193 * for the regions to be updated as specified in the frame. 2194 * 2195 * A state has an event-driven lifecycle: 2196 * 2197 * 'ready' triggers when a state is added to a state machine's collection. 2198 * 'activate' triggers when a state is activated by a state machine. 2199 * 'deactivate' triggers when a state is deactivated by a state machine. 2200 * 'reset' is not triggered automatically. It should be invoked by the 2201 * proper controller to reset the state to its default. 2202 * 2203 * @memberOf wp.media.controller 2204 * 2205 * @class 2206 * @augments Backbone.Model 2207 */ 2208 var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{ 2209 /** 2210 * Constructor. 2211 * 2212 * @since 3.5.0 2213 */ 2214 constructor: function() { 2215 this.on( 'activate', this._preActivate, this ); 2216 this.on( 'activate', this.activate, this ); 2217 this.on( 'activate', this._postActivate, this ); 2218 this.on( 'deactivate', this._deactivate, this ); 2219 this.on( 'deactivate', this.deactivate, this ); 2220 this.on( 'reset', this.reset, this ); 2221 this.on( 'ready', this._ready, this ); 2222 this.on( 'ready', this.ready, this ); 2223 /** 2224 * Call parent constructor with passed arguments 2225 */ 2226 Backbone.Model.apply( this, arguments ); 2227 this.on( 'change:menu', this._updateMenu, this ); 2228 }, 2229 /** 2230 * Ready event callback. 2231 * 2232 * @abstract 2233 * @since 3.5.0 2234 */ 2235 ready: function() {}, 2236 2237 /** 2238 * Activate event callback. 2239 * 2240 * @abstract 2241 * @since 3.5.0 2242 */ 2243 activate: function() {}, 2244 2245 /** 2246 * Deactivate event callback. 2247 * 2248 * @abstract 2249 * @since 3.5.0 2250 */ 2251 deactivate: function() {}, 2252 2253 /** 2254 * Reset event callback. 2255 * 2256 * @abstract 2257 * @since 3.5.0 2258 */ 2259 reset: function() {}, 2260 2261 /** 2262 * Ready event callback. 2263 * 2264 * @since 3.5.0 2265 * @access private 2266 */ 2267 _ready: function() { 2268 this._updateMenu(); 2269 }, 2270 2271 /** 2272 * Pre-activate event callback. 2273 * 2274 * @since 3.5.0 2275 * @access private 2276 */ 2277 _preActivate: function() { 2278 this.active = true; 2279 }, 2280 2281 /** 2282 * Post-activate event callback. 2283 * 2284 * @since 3.5.0 2285 * @access private 2286 */ 2287 _postActivate: function() { 2288 this.on( 'change:menu', this._menu, this ); 2289 this.on( 'change:titleMode', this._title, this ); 2290 this.on( 'change:content', this._content, this ); 2291 this.on( 'change:toolbar', this._toolbar, this ); 2292 2293 this.frame.on( 'title:render:default', this._renderTitle, this ); 2294 2295 this._title(); 2296 this._menu(); 2297 this._toolbar(); 2298 this._content(); 2299 this._router(); 2300 }, 2301 2302 /** 2303 * Deactivate event callback. 2304 * 2305 * @since 3.5.0 2306 * @access private 2307 */ 2308 _deactivate: function() { 2309 this.active = false; 2310 2311 this.frame.off( 'title:render:default', this._renderTitle, this ); 2312 2313 this.off( 'change:menu', this._menu, this ); 2314 this.off( 'change:titleMode', this._title, this ); 2315 this.off( 'change:content', this._content, this ); 2316 this.off( 'change:toolbar', this._toolbar, this ); 2317 }, 2318 2319 /** 2320 * Renders the frame's title using the titleMode property. 2321 * 2322 * 2323 * @since 3.5.0 2324 * @access private 2325 */ 2326 _title: function() { 2327 this.frame.title.render( this.get('titleMode') || 'default' ); 2328 }, 2329 2330 /** 2331 * Renders the title in the media frame. 2332 * 2333 * @param {media.view.Title} view The title view. 2334 * @since 3.5.0 2335 * @access private 2336 */ 2337 _renderTitle: function( view ) { 2338 view.$el.text( this.get('title') || '' ); 2339 }, 2340 2341 /** 2342 * Renders and manages the router region. 2343 * 2344 * @since 3.5.0 2345 * @access private 2346 */ 2347 _router: function() { 2348 var router = this.frame.router, 2349 mode = this.get('router'), 2350 view; 2351 2352 this.frame.$el.toggleClass( 'hide-router', ! mode ); 2353 if ( ! mode ) { 2354 return; 2355 } 2356 2357 this.frame.router.render( mode ); 2358 2359 view = router.get(); 2360 if ( view && view.select ) { 2361 view.select( this.frame.content.mode() ); 2362 } 2363 }, 2364 2365 /** 2366 * Renders and manages the menu region. 2367 * 2368 * @since 3.5.0 2369 * @access private 2370 */ 2371 _menu: function() { 2372 var menu = this.frame.menu, 2373 mode = this.get('menu'), 2374 actionMenuItems, 2375 actionMenuLength, 2376 view; 2377 2378 if ( this.frame.menu ) { 2379 actionMenuItems = this.frame.menu.get('views'), 2380 actionMenuLength = actionMenuItems ? actionMenuItems.views.get().length : 0, 2381 // Show action menu only if it is active and has more than one default element. 2382 this.frame.$el.toggleClass( 'hide-menu', ! mode || actionMenuLength < 2 ); 2383 } 2384 if ( ! mode ) { 2385 return; 2386 } 2387 2388 menu.mode( mode ); 2389 2390 view = menu.get(); 2391 if ( view && view.select ) { 2392 view.select( this.id ); 2393 } 2394 }, 2395 2396 /** 2397 * Updates the menu. 2398 * 2399 * @since 3.5.0 2400 * @access private 2401 */ 2402 _updateMenu: function() { 2403 var previous = this.previous('menu'), 2404 menu = this.get('menu'); 2405 2406 if ( previous ) { 2407 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 2408 } 2409 2410 if ( menu ) { 2411 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 2412 } 2413 }, 2414 2415 /** 2416 * Creates a view in the media menu for the state. 2417 * 2418 * @since 3.5.0 2419 * @access private 2420 * 2421 * @param {media.view.Menu} view The menu view. 2422 */ 2423 _renderMenu: function( view ) { 2424 var menuItem = this.get('menuItem'), 2425 title = this.get('title'), 2426 priority = this.get('priority'); 2427 2428 if ( ! menuItem && title ) { 2429 menuItem = { text: title }; 2430 2431 if ( priority ) { 2432 menuItem.priority = priority; 2433 } 2434 } 2435 2436 if ( ! menuItem ) { 2437 return; 2438 } 2439 2440 view.set( this.id, menuItem ); 2441 } 2442 }); 2443 2444 /** 2445 * Creates render methods for frame regions. 2446 */ 2447 _.each(['toolbar','content'], function( region ) { 2448 /** 2449 * Renders the region in the media frame. 2450 * 2451 * @access private 2452 */ 2453 State.prototype[ '_' + region ] = function() { 2454 var mode = this.get( region ); 2455 if ( mode ) { 2456 this.frame[ region ].render( mode ); 2457 } 2458 }; 2459 }); 2460 2461 module.exports = State; 2462 2463 2464 /***/ }, 2465 2466 /***/ 4181 2467 (module) { 2468 2469 /** 2470 * wp.media.selectionSync 2471 * 2472 * Sync an attachments selection in a state with another state. 2473 * 2474 * Allows for selecting multiple images in the Add Media workflow, and then 2475 * switching to the Insert Gallery workflow while preserving the attachments selection. 2476 * 2477 * @memberOf wp.media 2478 * 2479 * @mixin 2480 */ 2481 var selectionSync = { 2482 /** 2483 * Syncs the selection in this state with the master selection. 2484 * 2485 * @since 3.5.0 2486 */ 2487 syncSelection: function() { 2488 var selection = this.get('selection'), 2489 manager = this.frame._selection; 2490 2491 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2492 return; 2493 } 2494 2495 /* 2496 * If the selection supports multiple items, validate the stored 2497 * attachments based on the new selection's conditions. Record 2498 * the attachments that are not included; we'll maintain a 2499 * reference to those. Other attachments are considered in flux. 2500 */ 2501 if ( selection.multiple ) { 2502 selection.reset( [], { silent: true }); 2503 selection.validateAll( manager.attachments ); 2504 manager.difference = _.difference( manager.attachments.models, selection.models ); 2505 } 2506 2507 // Sync the selection's single item with the master. 2508 selection.single( manager.single ); 2509 }, 2510 2511 /** 2512 * Record the currently active attachments, which is a combination 2513 * of the selection's attachments and the set of selected 2514 * attachments that this specific selection considered invalid. 2515 * Reset the difference and record the single attachment. 2516 * 2517 * @since 3.5.0 2518 */ 2519 recordSelection: function() { 2520 var selection = this.get('selection'), 2521 manager = this.frame._selection; 2522 2523 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2524 return; 2525 } 2526 2527 if ( selection.multiple ) { 2528 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 2529 manager.difference = []; 2530 } else { 2531 manager.attachments.add( selection.toArray() ); 2532 } 2533 2534 manager.single = selection._single; 2535 } 2536 }; 2537 2538 module.exports = selectionSync; 2539 2540 2541 /***/ }, 2542 2543 /***/ 2982 2544 (module) { 2545 2546 var View = wp.media.View, 2547 AttachmentCompat; 2548 2549 /** 2550 * wp.media.view.AttachmentCompat 2551 * 2552 * A view to display fields added via the `attachment_fields_to_edit` filter. 2553 * 2554 * @memberOf wp.media.view 2555 * 2556 * @class 2557 * @augments wp.media.View 2558 * @augments wp.Backbone.View 2559 * @augments Backbone.View 2560 */ 2561 AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{ 2562 tagName: 'form', 2563 className: 'compat-item', 2564 2565 events: { 2566 'submit': 'preventDefault', 2567 'change input': 'save', 2568 'change select': 'save', 2569 'change textarea': 'save' 2570 }, 2571 2572 initialize: function() { 2573 // Render the view when a new item is added. 2574 this.listenTo( this.model, 'add', this.render ); 2575 }, 2576 2577 /** 2578 * Disposes of the view and its children. 2579 * 2580 * @return {wp.media.view.AttachmentCompat} Returns itself to allow chaining. 2581 */ 2582 dispose: function() { 2583 if ( this.$(':focus').length ) { 2584 this.save(); 2585 } 2586 /** 2587 * call 'dispose' directly on the parent class 2588 */ 2589 return View.prototype.dispose.apply( this, arguments ); 2590 }, 2591 /** 2592 * Renders the view. 2593 * 2594 * @return {void|wp.media.view.AttachmentCompat} Returns itself to allow chaining. 2595 */ 2596 render: function() { 2597 var compat = this.model.get('compat'); 2598 if ( ! compat || ! compat.item ) { 2599 return; 2600 } 2601 2602 this.views.detach(); 2603 this.$el.html( compat.item ); 2604 this.views.render(); 2605 return this; 2606 }, 2607 /** 2608 * Prevents the default action of the event. 2609 * 2610 * @param {JQuery.Event} event The jQuery event object associated with the action. 2611 */ 2612 preventDefault: function( event ) { 2613 event.preventDefault(); 2614 }, 2615 /** 2616 * Saves the attachment compat data. 2617 * 2618 * @param {JQuery.Event} event The jQuery event object associated with the action. 2619 */ 2620 save: function( event ) { 2621 var data = {}; 2622 2623 if ( event ) { 2624 event.preventDefault(); 2625 } 2626 2627 _.each( this.$el.serializeArray(), function( pair ) { 2628 data[ pair.name ] = pair.value; 2629 }); 2630 2631 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 2632 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 2633 }, 2634 2635 /** 2636 * Triggers the `attachment:compat:ready` event on the controller after saving the compat data. 2637 */ 2638 postSave: function() { 2639 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 2640 } 2641 }); 2642 2643 module.exports = AttachmentCompat; 2644 2645 2646 /***/ }, 2647 2648 /***/ 7709 2649 (module) { 2650 2651 var $ = jQuery, 2652 AttachmentFilters; 2653 2654 /** 2655 * wp.media.view.AttachmentFilters 2656 * 2657 * @memberOf wp.media.view 2658 * 2659 * @class 2660 * @augments wp.media.View 2661 * @augments wp.Backbone.View 2662 * @augments Backbone.View 2663 */ 2664 AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{ 2665 tagName: 'select', 2666 className: 'attachment-filters', 2667 id: 'media-attachment-filters', 2668 2669 events: { 2670 change: 'change' 2671 }, 2672 2673 keys: [], 2674 2675 initialize: function() { 2676 this.createFilters(); 2677 _.extend( this.filters, this.options.filters ); 2678 2679 // Build `<option>` elements. 2680 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 2681 return { 2682 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 2683 priority: filter.priority || 50 2684 }; 2685 }, this ).sortBy('priority').pluck('el').value() ); 2686 2687 this.listenTo( this.model, 'change', this.select ); 2688 this.select(); 2689 }, 2690 2691 /** 2692 * Creates the filters for the view. 2693 * 2694 * @abstract 2695 */ 2696 createFilters: function() { 2697 this.filters = {}; 2698 }, 2699 2700 /** 2701 * Updates the Attachment Query properties to match when the selected filter changes. 2702 */ 2703 change: function() { 2704 var filter = this.filters[ this.el.value ]; 2705 if ( filter ) { 2706 this.model.set( filter.props ); 2707 } 2708 }, 2709 2710 /** 2711 * Selects the filter based on the Attachment Query properties. 2712 */ 2713 select: function() { 2714 var model = this.model, 2715 value = 'all', 2716 props = model.toJSON(); 2717 2718 _.find( this.filters, function( filter, id ) { 2719 var equal = _.all( filter.props, function( prop, key ) { 2720 return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] ); 2721 }); 2722 2723 if ( equal ) { 2724 return value = id; 2725 } 2726 }); 2727 2728 this.$el.val( value ); 2729 } 2730 }); 2731 2732 module.exports = AttachmentFilters; 2733 2734 2735 /***/ }, 2736 2737 /***/ 7349 2738 (module) { 2739 2740 var l10n = wp.media.view.l10n, 2741 All; 2742 2743 /** 2744 * wp.media.view.AttachmentFilters.All 2745 * 2746 * @memberOf wp.media.view.AttachmentFilters 2747 * 2748 * @class 2749 * @augments wp.media.view.AttachmentFilters 2750 * @augments wp.media.View 2751 * @augments wp.Backbone.View 2752 * @augments Backbone.View 2753 */ 2754 All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{ 2755 createFilters: function() { 2756 var filters = {}, 2757 uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0; 2758 2759 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 2760 filters[ key ] = { 2761 text: text, 2762 props: { 2763 status: null, 2764 type: key, 2765 uploadedTo: null, 2766 orderby: 'date', 2767 order: 'DESC', 2768 author: null 2769 } 2770 }; 2771 }); 2772 2773 filters.all = { 2774 text: l10n.allMediaItems, 2775 props: { 2776 status: null, 2777 type: null, 2778 uploadedTo: null, 2779 orderby: 'date', 2780 order: 'DESC', 2781 author: null 2782 }, 2783 priority: 10 2784 }; 2785 2786 if ( wp.media.view.settings.post.id ) { 2787 filters.uploaded = { 2788 text: l10n.uploadedToThisPost, 2789 props: { 2790 status: null, 2791 type: null, 2792 uploadedTo: wp.media.view.settings.post.id, 2793 orderby: 'menuOrder', 2794 order: 'ASC', 2795 author: null 2796 }, 2797 priority: 20 2798 }; 2799 } 2800 2801 filters.unattached = { 2802 text: l10n.unattached, 2803 props: { 2804 status: null, 2805 uploadedTo: 0, 2806 type: null, 2807 orderby: 'menuOrder', 2808 order: 'ASC', 2809 author: null 2810 }, 2811 priority: 50 2812 }; 2813 2814 if ( uid ) { 2815 filters.mine = { 2816 text: l10n.mine, 2817 props: { 2818 status: null, 2819 type: null, 2820 uploadedTo: null, 2821 orderby: 'date', 2822 order: 'DESC', 2823 author: uid 2824 }, 2825 priority: 50 2826 }; 2827 } 2828 2829 if ( wp.media.view.settings.mediaTrash && 2830 this.controller.isModeActive( 'grid' ) ) { 2831 2832 filters.trash = { 2833 text: l10n.trash, 2834 props: { 2835 uploadedTo: null, 2836 status: 'trash', 2837 type: null, 2838 orderby: 'date', 2839 order: 'DESC', 2840 author: null 2841 }, 2842 priority: 50 2843 }; 2844 } 2845 2846 this.filters = filters; 2847 } 2848 }); 2849 2850 module.exports = All; 2851 2852 2853 /***/ }, 2854 2855 /***/ 6472 2856 (module) { 2857 2858 var l10n = wp.media.view.l10n, 2859 DateFilter; 2860 2861 /** 2862 * A filter dropdown for month/dates. 2863 * 2864 * @memberOf wp.media.view.AttachmentFilters 2865 * 2866 * @class 2867 * @augments wp.media.view.AttachmentFilters 2868 * @augments wp.media.View 2869 * @augments wp.Backbone.View 2870 * @augments Backbone.View 2871 */ 2872 DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{ 2873 id: 'media-attachment-date-filters', 2874 2875 createFilters: function() { 2876 var filters = {}; 2877 _.each( wp.media.view.settings.months || {}, function( value, index ) { 2878 filters[ index ] = { 2879 text: value.text, 2880 props: { 2881 year: value.year, 2882 monthnum: value.month 2883 } 2884 }; 2885 }); 2886 filters.all = { 2887 text: l10n.allDates, 2888 props: { 2889 monthnum: false, 2890 year: false 2891 }, 2892 priority: 10 2893 }; 2894 this.filters = filters; 2895 } 2896 }); 2897 2898 module.exports = DateFilter; 2899 2900 2901 /***/ }, 2902 2903 /***/ 1368 2904 (module) { 2905 2906 var l10n = wp.media.view.l10n, 2907 Uploaded; 2908 2909 /** 2910 * wp.media.view.AttachmentFilters.Uploaded 2911 * 2912 * @memberOf wp.media.view.AttachmentFilters 2913 * 2914 * @class 2915 * @augments wp.media.view.AttachmentFilters 2916 * @augments wp.media.View 2917 * @augments wp.Backbone.View 2918 * @augments Backbone.View 2919 */ 2920 Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{ 2921 createFilters: function() { 2922 var type = this.model.get('type'), 2923 types = wp.media.view.settings.mimeTypes, 2924 uid = window.userSettings ? parseInt( window.userSettings.uid, 10 ) : 0, 2925 text; 2926 2927 if ( types && type ) { 2928 text = types[ type ]; 2929 } 2930 2931 this.filters = { 2932 all: { 2933 text: text || l10n.allMediaItems, 2934 props: { 2935 uploadedTo: null, 2936 orderby: 'date', 2937 order: 'DESC', 2938 author: null 2939 }, 2940 priority: 10 2941 }, 2942 2943 uploaded: { 2944 text: l10n.uploadedToThisPost, 2945 props: { 2946 uploadedTo: wp.media.view.settings.post.id, 2947 orderby: 'menuOrder', 2948 order: 'ASC', 2949 author: null 2950 }, 2951 priority: 20 2952 }, 2953 2954 unattached: { 2955 text: l10n.unattached, 2956 props: { 2957 uploadedTo: 0, 2958 orderby: 'menuOrder', 2959 order: 'ASC', 2960 author: null 2961 }, 2962 priority: 50 2963 } 2964 }; 2965 2966 if ( uid ) { 2967 this.filters.mine = { 2968 text: l10n.mine, 2969 props: { 2970 orderby: 'date', 2971 order: 'DESC', 2972 author: uid 2973 }, 2974 priority: 50 2975 }; 2976 } 2977 } 2978 }); 2979 2980 module.exports = Uploaded; 2981 2982 2983 /***/ }, 2984 2985 /***/ 4075 2986 (module) { 2987 2988 var View = wp.media.View, 2989 $ = jQuery, 2990 Attachment; 2991 2992 /** 2993 * wp.media.view.Attachment 2994 * 2995 * @memberOf wp.media.view 2996 * 2997 * @class 2998 * @augments wp.media.View 2999 * @augments wp.Backbone.View 3000 * @augments Backbone.View 3001 */ 3002 Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{ 3003 tagName: 'li', 3004 className: 'attachment', 3005 template: wp.template('attachment'), 3006 3007 attributes: function() { 3008 var ariaLabel = this.model.get( 'title' ); 3009 3010 if ( ! ariaLabel ) { 3011 if ( this.model.get( 'uploading' ) ) { 3012 ariaLabel = wp.i18n.__( 'uploading…' ); 3013 } else { 3014 ariaLabel = wp.i18n.__( '(no title)' ); 3015 } 3016 } 3017 3018 return { 3019 'tabIndex': 0, 3020 'role': 'checkbox', 3021 'aria-label': ariaLabel, 3022 'aria-checked': false, 3023 'data-id': this.model.get( 'id' ) 3024 }; 3025 }, 3026 3027 events: { 3028 'click': 'toggleSelectionHandler', 3029 'change [data-setting]': 'updateSetting', 3030 'change [data-setting] input': 'updateSetting', 3031 'change [data-setting] select': 'updateSetting', 3032 'change [data-setting] textarea': 'updateSetting', 3033 'click .attachment-close': 'removeFromLibrary', 3034 'click .check': 'checkClickHandler', 3035 'keydown': 'toggleSelectionHandler' 3036 }, 3037 3038 buttons: {}, 3039 3040 initialize: function() { 3041 var selection = this.options.selection, 3042 options = _.defaults( this.options, { 3043 rerenderOnModelChange: true 3044 } ); 3045 3046 if ( options.rerenderOnModelChange ) { 3047 this.listenTo( this.model, 'change', this.render ); 3048 } else { 3049 this.listenTo( this.model, 'change:percent', this.progress ); 3050 } 3051 this.listenTo( this.model, 'change:title', this._syncTitle ); 3052 this.listenTo( this.model, 'change:caption', this._syncCaption ); 3053 this.listenTo( this.model, 'change:artist', this._syncArtist ); 3054 this.listenTo( this.model, 'change:album', this._syncAlbum ); 3055 3056 // Update the selection. 3057 this.listenTo( this.model, 'add', this.select ); 3058 this.listenTo( this.model, 'remove', this.deselect ); 3059 if ( selection ) { 3060 selection.on( 'reset', this.updateSelect, this ); 3061 // Update the model's details view. 3062 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 3063 this.details( this.model, this.controller.state().get('selection') ); 3064 } 3065 3066 this.listenTo( this.controller.states, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 3067 }, 3068 /** 3069 * Update the view after the model has been saved. 3070 * 3071 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3072 */ 3073 dispose: function() { 3074 var selection = this.options.selection; 3075 3076 // Make sure all settings are saved before removing the view. 3077 this.updateAll(); 3078 3079 if ( selection ) { 3080 selection.off( null, null, this ); 3081 } 3082 /** 3083 * call 'dispose' directly on the parent class 3084 */ 3085 View.prototype.dispose.apply( this, arguments ); 3086 return this; 3087 }, 3088 /** 3089 * Renders the attachment view. 3090 * 3091 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3092 */ 3093 render: function() { 3094 var options = _.defaults( this.model.toJSON(), { 3095 orientation: 'landscape', 3096 uploading: false, 3097 type: '', 3098 subtype: '', 3099 icon: '', 3100 filename: '', 3101 caption: '', 3102 title: '', 3103 dateFormatted: '', 3104 width: '', 3105 height: '', 3106 compat: false, 3107 alt: '', 3108 description: '' 3109 }, this.options ); 3110 3111 options.buttons = this.buttons; 3112 options.describe = this.controller.state().get('describe'); 3113 3114 if ( 'image' === options.type ) { 3115 options.size = this.imageSize(); 3116 } 3117 3118 options.can = {}; 3119 if ( options.nonces ) { 3120 options.can.remove = !! options.nonces['delete']; 3121 options.can.save = !! options.nonces.update; 3122 } 3123 3124 if ( this.controller.state().get('allowLocalEdits') && ! options.uploading ) { 3125 options.allowLocalEdits = true; 3126 } 3127 3128 if ( options.uploading && ! options.percent ) { 3129 options.percent = 0; 3130 } 3131 3132 this.views.detach(); 3133 this.$el.html( this.template( options ) ); 3134 3135 this.$el.toggleClass( 'uploading', options.uploading ); 3136 3137 if ( options.uploading ) { 3138 this.$bar = this.$('.media-progress-bar div'); 3139 } else { 3140 delete this.$bar; 3141 } 3142 3143 // Check if the model is selected. 3144 this.updateSelect(); 3145 3146 // Update the save status. 3147 this.updateSave(); 3148 3149 this.views.render(); 3150 3151 return this; 3152 }, 3153 3154 progress: function() { 3155 if ( this.$bar && this.$bar.length ) { 3156 this.$bar.width( this.model.get('percent') + '%' ); 3157 } 3158 }, 3159 3160 /** 3161 * Toggles the selection state of the attachment. 3162 * 3163 * @param {JQuery.Event} event The jQuery event object that triggered the selection toggle. 3164 */ 3165 toggleSelectionHandler: function( event ) { 3166 var method; 3167 3168 // Don't do anything inside inputs and on the attachment check and remove buttons. 3169 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 3170 return; 3171 } 3172 3173 // Catch arrow events. 3174 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 3175 this.controller.trigger( 'attachment:keydown:arrow', event ); 3176 return; 3177 } 3178 3179 // Catch enter and space events. 3180 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3181 return; 3182 } 3183 3184 event.preventDefault(); 3185 3186 // In the grid view, bubble up an edit:attachment event to the controller. 3187 if ( this.controller.isModeActive( 'grid' ) ) { 3188 if ( this.controller.isModeActive( 'edit' ) ) { 3189 // Pass the current target to restore focus when closing. 3190 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 3191 return; 3192 } 3193 3194 if ( this.controller.isModeActive( 'select' ) ) { 3195 method = 'toggle'; 3196 } 3197 } 3198 3199 if ( event.shiftKey ) { 3200 method = 'between'; 3201 } else if ( event.ctrlKey || event.metaKey ) { 3202 method = 'toggle'; 3203 } 3204 3205 // Avoid toggles when the command or control key is pressed with the enter key to prevent deselecting the last selected attachment. 3206 if ( ( event.metaKey || event.ctrlKey ) && ( 13 === event.keyCode || 10 === event.keyCode ) ) { 3207 return; 3208 } 3209 3210 this.toggleSelection({ 3211 method: method 3212 }); 3213 3214 this.controller.trigger( 'selection:toggle' ); 3215 }, 3216 /** 3217 * Toggles the selection state of the attachment. 3218 * 3219 * @param {Object} options The options for toggling the selection state of the attachment. 3220 */ 3221 toggleSelection: function( options ) { 3222 var collection = this.collection, 3223 selection = this.options.selection, 3224 model = this.model, 3225 method = options && options.method, 3226 single, models, singleIndex, modelIndex; 3227 3228 if ( ! selection ) { 3229 return; 3230 } 3231 3232 single = selection.single(); 3233 method = _.isUndefined( method ) ? selection.multiple : method; 3234 3235 // If the `method` is set to `between`, select all models that 3236 // exist between the current and the selected model. 3237 if ( 'between' === method && single && selection.multiple ) { 3238 // If the models are the same, short-circuit. 3239 if ( single === model ) { 3240 return; 3241 } 3242 3243 singleIndex = collection.indexOf( single ); 3244 modelIndex = collection.indexOf( this.model ); 3245 3246 if ( singleIndex < modelIndex ) { 3247 models = collection.models.slice( singleIndex, modelIndex + 1 ); 3248 } else { 3249 models = collection.models.slice( modelIndex, singleIndex + 1 ); 3250 } 3251 3252 selection.add( models ); 3253 selection.single( model ); 3254 return; 3255 3256 // If the `method` is set to `toggle`, just flip the selection 3257 // status, regardless of whether the model is the single model. 3258 } else if ( 'toggle' === method ) { 3259 selection[ this.selected() ? 'remove' : 'add' ]( model ); 3260 selection.single( model ); 3261 return; 3262 } else if ( 'add' === method ) { 3263 selection.add( model ); 3264 selection.single( model ); 3265 return; 3266 } 3267 3268 // Fixes bug that loses focus when selecting a featured image. 3269 if ( ! method ) { 3270 method = 'add'; 3271 } 3272 3273 if ( method !== 'add' ) { 3274 method = 'reset'; 3275 } 3276 3277 if ( this.selected() ) { 3278 /* 3279 * If the model is the single model, remove it. 3280 * If it is not the same as the single model, 3281 * it now becomes the single model. 3282 */ 3283 selection[ single === model ? 'remove' : 'single' ]( model ); 3284 } else { 3285 /* 3286 * If the model is not selected, run the `method` on the 3287 * selection. By default, we `reset` the selection, but the 3288 * `method` can be set to `add` the model to the selection. 3289 */ 3290 selection[ method ]( model ); 3291 selection.single( model ); 3292 } 3293 }, 3294 3295 updateSelect: function() { 3296 this[ this.selected() ? 'select' : 'deselect' ](); 3297 }, 3298 /** 3299 * Checks if the model is selected in the selection. 3300 * 3301 * @return {void|boolean} True if the model is selected in the selection, false otherwise. 3302 */ 3303 selected: function() { 3304 var selection = this.options.selection; 3305 if ( selection ) { 3306 return !! selection.get( this.model.cid ); 3307 } 3308 }, 3309 /** 3310 * Selects the model in the selection. 3311 * 3312 * @param {Backbone.Model} model The model to select. 3313 * @param {Backbone.Collection} collection The collection containing the model. 3314 */ 3315 select: function( model, collection ) { 3316 var selection = this.options.selection, 3317 controller = this.controller; 3318 3319 /* 3320 * Check if a selection exists and if it's the collection provided. 3321 * If they're not the same collection, bail; we're in another 3322 * selection's event loop. 3323 */ 3324 if ( ! selection || ( collection && collection !== selection ) ) { 3325 return; 3326 } 3327 3328 // Bail if the model is already selected. 3329 if ( this.$el.hasClass( 'selected' ) ) { 3330 return; 3331 } 3332 3333 // Add 'selected' class to model, set aria-checked to true. 3334 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 3335 // Make the checkbox tabable, except in media grid (bulk select mode). 3336 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 3337 this.$( '.check' ).attr( 'tabindex', '0' ); 3338 } 3339 }, 3340 /** 3341 * Deselects the model in the selection. 3342 * 3343 * @param {Backbone.Model} model The model to deselect. 3344 * @param {Backbone.Collection} collection The collection containing the model. 3345 */ 3346 deselect: function( model, collection ) { 3347 var selection = this.options.selection; 3348 3349 /* 3350 * Check if a selection exists and if it's the collection provided. 3351 * If they're not the same collection, bail; we're in another 3352 * selection's event loop. 3353 */ 3354 if ( ! selection || ( collection && collection !== selection ) ) { 3355 return; 3356 } 3357 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 3358 .find( '.check' ).attr( 'tabindex', '-1' ); 3359 }, 3360 /** 3361 * Updates the view to reflect whether the model is the single model in the selection. 3362 * 3363 * @param {Backbone.Model} model The model to check for details. 3364 * @param {Backbone.Collection} collection The collection containing the model. 3365 */ 3366 details: function( model, collection ) { 3367 var selection = this.options.selection, 3368 details; 3369 3370 if ( selection !== collection ) { 3371 return; 3372 } 3373 3374 details = selection.single(); 3375 this.$el.toggleClass( 'details', details === this.model ); 3376 }, 3377 /** 3378 * Gets the image size object for the specified size. 3379 * 3380 * @param {string} size The desired image size. 3381 * @return {Object} Returns an object containing the image size information. 3382 */ 3383 imageSize: function( size ) { 3384 var sizes = this.model.get('sizes'), matched = false; 3385 3386 size = size || 'medium'; 3387 3388 // Use the provided image size if possible. 3389 if ( sizes ) { 3390 if ( sizes[ size ] ) { 3391 matched = sizes[ size ]; 3392 } else if ( sizes.large ) { 3393 matched = sizes.large; 3394 } else if ( sizes.thumbnail ) { 3395 matched = sizes.thumbnail; 3396 } else if ( sizes.full ) { 3397 matched = sizes.full; 3398 } 3399 3400 if ( matched ) { 3401 return _.clone( matched ); 3402 } 3403 } 3404 3405 return { 3406 url: this.model.get('url'), 3407 width: this.model.get('width'), 3408 height: this.model.get('height'), 3409 orientation: this.model.get('orientation') 3410 }; 3411 }, 3412 /** 3413 * Update the model's setting with the value from the input. 3414 * 3415 * @param {JQuery.Event} event The jQuery event object that triggered the setting update. 3416 */ 3417 updateSetting: function( event ) { 3418 var $setting = $( event.target ).closest('[data-setting]'), 3419 setting, value; 3420 3421 if ( ! $setting.length ) { 3422 return; 3423 } 3424 3425 setting = $setting.data('setting'); 3426 value = event.target.value; 3427 3428 if ( this.model.get( setting ) !== value ) { 3429 this.save( setting, value ); 3430 } 3431 }, 3432 3433 /** 3434 * Pass all the arguments to the model's save method. 3435 * 3436 * Records the aggregate status of all save requests and updates the 3437 * view's classes accordingly. 3438 */ 3439 save: function() { 3440 var view = this, 3441 save = this._save = this._save || { status: 'ready' }, 3442 request = this.model.save.apply( this.model, arguments ), 3443 requests = save.requests ? $.when( request, save.requests ) : request; 3444 3445 // If we're waiting to remove 'Saved.', stop. 3446 if ( save.savedTimer ) { 3447 clearTimeout( save.savedTimer ); 3448 } 3449 3450 this.updateSave('waiting'); 3451 save.requests = requests; 3452 requests.always( function() { 3453 // If we've performed another request since this one, bail. 3454 if ( save.requests !== requests ) { 3455 return; 3456 } 3457 3458 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 3459 save.savedTimer = setTimeout( function() { 3460 view.updateSave('ready'); 3461 delete save.savedTimer; 3462 }, 2000 ); 3463 }); 3464 }, 3465 /** 3466 * Updates the view's save status. 3467 * 3468 * @param {string} status The new save status to apply to the view. 3469 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3470 */ 3471 updateSave: function( status ) { 3472 var save = this._save = this._save || { status: 'ready' }; 3473 3474 if ( status && status !== save.status ) { 3475 this.$el.removeClass( 'save-' + save.status ); 3476 save.status = status; 3477 } 3478 3479 this.$el.addClass( 'save-' + save.status ); 3480 return this; 3481 }, 3482 3483 updateAll: function() { 3484 var $settings = this.$('[data-setting]'), 3485 model = this.model, 3486 changed; 3487 3488 changed = _.chain( $settings ).map( function( el ) { 3489 var $input = $('input, textarea, select, [value]', el ), 3490 setting, value; 3491 3492 if ( ! $input.length ) { 3493 return; 3494 } 3495 3496 setting = $(el).data('setting'); 3497 value = $input.val(); 3498 3499 // Record the value if it changed. 3500 if ( model.get( setting ) !== value ) { 3501 return [ setting, value ]; 3502 } 3503 }).compact().object().value(); 3504 3505 if ( ! _.isEmpty( changed ) ) { 3506 model.save( changed ); 3507 } 3508 }, 3509 /** 3510 * Removes the model from the collection. 3511 * 3512 * @param {JQuery.Event} event The jQuery event object that triggered the removal from the library. 3513 */ 3514 removeFromLibrary: function( event ) { 3515 // Catch enter and space events. 3516 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3517 return; 3518 } 3519 3520 // Stop propagation so the model isn't selected. 3521 event.stopPropagation(); 3522 3523 this.collection.remove( this.model ); 3524 }, 3525 3526 /** 3527 * Adds the model if it isn't in the selection, if it is in the selection, 3528 * removes it. 3529 * 3530 * @param {JQuery.Event} event The jQuery event object that triggered the selection toggle. 3531 * @return {void} 3532 */ 3533 checkClickHandler: function ( event ) { 3534 var selection = this.options.selection; 3535 if ( ! selection ) { 3536 return; 3537 } 3538 event.stopPropagation(); 3539 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 3540 selection.remove( this.model ); 3541 // Move focus back to the attachment tile (from the check). 3542 this.$el.focus(); 3543 } else { 3544 selection.add( this.model ); 3545 } 3546 3547 // Trigger an action button update. 3548 this.controller.trigger( 'selection:toggle' ); 3549 } 3550 }); 3551 3552 // Ensure settings remain in sync between attachment views. 3553 _.each({ 3554 caption: '_syncCaption', 3555 title: '_syncTitle', 3556 artist: '_syncArtist', 3557 album: '_syncAlbum' 3558 }, function( method, setting ) { 3559 /** 3560 * Updates the DOM when the model's caption changes. 3561 * 3562 * @function _syncCaption 3563 * @memberOf wp.media.view.Attachment 3564 * @instance 3565 * 3566 * @param {Backbone.Model} model The model containing the caption. 3567 * @param {string} value The new caption value. 3568 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3569 */ 3570 /** 3571 * Updates the DOM when the model's title changes. 3572 * 3573 * @function _syncTitle 3574 * @memberOf wp.media.view.Attachment 3575 * @instance 3576 * 3577 * @param {Backbone.Model} model The model containing the title. 3578 * @param {string} value The new title value. 3579 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3580 */ 3581 /** 3582 * Updates the DOM when the model's artist changes. 3583 * 3584 * @function _syncArtist 3585 * @memberOf wp.media.view.Attachment 3586 * @instance 3587 * 3588 * @param {Backbone.Model} model The model containing the artist. 3589 * @param {string} value The new artist value. 3590 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3591 */ 3592 /** 3593 * Updates the DOM when the model's album changes. 3594 * 3595 * @function _syncAlbum 3596 * @memberOf wp.media.view.Attachment 3597 * @instance 3598 * 3599 * @param {Backbone.Model} model The model containing the album. 3600 * @param {string} value The new album value. 3601 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3602 */ 3603 Attachment.prototype[ method ] = function( model, value ) { 3604 var $setting = this.$('[data-setting="' + setting + '"]'); 3605 3606 if ( ! $setting.length ) { 3607 return this; 3608 } 3609 3610 /* 3611 * If the updated value is in sync with the value in the DOM, there 3612 * is no need to re-render. If we're currently editing the value, 3613 * it will automatically be in sync, suppressing the re-render for 3614 * the view we're editing, while updating any others. 3615 */ 3616 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 3617 return this; 3618 } 3619 3620 return this.render(); 3621 }; 3622 }); 3623 3624 module.exports = Attachment; 3625 3626 3627 /***/ }, 3628 3629 /***/ 6090 3630 (module) { 3631 3632 /* global ClipboardJS */ 3633 var Attachment = wp.media.view.Attachment, 3634 l10n = wp.media.view.l10n, 3635 $ = jQuery, 3636 Details, 3637 __ = wp.i18n.__; 3638 3639 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{ 3640 tagName: 'div', 3641 className: 'attachment-details', 3642 template: wp.template('attachment-details'), 3643 3644 /* 3645 * Reset all the attributes inherited from Attachment including role=checkbox, 3646 * tabindex, etc., as they are inappropriate for this view. See #47458 and [30483] / #30390. 3647 */ 3648 attributes: {}, 3649 3650 events: { 3651 'change [data-setting]': 'updateSetting', 3652 'change [data-setting] input': 'updateSetting', 3653 'change [data-setting] select': 'updateSetting', 3654 'change [data-setting] textarea': 'updateSetting', 3655 'click .delete-attachment': 'deleteAttachment', 3656 'click .trash-attachment': 'trashAttachment', 3657 'click .untrash-attachment': 'untrashAttachment', 3658 'click .edit-attachment': 'editAttachment', 3659 'keydown': 'toggleSelectionHandler' 3660 }, 3661 3662 /** 3663 * Copies the attachment URL to the clipboard. 3664 * 3665 * @since 5.5.0 3666 * 3667 * @return {void} 3668 */ 3669 copyAttachmentDetailsURLClipboard: function() { 3670 var clipboard = new ClipboardJS( '.copy-attachment-url' ), 3671 successTimeout; 3672 3673 clipboard.on( 'success', function( event ) { 3674 var triggerElement = $( event.trigger ), 3675 successElement = $( '.success', triggerElement.closest( '.copy-to-clipboard-container' ) ); 3676 3677 // Clear the selection and move focus back to the trigger. 3678 event.clearSelection(); 3679 3680 // Show success visual feedback. 3681 clearTimeout( successTimeout ); 3682 successElement.removeClass( 'hidden' ); 3683 3684 // Hide success visual feedback after 3 seconds since last success. 3685 successTimeout = setTimeout( function() { 3686 successElement.addClass( 'hidden' ); 3687 }, 3000 ); 3688 3689 // Handle success audible feedback. 3690 wp.a11y.speak( __( 'The file URL has been copied to your clipboard' ) ); 3691 } ); 3692 }, 3693 3694 /** 3695 * Shows the details of an attachment. 3696 * 3697 * @since 3.5.0 3698 * 3699 * @constructs wp.media.view.Attachment.Details 3700 * @augments wp.media.view.Attachment 3701 * 3702 * @return {void} 3703 */ 3704 initialize: function() { 3705 this.options = _.defaults( this.options, { 3706 rerenderOnModelChange: false 3707 }); 3708 3709 // Call 'initialize' directly on the parent class. 3710 Attachment.prototype.initialize.apply( this, arguments ); 3711 3712 this.copyAttachmentDetailsURLClipboard(); 3713 }, 3714 3715 /** 3716 * Gets the focusable elements to move focus to. 3717 * 3718 * @since 5.3.0 3719 */ 3720 getFocusableElements: function() { 3721 var editedAttachment = $( 'li[data-id="' + this.model.id + '"]' ); 3722 3723 this.previousAttachment = editedAttachment.prev(); 3724 this.nextAttachment = editedAttachment.next(); 3725 }, 3726 3727 /** 3728 * Moves focus to the previous or next attachment in the grid. 3729 * Fallbacks to the upload button or media frame when there are no attachments. 3730 * 3731 * @since 5.3.0 3732 */ 3733 moveFocus: function() { 3734 if ( this.previousAttachment.length ) { 3735 this.previousAttachment.trigger( 'focus' ); 3736 return; 3737 } 3738 3739 if ( this.nextAttachment.length ) { 3740 this.nextAttachment.trigger( 'focus' ); 3741 return; 3742 } 3743 3744 // Fallback: move focus to the "Select Files" button in the media modal. 3745 if ( this.controller.uploader && this.controller.uploader.$browser ) { 3746 this.controller.uploader.$browser.trigger( 'focus' ); 3747 return; 3748 } 3749 3750 // Last fallback. 3751 this.moveFocusToLastFallback(); 3752 }, 3753 3754 /** 3755 * Moves focus to the media frame as last fallback. 3756 * 3757 * @since 5.3.0 3758 */ 3759 moveFocusToLastFallback: function() { 3760 // Last fallback: make the frame focusable and move focus to it. 3761 $( '.media-frame' ) 3762 .attr( 'tabindex', '-1' ) 3763 .trigger( 'focus' ); 3764 }, 3765 3766 /** 3767 * Deletes an attachment. 3768 * 3769 * Deletes an attachment after asking for confirmation. After deletion, 3770 * keeps focus in the modal. 3771 * 3772 * @since 3.5.0 3773 * 3774 * @param {MouseEvent} event A click event. 3775 * 3776 * @return {void} 3777 */ 3778 deleteAttachment: function( event ) { 3779 event.preventDefault(); 3780 3781 this.getFocusableElements(); 3782 3783 if ( window.confirm( l10n.warnDelete ) ) { 3784 this.model.destroy( { 3785 wait: true, 3786 error: function() { 3787 window.alert( l10n.errorDeleting ); 3788 } 3789 } ); 3790 3791 this.moveFocus(); 3792 } 3793 }, 3794 3795 /** 3796 * Sets the Trash state on an attachment, or destroys the model itself. 3797 * 3798 * If the mediaTrash setting is set to true, trashes the attachment. 3799 * Otherwise, the model itself is destroyed. 3800 * 3801 * @since 3.9.0 3802 * 3803 * @param {MouseEvent} event A click event. 3804 * 3805 * @return {void} 3806 */ 3807 trashAttachment: function( event ) { 3808 var library = this.controller.library, 3809 self = this; 3810 event.preventDefault(); 3811 3812 this.getFocusableElements(); 3813 3814 // When in the Media Library and the Media Trash is enabled. 3815 if ( wp.media.view.settings.mediaTrash && 3816 'edit-metadata' === this.controller.content.mode() ) { 3817 3818 this.model.set( 'status', 'trash' ); 3819 this.model.save().done( function() { 3820 library._requery( true ); 3821 /* 3822 * @todo We need to move focus back to the previous, next, or first 3823 * attachment but the library gets re-queried and refreshed. 3824 * Thus, the references to the previous attachments are lost. 3825 * We need an alternate method. 3826 */ 3827 self.moveFocusToLastFallback(); 3828 } ); 3829 } else { 3830 this.model.destroy(); 3831 this.moveFocus(); 3832 } 3833 }, 3834 3835 /** 3836 * Untrashes an attachment. 3837 * 3838 * @since 4.0.0 3839 * 3840 * @param {MouseEvent} event A click event. 3841 * 3842 * @return {void} 3843 */ 3844 untrashAttachment: function( event ) { 3845 var library = this.controller.library; 3846 event.preventDefault(); 3847 3848 this.model.set( 'status', 'inherit' ); 3849 this.model.save().done( function() { 3850 library._requery( true ); 3851 } ); 3852 }, 3853 3854 /** 3855 * Opens the edit page for a specific attachment. 3856 * 3857 * @since 3.5.0 3858 * 3859 * @param {MouseEvent} event A click event. 3860 * 3861 * @return {void} 3862 */ 3863 editAttachment: function( event ) { 3864 var editState = this.controller.states.get( 'edit-image' ); 3865 if ( window.imageEdit && editState ) { 3866 event.preventDefault(); 3867 3868 editState.set( 'image', this.model ); 3869 this.controller.setState( 'edit-image' ); 3870 } else { 3871 this.$el.addClass('needs-refresh'); 3872 } 3873 }, 3874 3875 /** 3876 * Triggers an event on the controller when reverse tabbing (shift+tab). 3877 * 3878 * This event can be used to make sure to move the focus correctly. 3879 * 3880 * Fires `attachment:details:shift-tab` and `attachment:keydown:arrow` events on the controller. 3881 * 3882 * @since 4.0.0 3883 * 3884 * @param {KeyboardEvent} event A keyboard event. 3885 * 3886 * @return {boolean|void} Returns false or undefined. 3887 */ 3888 toggleSelectionHandler: function( event ) { 3889 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 3890 this.controller.trigger( 'attachment:details:shift-tab', event ); 3891 return false; 3892 } 3893 }, 3894 3895 render: function() { 3896 Attachment.prototype.render.apply( this, arguments ); 3897 3898 wp.media.mixin.removeAllPlayers(); 3899 this.$( 'audio, video' ).each( function (i, elem) { 3900 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 3901 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 3902 } ); 3903 } 3904 }); 3905 3906 module.exports = Details; 3907 3908 3909 /***/ }, 3910 3911 /***/ 5232 3912 (module) { 3913 3914 /** 3915 * wp.media.view.Attachment.EditLibrary 3916 * 3917 * @memberOf wp.media.view.Attachment 3918 * 3919 * @class 3920 * @augments wp.media.view.Attachment 3921 * @augments wp.media.View 3922 * @augments wp.Backbone.View 3923 * @augments Backbone.View 3924 */ 3925 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{ 3926 buttons: { 3927 close: true 3928 } 3929 }); 3930 3931 module.exports = EditLibrary; 3932 3933 3934 /***/ }, 3935 3936 /***/ 4593 3937 (module) { 3938 3939 /** 3940 * wp.media.view.Attachment.EditSelection 3941 * 3942 * @memberOf wp.media.view.Attachment 3943 * 3944 * @class 3945 * @augments wp.media.view.Attachment.Selection 3946 * @augments wp.media.view.Attachment 3947 * @augments wp.media.View 3948 * @augments wp.Backbone.View 3949 * @augments Backbone.View 3950 */ 3951 var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{ 3952 buttons: { 3953 close: true 3954 } 3955 }); 3956 3957 module.exports = EditSelection; 3958 3959 3960 /***/ }, 3961 3962 /***/ 3443 3963 (module) { 3964 3965 /** 3966 * wp.media.view.Attachment.Library 3967 * 3968 * @memberOf wp.media.view.Attachment 3969 * 3970 * @class 3971 * @augments wp.media.view.Attachment 3972 * @augments wp.media.View 3973 * @augments wp.Backbone.View 3974 * @augments Backbone.View 3975 */ 3976 var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{ 3977 buttons: { 3978 check: true 3979 } 3980 }); 3981 3982 module.exports = Library; 3983 3984 3985 /***/ }, 3986 3987 /***/ 3962 3988 (module) { 3989 3990 /** 3991 * wp.media.view.Attachment.Selection 3992 * 3993 * @memberOf wp.media.view.Attachment 3994 * 3995 * @class 3996 * @augments wp.media.view.Attachment 3997 * @augments wp.media.View 3998 * @augments wp.Backbone.View 3999 * @augments Backbone.View 4000 */ 4001 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{ 4002 className: 'attachment selection', 4003 4004 // On click, just select the model, instead of removing the model from 4005 // the selection. 4006 toggleSelection: function() { 4007 this.options.selection.single( this.model ); 4008 } 4009 }); 4010 4011 module.exports = Selection; 4012 4013 4014 /***/ }, 4015 4016 /***/ 8142 4017 (module) { 4018 4019 var View = wp.media.View, 4020 $ = jQuery, 4021 Attachments, 4022 infiniteScrolling = wp.media.view.settings.infiniteScrolling; 4023 4024 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 4025 tagName: 'ul', 4026 className: 'attachments', 4027 4028 attributes: { 4029 role: 'group', 4030 tabIndex: -1 4031 }, 4032 4033 /** 4034 * Represents the overview of attachments in the Media Library. 4035 * 4036 * The constructor binds events to the collection this view represents when 4037 * adding or removing attachments or resetting the entire collection. 4038 * 4039 * Listens for `collection:add`, `collection:remove`, `collection:reset`, 4040 * `controller:library:selection:add`, `scrollElement:scroll`, `this:ready`, 4041 * and `controller:open` events. 4042 * 4043 * @since 3.5.0 4044 * 4045 * @constructs 4046 * @memberof wp.media.view 4047 * 4048 * @augments wp.media.View 4049 */ 4050 initialize: function() { 4051 this.el.id = _.uniqueId('__attachments-view-'); 4052 4053 /** 4054 * @since 5.8.0 Added the `infiniteScrolling` parameter. 4055 * 4056 * @param infiniteScrolling Whether to enable infinite scrolling or use 4057 * the default "load more" button. 4058 * @param refreshSensitivity The time in milliseconds to throttle the scroll 4059 * handler. 4060 * @param refreshThreshold The amount of pixels that should be scrolled before 4061 * loading more attachments from the server. 4062 * @param AttachmentView The view class to be used for models in the 4063 * collection. 4064 * @param sortable A jQuery sortable options object 4065 * ( http://api.jqueryui.com/sortable/ ). 4066 * @param resize A boolean indicating whether or not to listen to 4067 * resize events. 4068 * @param idealColumnWidth The width in pixels which a column should have when 4069 * calculating the total number of columns. 4070 */ 4071 _.defaults( this.options, { 4072 infiniteScrolling: infiniteScrolling || false, 4073 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 4074 refreshThreshold: 3, 4075 AttachmentView: wp.media.view.Attachment, 4076 sortable: false, 4077 resize: true, 4078 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 4079 }); 4080 4081 this._viewsByCid = {}; 4082 this.$window = $( window ); 4083 this.resizeEvent = 'resize.media-modal-columns'; 4084 4085 this.collection.on( 'add', function( attachment ) { 4086 this.views.add( this.createAttachmentView( attachment ), { 4087 at: this.collection.indexOf( attachment ) 4088 }); 4089 }, this ); 4090 4091 /* 4092 * Find the view to be removed, delete it and call the remove function to clear 4093 * any set event handlers. 4094 */ 4095 this.collection.on( 'remove', function( attachment ) { 4096 var view = this._viewsByCid[ attachment.cid ]; 4097 delete this._viewsByCid[ attachment.cid ]; 4098 4099 if ( view ) { 4100 view.remove(); 4101 } 4102 }, this ); 4103 4104 this.collection.on( 'reset', this.render, this ); 4105 4106 this.controller.on( 'library:selection:add', this.attachmentFocus, this ); 4107 4108 if ( this.options.infiniteScrolling ) { 4109 // Throttle the scroll handler and bind this. 4110 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 4111 4112 this.options.scrollElement = this.options.scrollElement || this.el; 4113 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 4114 } 4115 4116 this.initSortable(); 4117 4118 _.bindAll( this, 'setColumns' ); 4119 4120 if ( this.options.resize ) { 4121 this.on( 'ready', this.bindEvents ); 4122 this.controller.on( 'open', this.setColumns ); 4123 4124 /* 4125 * Call this.setColumns() after this view has been rendered in the 4126 * DOM so attachments get proper width applied. 4127 */ 4128 _.defer( this.setColumns, this ); 4129 } 4130 }, 4131 4132 /** 4133 * Listens to the resizeEvent on the window. 4134 * 4135 * Adjusts the amount of columns accordingly. First removes any existing event 4136 * handlers to prevent duplicate listeners. 4137 * 4138 * Listens for the `window:resize` event. 4139 * 4140 * @since 4.0.0 4141 * 4142 * @return {void} 4143 */ 4144 bindEvents: function() { 4145 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 4146 }, 4147 4148 /** 4149 * Focuses the first item in the collection. 4150 * 4151 * @since 4.0.0 4152 * 4153 * @return {void} 4154 */ 4155 attachmentFocus: function() { 4156 /* 4157 * @todo When uploading new attachments, this tries to move focus to 4158 * the attachments grid. Actually, a progress bar gets initially displayed 4159 * and then updated when uploading completes, so focus is lost. 4160 * Additionally: this view is used for both the attachments list and 4161 * the list of selected attachments in the bottom media toolbar. Thus, when 4162 * uploading attachments, it is called twice and returns two different `this`. 4163 * `this.columns` is truthy within the modal. 4164 */ 4165 if ( this.columns ) { 4166 // Move focus to the grid list within the modal. 4167 this.$el.focus(); 4168 } 4169 }, 4170 4171 /** 4172 * Restores focus to the selected item in the collection. 4173 * 4174 * Moves focus back to the first selected attachment in the grid. Used when 4175 * tabbing backwards from the attachment details sidebar. 4176 * See media.view.AttachmentsBrowser. 4177 * 4178 * @since 4.0.0 4179 * 4180 * @return {void} 4181 */ 4182 restoreFocus: function() { 4183 this.$( 'li.selected:first' ).focus(); 4184 }, 4185 4186 /** 4187 * Handles events for arrow key presses. 4188 * 4189 * Focuses the attachment in the direction of the used arrow key if it exists. 4190 * 4191 * @since 4.0.0 4192 * 4193 * @param {KeyboardEvent} event The keyboard event that triggered this function. 4194 * 4195 * @return {void} 4196 */ 4197 arrowEvent: function( event ) { 4198 var attachments = this.$el.children( 'li' ), 4199 perRow = this.columns, 4200 index = attachments.filter( ':focus' ).index(), 4201 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 4202 4203 if ( index === -1 ) { 4204 return; 4205 } 4206 4207 // Left arrow = 37. 4208 if ( 37 === event.keyCode ) { 4209 if ( 0 === index ) { 4210 return; 4211 } 4212 attachments.eq( index - 1 ).focus(); 4213 } 4214 4215 // Up arrow = 38. 4216 if ( 38 === event.keyCode ) { 4217 if ( 1 === row ) { 4218 return; 4219 } 4220 attachments.eq( index - perRow ).focus(); 4221 } 4222 4223 // Right arrow = 39. 4224 if ( 39 === event.keyCode ) { 4225 if ( attachments.length === index ) { 4226 return; 4227 } 4228 attachments.eq( index + 1 ).focus(); 4229 } 4230 4231 // Down arrow = 40. 4232 if ( 40 === event.keyCode ) { 4233 if ( Math.ceil( attachments.length / perRow ) === row ) { 4234 return; 4235 } 4236 attachments.eq( index + perRow ).focus(); 4237 } 4238 }, 4239 4240 /** 4241 * Clears any set event handlers. 4242 * 4243 * @since 3.5.0 4244 * 4245 * @return {void} 4246 */ 4247 dispose: function() { 4248 this.collection.props.off( null, null, this ); 4249 if ( this.options.resize ) { 4250 this.$window.off( this.resizeEvent ); 4251 } 4252 4253 // Call 'dispose' directly on the parent class. 4254 View.prototype.dispose.apply( this, arguments ); 4255 }, 4256 4257 /** 4258 * Calculates the amount of columns. 4259 * 4260 * Calculates the amount of columns and sets it on the data-columns attribute 4261 * of .media-frame-content. 4262 * 4263 * @since 4.0.0 4264 * 4265 * @return {void} 4266 */ 4267 setColumns: function() { 4268 var prev = this.columns, 4269 width = this.$el.width(); 4270 4271 if ( width ) { 4272 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 4273 4274 if ( ! prev || prev !== this.columns ) { 4275 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 4276 } 4277 } 4278 }, 4279 4280 /** 4281 * Initializes jQuery sortable on the attachment list. 4282 * 4283 * Fails gracefully if jQuery sortable doesn't exist or isn't passed 4284 * in the options. 4285 * 4286 * Fires `collection:reset`. 4287 * 4288 * @since 3.5.0 4289 * 4290 * @return {void} 4291 */ 4292 initSortable: function() { 4293 var collection = this.collection; 4294 4295 if ( ! this.options.sortable || ! $.fn.sortable ) { 4296 return; 4297 } 4298 4299 this.$el.sortable( _.extend({ 4300 // If the `collection` has a `comparator`, disable sorting. 4301 disabled: !! collection.comparator, 4302 4303 /* 4304 * Change the position of the attachment as soon as the mouse pointer 4305 * overlaps a thumbnail. 4306 */ 4307 tolerance: 'pointer', 4308 4309 // Record the initial `index` of the dragged model. 4310 start: function( event, ui ) { 4311 ui.item.data('sortableIndexStart', ui.item.index()); 4312 }, 4313 4314 /* 4315 * Update the model's index in the collection. Do so silently, as the view 4316 * is already accurate. 4317 */ 4318 update: function( event, ui ) { 4319 var model = collection.at( ui.item.data('sortableIndexStart') ), 4320 comparator = collection.comparator; 4321 4322 // Temporarily disable the comparator to prevent `add` 4323 // from re-sorting. 4324 delete collection.comparator; 4325 4326 // Silently shift the model to its new index. 4327 collection.remove( model, { 4328 silent: true 4329 }); 4330 collection.add( model, { 4331 silent: true, 4332 at: ui.item.index() 4333 }); 4334 4335 // Restore the comparator. 4336 collection.comparator = comparator; 4337 4338 // Fire the `reset` event to ensure other collections sync. 4339 collection.trigger( 'reset', collection ); 4340 4341 // If the collection is sorted by menu order, update the menu order. 4342 collection.saveMenuOrder(); 4343 } 4344 }, this.options.sortable ) ); 4345 4346 /* 4347 * If the `orderby` property is changed on the `collection`, 4348 * check to see if we have a `comparator`. If so, disable sorting. 4349 */ 4350 collection.props.on( 'change:orderby', function() { 4351 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 4352 }, this ); 4353 4354 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 4355 this.refreshSortable(); 4356 }, 4357 4358 /** 4359 * Disables jQuery sortable if collection has a comparator or collection.orderby 4360 * equals menuOrder. 4361 * 4362 * @since 3.5.0 4363 * 4364 * @return {void} 4365 */ 4366 refreshSortable: function() { 4367 if ( ! this.options.sortable || ! $.fn.sortable ) { 4368 return; 4369 } 4370 4371 var collection = this.collection, 4372 orderby = collection.props.get('orderby'), 4373 enabled = 'menuOrder' === orderby || ! collection.comparator; 4374 4375 this.$el.sortable( 'option', 'disabled', ! enabled ); 4376 }, 4377 4378 /** 4379 * Creates a new view for an attachment and adds it to _viewsByCid. 4380 * 4381 * @since 3.5.0 4382 * 4383 * @param {wp.media.model.Attachment} attachment The attachment model for which to create a view. 4384 * 4385 * @return {wp.media.View} The created view. 4386 */ 4387 createAttachmentView: function( attachment ) { 4388 var view = new this.options.AttachmentView({ 4389 controller: this.controller, 4390 model: attachment, 4391 collection: this.collection, 4392 selection: this.options.selection 4393 }); 4394 4395 return this._viewsByCid[ attachment.cid ] = view; 4396 }, 4397 4398 /** 4399 * Prepares view for display. 4400 * 4401 * Creates views for every attachment in collection if the collection is not 4402 * empty, otherwise clears all views and loads more attachments. 4403 * 4404 * @since 3.5.0 4405 * 4406 * @return {void} 4407 */ 4408 prepare: function() { 4409 if ( this.collection.length ) { 4410 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 4411 } else { 4412 this.views.unset(); 4413 if ( this.options.infiniteScrolling ) { 4414 this.collection.more().done( this.scroll ); 4415 } 4416 } 4417 }, 4418 4419 /** 4420 * Triggers the scroll function to check if we should query for additional 4421 * attachments right away. 4422 * 4423 * @since 3.5.0 4424 * 4425 * @return {void} 4426 */ 4427 ready: function() { 4428 if ( this.options.infiniteScrolling ) { 4429 this.scroll(); 4430 } 4431 }, 4432 4433 /** 4434 * Handles scroll events. 4435 * 4436 * Shows the spinner if we're close to the bottom. Loads more attachments from 4437 * server if we're {refreshThreshold} times away from the bottom. 4438 * 4439 * @since 3.5.0 4440 * 4441 * @return {void} 4442 */ 4443 scroll: function() { 4444 var view = this, 4445 el = this.options.scrollElement, 4446 scrollTop = el.scrollTop, 4447 toolbar; 4448 4449 /* 4450 * The scroll event occurs on the document, but the element that should be 4451 * checked is the document body. 4452 */ 4453 if ( el === document ) { 4454 el = document.body; 4455 scrollTop = $(document).scrollTop(); 4456 } 4457 4458 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 4459 return; 4460 } 4461 4462 toolbar = this.views.parent.toolbar; 4463 4464 // Show the spinner only if we are close to the bottom. 4465 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 4466 toolbar.get('spinner').show(); 4467 } 4468 4469 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 4470 this.collection.more().done(function() { 4471 view.scroll(); 4472 toolbar.get('spinner').hide(); 4473 }); 4474 } 4475 } 4476 }); 4477 4478 module.exports = Attachments; 4479 4480 4481 /***/ }, 4482 4483 /***/ 6829 4484 (module) { 4485 4486 var View = wp.media.View, 4487 mediaTrash = wp.media.view.settings.mediaTrash, 4488 l10n = wp.media.view.l10n, 4489 $ = jQuery, 4490 AttachmentsBrowser, 4491 infiniteScrolling = wp.media.view.settings.infiniteScrolling, 4492 __ = wp.i18n.__, 4493 sprintf = wp.i18n.sprintf; 4494 4495 /** 4496 * wp.media.view.AttachmentsBrowser 4497 * 4498 * @memberOf wp.media.view 4499 * 4500 * @class 4501 * @augments wp.media.View 4502 * @augments wp.Backbone.View 4503 * @augments Backbone.View 4504 * 4505 * @param {Object} [options] The options hash passed to the view. 4506 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar. 4507 * Accepts 'uploaded' and 'all'. 4508 * @param {boolean} [options.search=true] Whether to show the search interface in the 4509 * browser's toolbar. 4510 * @param {boolean} [options.date=true] Whether to show the date filter in the 4511 * browser's toolbar. 4512 * @param {boolean} [options.display=false] Whether to show the attachments display settings 4513 * view in the sidebar. 4514 * @param {boolean|string} [options.sidebar=true] Whether to create a sidebar for the browser. 4515 * Accepts true, false, and 'errors'. 4516 */ 4517 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{ 4518 tagName: 'div', 4519 className: 'attachments-browser', 4520 4521 /** 4522 * Initializes the AttachmentsBrowser view. 4523 */ 4524 initialize: function() { 4525 _.defaults( this.options, { 4526 filters: false, 4527 search: true, 4528 date: true, 4529 display: false, 4530 sidebar: true, 4531 AttachmentView: wp.media.view.Attachment.Library 4532 }); 4533 4534 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this ); 4535 this.controller.on( 'edit:selection', this.editSelection ); 4536 4537 // In the Media Library, the sidebar is used to display errors before the attachments grid. 4538 if ( this.options.sidebar && 'errors' === this.options.sidebar ) { 4539 this.createSidebar(); 4540 } 4541 4542 /* 4543 * In the grid mode (the Media Library), place the Inline Uploader before 4544 * other sections so that the visual order and the DOM order match. This way, 4545 * the Inline Uploader in the Media Library is right after the "Add New" 4546 * button, see ticket #37188. 4547 */ 4548 if ( this.controller.isModeActive( 'grid' ) ) { 4549 this.createUploader(); 4550 4551 /* 4552 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library 4553 * and also for other things, for example the "Drag and drop to reorder" and 4554 * "Suggested dimensions" info in the media modal. 4555 */ 4556 this.createToolbar(); 4557 } else { 4558 this.createToolbar(); 4559 this.createUploader(); 4560 } 4561 4562 // Add a heading before the attachments list. 4563 this.createAttachmentsHeading(); 4564 4565 // Create the attachments wrapper view. 4566 this.createAttachmentsWrapperView(); 4567 4568 if ( ! infiniteScrolling ) { 4569 this.$el.addClass( 'has-load-more' ); 4570 this.createLoadMoreView(); 4571 } 4572 4573 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. 4574 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { 4575 this.createSidebar(); 4576 } 4577 4578 this.updateContent(); 4579 4580 if ( ! infiniteScrolling ) { 4581 this.updateLoadMoreView(); 4582 } 4583 4584 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 4585 this.$el.addClass( 'hide-sidebar' ); 4586 4587 if ( 'errors' === this.options.sidebar ) { 4588 this.$el.addClass( 'sidebar-for-errors' ); 4589 } 4590 } 4591 4592 this.collection.on( 'add remove reset', this.updateContent, this ); 4593 4594 if ( ! infiniteScrolling ) { 4595 this.collection.on( 'add remove reset', this.updateLoadMoreView, this ); 4596 } 4597 4598 // The non-cached or cached attachments query has completed. 4599 this.collection.on( 'attachments:received', this.announceSearchResults, this ); 4600 }, 4601 4602 /** 4603 * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate 4604 * the number of search results to screen reader users. This function is 4605 * debounced because the collection updates multiple times. 4606 * 4607 * @since 5.3.0 4608 * 4609 * @return {void} 4610 */ 4611 announceSearchResults: _.debounce( function() { 4612 var count, 4613 /* translators: Accessibility text. %d: Number of attachments found in a search. */ 4614 mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Click load more for more results.' ); 4615 4616 if ( infiniteScrolling ) { 4617 /* translators: Accessibility text. %d: Number of attachments found in a search. */ 4618 mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Scroll the page for more results.' ); 4619 } 4620 4621 if ( this.collection.mirroring && this.collection.mirroring.args.s ) { 4622 count = this.collection.length; 4623 4624 if ( 0 === count ) { 4625 wp.a11y.speak( l10n.noMediaTryNewSearch ); 4626 return; 4627 } 4628 4629 if ( this.collection.hasMore() ) { 4630 wp.a11y.speak( mediaFoundHasMoreResultsMessage.replace( '%d', count ) ); 4631 return; 4632 } 4633 4634 wp.a11y.speak( l10n.mediaFound.replace( '%d', count ) ); 4635 } 4636 }, 200 ), 4637 4638 /** 4639 * Edits the selection in the modal. This is used when the user clicks the "Edit" button in the modal. 4640 * 4641 * @param {wp.media.view.Modal} modal The modal view. 4642 */ 4643 editSelection: function( modal ) { 4644 // When editing a selection, move focus to the "Go to library" button. 4645 modal.$( '.media-button-backToLibrary' ).focus(); 4646 }, 4647 4648 /** 4649 * Disposes of the view and its children. 4650 * 4651 * @return {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining. 4652 */ 4653 dispose: function() { 4654 this.options.selection.off( null, null, this ); 4655 View.prototype.dispose.apply( this, arguments ); 4656 return this; 4657 }, 4658 4659 /** 4660 * Creates the toolbar view. 4661 */ 4662 createToolbar: function() { 4663 var LibraryViewSwitcher, Filters, toolbarOptions, 4664 showFilterByType = -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ); 4665 4666 toolbarOptions = { 4667 controller: this.controller 4668 }; 4669 4670 if ( this.controller.isModeActive( 'grid' ) ) { 4671 toolbarOptions.className = 'media-toolbar wp-filter'; 4672 } 4673 4674 /** 4675 * @member {wp.media.view.Toolbar} 4676 */ 4677 this.toolbar = new wp.media.view.Toolbar( toolbarOptions ); 4678 4679 this.views.add( this.toolbar ); 4680 4681 this.toolbar.set( 'spinner', new wp.media.view.Spinner({ 4682 priority: -20 4683 }) ); 4684 4685 if ( showFilterByType || this.options.date ) { 4686 /* 4687 * Create a h2 heading before the select elements that filter attachments. 4688 * This heading is visible in the modal and visually hidden in the grid. 4689 */ 4690 this.toolbar.set( 'filters-heading', new wp.media.view.Heading( { 4691 priority: -100, 4692 text: l10n.filterAttachments, 4693 level: 'h2', 4694 className: 'media-attachments-filter-heading screen-reader-text' 4695 }).render() ); 4696 } 4697 4698 if ( showFilterByType ) { 4699 // "Filters" is a <select>, a label element needs to be rendered before. 4700 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ 4701 value: l10n.filterByType, 4702 attributes: { 4703 'for': 'media-attachment-filters' 4704 }, 4705 priority: -80 4706 }).render() ); 4707 4708 if ( 'uploaded' === this.options.filters ) { 4709 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({ 4710 controller: this.controller, 4711 model: this.collection.props, 4712 priority: -80 4713 }).render() ); 4714 } else { 4715 Filters = new wp.media.view.AttachmentFilters.All({ 4716 controller: this.controller, 4717 model: this.collection.props, 4718 priority: -80 4719 }); 4720 4721 this.toolbar.set( 'filters', Filters.render() ); 4722 } 4723 } 4724 4725 /* 4726 * Feels odd to bring the global media library switcher into the Attachment browser view. 4727 * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 4728 * which the controller can tap into and add this view? 4729 */ 4730 if ( this.controller.isModeActive( 'grid' ) ) { 4731 LibraryViewSwitcher = View.extend({ 4732 className: 'view-switch media-grid-view-switch', 4733 template: wp.template( 'media-library-view-switcher') 4734 }); 4735 4736 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 4737 controller: this.controller, 4738 priority: -90 4739 }).render() ); 4740 4741 // DateFilter is a <select>, a label element needs to be rendered before. 4742 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 4743 value: l10n.filterByDate, 4744 attributes: { 4745 'for': 'media-attachment-date-filters' 4746 }, 4747 priority: -75 4748 }).render() ); 4749 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 4750 controller: this.controller, 4751 model: this.collection.props, 4752 priority: -75, 4753 }).render() ); 4754 4755 // BulkSelection is a <div> with subviews, including screen reader text. 4756 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 4757 text: l10n.bulkSelect, 4758 controller: this.controller, 4759 priority: -70 4760 }).render() ); 4761 4762 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 4763 filters: Filters, 4764 style: 'primary', 4765 disabled: true, 4766 text: mediaTrash ? l10n.trashSelected : l10n.deletePermanently, 4767 controller: this.controller, 4768 priority: -80, 4769 click: function() { 4770 var changed = [], removed = [], 4771 selection = this.controller.state().get( 'selection' ), 4772 library = this.controller.state().get( 'library' ); 4773 4774 if ( ! selection.length ) { 4775 return; 4776 } 4777 4778 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 4779 return; 4780 } 4781 4782 if ( mediaTrash && 4783 'trash' !== selection.at( 0 ).get( 'status' ) && 4784 ! window.confirm( l10n.warnBulkTrash ) ) { 4785 4786 return; 4787 } 4788 4789 selection.each( function( model ) { 4790 if ( ! model.get( 'nonces' )['delete'] ) { 4791 removed.push( model ); 4792 return; 4793 } 4794 4795 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 4796 model.set( 'status', 'inherit' ); 4797 changed.push( model.save() ); 4798 removed.push( model ); 4799 } else if ( mediaTrash ) { 4800 model.set( 'status', 'trash' ); 4801 changed.push( model.save() ); 4802 removed.push( model ); 4803 } else { 4804 model.destroy({wait: true}); 4805 } 4806 } ); 4807 4808 if ( changed.length ) { 4809 selection.remove( removed ); 4810 4811 $.when.apply( null, changed ).then( _.bind( function() { 4812 library._requery( true ); 4813 this.controller.trigger( 'selection:action:done' ); 4814 }, this ) ); 4815 } else { 4816 this.controller.trigger( 'selection:action:done' ); 4817 } 4818 } 4819 }).render() ); 4820 4821 if ( mediaTrash ) { 4822 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 4823 filters: Filters, 4824 style: 'link button-link-delete', 4825 disabled: true, 4826 text: l10n.deletePermanently, 4827 controller: this.controller, 4828 priority: -55, 4829 size: '', 4830 click: function() { 4831 var removed = [], 4832 destroy = [], 4833 selection = this.controller.state().get( 'selection' ); 4834 4835 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 4836 return; 4837 } 4838 4839 selection.each( function( model ) { 4840 if ( ! model.get( 'nonces' )['delete'] ) { 4841 removed.push( model ); 4842 return; 4843 } 4844 4845 destroy.push( model ); 4846 } ); 4847 4848 if ( removed.length ) { 4849 selection.remove( removed ); 4850 } 4851 4852 if ( destroy.length ) { 4853 $.when.apply( null, destroy.map( function (item) { 4854 return item.destroy(); 4855 } ) ).then( _.bind( function() { 4856 this.controller.trigger( 'selection:action:done' ); 4857 }, this ) ); 4858 } 4859 } 4860 }).render() ); 4861 } 4862 4863 } else if ( this.options.date ) { 4864 // DateFilter is a <select>, a label element needs to be rendered before. 4865 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 4866 value: l10n.filterByDate, 4867 attributes: { 4868 'for': 'media-attachment-date-filters' 4869 }, 4870 priority: -75 4871 }).render() ); 4872 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 4873 controller: this.controller, 4874 model: this.collection.props, 4875 priority: -75 4876 }).render() ); 4877 } 4878 4879 if ( this.options.search ) { 4880 // Search is an input, a label element needs to be rendered before. 4881 this.toolbar.set( 'searchLabel', new wp.media.view.Label({ 4882 value: l10n.searchLabel, 4883 className: 'media-search-input-label', 4884 attributes: { 4885 'for': 'media-search-input' 4886 }, 4887 priority: 60 4888 }).render() ); 4889 this.toolbar.set( 'search', new wp.media.view.Search({ 4890 controller: this.controller, 4891 model: this.collection.props, 4892 priority: 60 4893 }).render() ); 4894 } 4895 4896 if ( this.options.dragInfo ) { 4897 this.toolbar.set( 'dragInfo', new View({ 4898 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0], 4899 priority: -40 4900 }) ); 4901 } 4902 4903 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 4904 this.toolbar.set( 'suggestedDimensions', new View({ 4905 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0], 4906 priority: -40 4907 }) ); 4908 } 4909 }, 4910 4911 /** 4912 * Updates the content of the attachments browser. 4913 */ 4914 updateContent: function() { 4915 var view = this, 4916 noItemsView; 4917 4918 if ( this.controller.isModeActive( 'grid' ) ) { 4919 // Usually the media library. 4920 noItemsView = view.attachmentsNoResults; 4921 } else { 4922 // Usually the media modal. 4923 noItemsView = view.uploader; 4924 } 4925 4926 if ( ! this.collection.length ) { 4927 this.toolbar.get( 'spinner' ).show(); 4928 this.toolbar.$( '.media-bg-overlay' ).show(); 4929 this.dfd = this.collection.more().done( function() { 4930 if ( ! view.collection.length ) { 4931 noItemsView.$el.removeClass( 'hidden' ); 4932 } else { 4933 noItemsView.$el.addClass( 'hidden' ); 4934 } 4935 view.toolbar.get( 'spinner' ).hide(); 4936 view.toolbar.$( '.media-bg-overlay' ).hide(); 4937 } ); 4938 } else { 4939 noItemsView.$el.addClass( 'hidden' ); 4940 view.toolbar.get( 'spinner' ).hide(); 4941 this.toolbar.$( '.media-bg-overlay' ).hide(); 4942 } 4943 }, 4944 4945 /** 4946 * Creates the uploader view. 4947 */ 4948 createUploader: function() { 4949 this.uploader = new wp.media.view.UploaderInline({ 4950 controller: this.controller, 4951 status: false, 4952 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 4953 canClose: this.controller.isModeActive( 'grid' ) 4954 }); 4955 4956 this.uploader.$el.addClass( 'hidden' ); 4957 this.views.add( this.uploader ); 4958 }, 4959 4960 /** 4961 * Toggles the uploader view. 4962 */ 4963 toggleUploader: function() { 4964 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 4965 this.uploader.show(); 4966 } else { 4967 this.uploader.hide(); 4968 } 4969 }, 4970 4971 /** 4972 * Creates the Attachments wrapper view. 4973 * 4974 * @since 5.8.0 4975 * 4976 * @return {void} 4977 */ 4978 createAttachmentsWrapperView: function() { 4979 this.attachmentsWrapper = new wp.media.View( { 4980 className: 'attachments-wrapper' 4981 } ); 4982 4983 // Create the list of attachments. 4984 this.views.add( this.attachmentsWrapper ); 4985 this.createAttachments(); 4986 }, 4987 4988 /** 4989 * Creates the attachments view. 4990 */ 4991 createAttachments: function() { 4992 this.attachments = new wp.media.view.Attachments({ 4993 controller: this.controller, 4994 collection: this.collection, 4995 selection: this.options.selection, 4996 model: this.model, 4997 sortable: this.options.sortable, 4998 scrollElement: this.options.scrollElement, 4999 idealColumnWidth: this.options.idealColumnWidth, 5000 5001 // The single `Attachment` view to be used in the `Attachments` view. 5002 AttachmentView: this.options.AttachmentView 5003 }); 5004 5005 // Add keydown listener to the instance of the Attachments view. 5006 this.controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) ); 5007 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) ); 5008 5009 this.views.add( '.attachments-wrapper', this.attachments ); 5010 5011 if ( this.controller.isModeActive( 'grid' ) ) { 5012 this.attachmentsNoResults = new View({ 5013 controller: this.controller, 5014 tagName: 'p' 5015 }); 5016 5017 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 5018 this.attachmentsNoResults.$el.html( l10n.noMedia ); 5019 5020 this.views.add( this.attachmentsNoResults ); 5021 } 5022 }, 5023 5024 /** 5025 * Creates the load more button and attachments counter view. 5026 * 5027 * @since 5.8.0 5028 * 5029 * @return {void} 5030 */ 5031 createLoadMoreView: function() { 5032 var view = this; 5033 5034 this.loadMoreWrapper = new View( { 5035 controller: this.controller, 5036 className: 'load-more-wrapper' 5037 } ); 5038 5039 this.loadMoreCount = new View( { 5040 controller: this.controller, 5041 tagName: 'p', 5042 className: 'load-more-count hidden' 5043 } ); 5044 5045 this.loadMoreButton = new wp.media.view.Button( { 5046 text: __( 'Load more' ), 5047 className: 'load-more hidden', 5048 style: 'primary', 5049 size: '', 5050 click: function() { 5051 view.loadMoreAttachments(); 5052 } 5053 } ); 5054 5055 this.loadMoreSpinner = new wp.media.view.Spinner(); 5056 5057 this.loadMoreJumpToFirst = new wp.media.view.Button( { 5058 text: __( 'Jump to first loaded item' ), 5059 className: 'load-more-jump hidden', 5060 size: '', 5061 click: function() { 5062 view.jumpToFirstAddedItem(); 5063 } 5064 } ); 5065 5066 this.views.add( '.attachments-wrapper', this.loadMoreWrapper ); 5067 this.views.add( '.load-more-wrapper', this.loadMoreSpinner ); 5068 this.views.add( '.load-more-wrapper', this.loadMoreCount ); 5069 this.views.add( '.load-more-wrapper', this.loadMoreButton ); 5070 this.views.add( '.load-more-wrapper', this.loadMoreJumpToFirst ); 5071 }, 5072 5073 /** 5074 * Updates the Load More view. This function is debounced because the 5075 * collection updates multiple times at the add, remove, and reset events. 5076 * We need it to run only once, after all attachments are added or removed. 5077 * 5078 * @since 5.8.0 5079 * 5080 * @return {void} 5081 */ 5082 updateLoadMoreView: _.debounce( function() { 5083 // Ensure the load more view elements are initially hidden at each update. 5084 this.loadMoreButton.$el.addClass( 'hidden' ); 5085 this.loadMoreCount.$el.addClass( 'hidden' ); 5086 this.loadMoreJumpToFirst.$el.addClass( 'hidden' ).prop( 'disabled', true ); 5087 5088 if ( ! this.collection.getTotalAttachments() ) { 5089 return; 5090 } 5091 5092 if ( this.collection.length ) { 5093 this.loadMoreCount.$el.text( 5094 /* translators: 1: Number of displayed attachments, 2: Number of total attachments. */ 5095 sprintf( 5096 __( 'Showing %1$s of %2$s media items' ), 5097 this.collection.length, 5098 this.collection.getTotalAttachments() 5099 ) 5100 ); 5101 5102 this.loadMoreCount.$el.removeClass( 'hidden' ); 5103 } 5104 5105 /* 5106 * Notice that while the collection updates multiple times hasMore() may 5107 * return true when it's actually not true. 5108 */ 5109 if ( this.collection.hasMore() ) { 5110 this.loadMoreButton.$el.removeClass( 'hidden' ); 5111 } 5112 5113 // Find the media item to move focus to. The jQuery `eq()` index is zero-based. 5114 this.firstAddedMediaItem = this.$el.find( '.attachment' ).eq( this.firstAddedMediaItemIndex ); 5115 5116 // If there's a media item to move focus to, make the "Jump to" button available. 5117 if ( this.firstAddedMediaItem.length ) { 5118 this.firstAddedMediaItem.addClass( 'new-media' ); 5119 this.loadMoreJumpToFirst.$el.removeClass( 'hidden' ).prop( 'disabled', false ); 5120 } 5121 5122 // If there are new items added, but no more to be added, move focus to Jump button. 5123 if ( this.firstAddedMediaItem.length && ! this.collection.hasMore() ) { 5124 this.loadMoreJumpToFirst.$el.trigger( 'focus' ); 5125 } 5126 }, 10 ), 5127 5128 /** 5129 * Loads more attachments. 5130 * 5131 * @since 5.8.0 5132 * 5133 * @return {void} 5134 */ 5135 loadMoreAttachments: function() { 5136 var view = this; 5137 5138 if ( ! this.collection.hasMore() ) { 5139 return; 5140 } 5141 5142 /* 5143 * The collection index is zero-based while the length counts the actual 5144 * amount of items. Thus the length is equivalent to the position of the 5145 * first added item. 5146 */ 5147 this.firstAddedMediaItemIndex = this.collection.length; 5148 5149 this.$el.addClass( 'more-loaded' ); 5150 this.collection.each( function( attachment ) { 5151 var attach_id = attachment.attributes.id; 5152 $( '[data-id="' + attach_id + '"]' ).addClass( 'found-media' ); 5153 }); 5154 5155 view.loadMoreSpinner.show(); 5156 this.collection.once( 'attachments:received', function() { 5157 view.loadMoreSpinner.hide(); 5158 } ); 5159 this.collection.more(); 5160 }, 5161 5162 /** 5163 * Moves focus to the first new added item. . 5164 * 5165 * @since 5.8.0 5166 * 5167 * @return {void} 5168 */ 5169 jumpToFirstAddedItem: function() { 5170 // Set focus on first added item. 5171 this.firstAddedMediaItem.focus(); 5172 }, 5173 5174 /** 5175 * Creates the attachments heading view. 5176 */ 5177 createAttachmentsHeading: function() { 5178 this.attachmentsHeading = new wp.media.view.Heading( { 5179 text: l10n.attachmentsList, 5180 level: 'h2', 5181 className: 'media-views-heading screen-reader-text' 5182 } ); 5183 this.views.add( this.attachmentsHeading ); 5184 }, 5185 5186 /** 5187 * Creates the sidebar view. 5188 */ 5189 createSidebar: function() { 5190 var options = this.options, 5191 selection = options.selection, 5192 sidebar = this.sidebar = new wp.media.view.Sidebar({ 5193 controller: this.controller 5194 }); 5195 5196 this.views.add( sidebar ); 5197 5198 if ( this.controller.uploader ) { 5199 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({ 5200 controller: this.controller, 5201 priority: 40 5202 }) ); 5203 } 5204 5205 selection.on( 'selection:single', this.createSingle, this ); 5206 selection.on( 'selection:unsingle', this.disposeSingle, this ); 5207 5208 if ( selection.single() ) { 5209 this.createSingle(); 5210 } 5211 }, 5212 5213 /** 5214 * Creates the single attachment view. 5215 */ 5216 createSingle: function() { 5217 var sidebar = this.sidebar, 5218 single = this.options.selection.single(); 5219 5220 sidebar.set( 'details', new wp.media.view.Attachment.Details({ 5221 controller: this.controller, 5222 model: single, 5223 priority: 80 5224 }) ); 5225 5226 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({ 5227 controller: this.controller, 5228 model: single, 5229 priority: 120 5230 }) ); 5231 5232 if ( this.options.display ) { 5233 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({ 5234 controller: this.controller, 5235 model: this.model.display( single ), 5236 attachment: single, 5237 priority: 160, 5238 userSettings: this.model.get('displayUserSettings') 5239 }) ); 5240 } 5241 5242 // Show the sidebar on mobile. 5243 if ( this.model.id === 'insert' ) { 5244 sidebar.$el.addClass( 'visible' ); 5245 } 5246 }, 5247 5248 /** 5249 * Disposes of the single attachment view. 5250 */ 5251 disposeSingle: function() { 5252 var sidebar = this.sidebar; 5253 sidebar.unset('details'); 5254 sidebar.unset('compat'); 5255 sidebar.unset('display'); 5256 // Hide the sidebar on mobile. 5257 sidebar.$el.removeClass( 'visible' ); 5258 } 5259 }); 5260 5261 module.exports = AttachmentsBrowser; 5262 5263 5264 /***/ }, 5265 5266 /***/ 3479 5267 (module) { 5268 5269 var Attachments = wp.media.view.Attachments, 5270 Selection; 5271 5272 /** 5273 * wp.media.view.Attachments.Selection 5274 * 5275 * @memberOf wp.media.view.Attachments 5276 * 5277 * @class 5278 * @augments wp.media.view.Attachments 5279 * @augments wp.media.View 5280 * @augments wp.Backbone.View 5281 * @augments Backbone.View 5282 */ 5283 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{ 5284 events: {}, 5285 initialize: function() { 5286 _.defaults( this.options, { 5287 sortable: false, 5288 resize: false, 5289 5290 // The single `Attachment` view to be used in the `Attachments` view. 5291 AttachmentView: wp.media.view.Attachment.Selection 5292 }); 5293 // Call 'initialize' directly on the parent class. 5294 return Attachments.prototype.initialize.apply( this, arguments ); 5295 } 5296 }); 5297 5298 module.exports = Selection; 5299 5300 5301 /***/ }, 5302 5303 /***/ 168 5304 (module) { 5305 5306 var $ = Backbone.$, 5307 ButtonGroup; 5308 5309 /** 5310 * wp.media.view.ButtonGroup 5311 * 5312 * @memberOf wp.media.view 5313 * 5314 * @class 5315 * @augments wp.media.View 5316 * @augments wp.Backbone.View 5317 * @augments Backbone.View 5318 */ 5319 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{ 5320 tagName: 'div', 5321 className: 'button-group button-large media-button-group', 5322 5323 initialize: function() { 5324 /** 5325 * @member {wp.media.view.Button[]} 5326 */ 5327 this.buttons = _.map( this.options.buttons || [], function( button ) { 5328 if ( button instanceof Backbone.View ) { 5329 return button; 5330 } else { 5331 return new wp.media.view.Button( button ).render(); 5332 } 5333 }); 5334 5335 delete this.options.buttons; 5336 5337 if ( this.options.classes ) { 5338 this.$el.addClass( this.options.classes ); 5339 } 5340 }, 5341 5342 /** 5343 * Renders the button group. 5344 * 5345 * @return {wp.media.view.ButtonGroup} The button group. 5346 */ 5347 render: function() { 5348 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 5349 return this; 5350 } 5351 }); 5352 5353 module.exports = ButtonGroup; 5354 5355 5356 /***/ }, 5357 5358 /***/ 846 5359 (module) { 5360 5361 /** 5362 * wp.media.view.Button 5363 * 5364 * @memberOf wp.media.view 5365 * 5366 * @class 5367 * @augments wp.media.View 5368 * @augments wp.Backbone.View 5369 * @augments Backbone.View 5370 */ 5371 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{ 5372 tagName: 'button', 5373 className: 'media-button', 5374 attributes: { type: 'button' }, 5375 5376 events: { 5377 'click': 'click' 5378 }, 5379 5380 defaults: { 5381 text: '', 5382 style: '', 5383 size: 'large', 5384 disabled: false 5385 }, 5386 5387 initialize: function() { 5388 /** 5389 * Create a model with the provided `defaults`. 5390 * 5391 * @member {Backbone.Model} 5392 */ 5393 this.model = new Backbone.Model( this.defaults ); 5394 5395 // If any of the `options` have a key from `defaults`, apply its 5396 // value to the `model` and remove it from the `options` object. 5397 _.each( this.defaults, function( def, key ) { 5398 var value = this.options[ key ]; 5399 if ( _.isUndefined( value ) ) { 5400 return; 5401 } 5402 5403 this.model.set( key, value ); 5404 delete this.options[ key ]; 5405 }, this ); 5406 5407 this.listenTo( this.model, 'change', this.render ); 5408 }, 5409 /** 5410 * Renders the button. 5411 * 5412 * @return {wp.media.view.Button} Returns itself to allow chaining. 5413 */ 5414 render: function() { 5415 var classes = [ 'button', this.className ], 5416 model = this.model.toJSON(); 5417 5418 if ( model.style ) { 5419 classes.push( 'button-' + model.style ); 5420 } 5421 5422 if ( model.size ) { 5423 classes.push( 'button-' + model.size ); 5424 } 5425 5426 classes = _.uniq( classes.concat( this.options.classes ) ); 5427 this.el.className = classes.join(' '); 5428 5429 this.$el.prop( 'disabled', model.disabled ); 5430 this.$el.text( this.model.get('text') ); 5431 5432 return this; 5433 }, 5434 /** 5435 * Handles the click event. 5436 * 5437 * @param {JQuery.Event} event The jQuery event object for the click action. 5438 */ 5439 click: function( event ) { 5440 if ( '#' === this.attributes.href ) { 5441 event.preventDefault(); 5442 } 5443 5444 if ( this.options.click && ! this.model.get('disabled') ) { 5445 this.options.click.apply( this, arguments ); 5446 } 5447 } 5448 }); 5449 5450 module.exports = Button; 5451 5452 5453 /***/ }, 5454 5455 /***/ 7637 5456 (module) { 5457 5458 var View = wp.media.View, 5459 UploaderStatus = wp.media.view.UploaderStatus, 5460 l10n = wp.media.view.l10n, 5461 $ = jQuery, 5462 Cropper; 5463 5464 /** 5465 * wp.media.view.Cropper 5466 * 5467 * Uses the imgAreaSelect plugin to allow a user to crop an image. 5468 * 5469 * Takes imgAreaSelect options from 5470 * wp.customize.HeaderControl.calculateImageSelectOptions via 5471 * wp.customize.HeaderControl.openMM. 5472 * 5473 * @memberOf wp.media.view 5474 * 5475 * @class 5476 * @augments wp.media.View 5477 * @augments wp.Backbone.View 5478 * @augments Backbone.View 5479 */ 5480 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{ 5481 className: 'crop-content', 5482 template: wp.template('crop-content'), 5483 initialize: function() { 5484 _.bindAll(this, 'onImageLoad'); 5485 }, 5486 ready: function() { 5487 this.controller.frame.on('content:error:crop', this.onError, this); 5488 this.$image = this.$el.find('.crop-image'); 5489 this.$image.on('load', this.onImageLoad); 5490 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 5491 }, 5492 remove: function() { 5493 $(window).off('resize.cropper'); 5494 this.$el.remove(); 5495 this.$el.off(); 5496 View.prototype.remove.apply(this, arguments); 5497 }, 5498 prepare: function() { 5499 return { 5500 title: l10n.cropYourImage, 5501 url: this.options.attachment.get('url') 5502 }; 5503 }, 5504 onImageLoad: function() { 5505 var imgOptions = this.controller.get('imgSelectOptions'), 5506 imgSelect; 5507 5508 if (typeof imgOptions === 'function') { 5509 imgOptions = imgOptions(this.options.attachment, this.controller); 5510 } 5511 5512 imgOptions = _.extend(imgOptions, { 5513 parent: this.$el, 5514 onInit: function() { 5515 5516 // Store the set ratio. 5517 var setRatio = imgSelect.getOptions().aspectRatio; 5518 5519 // On mousedown, if no ratio is set and the Shift key is down, use a 1:1 ratio. 5520 this.parent.children().on( 'mousedown touchstart', function( e ) { 5521 5522 // If no ratio is set and the shift key is down, use a 1:1 ratio. 5523 if ( ! setRatio && e.shiftKey ) { 5524 imgSelect.setOptions( { 5525 aspectRatio: '1:1' 5526 } ); 5527 } 5528 } ); 5529 5530 this.parent.children().on( 'mouseup touchend', function() { 5531 5532 // Restore the set ratio. 5533 imgSelect.setOptions( { 5534 aspectRatio: setRatio ? setRatio : false 5535 } ); 5536 } ); 5537 } 5538 } ); 5539 this.trigger('image-loaded'); 5540 imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 5541 }, 5542 onError: function() { 5543 var filename = this.options.attachment.get('filename'); 5544 5545 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 5546 filename: UploaderStatus.prototype.filename(filename), 5547 message: window._wpMediaViewsL10n.cropError 5548 }), { at: 0 }); 5549 } 5550 }); 5551 5552 module.exports = Cropper; 5553 5554 5555 /***/ }, 5556 5557 /***/ 6126 5558 (module) { 5559 5560 var View = wp.media.View, 5561 EditImage; 5562 5563 /** 5564 * wp.media.view.EditImage 5565 * 5566 * @memberOf wp.media.view 5567 * 5568 * @class 5569 * @augments wp.media.View 5570 * @augments wp.Backbone.View 5571 * @augments Backbone.View 5572 */ 5573 EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{ 5574 className: 'image-editor', 5575 template: wp.template('image-editor'), 5576 5577 initialize: function( options ) { 5578 this.editor = window.imageEdit; 5579 this.controller = options.controller; 5580 View.prototype.initialize.apply( this, arguments ); 5581 }, 5582 5583 prepare: function() { 5584 return this.model.toJSON(); 5585 }, 5586 5587 loadEditor: function() { 5588 this.editor.open( this.model.get( 'id' ), this.model.get( 'nonces' ).edit, this ); 5589 }, 5590 5591 back: function() { 5592 var lastState = this.controller.lastState(); 5593 this.controller.setState( lastState ); 5594 }, 5595 5596 refresh: function() { 5597 this.model.fetch(); 5598 }, 5599 5600 save: function() { 5601 var lastState = this.controller.lastState(); 5602 5603 this.model.fetch().done( _.bind( function() { 5604 this.controller.setState( lastState ); 5605 }, this ) ); 5606 } 5607 5608 }); 5609 5610 module.exports = EditImage; 5611 5612 5613 /***/ }, 5614 5615 /***/ 5741 5616 (module) { 5617 5618 /** 5619 * wp.media.view.Embed 5620 * 5621 * @memberOf wp.media.view 5622 * 5623 * @class 5624 * @augments wp.media.View 5625 * @augments wp.Backbone.View 5626 * @augments Backbone.View 5627 */ 5628 var Embed = wp.media.View.extend(/** @lends wp.media.view.Embed.prototype */{ 5629 className: 'media-embed', 5630 5631 /** 5632 * Initializes the embed view. 5633 */ 5634 initialize: function() { 5635 /** 5636 * @member {wp.media.view.EmbedUrl} 5637 */ 5638 this.url = new wp.media.view.EmbedUrl({ 5639 controller: this.controller, 5640 model: this.model.props 5641 }).render(); 5642 5643 this.views.set([ this.url ]); 5644 this.refresh(); 5645 this.listenTo( this.model, 'change:type', this.refresh ); 5646 this.listenTo( this.model, 'change:loading', this.loading ); 5647 }, 5648 5649 /** 5650 * Sets the settings for the embed view. 5651 * 5652 * @param {Object} view The view object to be set as the settings for the embed view. 5653 */ 5654 settings: function( view ) { 5655 if ( this._settings ) { 5656 this._settings.remove(); 5657 } 5658 this._settings = view; 5659 this.views.add( view ); 5660 }, 5661 5662 /** 5663 * Refreshes the embed view based on the type of embed. 5664 */ 5665 refresh: function() { 5666 var type = this.model.get('type'), 5667 constructor; 5668 5669 if ( 'image' === type ) { 5670 constructor = wp.media.view.EmbedImage; 5671 } else if ( 'link' === type ) { 5672 constructor = wp.media.view.EmbedLink; 5673 } else { 5674 return; 5675 } 5676 5677 this.settings( new constructor({ 5678 controller: this.controller, 5679 model: this.model.props, 5680 priority: 40 5681 }) ); 5682 }, 5683 5684 /** 5685 * Toggles the loading state of the embed view. 5686 */ 5687 loading: function() { 5688 this.$el.toggleClass( 'embed-loading', this.model.get('loading') ); 5689 } 5690 }); 5691 5692 module.exports = Embed; 5693 5694 5695 /***/ }, 5696 5697 /***/ 2395 5698 (module) { 5699 5700 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 5701 EmbedImage; 5702 5703 /** 5704 * wp.media.view.EmbedImage 5705 * 5706 * @memberOf wp.media.view 5707 * 5708 * @class 5709 * @augments wp.media.view.Settings.AttachmentDisplay 5710 * @augments wp.media.view.Settings 5711 * @augments wp.media.View 5712 * @augments wp.Backbone.View 5713 * @augments Backbone.View 5714 */ 5715 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{ 5716 className: 'embed-media-settings', 5717 template: wp.template('embed-image-settings'), 5718 5719 initialize: function() { 5720 /** 5721 * Call `initialize` directly on parent class with passed arguments 5722 */ 5723 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 5724 this.listenTo( this.model, 'change:url', this.updateImage ); 5725 }, 5726 5727 updateImage: function() { 5728 this.$('img').attr( 'src', this.model.get('url') ); 5729 } 5730 }); 5731 5732 module.exports = EmbedImage; 5733 5734 5735 /***/ }, 5736 5737 /***/ 8232 5738 (module) { 5739 5740 var $ = jQuery, 5741 EmbedLink; 5742 5743 /** 5744 * wp.media.view.EmbedLink 5745 * 5746 * @memberOf wp.media.view 5747 * 5748 * @class 5749 * @augments wp.media.view.Settings 5750 * @augments wp.media.View 5751 * @augments wp.Backbone.View 5752 * @augments Backbone.View 5753 */ 5754 EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{ 5755 className: 'embed-link-settings', 5756 template: wp.template('embed-link-settings'), 5757 5758 initialize: function() { 5759 this.listenTo( this.model, 'change:url', this.updateoEmbed ); 5760 }, 5761 5762 updateoEmbed: _.debounce( function() { 5763 var url = this.model.get( 'url' ); 5764 5765 // Clear out previous results. 5766 this.$('.embed-container').hide().find('.embed-preview').empty(); 5767 this.$( '.setting' ).hide(); 5768 5769 // Only proceed with embed if the field contains more than 11 characters. 5770 // Example: http://a.io is 11 chars 5771 if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) { 5772 return; 5773 } 5774 5775 this.fetch(); 5776 }, wp.media.controller.Embed.sensitivity ), 5777 5778 fetch: function() { 5779 var url = this.model.get( 'url' ), re, youTubeEmbedMatch; 5780 5781 // Check if they haven't typed in 500 ms. 5782 if ( $('#embed-url-field').val() !== url ) { 5783 return; 5784 } 5785 5786 if ( this.dfd && 'pending' === this.dfd.state() ) { 5787 this.dfd.abort(); 5788 } 5789 5790 // Support YouTube embed urls, since they work once in the editor. 5791 re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; 5792 youTubeEmbedMatch = re.exec( url ); 5793 if ( youTubeEmbedMatch ) { 5794 url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; 5795 } 5796 5797 this.dfd = wp.apiRequest({ 5798 url: wp.media.view.settings.oEmbedProxyUrl, 5799 data: { 5800 url: url, 5801 maxwidth: this.model.get( 'width' ), 5802 maxheight: this.model.get( 'height' ) 5803 }, 5804 type: 'GET', 5805 dataType: 'json', 5806 context: this 5807 }) 5808 .done( function( response ) { 5809 this.renderoEmbed( { 5810 data: { 5811 body: response.html || '' 5812 } 5813 } ); 5814 } ) 5815 .fail( this.renderFail ); 5816 }, 5817 5818 renderFail: function ( response, status ) { 5819 if ( 'abort' === status ) { 5820 return; 5821 } 5822 this.$( '.link-text' ).show(); 5823 }, 5824 5825 renderoEmbed: function( response ) { 5826 var html = ( response && response.data && response.data.body ) || ''; 5827 5828 if ( html ) { 5829 this.$('.embed-container').show().find('.embed-preview').html( html ); 5830 } else { 5831 this.renderFail(); 5832 } 5833 } 5834 }); 5835 5836 module.exports = EmbedLink; 5837 5838 5839 /***/ }, 5840 5841 /***/ 7327 5842 (module) { 5843 5844 var View = wp.media.View, 5845 $ = jQuery, 5846 l10n = wp.media.view.l10n, 5847 EmbedUrl; 5848 5849 /** 5850 * wp.media.view.EmbedUrl 5851 * 5852 * @memberOf wp.media.view 5853 * 5854 * @class 5855 * @augments wp.media.View 5856 * @augments wp.Backbone.View 5857 * @augments Backbone.View 5858 */ 5859 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{ 5860 tagName: 'span', 5861 className: 'embed-url', 5862 5863 events: { 5864 'input': 'url' 5865 }, 5866 5867 initialize: function() { 5868 this.$input = $( '<input id="embed-url-field" type="url" />' ) 5869 .attr( 'aria-label', l10n.insertFromUrlTitle ) 5870 .val( this.model.get('url') ); 5871 this.input = this.$input[0]; 5872 5873 this.spinner = $('<span class="spinner" />')[0]; 5874 this.$el.append([ this.input, this.spinner ]); 5875 5876 this.listenTo( this.model, 'change:url', this.render ); 5877 5878 if ( this.model.get( 'url' ) ) { 5879 _.delay( _.bind( function () { 5880 this.model.trigger( 'change:url' ); 5881 }, this ), 500 ); 5882 } 5883 }, 5884 /** 5885 * Renders the view. 5886 * 5887 * @return {void|wp.media.view.EmbedUrl} Returns itself to allow chaining. 5888 */ 5889 render: function() { 5890 var $input = this.$input; 5891 5892 if ( $input.is(':focus') ) { 5893 return; 5894 } 5895 5896 if ( this.model.get( 'url' ) ) { 5897 this.input.value = this.model.get('url'); 5898 } else { 5899 this.input.setAttribute( 'placeholder', 'https://' ); 5900 } 5901 5902 /** 5903 * Call `render` directly on parent class with passed arguments 5904 */ 5905 View.prototype.render.apply( this, arguments ); 5906 return this; 5907 }, 5908 5909 url: function( event ) { 5910 var url = event.target.value || ''; 5911 this.model.set( 'url', url.trim() ); 5912 } 5913 }); 5914 5915 module.exports = EmbedUrl; 5916 5917 5918 /***/ }, 5919 5920 /***/ 718 5921 (module) { 5922 5923 var $ = jQuery; 5924 5925 /** 5926 * wp.media.view.FocusManager 5927 * 5928 * @memberOf wp.media.view 5929 * 5930 * @class 5931 * @augments wp.media.View 5932 * @augments wp.Backbone.View 5933 * @augments Backbone.View 5934 */ 5935 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 5936 5937 events: { 5938 'keydown': 'focusManagementMode' 5939 }, 5940 5941 /** 5942 * Initializes the Focus Manager. 5943 * 5944 * @param {Object} options The Focus Manager options. 5945 * 5946 * @since 5.3.0 5947 * 5948 * @return {void} 5949 */ 5950 initialize: function( options ) { 5951 this.mode = options.mode || 'constrainTabbing'; 5952 this.tabsAutomaticActivation = options.tabsAutomaticActivation || false; 5953 }, 5954 5955 /** 5956 * Determines which focus management mode to use. 5957 * 5958 * @since 5.3.0 5959 * 5960 * @param {Object} event jQuery event object. 5961 * 5962 * @return {void} 5963 */ 5964 focusManagementMode: function( event ) { 5965 if ( this.mode === 'constrainTabbing' ) { 5966 this.constrainTabbing( event ); 5967 } 5968 5969 if ( this.mode === 'tabsNavigation' ) { 5970 this.tabsNavigation( event ); 5971 } 5972 }, 5973 5974 /** 5975 * Gets all the tabbable elements. 5976 * 5977 * @since 5.3.0 5978 * 5979 * @return {Object} A jQuery collection of tabbable elements. 5980 */ 5981 getTabbables: function() { 5982 // Skip the file input added by Plupload. 5983 return this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 5984 }, 5985 5986 /** 5987 * Moves focus to the modal dialog. 5988 * 5989 * @since 3.5.0 5990 * 5991 * @return {void} 5992 */ 5993 focus: function() { 5994 this.$( '.media-modal' ).trigger( 'focus' ); 5995 }, 5996 5997 /** 5998 * Constrains navigation with the Tab key within the media view element. 5999 * 6000 * @since 4.0.0 6001 * 6002 * @param {Object} event A keydown jQuery event. 6003 * 6004 * @return {void} 6005 */ 6006 constrainTabbing: function( event ) { 6007 var tabbables; 6008 6009 // Look for the tab key. 6010 if ( 9 !== event.keyCode ) { 6011 return; 6012 } 6013 6014 tabbables = this.getTabbables(); 6015 6016 // Keep tab focus within media modal while it's open. 6017 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 6018 tabbables.first().focus(); 6019 return false; 6020 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 6021 tabbables.last().focus(); 6022 return false; 6023 } 6024 }, 6025 6026 /** 6027 * Hides from assistive technologies all the body children. 6028 * 6029 * Sets an `aria-hidden="true"` attribute on all the body children except 6030 * the provided element and other elements that should not be hidden. 6031 * 6032 * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy 6033 * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"` 6034 * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside 6035 * of the modal dialog and get hidden from assistive technologies. 6036 * 6037 * @since 5.2.3 6038 * 6039 * @param {Object} visibleElement The jQuery object representing the element that should not be hidden. 6040 * 6041 * @return {void} 6042 */ 6043 setAriaHiddenOnBodyChildren: function( visibleElement ) { 6044 var bodyChildren, 6045 self = this; 6046 6047 if ( this.isBodyAriaHidden ) { 6048 return; 6049 } 6050 6051 // Get all the body children. 6052 bodyChildren = document.body.children; 6053 6054 // Loop through the body children and hide the ones that should be hidden. 6055 _.each( bodyChildren, function( element ) { 6056 // Don't hide the modal element. 6057 if ( element === visibleElement[0] ) { 6058 return; 6059 } 6060 6061 // Determine the body children to hide. 6062 if ( self.elementShouldBeHidden( element ) ) { 6063 element.setAttribute( 'aria-hidden', 'true' ); 6064 // Store the hidden elements. 6065 self.ariaHiddenElements.push( element ); 6066 } 6067 } ); 6068 6069 this.isBodyAriaHidden = true; 6070 }, 6071 6072 /** 6073 * Unhides from assistive technologies all the body children. 6074 * 6075 * Makes visible again to assistive technologies all the body children 6076 * previously hidden and stored in this.ariaHiddenElements. 6077 * 6078 * @since 5.2.3 6079 * 6080 * @return {void} 6081 */ 6082 removeAriaHiddenFromBodyChildren: function() { 6083 _.each( this.ariaHiddenElements, function( element ) { 6084 element.removeAttribute( 'aria-hidden' ); 6085 } ); 6086 6087 this.ariaHiddenElements = []; 6088 this.isBodyAriaHidden = false; 6089 }, 6090 6091 /** 6092 * Determines if the passed element should not be hidden from assistive technologies. 6093 * 6094 * @since 5.2.3 6095 * 6096 * @param {Object} element The DOM element that should be checked. 6097 * 6098 * @return {boolean} Whether the element should not be hidden from assistive technologies. 6099 */ 6100 elementShouldBeHidden: function( element ) { 6101 var role = element.getAttribute( 'role' ), 6102 liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ]; 6103 6104 /* 6105 * Don't hide scripts, elements that already have `aria-hidden`, and 6106 * ARIA live regions. 6107 */ 6108 return ! ( 6109 element.tagName === 'SCRIPT' || 6110 element.hasAttribute( 'aria-hidden' ) || 6111 element.hasAttribute( 'aria-live' ) || 6112 liveRegionsRoles.indexOf( role ) !== -1 6113 ); 6114 }, 6115 6116 /** 6117 * Whether the body children are hidden from assistive technologies. 6118 * 6119 * @since 5.2.3 6120 */ 6121 isBodyAriaHidden: false, 6122 6123 /** 6124 * Stores an array of DOM elements that should be hidden from assistive 6125 * technologies, for example when the media modal dialog opens. 6126 * 6127 * @since 5.2.3 6128 */ 6129 ariaHiddenElements: [], 6130 6131 /** 6132 * Holds the jQuery collection of ARIA tabs. 6133 * 6134 * @since 5.3.0 6135 */ 6136 tabs: $(), 6137 6138 /** 6139 * Sets up tabs in an ARIA tabbed interface. 6140 * 6141 * @since 5.3.0 6142 * 6143 * @return {void} 6144 */ 6145 setupAriaTabs: function() { 6146 this.tabs = this.$( '[role="tab"]' ); 6147 6148 // Set up initial attributes. 6149 this.tabs.attr( { 6150 'aria-selected': 'false', 6151 tabIndex: '-1' 6152 } ); 6153 6154 // Set up attributes on the initially active tab. 6155 this.tabs.filter( '.active' ) 6156 .removeAttr( 'tabindex' ) 6157 .attr( 'aria-selected', 'true' ); 6158 }, 6159 6160 /** 6161 * Enables arrows navigation within the ARIA tabbed interface. 6162 * 6163 * @since 5.3.0 6164 * 6165 * @param {Object} event jQuery event object. 6166 * 6167 * @return {void} 6168 */ 6169 tabsNavigation: function( event ) { 6170 var orientation = 'horizontal', 6171 keys = [ 32, 35, 36, 37, 38, 39, 40 ]; 6172 6173 // Return if not Spacebar, End, Home, or Arrow keys. 6174 if ( keys.indexOf( event.which ) === -1 ) { 6175 return; 6176 } 6177 6178 // Determine navigation direction. 6179 if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) { 6180 orientation = 'vertical'; 6181 } 6182 6183 // Make Up and Down arrow keys do nothing with horizontal tabs. 6184 if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { 6185 return; 6186 } 6187 6188 // Make Left and Right arrow keys do nothing with vertical tabs. 6189 if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { 6190 return; 6191 } 6192 6193 this.switchTabs( event, this.tabs ); 6194 }, 6195 6196 /** 6197 * Switches tabs in the ARIA tabbed interface. 6198 * 6199 * @since 5.3.0 6200 * 6201 * @param {Object} event jQuery event object. 6202 * 6203 * @return {void} 6204 */ 6205 switchTabs: function( event ) { 6206 var key = event.which, 6207 index = this.tabs.index( $( event.target ) ), 6208 newIndex; 6209 6210 switch ( key ) { 6211 // Space bar: Activate current targeted tab. 6212 case 32: { 6213 this.activateTab( this.tabs[ index ] ); 6214 break; 6215 } 6216 // End key: Activate last tab. 6217 case 35: { 6218 event.preventDefault(); 6219 this.activateTab( this.tabs[ this.tabs.length - 1 ] ); 6220 break; 6221 } 6222 // Home key: Activate first tab. 6223 case 36: { 6224 event.preventDefault(); 6225 this.activateTab( this.tabs[ 0 ] ); 6226 break; 6227 } 6228 // Left and up keys: Activate previous tab. 6229 case 37: 6230 case 38: { 6231 event.preventDefault(); 6232 newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1; 6233 this.activateTab( this.tabs[ newIndex ] ); 6234 break; 6235 } 6236 // Right and down keys: Activate next tab. 6237 case 39: 6238 case 40: { 6239 event.preventDefault(); 6240 newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1; 6241 this.activateTab( this.tabs[ newIndex ] ); 6242 break; 6243 } 6244 } 6245 }, 6246 6247 /** 6248 * Sets a single tab to be focusable and semantically selected. 6249 * 6250 * @since 5.3.0 6251 * 6252 * @param {Object} tab The tab DOM element. 6253 * 6254 * @return {void} 6255 */ 6256 activateTab: function( tab ) { 6257 if ( ! tab ) { 6258 return; 6259 } 6260 6261 // The tab is a DOM element: no need for jQuery methods. 6262 tab.focus(); 6263 6264 // Handle automatic activation. 6265 if ( this.tabsAutomaticActivation ) { 6266 tab.removeAttribute( 'tabindex' ); 6267 tab.setAttribute( 'aria-selected', 'true' ); 6268 tab.click(); 6269 6270 return; 6271 } 6272 6273 // Handle manual activation. 6274 $( tab ).on( 'click', function() { 6275 tab.removeAttribute( 'tabindex' ); 6276 tab.setAttribute( 'aria-selected', 'true' ); 6277 } ); 6278 } 6279 }); 6280 6281 module.exports = FocusManager; 6282 6283 6284 /***/ }, 6285 6286 /***/ 1061 6287 (module) { 6288 6289 /** 6290 * wp.media.view.Frame 6291 * 6292 * A frame is a composite view consisting of one or more regions and one or more 6293 * states. 6294 * 6295 * @memberOf wp.media.view 6296 * 6297 * @see wp.media.controller.State 6298 * @see wp.media.controller.Region 6299 * 6300 * @class 6301 * @augments wp.media.View 6302 * @augments wp.Backbone.View 6303 * @augments Backbone.View 6304 * @mixes wp.media.controller.StateMachine 6305 */ 6306 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{ 6307 initialize: function() { 6308 _.defaults( this.options, { 6309 mode: [ 'select' ] 6310 }); 6311 this._createRegions(); 6312 this._createStates(); 6313 this._createModes(); 6314 }, 6315 6316 _createRegions: function() { 6317 // Clone the regions array. 6318 this.regions = this.regions ? this.regions.slice() : []; 6319 6320 // Initialize regions. 6321 _.each( this.regions, function( region ) { 6322 this[ region ] = new wp.media.controller.Region({ 6323 view: this, 6324 id: region, 6325 selector: '.media-frame-' + region 6326 }); 6327 }, this ); 6328 }, 6329 /** 6330 * Create the frame's states. 6331 * 6332 * @see wp.media.controller.State 6333 * @see wp.media.controller.StateMachine 6334 * 6335 * @fires wp.media.controller.State#ready 6336 */ 6337 _createStates: function() { 6338 // Create the default `states` collection. 6339 this.states = new Backbone.Collection( null, { 6340 model: wp.media.controller.State 6341 }); 6342 6343 // Ensure states have a reference to the frame. 6344 this.states.on( 'add', function( model ) { 6345 model.frame = this; 6346 model.trigger('ready'); 6347 }, this ); 6348 6349 if ( this.options.states ) { 6350 this.states.add( this.options.states ); 6351 } 6352 }, 6353 6354 /** 6355 * A frame can be in a mode or multiple modes at one time. 6356 * 6357 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 6358 */ 6359 _createModes: function() { 6360 // Store active "modes" that the frame is in. Unrelated to region modes. 6361 this.activeModes = new Backbone.Collection(); 6362 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 6363 6364 _.each( this.options.mode, function( mode ) { 6365 this.activateMode( mode ); 6366 }, this ); 6367 }, 6368 /** 6369 * Reset all states on the frame to their defaults. 6370 * 6371 * @return {wp.media.view.Frame} Returns itself to allow chaining. 6372 */ 6373 reset: function() { 6374 this.states.invoke( 'trigger', 'reset' ); 6375 return this; 6376 }, 6377 /** 6378 * Map activeMode collection events to the frame. 6379 * 6380 * @param {Backbone.Model} model The model that triggered the event. 6381 * @param {Backbone.Collection} collection The collection to which the model belongs. 6382 * @param {Object} options The options object associated with the event. 6383 */ 6384 triggerModeEvents: function( model, collection, options ) { 6385 var collectionEvent, 6386 modeEventMap = { 6387 add: 'activate', 6388 remove: 'deactivate' 6389 }, 6390 eventToTrigger; 6391 // Probably a better way to do this. 6392 _.each( options, function( value, key ) { 6393 if ( value ) { 6394 collectionEvent = key; 6395 } 6396 } ); 6397 6398 if ( ! _.has( modeEventMap, collectionEvent ) ) { 6399 return; 6400 } 6401 6402 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 6403 this.trigger( eventToTrigger ); 6404 }, 6405 /** 6406 * Activate a mode on the frame. 6407 * 6408 * @param {string} mode Mode ID. 6409 * @return {void|this} Returns itself to allow chaining. 6410 */ 6411 activateMode: function( mode ) { 6412 // Bail if the mode is already active. 6413 if ( this.isModeActive( mode ) ) { 6414 return; 6415 } 6416 this.activeModes.add( [ { id: mode } ] ); 6417 // Add a CSS class to the frame so elements can be styled for the mode. 6418 this.$el.addClass( 'mode-' + mode ); 6419 6420 return this; 6421 }, 6422 /** 6423 * Deactivate a mode on the frame. 6424 * 6425 * @param {string} mode Mode ID. 6426 * @return {this} Returns itself to allow chaining. 6427 */ 6428 deactivateMode: function( mode ) { 6429 // Bail if the mode isn't active. 6430 if ( ! this.isModeActive( mode ) ) { 6431 return this; 6432 } 6433 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 6434 this.$el.removeClass( 'mode-' + mode ); 6435 6436 // Fires a `#{mode}:deactivate` event on the frame. 6437 this.trigger( mode + ':deactivate' ); 6438 6439 return this; 6440 }, 6441 /** 6442 * Check if a mode is enabled on the frame. 6443 * 6444 * @param {string} mode Mode ID. 6445 * @return {boolean} True if the mode is active, false otherwise. 6446 */ 6447 isModeActive: function( mode ) { 6448 return Boolean( this.activeModes.where( { id: mode } ).length ); 6449 } 6450 }); 6451 6452 // Make the `Frame` a `StateMachine`. 6453 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 6454 6455 module.exports = Frame; 6456 6457 6458 /***/ }, 6459 6460 /***/ 5424 6461 (module) { 6462 6463 var Select = wp.media.view.MediaFrame.Select, 6464 l10n = wp.media.view.l10n, 6465 ImageDetails; 6466 6467 /** 6468 * wp.media.view.MediaFrame.ImageDetails 6469 * 6470 * A media frame for manipulating an image that's already been inserted 6471 * into a post. 6472 * 6473 * @memberOf wp.media.view.MediaFrame 6474 * 6475 * @class 6476 * @augments wp.media.view.MediaFrame.Select 6477 * @augments wp.media.view.MediaFrame 6478 * @augments wp.media.view.Frame 6479 * @augments wp.media.View 6480 * @augments wp.Backbone.View 6481 * @augments Backbone.View 6482 * @mixes wp.media.controller.StateMachine 6483 */ 6484 ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{ 6485 defaults: { 6486 id: 'image', 6487 url: '', 6488 menu: 'image-details', 6489 content: 'image-details', 6490 toolbar: 'image-details', 6491 type: 'link', 6492 title: l10n.imageDetailsTitle, 6493 priority: 120 6494 }, 6495 6496 initialize: function( options ) { 6497 this.image = new wp.media.model.PostImage( options.metadata ); 6498 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 6499 Select.prototype.initialize.apply( this, arguments ); 6500 }, 6501 6502 bindHandlers: function() { 6503 Select.prototype.bindHandlers.apply( this, arguments ); 6504 this.on( 'menu:create:image-details', this.createMenu, this ); 6505 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 6506 this.on( 'content:render:edit-image', this.editImageContent, this ); 6507 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 6508 // Override the select toolbar. 6509 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 6510 }, 6511 6512 createStates: function() { 6513 this.states.add([ 6514 new wp.media.controller.ImageDetails({ 6515 image: this.image, 6516 editable: false 6517 }), 6518 new wp.media.controller.ReplaceImage({ 6519 id: 'replace-image', 6520 library: wp.media.query( { type: 'image' } ), 6521 image: this.image, 6522 multiple: false, 6523 title: l10n.imageReplaceTitle, 6524 toolbar: 'replace', 6525 priority: 80, 6526 displaySettings: true 6527 }), 6528 new wp.media.controller.EditImage( { 6529 image: this.image, 6530 selection: this.options.selection 6531 } ) 6532 ]); 6533 }, 6534 6535 imageDetailsContent: function( options ) { 6536 options.view = new wp.media.view.ImageDetails({ 6537 controller: this, 6538 model: this.state().image, 6539 attachment: this.state().image.attachment 6540 }); 6541 }, 6542 6543 editImageContent: function() { 6544 var state = this.state(), 6545 model = state.get('image'), 6546 view; 6547 6548 if ( ! model ) { 6549 return; 6550 } 6551 6552 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 6553 6554 this.content.set( view ); 6555 6556 // After bringing in the frame, load the actual editor via an Ajax call. 6557 view.loadEditor(); 6558 6559 }, 6560 6561 renderImageDetailsToolbar: function() { 6562 this.toolbar.set( new wp.media.view.Toolbar({ 6563 controller: this, 6564 items: { 6565 select: { 6566 style: 'primary', 6567 text: l10n.update, 6568 priority: 80, 6569 6570 click: function() { 6571 var controller = this.controller, 6572 state = controller.state(); 6573 6574 controller.close(); 6575 6576 // Not sure if we want to use wp.media.string.image which will create a shortcode or 6577 // perhaps wp.html.string to at least to build the <img />. 6578 state.trigger( 'update', controller.image.toJSON() ); 6579 6580 // Restore and reset the default state. 6581 controller.setState( controller.options.state ); 6582 controller.reset(); 6583 } 6584 } 6585 } 6586 }) ); 6587 }, 6588 6589 renderReplaceImageToolbar: function() { 6590 var frame = this, 6591 lastState = frame.lastState(), 6592 previous = lastState && lastState.id; 6593 6594 this.toolbar.set( new wp.media.view.Toolbar({ 6595 controller: this, 6596 items: { 6597 back: { 6598 text: l10n.back, 6599 priority: 80, 6600 click: function() { 6601 if ( previous ) { 6602 frame.setState( previous ); 6603 } else { 6604 frame.close(); 6605 } 6606 } 6607 }, 6608 6609 replace: { 6610 style: 'primary', 6611 text: l10n.replace, 6612 priority: 20, 6613 requires: { selection: true }, 6614 6615 click: function() { 6616 var controller = this.controller, 6617 state = controller.state(), 6618 selection = state.get( 'selection' ), 6619 attachment = selection.single(); 6620 6621 controller.close(); 6622 6623 controller.image.changeAttachment( attachment, state.display( attachment ) ); 6624 6625 // Not sure if we want to use wp.media.string.image which will create a shortcode or 6626 // perhaps wp.html.string to at least to build the <img />. 6627 state.trigger( 'replace', controller.image.toJSON() ); 6628 6629 // Restore and reset the default state. 6630 controller.setState( controller.options.state ); 6631 controller.reset(); 6632 } 6633 } 6634 } 6635 }) ); 6636 } 6637 6638 }); 6639 6640 module.exports = ImageDetails; 6641 6642 6643 /***/ }, 6644 6645 /***/ 4274 6646 (module) { 6647 6648 var Select = wp.media.view.MediaFrame.Select, 6649 Library = wp.media.controller.Library, 6650 l10n = wp.media.view.l10n, 6651 Post; 6652 6653 /** 6654 * wp.media.view.MediaFrame.Post 6655 * 6656 * The frame for manipulating media on the Edit Post page. 6657 * 6658 * @memberOf wp.media.view.MediaFrame 6659 * 6660 * @class 6661 * @augments wp.media.view.MediaFrame.Select 6662 * @augments wp.media.view.MediaFrame 6663 * @augments wp.media.view.Frame 6664 * @augments wp.media.View 6665 * @augments wp.Backbone.View 6666 * @augments Backbone.View 6667 * @mixes wp.media.controller.StateMachine 6668 */ 6669 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{ 6670 initialize: function() { 6671 this.counts = { 6672 audio: { 6673 count: wp.media.view.settings.attachmentCounts.audio, 6674 state: 'playlist' 6675 }, 6676 video: { 6677 count: wp.media.view.settings.attachmentCounts.video, 6678 state: 'video-playlist' 6679 } 6680 }; 6681 6682 _.defaults( this.options, { 6683 multiple: true, 6684 editing: false, 6685 state: 'insert', 6686 metadata: {} 6687 }); 6688 6689 // Call 'initialize' directly on the parent class. 6690 Select.prototype.initialize.apply( this, arguments ); 6691 this.createIframeStates(); 6692 6693 }, 6694 6695 /** 6696 * Create the default states. 6697 */ 6698 createStates: function() { 6699 var options = this.options; 6700 6701 this.states.add([ 6702 // Main states. 6703 new Library({ 6704 id: 'insert', 6705 title: l10n.insertMediaTitle, 6706 priority: 20, 6707 toolbar: 'main-insert', 6708 filterable: 'all', 6709 library: wp.media.query( options.library ), 6710 multiple: options.multiple ? 'reset' : false, 6711 editable: true, 6712 6713 // If the user isn't allowed to edit fields, 6714 // can they still edit it locally? 6715 allowLocalEdits: true, 6716 6717 // Show the attachment display settings. 6718 displaySettings: true, 6719 // Update user settings when users adjust the 6720 // attachment display settings. 6721 displayUserSettings: true 6722 }), 6723 6724 new Library({ 6725 id: 'gallery', 6726 title: l10n.createGalleryTitle, 6727 priority: 40, 6728 toolbar: 'main-gallery', 6729 filterable: 'uploaded', 6730 multiple: 'add', 6731 editable: false, 6732 6733 library: wp.media.query( _.defaults({ 6734 type: 'image' 6735 }, options.library ) ) 6736 }), 6737 6738 // Embed states. 6739 new wp.media.controller.Embed( { metadata: options.metadata } ), 6740 6741 new wp.media.controller.EditImage( { model: options.editImage } ), 6742 6743 // Gallery states. 6744 new wp.media.controller.GalleryEdit({ 6745 library: options.selection, 6746 editing: options.editing, 6747 menu: 'gallery' 6748 }), 6749 6750 new wp.media.controller.GalleryAdd(), 6751 6752 new Library({ 6753 id: 'playlist', 6754 title: l10n.createPlaylistTitle, 6755 priority: 60, 6756 toolbar: 'main-playlist', 6757 filterable: 'uploaded', 6758 multiple: 'add', 6759 editable: false, 6760 6761 library: wp.media.query( _.defaults({ 6762 type: 'audio' 6763 }, options.library ) ) 6764 }), 6765 6766 // Playlist states. 6767 new wp.media.controller.CollectionEdit({ 6768 type: 'audio', 6769 collectionType: 'playlist', 6770 title: l10n.editPlaylistTitle, 6771 SettingsView: wp.media.view.Settings.Playlist, 6772 library: options.selection, 6773 editing: options.editing, 6774 menu: 'playlist', 6775 dragInfoText: l10n.playlistDragInfo, 6776 dragInfo: false 6777 }), 6778 6779 new wp.media.controller.CollectionAdd({ 6780 type: 'audio', 6781 collectionType: 'playlist', 6782 title: l10n.addToPlaylistTitle 6783 }), 6784 6785 new Library({ 6786 id: 'video-playlist', 6787 title: l10n.createVideoPlaylistTitle, 6788 priority: 60, 6789 toolbar: 'main-video-playlist', 6790 filterable: 'uploaded', 6791 multiple: 'add', 6792 editable: false, 6793 6794 library: wp.media.query( _.defaults({ 6795 type: 'video' 6796 }, options.library ) ) 6797 }), 6798 6799 new wp.media.controller.CollectionEdit({ 6800 type: 'video', 6801 collectionType: 'playlist', 6802 title: l10n.editVideoPlaylistTitle, 6803 SettingsView: wp.media.view.Settings.Playlist, 6804 library: options.selection, 6805 editing: options.editing, 6806 menu: 'video-playlist', 6807 dragInfoText: l10n.videoPlaylistDragInfo, 6808 dragInfo: false 6809 }), 6810 6811 new wp.media.controller.CollectionAdd({ 6812 type: 'video', 6813 collectionType: 'playlist', 6814 title: l10n.addToVideoPlaylistTitle 6815 }) 6816 ]); 6817 6818 if ( wp.media.view.settings.post.featuredImageId ) { 6819 this.states.add( new wp.media.controller.FeaturedImage() ); 6820 } 6821 }, 6822 6823 bindHandlers: function() { 6824 var handlers, checkCounts; 6825 6826 Select.prototype.bindHandlers.apply( this, arguments ); 6827 6828 this.on( 'activate', this.activate, this ); 6829 6830 // Only bother checking media type counts if one of the counts is zero. 6831 checkCounts = _.find( this.counts, function( type ) { 6832 return type.count === 0; 6833 } ); 6834 6835 if ( typeof checkCounts !== 'undefined' ) { 6836 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 6837 } 6838 6839 this.on( 'menu:create:gallery', this.createMenu, this ); 6840 this.on( 'menu:create:playlist', this.createMenu, this ); 6841 this.on( 'menu:create:video-playlist', this.createMenu, this ); 6842 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 6843 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 6844 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 6845 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 6846 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 6847 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 6848 6849 handlers = { 6850 menu: { 6851 'default': 'mainMenu', 6852 'gallery': 'galleryMenu', 6853 'playlist': 'playlistMenu', 6854 'video-playlist': 'videoPlaylistMenu' 6855 }, 6856 6857 content: { 6858 'embed': 'embedContent', 6859 'edit-image': 'editImageContent', 6860 'edit-selection': 'editSelectionContent' 6861 }, 6862 6863 toolbar: { 6864 'main-insert': 'mainInsertToolbar', 6865 'main-gallery': 'mainGalleryToolbar', 6866 'gallery-edit': 'galleryEditToolbar', 6867 'gallery-add': 'galleryAddToolbar', 6868 'main-playlist': 'mainPlaylistToolbar', 6869 'playlist-edit': 'playlistEditToolbar', 6870 'playlist-add': 'playlistAddToolbar', 6871 'main-video-playlist': 'mainVideoPlaylistToolbar', 6872 'video-playlist-edit': 'videoPlaylistEditToolbar', 6873 'video-playlist-add': 'videoPlaylistAddToolbar' 6874 } 6875 }; 6876 6877 _.each( handlers, function( regionHandlers, region ) { 6878 _.each( regionHandlers, function( callback, handler ) { 6879 this.on( region + ':render:' + handler, this[ callback ], this ); 6880 }, this ); 6881 }, this ); 6882 }, 6883 6884 /** 6885 * Activates the frame. 6886 */ 6887 activate: function() { 6888 // Hide menu items for states tied to particular media types if there are no items. 6889 _.each( this.counts, function( type ) { 6890 if ( type.count < 1 ) { 6891 this.menuItemVisibility( type.state, 'hide' ); 6892 } 6893 }, this ); 6894 }, 6895 6896 /** 6897 * Handles the counts of media types. 6898 * 6899 * @param {wp.media.model.Attachments} model The attachment model that changed. 6900 * @param {string} attr The attribute that changed on the model. 6901 */ 6902 mediaTypeCounts: function( model, attr ) { 6903 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 6904 this.counts[ attr ].count++; 6905 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 6906 } 6907 }, 6908 6909 // Menus. 6910 /** 6911 * Handles the main menu for the frame. 6912 * 6913 * @param {wp.Backbone.View} view The menu view for the main menu. 6914 */ 6915 mainMenu: function( view ) { 6916 view.set({ 6917 'library-separator': new wp.media.View({ 6918 className: 'separator', 6919 priority: 100, 6920 attributes: { 6921 role: 'presentation' 6922 } 6923 }) 6924 }); 6925 }, 6926 6927 /** 6928 * Handles the visibility of menu items for the frame. 6929 * 6930 * @param {string} state The state to show or hide. 6931 * @param {string} visibility The visibility of the menu item, either 'show' or 'hide'. 6932 */ 6933 menuItemVisibility: function( state, visibility ) { 6934 var menu = this.menu.get(); 6935 if ( visibility === 'hide' ) { 6936 menu.hide( state ); 6937 } else if ( visibility === 'show' ) { 6938 menu.show( state ); 6939 } 6940 }, 6941 /** 6942 * Handles the gallery menu for the frame. 6943 * 6944 * @param {wp.Backbone.View} view The menu view for the gallery menu. 6945 */ 6946 galleryMenu: function( view ) { 6947 var lastState = this.lastState(), 6948 previous = lastState && lastState.id, 6949 frame = this; 6950 6951 view.set({ 6952 cancel: { 6953 text: l10n.cancelGalleryTitle, 6954 priority: 20, 6955 click: function() { 6956 if ( previous ) { 6957 frame.setState( previous ); 6958 } else { 6959 frame.close(); 6960 } 6961 6962 // Move focus to the modal after canceling a Gallery. 6963 this.controller.modal.focusManager.focus(); 6964 } 6965 }, 6966 separateCancel: new wp.media.View({ 6967 className: 'separator', 6968 priority: 40 6969 }) 6970 }); 6971 }, 6972 6973 /** 6974 * Handles the playlist menu for the frame. 6975 * 6976 * @param {wp.Backbone.View} view The menu view for the playlist menu. 6977 */ 6978 playlistMenu: function( view ) { 6979 var lastState = this.lastState(), 6980 previous = lastState && lastState.id, 6981 frame = this; 6982 6983 view.set({ 6984 cancel: { 6985 text: l10n.cancelPlaylistTitle, 6986 priority: 20, 6987 click: function() { 6988 if ( previous ) { 6989 frame.setState( previous ); 6990 } else { 6991 frame.close(); 6992 } 6993 6994 // Move focus to the modal after canceling an Audio Playlist. 6995 this.controller.modal.focusManager.focus(); 6996 } 6997 }, 6998 separateCancel: new wp.media.View({ 6999 className: 'separator', 7000 priority: 40 7001 }) 7002 }); 7003 }, 7004 7005 /** 7006 * Handles the video playlist menu for the frame. 7007 * 7008 * @param {wp.Backbone.View} view The menu view. 7009 */ 7010 videoPlaylistMenu: function( view ) { 7011 var lastState = this.lastState(), 7012 previous = lastState && lastState.id, 7013 frame = this; 7014 7015 view.set({ 7016 cancel: { 7017 text: l10n.cancelVideoPlaylistTitle, 7018 priority: 20, 7019 click: function() { 7020 if ( previous ) { 7021 frame.setState( previous ); 7022 } else { 7023 frame.close(); 7024 } 7025 7026 // Move focus to the modal after canceling a Video Playlist. 7027 this.controller.modal.focusManager.focus(); 7028 } 7029 }, 7030 separateCancel: new wp.media.View({ 7031 className: 'separator', 7032 priority: 40 7033 }) 7034 }); 7035 }, 7036 7037 // Content. 7038 /** 7039 * Handles the embed content for the frame. 7040 */ 7041 embedContent: function() { 7042 var view = new wp.media.view.Embed({ 7043 controller: this, 7044 model: this.state() 7045 }).render(); 7046 7047 this.content.set( view ); 7048 }, 7049 7050 /** 7051 * Handles the edit selection content for the frame. 7052 */ 7053 editSelectionContent: function() { 7054 var state = this.state(), 7055 selection = state.get('selection'), 7056 view; 7057 7058 view = new wp.media.view.AttachmentsBrowser({ 7059 controller: this, 7060 collection: selection, 7061 selection: selection, 7062 model: state, 7063 sortable: true, 7064 search: false, 7065 date: false, 7066 dragInfo: true, 7067 7068 AttachmentView: wp.media.view.Attachments.EditSelection 7069 }).render(); 7070 7071 view.toolbar.set( 'backToLibrary', { 7072 text: l10n.returnToLibrary, 7073 priority: -100, 7074 7075 click: function() { 7076 this.controller.content.mode('browse'); 7077 // Move focus to the modal when jumping back from Edit Selection to Add Media view. 7078 this.controller.modal.focusManager.focus(); 7079 } 7080 }); 7081 7082 // Browse our library of attachments. 7083 this.content.set( view ); 7084 7085 // Trigger the controller to set focus. 7086 this.trigger( 'edit:selection', this ); 7087 }, 7088 7089 /** 7090 * Handles the edit image content for the frame. 7091 */ 7092 editImageContent: function() { 7093 var image = this.state().get('image'), 7094 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 7095 7096 this.content.set( view ); 7097 7098 // After creating the wrapper view, load the actual editor via an Ajax call. 7099 view.loadEditor(); 7100 7101 }, 7102 7103 // Toolbars. 7104 7105 /** 7106 * Handles the selection status toolbar for the frame 7107 * 7108 * @param {wp.Backbone.View} view The toolbar view. 7109 */ 7110 selectionStatusToolbar: function( view ) { 7111 var editable = this.state().get('editable'); 7112 7113 view.set( 'selection', new wp.media.view.Selection({ 7114 controller: this, 7115 collection: this.state().get('selection'), 7116 priority: -40, 7117 7118 // If the selection is editable, pass the callback to 7119 // switch the content mode. 7120 editable: editable && function() { 7121 this.controller.content.mode('edit-selection'); 7122 } 7123 }).render() ); 7124 }, 7125 7126 /** 7127 * Handles the main insert toolbar for the frame. 7128 * 7129 * @param {wp.Backbone.View} view The toolbar view. 7130 */ 7131 mainInsertToolbar: function( view ) { 7132 var controller = this; 7133 7134 this.selectionStatusToolbar( view ); 7135 7136 view.set( 'insert', { 7137 style: 'primary', 7138 priority: 80, 7139 text: l10n.insertIntoPost, 7140 requires: { selection: true }, 7141 7142 /** 7143 * @ignore 7144 * 7145 * @fires wp.media.controller.State#insert 7146 */ 7147 click: function() { 7148 var state = controller.state(), 7149 selection = state.get('selection'); 7150 7151 controller.close(); 7152 state.trigger( 'insert', selection ).reset(); 7153 } 7154 }); 7155 }, 7156 7157 /** 7158 * Handles the main gallery toolbar for the frame. 7159 * 7160 * @param {wp.Backbone.View} view The toolbar view. 7161 */ 7162 mainGalleryToolbar: function( view ) { 7163 var controller = this; 7164 7165 this.selectionStatusToolbar( view ); 7166 7167 view.set( 'gallery', { 7168 style: 'primary', 7169 text: l10n.createNewGallery, 7170 priority: 60, 7171 requires: { selection: true }, 7172 7173 click: function() { 7174 var selection = controller.state().get('selection'), 7175 edit = controller.state('gallery-edit'), 7176 models = selection.where({ type: 'image' }); 7177 7178 edit.set( 'library', new wp.media.model.Selection( models, { 7179 props: selection.props.toJSON(), 7180 multiple: true 7181 }) ); 7182 7183 // Jump to Edit Gallery view. 7184 this.controller.setState( 'gallery-edit' ); 7185 7186 // Move focus to the modal after jumping to Edit Gallery view. 7187 this.controller.modal.focusManager.focus(); 7188 } 7189 }); 7190 }, 7191 7192 /** 7193 * Handles the main playlist toolbar for the frame. 7194 * 7195 * @param {wp.Backbone.View} view The toolbar view. 7196 */ 7197 mainPlaylistToolbar: function( view ) { 7198 var controller = this; 7199 7200 this.selectionStatusToolbar( view ); 7201 7202 view.set( 'playlist', { 7203 style: 'primary', 7204 text: l10n.createNewPlaylist, 7205 priority: 100, 7206 requires: { selection: true }, 7207 7208 click: function() { 7209 var selection = controller.state().get('selection'), 7210 edit = controller.state('playlist-edit'), 7211 models = selection.where({ type: 'audio' }); 7212 7213 edit.set( 'library', new wp.media.model.Selection( models, { 7214 props: selection.props.toJSON(), 7215 multiple: true 7216 }) ); 7217 7218 // Jump to Edit Audio Playlist view. 7219 this.controller.setState( 'playlist-edit' ); 7220 7221 // Move focus to the modal after jumping to Edit Audio Playlist view. 7222 this.controller.modal.focusManager.focus(); 7223 } 7224 }); 7225 }, 7226 7227 /** 7228 * Handles the main video playlist toolbar for the frame. 7229 * 7230 * @param {wp.Backbone.View} view The toolbar view. 7231 */ 7232 mainVideoPlaylistToolbar: function( view ) { 7233 var controller = this; 7234 7235 this.selectionStatusToolbar( view ); 7236 7237 view.set( 'video-playlist', { 7238 style: 'primary', 7239 text: l10n.createNewVideoPlaylist, 7240 priority: 100, 7241 requires: { selection: true }, 7242 7243 click: function() { 7244 var selection = controller.state().get('selection'), 7245 edit = controller.state('video-playlist-edit'), 7246 models = selection.where({ type: 'video' }); 7247 7248 edit.set( 'library', new wp.media.model.Selection( models, { 7249 props: selection.props.toJSON(), 7250 multiple: true 7251 }) ); 7252 7253 // Jump to Edit Video Playlist view. 7254 this.controller.setState( 'video-playlist-edit' ); 7255 7256 // Move focus to the modal after jumping to Edit Video Playlist view. 7257 this.controller.modal.focusManager.focus(); 7258 } 7259 }); 7260 }, 7261 7262 /** 7263 * Handles the featured image toolbar for the frame. 7264 * 7265 * @param {wp.media.view.Toolbar} toolbar The toolbar view. 7266 */ 7267 featuredImageToolbar: function( toolbar ) { 7268 this.createSelectToolbar( toolbar, { 7269 text: l10n.setFeaturedImage, 7270 state: this.options.state 7271 }); 7272 }, 7273 7274 /** 7275 * Handles the main embed toolbar for the frame. 7276 * 7277 * @param {wp.media.view.Toolbar} toolbar The toolbar view. 7278 */ 7279 mainEmbedToolbar: function( toolbar ) { 7280 toolbar.view = new wp.media.view.Toolbar.Embed({ 7281 controller: this 7282 }); 7283 }, 7284 7285 /** 7286 * Handles the edit image toolbar for the frame. 7287 */ 7288 galleryEditToolbar: function() { 7289 var editing = this.state().get('editing'); 7290 this.toolbar.set( new wp.media.view.Toolbar({ 7291 controller: this, 7292 items: { 7293 insert: { 7294 style: 'primary', 7295 text: editing ? l10n.updateGallery : l10n.insertGallery, 7296 priority: 80, 7297 requires: { library: true, uploadingComplete: true }, 7298 7299 /** 7300 * Handles the click event for the insert button. 7301 * 7302 * @fires wp.media.controller.State#update 7303 */ 7304 click: function() { 7305 var controller = this.controller, 7306 state = controller.state(); 7307 7308 controller.close(); 7309 state.trigger( 'update', state.get('library') ); 7310 7311 // Restore and reset the default state. 7312 controller.setState( controller.options.state ); 7313 controller.reset(); 7314 } 7315 } 7316 } 7317 }) ); 7318 }, 7319 7320 /** 7321 * Handles the add to gallery toolbar for the frame. 7322 */ 7323 galleryAddToolbar: function() { 7324 this.toolbar.set( new wp.media.view.Toolbar({ 7325 controller: this, 7326 items: { 7327 insert: { 7328 style: 'primary', 7329 text: l10n.addToGallery, 7330 priority: 80, 7331 requires: { selection: true }, 7332 7333 /** 7334 * Handles the click event for the insert button. 7335 * 7336 * @fires wp.media.controller.State#reset 7337 */ 7338 click: function() { 7339 var controller = this.controller, 7340 state = controller.state(), 7341 edit = controller.state('gallery-edit'); 7342 7343 edit.get('library').add( state.get('selection').models ); 7344 state.trigger('reset'); 7345 controller.setState('gallery-edit'); 7346 // Move focus to the modal when jumping back from Add to Gallery to Edit Gallery view. 7347 this.controller.modal.focusManager.focus(); 7348 } 7349 } 7350 } 7351 }) ); 7352 }, 7353 7354 /** 7355 * Handles the edit playlist toolbar for the frame. 7356 */ 7357 playlistEditToolbar: function() { 7358 var editing = this.state().get('editing'); 7359 this.toolbar.set( new wp.media.view.Toolbar({ 7360 controller: this, 7361 items: { 7362 insert: { 7363 style: 'primary', 7364 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 7365 priority: 80, 7366 requires: { library: true }, 7367 7368 /** 7369 * Handles the click event for the insert button. 7370 * 7371 * @fires wp.media.controller.State#update 7372 */ 7373 click: function() { 7374 var controller = this.controller, 7375 state = controller.state(); 7376 7377 controller.close(); 7378 state.trigger( 'update', state.get('library') ); 7379 7380 // Restore and reset the default state. 7381 controller.setState( controller.options.state ); 7382 controller.reset(); 7383 } 7384 } 7385 } 7386 }) ); 7387 }, 7388 7389 /** 7390 * Handles the add to playlist toolbar for the frame. 7391 */ 7392 playlistAddToolbar: function() { 7393 this.toolbar.set( new wp.media.view.Toolbar({ 7394 controller: this, 7395 items: { 7396 insert: { 7397 style: 'primary', 7398 text: l10n.addToPlaylist, 7399 priority: 80, 7400 requires: { selection: true }, 7401 7402 /** 7403 * Handles the click event for the insert button. 7404 * 7405 * @fires wp.media.controller.State#reset 7406 */ 7407 click: function() { 7408 var controller = this.controller, 7409 state = controller.state(), 7410 edit = controller.state('playlist-edit'); 7411 7412 edit.get('library').add( state.get('selection').models ); 7413 state.trigger('reset'); 7414 controller.setState('playlist-edit'); 7415 // Move focus to the modal when jumping back from Add to Audio Playlist to Edit Audio Playlist view. 7416 this.controller.modal.focusManager.focus(); 7417 } 7418 } 7419 } 7420 }) ); 7421 }, 7422 7423 /** 7424 * Handles the edit video playlist toolbar for the frame. 7425 */ 7426 videoPlaylistEditToolbar: function() { 7427 var editing = this.state().get('editing'); 7428 this.toolbar.set( new wp.media.view.Toolbar({ 7429 controller: this, 7430 items: { 7431 insert: { 7432 style: 'primary', 7433 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 7434 priority: 140, 7435 requires: { library: true }, 7436 7437 click: function() { 7438 var controller = this.controller, 7439 state = controller.state(), 7440 library = state.get('library'); 7441 7442 library.type = 'video'; 7443 7444 controller.close(); 7445 state.trigger( 'update', library ); 7446 7447 // Restore and reset the default state. 7448 controller.setState( controller.options.state ); 7449 controller.reset(); 7450 } 7451 } 7452 } 7453 }) ); 7454 }, 7455 7456 /** 7457 * Handles the add to video playlist toolbar for the frame. 7458 */ 7459 videoPlaylistAddToolbar: function() { 7460 this.toolbar.set( new wp.media.view.Toolbar({ 7461 controller: this, 7462 items: { 7463 insert: { 7464 style: 'primary', 7465 text: l10n.addToVideoPlaylist, 7466 priority: 140, 7467 requires: { selection: true }, 7468 7469 click: function() { 7470 var controller = this.controller, 7471 state = controller.state(), 7472 edit = controller.state('video-playlist-edit'); 7473 7474 edit.get('library').add( state.get('selection').models ); 7475 state.trigger('reset'); 7476 controller.setState('video-playlist-edit'); 7477 // Move focus to the modal when jumping back from Add to Video Playlist to Edit Video Playlist view. 7478 this.controller.modal.focusManager.focus(); 7479 } 7480 } 7481 } 7482 }) ); 7483 } 7484 }); 7485 7486 module.exports = Post; 7487 7488 7489 /***/ }, 7490 7491 /***/ 455 7492 (module) { 7493 7494 var MediaFrame = wp.media.view.MediaFrame, 7495 l10n = wp.media.view.l10n, 7496 Select; 7497 7498 /** 7499 * wp.media.view.MediaFrame.Select 7500 * 7501 * A frame for selecting an item or items from the media library. 7502 * 7503 * @memberOf wp.media.view.MediaFrame 7504 * 7505 * @class 7506 * @augments wp.media.view.MediaFrame 7507 * @augments wp.media.view.Frame 7508 * @augments wp.media.View 7509 * @augments wp.Backbone.View 7510 * @augments Backbone.View 7511 * @mixes wp.media.controller.StateMachine 7512 */ 7513 Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{ 7514 initialize: function() { 7515 // Call 'initialize' directly on the parent class. 7516 MediaFrame.prototype.initialize.apply( this, arguments ); 7517 7518 _.defaults( this.options, { 7519 selection: [], 7520 library: {}, 7521 multiple: false, 7522 state: 'library' 7523 }); 7524 7525 this.createSelection(); 7526 this.createStates(); 7527 this.bindHandlers(); 7528 }, 7529 7530 /** 7531 * Attach a selection collection to the frame. 7532 * 7533 * A selection is a collection of attachments used for a specific purpose 7534 * by a media frame. e.g. Selecting an attachment (or many) to insert into 7535 * post content. 7536 * 7537 * @see media.model.Selection 7538 */ 7539 createSelection: function() { 7540 var selection = this.options.selection; 7541 7542 if ( ! (selection instanceof wp.media.model.Selection) ) { 7543 this.options.selection = new wp.media.model.Selection( selection, { 7544 multiple: this.options.multiple 7545 }); 7546 } 7547 7548 this._selection = { 7549 attachments: new wp.media.model.Attachments(), 7550 difference: [] 7551 }; 7552 }, 7553 7554 editImageContent: function() { 7555 var image = this.state().get('image'), 7556 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 7557 7558 this.content.set( view ); 7559 7560 // After creating the wrapper view, load the actual editor via an Ajax call. 7561 view.loadEditor(); 7562 }, 7563 7564 /** 7565 * Create the default states on the frame. 7566 */ 7567 createStates: function() { 7568 var options = this.options; 7569 7570 if ( this.options.states ) { 7571 return; 7572 } 7573 7574 // Add the default states. 7575 this.states.add([ 7576 // Main states. 7577 new wp.media.controller.Library({ 7578 library: wp.media.query( options.library ), 7579 multiple: options.multiple, 7580 title: options.title, 7581 priority: 20 7582 }), 7583 new wp.media.controller.EditImage( { model: options.editImage } ) 7584 ]); 7585 }, 7586 7587 /** 7588 * Bind region mode event callbacks. 7589 * 7590 * @see media.controller.Region.render 7591 */ 7592 bindHandlers: function() { 7593 this.on( 'router:create:browse', this.createRouter, this ); 7594 this.on( 'router:render:browse', this.browseRouter, this ); 7595 this.on( 'content:create:browse', this.browseContent, this ); 7596 this.on( 'content:render:upload', this.uploadContent, this ); 7597 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 7598 this.on( 'content:render:edit-image', this.editImageContent, this ); 7599 }, 7600 7601 /** 7602 * Render callback for the router region in the `browse` mode. 7603 * 7604 * @param {wp.media.view.Router} routerView The router view for the `browse` mode. 7605 */ 7606 browseRouter: function( routerView ) { 7607 routerView.set({ 7608 upload: { 7609 text: l10n.uploadFilesTitle, 7610 priority: 20 7611 }, 7612 browse: { 7613 text: l10n.mediaLibraryTitle, 7614 priority: 40 7615 } 7616 }); 7617 }, 7618 7619 /** 7620 * Render callback for the content region in the `browse` mode. 7621 * 7622 * @param {wp.media.controller.Region} contentRegion The content region for the `browse` mode. 7623 */ 7624 browseContent: function( contentRegion ) { 7625 var state = this.state(); 7626 7627 this.$el.removeClass('hide-toolbar'); 7628 7629 // Browse our library of attachments. 7630 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 7631 controller: this, 7632 collection: state.get('library'), 7633 selection: state.get('selection'), 7634 model: state, 7635 sortable: state.get('sortable'), 7636 search: state.get('searchable'), 7637 filters: state.get('filterable'), 7638 date: state.get('date'), 7639 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 7640 dragInfo: state.get('dragInfo'), 7641 7642 idealColumnWidth: state.get('idealColumnWidth'), 7643 suggestedWidth: state.get('suggestedWidth'), 7644 suggestedHeight: state.get('suggestedHeight'), 7645 7646 AttachmentView: state.get('AttachmentView') 7647 }); 7648 }, 7649 7650 /** 7651 * Render callback for the content region in the `upload` mode. 7652 */ 7653 uploadContent: function() { 7654 this.$el.removeClass( 'hide-toolbar' ); 7655 this.content.set( new wp.media.view.UploaderInline({ 7656 controller: this 7657 }) ); 7658 }, 7659 7660 /** 7661 * Toolbars 7662 * 7663 * @param {Object} toolbar The toolbar object for the `select` mode. 7664 * @param {Object} [options={}] The options for creating the select toolbar view. 7665 * @this {wp.media.controller.Region} 7666 */ 7667 createSelectToolbar: function( toolbar, options ) { 7668 options = options || this.options.button || {}; 7669 options.controller = this; 7670 7671 toolbar.view = new wp.media.view.Toolbar.Select( options ); 7672 } 7673 }); 7674 7675 module.exports = Select; 7676 7677 7678 /***/ }, 7679 7680 /***/ 170 7681 (module) { 7682 7683 /** 7684 * wp.media.view.Heading 7685 * 7686 * A reusable heading component for the media library 7687 * 7688 * Used to add accessibility friendly headers in the media library/modal. 7689 * 7690 * @class 7691 * @augments wp.media.View 7692 * @augments wp.Backbone.View 7693 * @augments Backbone.View 7694 */ 7695 var Heading = wp.media.View.extend( { 7696 tagName: function() { 7697 return this.options.level || 'h1'; 7698 }, 7699 className: 'media-views-heading', 7700 7701 initialize: function() { 7702 7703 if ( this.options.className ) { 7704 this.$el.addClass( this.options.className ); 7705 } 7706 7707 this.text = this.options.text; 7708 }, 7709 7710 render: function() { 7711 this.$el.html( this.text ); 7712 return this; 7713 } 7714 } ); 7715 7716 module.exports = Heading; 7717 7718 7719 /***/ }, 7720 7721 /***/ 1982 7722 (module) { 7723 7724 /** 7725 * wp.media.view.Iframe 7726 * 7727 * @memberOf wp.media.view 7728 * 7729 * @class 7730 * @augments wp.media.View 7731 * @augments wp.Backbone.View 7732 * @augments Backbone.View 7733 */ 7734 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{ 7735 className: 'media-iframe', 7736 /** 7737 * Renders the iframe view. 7738 * 7739 * @return {wp.media.view.Iframe} Returns itself to allow chaining. 7740 */ 7741 render: function() { 7742 this.views.detach(); 7743 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 7744 this.views.render(); 7745 return this; 7746 } 7747 }); 7748 7749 module.exports = Iframe; 7750 7751 7752 /***/ }, 7753 7754 /***/ 2650 7755 (module) { 7756 7757 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 7758 $ = jQuery, 7759 ImageDetails; 7760 7761 /** 7762 * wp.media.view.ImageDetails 7763 * 7764 * @memberOf wp.media.view 7765 * 7766 * @class 7767 * @augments wp.media.view.Settings.AttachmentDisplay 7768 * @augments wp.media.view.Settings 7769 * @augments wp.media.View 7770 * @augments wp.Backbone.View 7771 * @augments Backbone.View 7772 */ 7773 ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{ 7774 className: 'image-details', 7775 template: wp.template('image-details'), 7776 events: _.defaults( AttachmentDisplay.prototype.events, { 7777 'click .edit-attachment': 'editAttachment', 7778 'click .replace-attachment': 'replaceAttachment', 7779 'click .advanced-toggle': 'onToggleAdvanced', 7780 'change [data-setting="customWidth"]': 'onCustomSize', 7781 'change [data-setting="customHeight"]': 'onCustomSize', 7782 'keyup [data-setting="customWidth"]': 'onCustomSize', 7783 'keyup [data-setting="customHeight"]': 'onCustomSize' 7784 } ), 7785 initialize: function() { 7786 // Used in AttachmentDisplay.prototype.updateLinkTo. 7787 this.options.attachment = this.model.attachment; 7788 this.listenTo( this.model, 'change:url', this.updateUrl ); 7789 this.listenTo( this.model, 'change:link', this.toggleLinkSettings ); 7790 this.listenTo( this.model, 'change:size', this.toggleCustomSize ); 7791 7792 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 7793 }, 7794 7795 prepare: function() { 7796 var attachment = false; 7797 7798 if ( this.model.attachment ) { 7799 attachment = this.model.attachment.toJSON(); 7800 } 7801 return _.defaults({ 7802 model: this.model.toJSON(), 7803 attachment: attachment 7804 }, this.options ); 7805 }, 7806 7807 render: function() { 7808 var args = arguments; 7809 7810 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) { 7811 this.model.dfd 7812 .done( _.bind( function() { 7813 AttachmentDisplay.prototype.render.apply( this, args ); 7814 this.postRender(); 7815 }, this ) ) 7816 .fail( _.bind( function() { 7817 this.model.attachment = false; 7818 AttachmentDisplay.prototype.render.apply( this, args ); 7819 this.postRender(); 7820 }, this ) ); 7821 } else { 7822 AttachmentDisplay.prototype.render.apply( this, arguments ); 7823 this.postRender(); 7824 } 7825 7826 return this; 7827 }, 7828 7829 postRender: function() { 7830 setTimeout( _.bind( this.scrollToTop, this ), 10 ); 7831 this.toggleLinkSettings(); 7832 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) { 7833 this.toggleAdvanced( true ); 7834 } 7835 this.trigger( 'post-render' ); 7836 }, 7837 7838 scrollToTop: function() { 7839 this.$( '.embed-media-settings' ).scrollTop( 0 ); 7840 }, 7841 7842 updateUrl: function() { 7843 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) ); 7844 this.$( '.url' ).val( this.model.get( 'url' ) ); 7845 }, 7846 7847 toggleLinkSettings: function() { 7848 if ( this.model.get( 'link' ) === 'none' ) { 7849 this.$( '.link-settings' ).addClass('hidden'); 7850 } else { 7851 this.$( '.link-settings' ).removeClass('hidden'); 7852 } 7853 }, 7854 7855 toggleCustomSize: function() { 7856 if ( this.model.get( 'size' ) !== 'custom' ) { 7857 this.$( '.custom-size' ).addClass('hidden'); 7858 } else { 7859 this.$( '.custom-size' ).removeClass('hidden'); 7860 } 7861 }, 7862 7863 onCustomSize: function( event ) { 7864 var dimension = $( event.target ).data('setting'), 7865 num = $( event.target ).val(), 7866 value; 7867 7868 // Ignore bogus input. 7869 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) { 7870 event.preventDefault(); 7871 return; 7872 } 7873 7874 if ( dimension === 'customWidth' ) { 7875 value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num ); 7876 this.model.set( 'customHeight', value, { silent: true } ); 7877 this.$( '[data-setting="customHeight"]' ).val( value ); 7878 } else { 7879 value = Math.round( this.model.get( 'aspectRatio' ) * num ); 7880 this.model.set( 'customWidth', value, { silent: true } ); 7881 this.$( '[data-setting="customWidth"]' ).val( value ); 7882 } 7883 }, 7884 7885 onToggleAdvanced: function( event ) { 7886 event.preventDefault(); 7887 this.toggleAdvanced(); 7888 }, 7889 7890 toggleAdvanced: function( show ) { 7891 var $advanced = this.$el.find( '.advanced-section' ), 7892 mode; 7893 7894 if ( $advanced.hasClass('advanced-visible') || show === false ) { 7895 $advanced.removeClass('advanced-visible'); 7896 $advanced.find('.advanced-settings').addClass('hidden'); 7897 mode = 'hide'; 7898 } else { 7899 $advanced.addClass('advanced-visible'); 7900 $advanced.find('.advanced-settings').removeClass('hidden'); 7901 mode = 'show'; 7902 } 7903 7904 window.setUserSetting( 'advImgDetails', mode ); 7905 }, 7906 7907 editAttachment: function( event ) { 7908 var editState = this.controller.states.get( 'edit-image' ); 7909 7910 if ( window.imageEdit && editState ) { 7911 event.preventDefault(); 7912 editState.set( 'image', this.model.attachment ); 7913 this.controller.setState( 'edit-image' ); 7914 } 7915 }, 7916 7917 replaceAttachment: function( event ) { 7918 event.preventDefault(); 7919 this.controller.setState( 'replace-image' ); 7920 } 7921 }); 7922 7923 module.exports = ImageDetails; 7924 7925 7926 /***/ }, 7927 7928 /***/ 4338 7929 (module) { 7930 7931 /** 7932 * wp.media.view.Label 7933 * 7934 * @memberOf wp.media.view 7935 * 7936 * @class 7937 * @augments wp.media.View 7938 * @augments wp.Backbone.View 7939 * @augments Backbone.View 7940 */ 7941 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{ 7942 tagName: 'label', 7943 7944 initialize: function() { 7945 this.value = this.options.value; 7946 }, 7947 7948 render: function() { 7949 this.$el.html( this.value ); 7950 7951 return this; 7952 } 7953 }); 7954 7955 module.exports = Label; 7956 7957 7958 /***/ }, 7959 7960 /***/ 2836 7961 (module) { 7962 7963 var Frame = wp.media.view.Frame, 7964 l10n = wp.media.view.l10n, 7965 $ = jQuery, 7966 MediaFrame; 7967 7968 /** 7969 * wp.media.view.MediaFrame 7970 * 7971 * The frame used to create the media modal. 7972 * 7973 * @memberOf wp.media.view 7974 * 7975 * @class 7976 * @augments wp.media.view.Frame 7977 * @augments wp.media.View 7978 * @augments wp.Backbone.View 7979 * @augments Backbone.View 7980 * @mixes wp.media.controller.StateMachine 7981 */ 7982 MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{ 7983 className: 'media-frame', 7984 template: wp.template('media-frame'), 7985 regions: ['menu','title','content','toolbar','router'], 7986 7987 events: { 7988 'click .media-frame-menu-toggle': 'toggleMenu' 7989 }, 7990 7991 /** 7992 * Initializes the media frame. 7993 * 7994 * @constructs 7995 */ 7996 initialize: function() { 7997 Frame.prototype.initialize.apply( this, arguments ); 7998 7999 _.defaults( this.options, { 8000 title: l10n.mediaFrameDefaultTitle, 8001 modal: true, 8002 uploader: true 8003 }); 8004 8005 // Ensure core UI is enabled. 8006 this.$el.addClass('wp-core-ui'); 8007 8008 // Initialize modal container view. 8009 if ( this.options.modal ) { 8010 this.modal = new wp.media.view.Modal({ 8011 controller: this, 8012 title: this.options.title 8013 }); 8014 8015 this.modal.content( this ); 8016 } 8017 8018 // Force the uploader off if the upload limit has been exceeded or 8019 // if the browser isn't supported. 8020 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 8021 this.options.uploader = false; 8022 } 8023 8024 // Initialize window-wide uploader. 8025 if ( this.options.uploader ) { 8026 this.uploader = new wp.media.view.UploaderWindow({ 8027 controller: this, 8028 uploader: { 8029 dropzone: this.modal ? this.modal.$el : this.$el, 8030 container: this.$el 8031 } 8032 }); 8033 this.views.set( '.media-frame-uploader', this.uploader ); 8034 } 8035 8036 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 8037 8038 // Bind default title creation. 8039 this.on( 'title:create:default', this.createTitle, this ); 8040 this.title.mode('default'); 8041 8042 // Bind default menu. 8043 this.on( 'menu:create:default', this.createMenu, this ); 8044 8045 // Set the menu ARIA tab panel attributes when the modal opens. 8046 this.on( 'open', this.setMenuTabPanelAriaAttributes, this ); 8047 // Set the router ARIA tab panel attributes when the modal opens. 8048 this.on( 'open', this.setRouterTabPanelAriaAttributes, this ); 8049 8050 // Update the menu ARIA tab panel attributes when the content updates. 8051 this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this ); 8052 // Update the router ARIA tab panel attributes when the content updates. 8053 this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this ); 8054 }, 8055 8056 /** 8057 * Sets the attributes to be used on the menu ARIA tab panel. 8058 * 8059 * @since 5.3.0 8060 * 8061 * @return {void} 8062 */ 8063 setMenuTabPanelAriaAttributes: function() { 8064 var stateId = this.state().get( 'id' ), 8065 tabPanelEl = this.$el.find( '.media-frame-tab-panel' ), 8066 ariaLabelledby; 8067 8068 tabPanelEl.removeAttr( 'role aria-labelledby' ); 8069 8070 if ( this.state().get( 'menu' ) && this.menuView && this.menuView.isVisible ) { 8071 ariaLabelledby = 'menu-item-' + stateId; 8072 8073 // Set the tab panel attributes only if the tabs are visible. 8074 tabPanelEl 8075 .attr( { 8076 role: 'tabpanel', 8077 'aria-labelledby': ariaLabelledby, 8078 } ); 8079 } 8080 }, 8081 8082 /** 8083 * Sets the attributes to be used on the router ARIA tab panel. 8084 * 8085 * @since 5.3.0 8086 * 8087 * @return {void} 8088 */ 8089 setRouterTabPanelAriaAttributes: function() { 8090 var tabPanelEl = this.$el.find( '.media-frame-content' ), 8091 ariaLabelledby; 8092 8093 tabPanelEl.removeAttr( 'role aria-labelledby' ); 8094 8095 // Set the tab panel attributes only if the tabs are visible. 8096 if ( this.state().get( 'router' ) && this.routerView && this.routerView.isVisible && this.content._mode ) { 8097 ariaLabelledby = 'menu-item-' + this.content._mode; 8098 8099 tabPanelEl 8100 .attr( { 8101 role: 'tabpanel', 8102 'aria-labelledby': ariaLabelledby, 8103 } ); 8104 } 8105 }, 8106 8107 /** 8108 * Renders the media frame. 8109 * 8110 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8111 */ 8112 render: function() { 8113 // Activate the default state if no active state exists. 8114 if ( ! this.state() && this.options.state ) { 8115 this.setState( this.options.state ); 8116 } 8117 /** 8118 * call 'render' directly on the parent class 8119 */ 8120 return Frame.prototype.render.apply( this, arguments ); 8121 }, 8122 /** 8123 * Creates the title view. 8124 * 8125 * @param {Object} title The title object for creating the title view. 8126 * @this {wp.media.controller.Region} 8127 */ 8128 createTitle: function( title ) { 8129 // A modal's dialog element points `aria-labelledby` at its frame heading. 8130 title.view = new wp.media.View({ 8131 controller: this, 8132 tagName: 'h1', 8133 attributes: this.modal ? { id: this.modal.titleId } : {} 8134 }); 8135 }, 8136 /** 8137 * Creates the menu view. 8138 * 8139 * @param {Object} menu The menu object for creating the menu view. 8140 * @this {wp.media.controller.Region} 8141 */ 8142 createMenu: function( menu ) { 8143 menu.view = new wp.media.view.Menu({ 8144 controller: this, 8145 8146 attributes: { 8147 role: 'tablist', 8148 'aria-orientation': 'vertical' 8149 } 8150 }); 8151 8152 this.menuView = menu.view; 8153 }, 8154 8155 /** 8156 * Toggles the menu visibility. 8157 * 8158 * @param {JQuery.Event} event The click event. 8159 */ 8160 toggleMenu: function( event ) { 8161 var menu = this.$el.find( '.media-menu' ); 8162 8163 menu.toggleClass( 'visible' ); 8164 $( event.target ).attr( 'aria-expanded', menu.hasClass( 'visible' ) ); 8165 }, 8166 8167 /** 8168 * Creates the toolbar view. 8169 * 8170 * @param {Object} toolbar The toolbar object for creating the toolbar view. 8171 * @this {wp.media.controller.Region} 8172 */ 8173 createToolbar: function( toolbar ) { 8174 toolbar.view = new wp.media.view.Toolbar({ 8175 controller: this 8176 }); 8177 }, 8178 /** 8179 * Creates the router view. 8180 * 8181 * @param {Object} router The router object for creating the router view. 8182 * @this {wp.media.controller.Region} 8183 */ 8184 createRouter: function( router ) { 8185 router.view = new wp.media.view.Router({ 8186 controller: this, 8187 8188 attributes: { 8189 role: 'tablist', 8190 'aria-orientation': 'horizontal' 8191 } 8192 }); 8193 8194 this.routerView = router.view; 8195 }, 8196 /** 8197 * Creates the iframe states. 8198 * 8199 * @param {Object} options The options for creating the iframe states. 8200 */ 8201 createIframeStates: function( options ) { 8202 var settings = wp.media.view.settings, 8203 tabs = settings.tabs, 8204 tabUrl = settings.tabUrl, 8205 $postId; 8206 8207 if ( ! tabs || ! tabUrl ) { 8208 return; 8209 } 8210 8211 // Add the post ID to the tab URL if it exists. 8212 $postId = $('#post_ID'); 8213 if ( $postId.length ) { 8214 tabUrl += '&post_id=' + $postId.val(); 8215 } 8216 8217 // Generate the tab states. 8218 _.each( tabs, function( title, id ) { 8219 this.state( 'iframe:' + id ).set( _.defaults({ 8220 tab: id, 8221 src: tabUrl + '&tab=' + id, 8222 title: title, 8223 content: 'iframe', 8224 menu: 'default' 8225 }, options ) ); 8226 }, this ); 8227 8228 this.on( 'content:create:iframe', this.iframeContent, this ); 8229 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 8230 this.on( 'menu:render:default', this.iframeMenu, this ); 8231 this.on( 'open', this.hijackThickbox, this ); 8232 this.on( 'close', this.restoreThickbox, this ); 8233 }, 8234 8235 /** 8236 * Creates the iframe content view. 8237 * 8238 * @param {Object} content The content object for creating the iframe content view. 8239 * @this {wp.media.controller.Region} 8240 */ 8241 iframeContent: function( content ) { 8242 this.$el.addClass('hide-toolbar'); 8243 content.view = new wp.media.view.Iframe({ 8244 controller: this 8245 }); 8246 }, 8247 8248 /** 8249 * Cleans up the iframe content view. 8250 */ 8251 iframeContentCleanup: function() { 8252 this.$el.removeClass('hide-toolbar'); 8253 }, 8254 8255 /** 8256 * Creates the iframe menu. 8257 * 8258 * @param {wp.media.view.Menu} view The menu view. 8259 */ 8260 iframeMenu: function( view ) { 8261 var views = {}; 8262 8263 if ( ! view ) { 8264 return; 8265 } 8266 8267 _.each( wp.media.view.settings.tabs, function( title, id ) { 8268 views[ 'iframe:' + id ] = { 8269 text: this.state( 'iframe:' + id ).get('title'), 8270 priority: 200 8271 }; 8272 }, this ); 8273 8274 view.set( views ); 8275 }, 8276 8277 /** 8278 * Hijacks the Thickbox close function to close the media modal. 8279 */ 8280 hijackThickbox: function() { 8281 var frame = this; 8282 8283 if ( ! window.tb_remove || this._tb_remove ) { 8284 return; 8285 } 8286 8287 this._tb_remove = window.tb_remove; 8288 window.tb_remove = function() { 8289 frame.close(); 8290 frame.reset(); 8291 frame.setState( frame.options.state ); 8292 frame._tb_remove.call( window ); 8293 }; 8294 }, 8295 8296 /** 8297 * Restores the Thickbox close function. 8298 */ 8299 restoreThickbox: function() { 8300 if ( ! this._tb_remove ) { 8301 return; 8302 } 8303 8304 window.tb_remove = this._tb_remove; 8305 delete this._tb_remove; 8306 } 8307 }); 8308 8309 // Map some of the modal's methods to the frame. 8310 _.each(['open','close','attach','detach','escape'], function( method ) { 8311 /** 8312 * Opens the media frame modal. 8313 * 8314 * @function open 8315 * @memberOf wp.media.view.MediaFrame 8316 * @instance 8317 * 8318 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8319 */ 8320 /** 8321 * Closes the media frame modal. 8322 * 8323 * @function close 8324 * @memberOf wp.media.view.MediaFrame 8325 * @instance 8326 * 8327 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8328 */ 8329 /** 8330 * Attaches the media frame to the DOM. 8331 * 8332 * @function attach 8333 * @memberOf wp.media.view.MediaFrame 8334 * @instance 8335 * 8336 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8337 */ 8338 /** 8339 * Detaches the media frame from the DOM. 8340 * 8341 * @function detach 8342 * @memberOf wp.media.view.MediaFrame 8343 * @instance 8344 * 8345 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8346 */ 8347 /** 8348 * Triggers the escape action on the media frame modal. 8349 * 8350 * @function escape 8351 * @memberOf wp.media.view.MediaFrame 8352 * @instance 8353 * 8354 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8355 */ 8356 MediaFrame.prototype[ method ] = function() { 8357 if ( this.modal ) { 8358 this.modal[ method ].apply( this.modal, arguments ); 8359 } 8360 return this; 8361 }; 8362 }); 8363 8364 module.exports = MediaFrame; 8365 8366 8367 /***/ }, 8368 8369 /***/ 9013 8370 (module) { 8371 8372 var MenuItem; 8373 8374 /** 8375 * wp.media.view.MenuItem 8376 * 8377 * @memberOf wp.media.view 8378 * 8379 * @class 8380 * @augments wp.media.View 8381 * @augments wp.Backbone.View 8382 * @augments Backbone.View 8383 */ 8384 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{ 8385 tagName: 'button', 8386 className: 'media-menu-item', 8387 8388 attributes: { 8389 type: 'button', 8390 role: 'tab' 8391 }, 8392 8393 events: { 8394 'click': '_click' 8395 }, 8396 8397 /** 8398 * Allows to override the click event. 8399 */ 8400 _click: function() { 8401 var clickOverride = this.options.click; 8402 8403 if ( clickOverride ) { 8404 clickOverride.call( this ); 8405 } else { 8406 this.click(); 8407 } 8408 }, 8409 8410 /** 8411 * Handles the click event. 8412 */ 8413 click: function() { 8414 var state = this.options.state; 8415 8416 if ( state ) { 8417 this.controller.setState( state ); 8418 // Toggle the menu visibility in the responsive view. 8419 this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below. 8420 } 8421 }, 8422 8423 /** 8424 * Renders the menu item. 8425 * 8426 * @return {wp.media.view.MenuItem} returns itself to allow chaining. 8427 */ 8428 render: function() { 8429 var options = this.options, 8430 menuProperty = options.state || options.contentMode; 8431 8432 if ( options.text ) { 8433 this.$el.text( options.text ); 8434 } else if ( options.html ) { 8435 this.$el.html( options.html ); 8436 } 8437 8438 // Set the menu item ID based on the frame state associated to the menu item. 8439 this.$el.attr( 'id', 'menu-item-' + menuProperty ); 8440 8441 return this; 8442 } 8443 }); 8444 8445 module.exports = MenuItem; 8446 8447 8448 /***/ }, 8449 8450 /***/ 1 8451 (module) { 8452 8453 var MenuItem = wp.media.view.MenuItem, 8454 PriorityList = wp.media.view.PriorityList, 8455 Menu; 8456 8457 /** 8458 * wp.media.view.Menu 8459 * 8460 * @memberOf wp.media.view 8461 * 8462 * @class 8463 * @augments wp.media.view.PriorityList 8464 * @augments wp.media.View 8465 * @augments wp.Backbone.View 8466 * @augments Backbone.View 8467 */ 8468 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{ 8469 tagName: 'div', 8470 className: 'media-menu', 8471 property: 'state', 8472 ItemView: MenuItem, 8473 region: 'menu', 8474 8475 attributes: { 8476 role: 'tablist', 8477 'aria-orientation': 'horizontal' 8478 }, 8479 8480 /** 8481 * Initializes the menu view. 8482 */ 8483 initialize: function() { 8484 this._views = {}; 8485 8486 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 8487 delete this.options.views; 8488 8489 if ( ! this.options.silent ) { 8490 this.render(); 8491 } 8492 8493 // Initialize the Focus Manager. 8494 this.focusManager = new wp.media.view.FocusManager( { 8495 el: this.el, 8496 mode: 'tabsNavigation' 8497 } ); 8498 8499 // The menu is always rendered and can be visible or hidden on some frames. 8500 this.isVisible = true; 8501 }, 8502 8503 /** 8504 * Creates a view for the given options and id. 8505 * 8506 * @param {Object} options The options for creating the view. 8507 * @param {string} id The id of the view to create. 8508 * @return {wp.media.View} The view instance. 8509 */ 8510 toView: function( options, id ) { 8511 options = options || {}; 8512 options[ this.property ] = options[ this.property ] || id; 8513 return new this.ItemView( options ).render(); 8514 }, 8515 8516 /** 8517 * Updates the menu when the state changes. 8518 */ 8519 ready: function() { 8520 /** 8521 * call 'ready' directly on the parent class 8522 */ 8523 PriorityList.prototype.ready.apply( this, arguments ); 8524 this.visibility(); 8525 8526 // Set up aria tabs initial attributes. 8527 this.focusManager.setupAriaTabs(); 8528 }, 8529 8530 /** 8531 * Sets the menu items. 8532 */ 8533 set: function() { 8534 /** 8535 * call 'set' directly on the parent class 8536 */ 8537 PriorityList.prototype.set.apply( this, arguments ); 8538 this.visibility(); 8539 }, 8540 8541 /** 8542 * Unsets the menu items. 8543 */ 8544 unset: function() { 8545 /** 8546 * call 'unset' directly on the parent class 8547 */ 8548 PriorityList.prototype.unset.apply( this, arguments ); 8549 this.visibility(); 8550 }, 8551 8552 /** 8553 * Updates the menu visibility. 8554 */ 8555 visibility: function() { 8556 var region = this.region, 8557 view = this.controller[ region ].get(), 8558 views = this.views.get(), 8559 hide = ! views || views.length < 2; 8560 8561 if ( this === view ) { 8562 // Flag this menu as hidden or visible. 8563 this.isVisible = ! hide; 8564 // Set or remove a CSS class to hide the menu. 8565 this.controller.$el.toggleClass( 'hide-' + region, hide ); 8566 } 8567 }, 8568 /** 8569 * Selects the menu item with the given id. 8570 * 8571 * @param {string} id The menu item id. 8572 */ 8573 select: function( id ) { 8574 var view = this.get( id ); 8575 8576 if ( ! view ) { 8577 return; 8578 } 8579 8580 this.deselect(); 8581 view.$el.addClass('active'); 8582 8583 // Set up again the aria tabs initial attributes after the menu updates. 8584 this.focusManager.setupAriaTabs(); 8585 }, 8586 8587 /** 8588 * Deselects the menu items. 8589 */ 8590 deselect: function() { 8591 this.$el.children().removeClass('active'); 8592 }, 8593 8594 /** 8595 * Hides the menu item with the given id. 8596 * 8597 * @param {string} id The menu item id. 8598 */ 8599 hide: function( id ) { 8600 var view = this.get( id ); 8601 8602 if ( ! view ) { 8603 return; 8604 } 8605 8606 view.$el.addClass('hidden'); 8607 }, 8608 8609 /** 8610 * Shows the menu item with the given id. 8611 * 8612 * @param {string} id The menu item id. 8613 */ 8614 show: function( id ) { 8615 var view = this.get( id ); 8616 8617 if ( ! view ) { 8618 return; 8619 } 8620 8621 view.$el.removeClass('hidden'); 8622 } 8623 }); 8624 8625 module.exports = Menu; 8626 8627 8628 /***/ }, 8629 8630 /***/ 2621 8631 (module) { 8632 8633 var $ = jQuery, 8634 Modal; 8635 8636 /** 8637 * wp.media.view.Modal 8638 * 8639 * A modal view, which the media modal uses as its default container. 8640 * 8641 * @memberOf wp.media.view 8642 * 8643 * @class 8644 * @augments wp.media.View 8645 * @augments wp.Backbone.View 8646 * @augments Backbone.View 8647 */ 8648 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ 8649 tagName: 'div', 8650 template: wp.template('media-modal'), 8651 8652 events: { 8653 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 8654 'keydown': 'keydown' 8655 }, 8656 8657 clickedOpenerEl: null, 8658 8659 initialize: function() { 8660 _.defaults( this.options, { 8661 container: document.body, 8662 title: '', 8663 propagate: true, 8664 hasCloseButton: true 8665 }); 8666 8667 this.titleId = _.uniqueId( 'media-frame-title-' ); 8668 8669 this.focusManager = new wp.media.view.FocusManager({ 8670 el: this.el 8671 }); 8672 }, 8673 /** 8674 * Prepares the data for the modal template. 8675 * 8676 * @since 7.2.0 Added the `titleId` property. 8677 * 8678 * @return {Object} The prepared data. 8679 */ 8680 prepare: function() { 8681 return { 8682 title: this.options.title, 8683 hasCloseButton: this.options.hasCloseButton, 8684 titleId: this.titleId 8685 }; 8686 }, 8687 8688 /** 8689 * Attaches the modal to the DOM and triggers the ready event. 8690 * 8691 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8692 */ 8693 attach: function() { 8694 if ( this.views.attached ) { 8695 return this; 8696 } 8697 8698 if ( ! this.views.rendered ) { 8699 this.render(); 8700 } 8701 8702 this.$el.appendTo( this.options.container ); 8703 8704 // Manually mark the view as attached and trigger ready. 8705 this.views.attached = true; 8706 this.views.ready(); 8707 8708 return this.propagate('attach'); 8709 }, 8710 8711 /** 8712 * Detaches the modal from the DOM and triggers the detach event. 8713 * 8714 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8715 */ 8716 detach: function() { 8717 if ( this.$el.is(':visible') ) { 8718 this.close(); 8719 } 8720 8721 this.$el.detach(); 8722 this.views.attached = false; 8723 return this.propagate('detach'); 8724 }, 8725 8726 /** 8727 * Opens the modal and triggers the open event. 8728 * 8729 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8730 */ 8731 open: function() { 8732 var $el = this.$el, 8733 mceEditor; 8734 8735 if ( $el.is(':visible') ) { 8736 return this; 8737 } 8738 8739 this.clickedOpenerEl = document.activeElement; 8740 8741 if ( ! this.views.attached ) { 8742 this.attach(); 8743 } 8744 8745 // Disable page scrolling. 8746 $( 'body' ).addClass( 'modal-open' ); 8747 8748 $el.show(); 8749 8750 // Try to close the onscreen keyboard. 8751 if ( 'ontouchend' in document ) { 8752 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 8753 mceEditor.iframeElement.focus(); 8754 mceEditor.iframeElement.blur(); 8755 8756 setTimeout( function() { 8757 mceEditor.iframeElement.blur(); 8758 }, 100 ); 8759 } 8760 } 8761 8762 // Set initial focus on the content instead of this view element, to avoid page scrolling. 8763 this.$( '.media-modal' ).trigger( 'focus' ); 8764 8765 // Hide the page content from assistive technologies. 8766 this.focusManager.setAriaHiddenOnBodyChildren( $el ); 8767 8768 return this.propagate('open'); 8769 }, 8770 8771 /** 8772 * Closes the modal and triggers the close event. 8773 * 8774 * @param {Object} [options={}] The options for closing the modal. 8775 * @param {boolean} [options.escape] Whether the modal is being closed due to the escape key. 8776 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8777 */ 8778 close: function( options ) { 8779 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 8780 return this; 8781 } 8782 8783 // Pause current audio/video even after closing the modal. 8784 $( '.mejs-pause button' ).trigger( 'click' ); 8785 8786 // Enable page scrolling. 8787 $( 'body' ).removeClass( 'modal-open' ); 8788 8789 // Hide the modal element by adding display:none. 8790 this.$el.hide(); 8791 8792 /* 8793 * Make visible again to assistive technologies all body children that 8794 * have been made hidden when the modal opened. 8795 */ 8796 this.focusManager.removeAriaHiddenFromBodyChildren(); 8797 8798 // Move focus back in useful location once modal is closed. 8799 if ( null !== this.clickedOpenerEl ) { 8800 // Move focus back to the element that opened the modal. 8801 this.clickedOpenerEl.focus(); 8802 } else { 8803 // Fallback to the admin page main element. 8804 $( '#wpbody-content' ) 8805 .attr( 'tabindex', '-1' ) 8806 .trigger( 'focus' ); 8807 } 8808 8809 this.propagate('close'); 8810 8811 if ( options && options.escape ) { 8812 this.propagate('escape'); 8813 } 8814 8815 return this; 8816 }, 8817 /** 8818 * Closes the modal and triggers the escape event. 8819 * 8820 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8821 */ 8822 escape: function() { 8823 return this.close({ escape: true }); 8824 }, 8825 /** 8826 * Handles the escape key press event to close the modal. 8827 * 8828 * @param {JQuery.Event} event The jQuery event object. 8829 */ 8830 escapeHandler: function( event ) { 8831 event.preventDefault(); 8832 this.escape(); 8833 }, 8834 8835 /** 8836 * Handles the selection of attachments when the command or control key is pressed with the enter key. 8837 * 8838 * @since 6.7 8839 * 8840 * @param {JQuery.Event} event The jQuery event object. 8841 */ 8842 selectHandler: function( event ) { 8843 var selection = this.controller.state().get( 'selection' ); 8844 8845 if ( selection.length <= 0 ) { 8846 return; 8847 } 8848 8849 if ( 'insert' === this.controller.options.state ) { 8850 this.controller.trigger( 'insert', selection ); 8851 } else { 8852 this.controller.trigger( 'select', selection ); 8853 event.preventDefault(); 8854 this.escape(); 8855 } 8856 }, 8857 8858 /** 8859 * Sets the content of the modal by registering views to the '.media-modal-content' selector. 8860 * 8861 * @param {Array|Object} content Views to register to '.media-modal-content' 8862 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8863 */ 8864 content: function( content ) { 8865 this.views.set( '.media-modal-content', content ); 8866 return this; 8867 }, 8868 8869 /** 8870 * Triggers a modal event and if the `propagate` option is set, 8871 * forwards events to the modal's controller. 8872 * 8873 * @param {string} id The ID of the event to propagate. 8874 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8875 */ 8876 propagate: function( id ) { 8877 this.trigger( id ); 8878 8879 if ( this.options.propagate ) { 8880 this.controller.trigger( id ); 8881 } 8882 8883 return this; 8884 }, 8885 /** 8886 * Handles keydown events within the modal. 8887 * 8888 * @param {JQuery.Event} event The jQuery keydown event object. 8889 */ 8890 keydown: function( event ) { 8891 // Close the modal when escape is pressed. 8892 if ( 27 === event.which && this.$el.is(':visible') ) { 8893 this.escape(); 8894 event.stopImmediatePropagation(); 8895 } 8896 8897 // Select the attachment when command or control and enter are pressed. 8898 if ( ( 13 === event.which || 10 === event.which ) && ( event.metaKey || event.ctrlKey ) ) { 8899 this.selectHandler( event ); 8900 event.stopImmediatePropagation(); 8901 } 8902 8903 } 8904 }); 8905 8906 module.exports = Modal; 8907 8908 8909 /***/ }, 8910 8911 /***/ 8815 8912 (module) { 8913 8914 /** 8915 * wp.media.view.PriorityList 8916 * 8917 * @memberOf wp.media.view 8918 * 8919 * @class 8920 * @augments wp.media.View 8921 * @augments wp.Backbone.View 8922 * @augments Backbone.View 8923 */ 8924 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{ 8925 tagName: 'div', 8926 8927 initialize: function() { 8928 this._views = {}; 8929 8930 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 8931 delete this.options.views; 8932 8933 if ( ! this.options.silent ) { 8934 this.render(); 8935 } 8936 }, 8937 /** 8938 * Adds a view to the list, sorted by its priority. 8939 * 8940 * @param {string} id The ID of the view to set. 8941 * @param {wp.media.View|Object} view The view to set. 8942 * @param {Object} options The options for setting the view. 8943 * @return {wp.media.view.PriorityList} Returns itself to allow chaining. 8944 */ 8945 set: function( id, view, options ) { 8946 var priority, views, index; 8947 8948 options = options || {}; 8949 8950 // Accept an object with an `id` : `view` mapping. 8951 if ( _.isObject( id ) ) { 8952 _.each( id, function( view, id ) { 8953 this.set( id, view ); 8954 }, this ); 8955 return this; 8956 } 8957 8958 if ( ! (view instanceof Backbone.View) ) { 8959 view = this.toView( view, id, options ); 8960 } 8961 view.controller = view.controller || this.controller; 8962 8963 this.unset( id ); 8964 8965 priority = view.options.priority || 10; 8966 views = this.views.get() || []; 8967 8968 _.find( views, function( existing, i ) { 8969 if ( existing.options.priority > priority ) { 8970 index = i; 8971 return true; 8972 } 8973 }); 8974 8975 this._views[ id ] = view; 8976 this.views.add( view, { 8977 at: _.isNumber( index ) ? index : views.length || 0 8978 }); 8979 8980 return this; 8981 }, 8982 /** 8983 * Retrieves a view by its ID. 8984 * 8985 * @param {string} id The ID of the view to retrieve. 8986 * @return {wp.media.View} Returns the view if found, otherwise undefined. 8987 */ 8988 get: function( id ) { 8989 return this._views[ id ]; 8990 }, 8991 /** 8992 * Removes a view by its ID. 8993 * 8994 * @param {string} id The ID of the view to remove. 8995 * @return {wp.media.view.PriorityList} Returns itself to allow chaining. 8996 */ 8997 unset: function( id ) { 8998 var view = this.get( id ); 8999 9000 if ( view ) { 9001 view.remove(); 9002 } 9003 9004 delete this._views[ id ]; 9005 return this; 9006 }, 9007 /** 9008 * Creates a view from an object of options. 9009 * 9010 * @param {Object} options The options for creating the view. 9011 * @return {wp.media.View} Returns the created view. 9012 */ 9013 toView: function( options ) { 9014 return new wp.media.View( options ); 9015 } 9016 }); 9017 9018 module.exports = PriorityList; 9019 9020 9021 /***/ }, 9022 9023 /***/ 6327 9024 (module) { 9025 9026 /** 9027 * wp.media.view.RouterItem 9028 * 9029 * @memberOf wp.media.view 9030 * 9031 * @class 9032 * @augments wp.media.view.MenuItem 9033 * @augments wp.media.View 9034 * @augments wp.Backbone.View 9035 * @augments Backbone.View 9036 */ 9037 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{ 9038 /** 9039 * On click handler to activate the content region's corresponding mode. 9040 */ 9041 click: function() { 9042 var contentMode = this.options.contentMode; 9043 if ( contentMode ) { 9044 this.controller.content.mode( contentMode ); 9045 } 9046 } 9047 }); 9048 9049 module.exports = RouterItem; 9050 9051 9052 /***/ }, 9053 9054 /***/ 4783 9055 (module) { 9056 9057 var Menu = wp.media.view.Menu, 9058 Router; 9059 9060 /** 9061 * wp.media.view.Router 9062 * 9063 * @memberOf wp.media.view 9064 * 9065 * @class 9066 * @augments wp.media.view.Menu 9067 * @augments wp.media.view.PriorityList 9068 * @augments wp.media.View 9069 * @augments wp.Backbone.View 9070 * @augments Backbone.View 9071 */ 9072 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{ 9073 tagName: 'div', 9074 className: 'media-router', 9075 property: 'contentMode', 9076 ItemView: wp.media.view.RouterItem, 9077 region: 'router', 9078 9079 attributes: { 9080 role: 'tablist', 9081 'aria-orientation': 'horizontal' 9082 }, 9083 9084 initialize: function() { 9085 this.controller.on( 'content:render', this.update, this ); 9086 // Call 'initialize' directly on the parent class. 9087 Menu.prototype.initialize.apply( this, arguments ); 9088 }, 9089 9090 update: function() { 9091 var mode = this.controller.content.mode(); 9092 if ( mode ) { 9093 this.select( mode ); 9094 } 9095 } 9096 }); 9097 9098 module.exports = Router; 9099 9100 9101 /***/ }, 9102 9103 /***/ 2102 9104 (module) { 9105 9106 var Search; 9107 9108 /** 9109 * wp.media.view.Search 9110 * 9111 * @memberOf wp.media.view 9112 * 9113 * @class 9114 * @augments wp.media.View 9115 * @augments wp.Backbone.View 9116 * @augments Backbone.View 9117 */ 9118 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{ 9119 tagName: 'input', 9120 className: 'search', 9121 id: 'media-search-input', 9122 9123 attributes: { 9124 type: 'search' 9125 }, 9126 9127 events: { 9128 'input': 'search' 9129 }, 9130 9131 /** 9132 * Renders the search input. 9133 * 9134 * @return {wp.media.view.Search} Returns itself to allow chaining. 9135 */ 9136 render: function() { 9137 this.el.value = this.model.escape('search'); 9138 return this; 9139 }, 9140 9141 /** 9142 * Searches the media library. 9143 * 9144 * @param {JQuery.Event} event The input event. 9145 */ 9146 search: _.debounce( function( event ) { 9147 var searchTerm = event.target.value.trim(); 9148 9149 // Trigger the search only after 2 ASCII characters. 9150 if ( searchTerm && searchTerm.length > 1 ) { 9151 this.model.set( 'search', searchTerm ); 9152 } else { 9153 this.model.unset( 'search' ); 9154 } 9155 }, 500 ) 9156 }); 9157 9158 module.exports = Search; 9159 9160 9161 /***/ }, 9162 9163 /***/ 8282 9164 (module) { 9165 9166 var _n = wp.i18n._n, 9167 sprintf = wp.i18n.sprintf, 9168 Selection; 9169 9170 /** 9171 * wp.media.view.Selection 9172 * 9173 * @memberOf wp.media.view 9174 * 9175 * @class 9176 * @augments wp.media.View 9177 * @augments wp.Backbone.View 9178 * @augments Backbone.View 9179 */ 9180 Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{ 9181 tagName: 'div', 9182 className: 'media-selection', 9183 template: wp.template('media-selection'), 9184 9185 events: { 9186 'click .edit-selection': 'edit', 9187 'click .clear-selection': 'clear' 9188 }, 9189 9190 initialize: function() { 9191 _.defaults( this.options, { 9192 editable: false, 9193 clearable: true 9194 }); 9195 9196 /** 9197 * @member {wp.media.view.Attachments.Selection} 9198 */ 9199 this.attachments = new wp.media.view.Attachments.Selection({ 9200 controller: this.controller, 9201 collection: this.collection, 9202 selection: this.collection, 9203 model: new Backbone.Model() 9204 }); 9205 9206 this.views.set( '.selection-view', this.attachments ); 9207 this.collection.on( 'add remove reset', this.refresh, this ); 9208 this.controller.on( 'content:activate', this.refresh, this ); 9209 }, 9210 9211 ready: function() { 9212 this.refresh(); 9213 }, 9214 9215 refresh: function() { 9216 // If the selection hasn't been rendered, bail. 9217 if ( ! this.$el.children().length ) { 9218 return; 9219 } 9220 9221 var collection = this.collection, 9222 editing = 'edit-selection' === this.controller.content.mode(); 9223 9224 // If nothing is selected, display nothing. 9225 this.$el.toggleClass( 'empty', ! collection.length ); 9226 this.$el.toggleClass( 'one', 1 === collection.length ); 9227 this.$el.toggleClass( 'editing', editing ); 9228 9229 this.$( '.count' ).text( 9230 /* translators: %s: Number of selected media attachments. */ 9231 sprintf( _n( '%s item selected', '%s items selected', collection.length ), collection.length ) 9232 ); 9233 }, 9234 9235 edit: function( event ) { 9236 event.preventDefault(); 9237 if ( this.options.editable ) { 9238 this.options.editable.call( this, this.collection ); 9239 } 9240 }, 9241 9242 clear: function( event ) { 9243 event.preventDefault(); 9244 this.collection.reset(); 9245 9246 // Move focus to the modal. 9247 this.controller.modal.focusManager.focus(); 9248 } 9249 }); 9250 9251 module.exports = Selection; 9252 9253 9254 /***/ }, 9255 9256 /***/ 1915 9257 (module) { 9258 9259 var View = wp.media.View, 9260 $ = Backbone.$, 9261 Settings; 9262 9263 /** 9264 * wp.media.view.Settings 9265 * 9266 * @memberOf wp.media.view 9267 * 9268 * @class 9269 * @augments wp.media.View 9270 * @augments wp.Backbone.View 9271 * @augments Backbone.View 9272 */ 9273 Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 9274 events: { 9275 'click button': 'updateHandler', 9276 'change input': 'updateHandler', 9277 'change select': 'updateHandler', 9278 'change textarea': 'updateHandler' 9279 }, 9280 9281 /** 9282 * Initializes the settings view. 9283 */ 9284 initialize: function() { 9285 this.model = this.model || new Backbone.Model(); 9286 this.listenTo( this.model, 'change', this.updateChanges ); 9287 }, 9288 9289 /** 9290 * Prepares the data for rendering. 9291 * 9292 * @return {Object} The data to be used in the template. 9293 */ 9294 prepare: function() { 9295 return _.defaults({ 9296 model: this.model.toJSON() 9297 }, this.options ); 9298 }, 9299 /** 9300 * Renders the settings view. 9301 * 9302 * @return {wp.media.view.Settings} Returns itself to allow chaining. 9303 */ 9304 render: function() { 9305 View.prototype.render.apply( this, arguments ); 9306 // Select the correct values. 9307 _( this.model.attributes ).chain().keys().each( this.update, this ); 9308 return this; 9309 }, 9310 /** 9311 * Updates the selected value for a setting. 9312 * 9313 * @param {string} key The key of the setting to update. 9314 */ 9315 update: function( key ) { 9316 var value = this.model.get( key ), 9317 $setting = this.$('[data-setting="' + key + '"]'), 9318 $buttons, $value; 9319 9320 // Bail if we didn't find a matching setting. 9321 if ( ! $setting.length ) { 9322 return; 9323 } 9324 9325 // Attempt to determine how the setting is rendered and update 9326 // the selected value. 9327 9328 // Handle dropdowns. 9329 if ( $setting.is('select') ) { 9330 $value = $setting.find('[value="' + value + '"]'); 9331 9332 if ( $value.length ) { 9333 $setting.find('option').prop( 'selected', false ); 9334 $value.prop( 'selected', true ); 9335 } else { 9336 // If we can't find the desired value, record what *is* selected. 9337 this.model.set( key, $setting.find(':selected').val() ); 9338 } 9339 9340 // Handle button groups. 9341 } else if ( $setting.hasClass('button-group') ) { 9342 $buttons = $setting.find( 'button' ) 9343 .removeClass( 'active' ) 9344 .attr( 'aria-pressed', 'false' ); 9345 $buttons.filter( '[value="' + value + '"]' ) 9346 .addClass( 'active' ) 9347 .attr( 'aria-pressed', 'true' ); 9348 9349 // Handle text inputs and textareas. 9350 } else if ( $setting.is('input[type="text"], textarea') ) { 9351 if ( ! $setting.is(':focus') ) { 9352 $setting.val( value ); 9353 } 9354 // Handle checkboxes. 9355 } else if ( $setting.is('input[type="checkbox"]') ) { 9356 $setting.prop( 'checked', !! value && 'false' !== value ); 9357 } 9358 }, 9359 /** 9360 * Updates the model when a setting is changed. 9361 * 9362 * @param {JQuery.Event} event The jQuery event object associated with the setting change. 9363 */ 9364 updateHandler: function( event ) { 9365 var $setting = $( event.target ).closest('[data-setting]'), 9366 value = event.target.value, 9367 userSetting; 9368 9369 event.preventDefault(); 9370 9371 if ( ! $setting.length ) { 9372 return; 9373 } 9374 9375 // Use the correct value for checkboxes. 9376 if ( $setting.is('input[type="checkbox"]') ) { 9377 value = $setting[0].checked; 9378 } 9379 9380 // Update the corresponding setting. 9381 this.model.set( $setting.data('setting'), value ); 9382 9383 // If the setting has a corresponding user setting, 9384 // update that as well. 9385 userSetting = $setting.data('userSetting'); 9386 if ( userSetting ) { 9387 window.setUserSetting( userSetting, value ); 9388 } 9389 }, 9390 9391 /** 9392 * Updates the view when the model changes. 9393 * 9394 * @param {Backbone.Model} model The model that changed. 9395 */ 9396 updateChanges: function( model ) { 9397 if ( model.hasChanged() ) { 9398 _( model.changed ).chain().keys().each( this.update, this ); 9399 } 9400 } 9401 }); 9402 9403 module.exports = Settings; 9404 9405 9406 /***/ }, 9407 9408 /***/ 7656 9409 (module) { 9410 9411 var Settings = wp.media.view.Settings, 9412 AttachmentDisplay; 9413 9414 /** 9415 * wp.media.view.Settings.AttachmentDisplay 9416 * 9417 * @memberOf wp.media.view.Settings 9418 * 9419 * @class 9420 * @augments wp.media.view.Settings 9421 * @augments wp.media.View 9422 * @augments wp.Backbone.View 9423 * @augments Backbone.View 9424 */ 9425 AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{ 9426 className: 'attachment-display-settings', 9427 template: wp.template('attachment-display-settings'), 9428 9429 /** 9430 * Initializes the attachment display settings view. 9431 */ 9432 initialize: function() { 9433 var attachment = this.options.attachment; 9434 9435 _.defaults( this.options, { 9436 userSettings: false 9437 }); 9438 // Call 'initialize' directly on the parent class. 9439 Settings.prototype.initialize.apply( this, arguments ); 9440 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 9441 9442 if ( attachment ) { 9443 attachment.on( 'change:uploading', this.render, this ); 9444 } 9445 }, 9446 9447 /** 9448 * Disposes of the attachment display settings view. 9449 */ 9450 dispose: function() { 9451 var attachment = this.options.attachment; 9452 if ( attachment ) { 9453 attachment.off( null, null, this ); 9454 } 9455 /** 9456 * call 'dispose' directly on the parent class 9457 */ 9458 Settings.prototype.dispose.apply( this, arguments ); 9459 }, 9460 /** 9461 * Renders the attachment display settings view. 9462 * 9463 * @return {wp.media.view.AttachmentDisplay} Returns itself to allow chaining. 9464 */ 9465 render: function() { 9466 var attachment = this.options.attachment; 9467 if ( attachment ) { 9468 _.extend( this.options, { 9469 sizes: attachment.get('sizes'), 9470 type: attachment.get('type') 9471 }); 9472 } 9473 /** 9474 * call 'render' directly on the parent class 9475 */ 9476 Settings.prototype.render.call( this ); 9477 this.updateLinkTo(); 9478 return this; 9479 }, 9480 9481 /** 9482 * Updates the linkTo setting. 9483 */ 9484 updateLinkTo: function() { 9485 var linkTo = this.model.get('link'), 9486 $input = this.$('.link-to-custom'), 9487 attachment = this.options.attachment; 9488 9489 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 9490 $input.closest( '.setting' ).addClass( 'hidden' ); 9491 return; 9492 } 9493 9494 if ( attachment ) { 9495 if ( 'post' === linkTo ) { 9496 $input.val( attachment.get('link') ); 9497 } else if ( 'file' === linkTo ) { 9498 $input.val( attachment.get('url') ); 9499 } else if ( ! this.model.get('linkUrl') ) { 9500 $input.val('http://'); 9501 } 9502 9503 $input.prop( 'readonly', 'custom' !== linkTo ); 9504 } 9505 9506 $input.closest( '.setting' ).removeClass( 'hidden' ); 9507 if ( $input.length ) { 9508 $input[0].scrollIntoView(); 9509 } 9510 } 9511 }); 9512 9513 module.exports = AttachmentDisplay; 9514 9515 9516 /***/ }, 9517 9518 /***/ 7266 9519 (module) { 9520 9521 /** 9522 * wp.media.view.Settings.Gallery 9523 * 9524 * @memberOf wp.media.view.Settings 9525 * 9526 * @class 9527 * @augments wp.media.view.Settings 9528 * @augments wp.media.View 9529 * @augments wp.Backbone.View 9530 * @augments Backbone.View 9531 */ 9532 var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{ 9533 className: 'collection-settings gallery-settings', 9534 template: wp.template('gallery-settings') 9535 }); 9536 9537 module.exports = Gallery; 9538 9539 9540 /***/ }, 9541 9542 /***/ 2356 9543 (module) { 9544 9545 /** 9546 * wp.media.view.Settings.Playlist 9547 * 9548 * @memberOf wp.media.view.Settings 9549 * 9550 * @class 9551 * @augments wp.media.view.Settings 9552 * @augments wp.media.View 9553 * @augments wp.Backbone.View 9554 * @augments Backbone.View 9555 */ 9556 var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{ 9557 className: 'collection-settings playlist-settings', 9558 template: wp.template('playlist-settings') 9559 }); 9560 9561 module.exports = Playlist; 9562 9563 9564 /***/ }, 9565 9566 /***/ 1992 9567 (module) { 9568 9569 /** 9570 * wp.media.view.Sidebar 9571 * 9572 * @memberOf wp.media.view 9573 * 9574 * @class 9575 * @augments wp.media.view.PriorityList 9576 * @augments wp.media.View 9577 * @augments wp.Backbone.View 9578 * @augments Backbone.View 9579 */ 9580 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{ 9581 className: 'media-sidebar' 9582 }); 9583 9584 module.exports = Sidebar; 9585 9586 9587 /***/ }, 9588 9589 /***/ 443 9590 (module) { 9591 9592 var View = wp.media.view, 9593 SiteIconCropper; 9594 9595 /** 9596 * wp.media.view.SiteIconCropper 9597 * 9598 * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon. 9599 * 9600 * Takes imgAreaSelect options from 9601 * wp.customize.SiteIconControl.calculateImageSelectOptions. 9602 * 9603 * @memberOf wp.media.view 9604 * 9605 * @class 9606 * @augments wp.media.view.Cropper 9607 * @augments wp.media.View 9608 * @augments wp.Backbone.View 9609 * @augments Backbone.View 9610 */ 9611 SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{ 9612 className: 'crop-content site-icon', 9613 9614 ready: function () { 9615 View.Cropper.prototype.ready.apply( this, arguments ); 9616 9617 this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) ); 9618 }, 9619 9620 addSidebar: function() { 9621 this.sidebar = new wp.media.view.Sidebar({ 9622 controller: this.controller 9623 }); 9624 9625 this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({ 9626 controller: this.controller, 9627 attachment: this.options.attachment 9628 }) ); 9629 9630 this.controller.cropperView.views.add( this.sidebar ); 9631 } 9632 }); 9633 9634 module.exports = SiteIconCropper; 9635 9636 9637 /***/ }, 9638 9639 /***/ 7810 9640 (module) { 9641 9642 var View = wp.media.View, 9643 $ = jQuery, 9644 SiteIconPreview; 9645 9646 /** 9647 * wp.media.view.SiteIconPreview 9648 * 9649 * Shows a preview of the Site Icon as a favicon and app icon while cropping. 9650 * 9651 * @memberOf wp.media.view 9652 * 9653 * @class 9654 * @augments wp.media.View 9655 * @augments wp.Backbone.View 9656 * @augments Backbone.View 9657 */ 9658 SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{ 9659 className: 'site-icon-preview-crop-modal', 9660 template: wp.template( 'site-icon-preview-crop' ), 9661 9662 ready: function() { 9663 this.controller.imgSelect.setOptions({ 9664 onInit: this.updatePreview, 9665 onSelectChange: this.updatePreview 9666 }); 9667 }, 9668 9669 prepare: function() { 9670 return { 9671 url: this.options.attachment.get( 'url' ) 9672 }; 9673 }, 9674 9675 updatePreview: function( img, coords ) { 9676 var rx = 64 / coords.width, 9677 ry = 64 / coords.height, 9678 preview_rx = 24 / coords.width, 9679 preview_ry = 24 / coords.height; 9680 9681 $( '#preview-app-icon' ).css({ 9682 width: Math.round(rx * this.imageWidth ) + 'px', 9683 height: Math.round(ry * this.imageHeight ) + 'px', 9684 marginLeft: '-' + Math.round(rx * coords.x1) + 'px', 9685 marginTop: '-' + Math.round(ry * coords.y1) + 'px' 9686 }); 9687 9688 $( '#preview-favicon' ).css({ 9689 width: Math.round( preview_rx * this.imageWidth ) + 'px', 9690 height: Math.round( preview_ry * this.imageHeight ) + 'px', 9691 marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px', 9692 marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px' 9693 }); 9694 } 9695 }); 9696 9697 module.exports = SiteIconPreview; 9698 9699 9700 /***/ }, 9701 9702 /***/ 9141 9703 (module) { 9704 9705 /** 9706 * wp.media.view.Spinner 9707 * 9708 * Represents a spinner in the Media Library. 9709 * 9710 * @since 3.9.0 9711 * 9712 * @memberOf wp.media.view 9713 * 9714 * @class 9715 * @augments wp.media.View 9716 * @augments wp.Backbone.View 9717 * @augments Backbone.View 9718 */ 9719 var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{ 9720 tagName: 'span', 9721 className: 'spinner', 9722 spinnerTimeout: false, 9723 delay: 400, 9724 9725 /** 9726 * Shows the spinner. Delays the visibility by the configured amount. 9727 * 9728 * @since 3.9.0 9729 * 9730 * @return {wp.media.view.Spinner} The spinner. 9731 */ 9732 show: function() { 9733 if ( ! this.spinnerTimeout ) { 9734 this.spinnerTimeout = _.delay(function( $el ) { 9735 $el.addClass( 'is-active' ); 9736 }, this.delay, this.$el ); 9737 } 9738 9739 return this; 9740 }, 9741 9742 /** 9743 * Hides the spinner. 9744 * 9745 * @since 3.9.0 9746 * 9747 * @return {wp.media.view.Spinner} The spinner. 9748 */ 9749 hide: function() { 9750 this.$el.removeClass( 'is-active' ); 9751 this.spinnerTimeout = clearTimeout( this.spinnerTimeout ); 9752 9753 return this; 9754 } 9755 }); 9756 9757 module.exports = Spinner; 9758 9759 9760 /***/ }, 9761 9762 /***/ 5275 9763 (module) { 9764 9765 var View = wp.media.View, 9766 Toolbar; 9767 9768 /** 9769 * wp.media.view.Toolbar 9770 * 9771 * A toolbar which consists of a primary and a secondary section. Each sections 9772 * can be filled with views. 9773 * 9774 * @memberOf wp.media.view 9775 * 9776 * @class 9777 * @augments wp.media.View 9778 * @augments wp.Backbone.View 9779 * @augments Backbone.View 9780 */ 9781 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ 9782 tagName: 'div', 9783 className: 'media-toolbar', 9784 9785 /** 9786 * Initializes the toolbar view. 9787 */ 9788 initialize: function() { 9789 var state = this.controller.state(), 9790 selection = this.selection = state.get('selection'), 9791 library = this.library = state.get('library'); 9792 9793 this._views = {}; 9794 9795 // The toolbar is composed of two `PriorityList` views. 9796 this.primary = new wp.media.view.PriorityList(); 9797 this.secondary = new wp.media.view.PriorityList(); 9798 this.tertiary = new wp.media.view.PriorityList(); 9799 this.primary.$el.addClass('media-toolbar-primary search-form'); 9800 this.secondary.$el.addClass('media-toolbar-secondary'); 9801 this.tertiary.$el.addClass('media-bg-overlay'); 9802 9803 this.views.set([ this.secondary, this.primary, this.tertiary ]); 9804 9805 if ( this.options.items ) { 9806 this.set( this.options.items, { silent: true }); 9807 } 9808 9809 if ( ! this.options.silent ) { 9810 this.render(); 9811 } 9812 9813 if ( selection ) { 9814 selection.on( 'add remove reset', this.refresh, this ); 9815 } 9816 9817 if ( library ) { 9818 library.on( 'add remove reset', this.refresh, this ); 9819 } 9820 }, 9821 /** 9822 * Disposes of the toolbar view. 9823 * 9824 * @return {wp.media.view.Toolbar} Returns itself to allow chaining 9825 */ 9826 dispose: function() { 9827 if ( this.selection ) { 9828 this.selection.off( null, null, this ); 9829 } 9830 9831 if ( this.library ) { 9832 this.library.off( null, null, this ); 9833 } 9834 /** 9835 * call 'dispose' directly on the parent class 9836 */ 9837 return View.prototype.dispose.apply( this, arguments ); 9838 }, 9839 9840 /** 9841 * Prepares the data for rendering. 9842 */ 9843 ready: function() { 9844 this.refresh(); 9845 }, 9846 9847 /** 9848 * Sets a view by its ID. 9849 * 9850 * @param {string} id The ID of the view to set. 9851 * @param {Backbone.View|Object} view The view to set. 9852 * @param {Object} [options={}] The options for setting the view. 9853 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 9854 */ 9855 set: function( id, view, options ) { 9856 var list; 9857 options = options || {}; 9858 9859 // Accept an object with an `id` : `view` mapping. 9860 if ( _.isObject( id ) ) { 9861 _.each( id, function( view, id ) { 9862 this.set( id, view, { silent: true }); 9863 }, this ); 9864 9865 } else { 9866 if ( ! ( view instanceof Backbone.View ) ) { 9867 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 9868 view = new wp.media.view.Button( view ).render(); 9869 } 9870 9871 view.controller = view.controller || this.controller; 9872 9873 this._views[ id ] = view; 9874 9875 list = view.options.priority < 0 ? 'secondary' : 'primary'; 9876 this[ list ].set( id, view, options ); 9877 } 9878 9879 if ( ! options.silent ) { 9880 this.refresh(); 9881 } 9882 9883 return this; 9884 }, 9885 /** 9886 * Retrieves a view by its ID. 9887 * 9888 * @param {string} id The ID of the view to retrieve. 9889 * @return {wp.media.view.Button} The view associated with the given ID, or undefined if no view is found. 9890 */ 9891 get: function( id ) { 9892 return this._views[ id ]; 9893 }, 9894 /** 9895 * Unsets a view by its ID. 9896 * 9897 * @param {string} id The ID of the view to unset. 9898 * @param {Object} [options={}] The options for unsetting the view. 9899 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 9900 */ 9901 unset: function( id, options ) { 9902 delete this._views[ id ]; 9903 this.primary.unset( id, options ); 9904 this.secondary.unset( id, options ); 9905 this.tertiary.unset( id, options ); 9906 9907 if ( ! options || ! options.silent ) { 9908 this.refresh(); 9909 } 9910 return this; 9911 }, 9912 9913 /** 9914 * Refreshes the toolbar view. 9915 */ 9916 refresh: function() { 9917 var state = this.controller.state(), 9918 library = state.get('library'), 9919 selection = state.get('selection'); 9920 9921 _.each( this._views, function( button ) { 9922 if ( ! button.model || ! button.options || ! button.options.requires ) { 9923 return; 9924 } 9925 9926 var requires = button.options.requires, 9927 disabled = false, 9928 modelsUploading = library && ! _.isEmpty( library.findWhere( { 'uploading': true } ) ); 9929 9930 // Prevent insertion of attachments if any of them are still uploading. 9931 if ( selection && selection.models ) { 9932 disabled = _.some( selection.models, function( attachment ) { 9933 return attachment.get('uploading') === true; 9934 }); 9935 } 9936 if ( requires.uploadingComplete && modelsUploading ) { 9937 disabled = true; 9938 } 9939 9940 if ( requires.selection && selection && ! selection.length ) { 9941 disabled = true; 9942 } else if ( requires.library && library && ! library.length ) { 9943 disabled = true; 9944 } 9945 button.model.set( 'disabled', disabled ); 9946 }); 9947 } 9948 }); 9949 9950 module.exports = Toolbar; 9951 9952 9953 /***/ }, 9954 9955 /***/ 397 9956 (module) { 9957 9958 var Select = wp.media.view.Toolbar.Select, 9959 l10n = wp.media.view.l10n, 9960 Embed; 9961 9962 /** 9963 * wp.media.view.Toolbar.Embed 9964 * 9965 * @memberOf wp.media.view.Toolbar 9966 * 9967 * @class 9968 * @augments wp.media.view.Toolbar.Select 9969 * @augments wp.media.view.Toolbar 9970 * @augments wp.media.View 9971 * @augments wp.Backbone.View 9972 * @augments Backbone.View 9973 */ 9974 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{ 9975 initialize: function() { 9976 _.defaults( this.options, { 9977 text: l10n.insertIntoPost, 9978 requires: false 9979 }); 9980 // Call 'initialize' directly on the parent class. 9981 Select.prototype.initialize.apply( this, arguments ); 9982 }, 9983 9984 refresh: function() { 9985 var url = this.controller.state().props.get('url'); 9986 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 9987 /** 9988 * call 'refresh' directly on the parent class 9989 */ 9990 Select.prototype.refresh.apply( this, arguments ); 9991 } 9992 }); 9993 9994 module.exports = Embed; 9995 9996 9997 /***/ }, 9998 9999 /***/ 9458 10000 (module) { 10001 10002 var Toolbar = wp.media.view.Toolbar, 10003 l10n = wp.media.view.l10n, 10004 Select; 10005 10006 /** 10007 * wp.media.view.Toolbar.Select 10008 * 10009 * @memberOf wp.media.view.Toolbar 10010 * 10011 * @class 10012 * @augments wp.media.view.Toolbar 10013 * @augments wp.media.View 10014 * @augments wp.Backbone.View 10015 * @augments Backbone.View 10016 */ 10017 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{ 10018 initialize: function() { 10019 var options = this.options; 10020 10021 _.bindAll( this, 'clickSelect' ); 10022 10023 _.defaults( options, { 10024 event: 'select', 10025 state: false, 10026 reset: true, 10027 close: true, 10028 text: l10n.select, 10029 10030 // Does the button rely on the selection? 10031 requires: { 10032 selection: true 10033 } 10034 }); 10035 10036 options.items = _.defaults( options.items || {}, { 10037 select: { 10038 style: 'primary', 10039 text: options.text, 10040 priority: 80, 10041 click: this.clickSelect, 10042 requires: options.requires 10043 } 10044 }); 10045 // Call 'initialize' directly on the parent class. 10046 Toolbar.prototype.initialize.apply( this, arguments ); 10047 }, 10048 10049 clickSelect: function() { 10050 var options = this.options, 10051 controller = this.controller; 10052 10053 if ( options.close ) { 10054 controller.close(); 10055 } 10056 10057 if ( options.event ) { 10058 controller.state().trigger( options.event ); 10059 } 10060 10061 if ( options.state ) { 10062 controller.setState( options.state ); 10063 } 10064 10065 if ( options.reset ) { 10066 controller.reset(); 10067 } 10068 } 10069 }); 10070 10071 module.exports = Select; 10072 10073 10074 /***/ }, 10075 10076 /***/ 3674 10077 (module) { 10078 10079 var View = wp.media.View, 10080 l10n = wp.media.view.l10n, 10081 $ = jQuery, 10082 EditorUploader; 10083 10084 /** 10085 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 10086 * and relays drag'n'dropped files to a media workflow. 10087 * 10088 * wp.media.view.EditorUploader 10089 * 10090 * @memberOf wp.media.view 10091 * 10092 * @class 10093 * @augments wp.media.View 10094 * @augments wp.Backbone.View 10095 * @augments Backbone.View 10096 */ 10097 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{ 10098 tagName: 'div', 10099 className: 'uploader-editor', 10100 template: wp.template( 'uploader-editor' ), 10101 10102 localDrag: false, 10103 overContainer: false, 10104 overDropzone: false, 10105 draggingFile: null, 10106 10107 /** 10108 * Bind drag'n'drop events to callbacks. 10109 * 10110 * @return {wp.media.view.EditorUploader} Chainable. 10111 */ 10112 initialize: function() { 10113 this.initialized = false; 10114 10115 // Bail if not enabled or UA does not support drag'n'drop or File API. 10116 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 10117 return this; 10118 } 10119 10120 this.$document = $(document); 10121 this.dropzones = []; 10122 this.files = []; 10123 10124 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 10125 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 10126 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 10127 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 10128 10129 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 10130 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 10131 10132 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 10133 this.localDrag = event.type === 'dragstart'; 10134 10135 if ( event.type === 'drop' ) { 10136 this.containerDragleave(); 10137 } 10138 }, this ) ); 10139 10140 this.initialized = true; 10141 return this; 10142 }, 10143 10144 /** 10145 * Check browser support for drag'n'drop. 10146 * 10147 * @return {boolean} True if the browser supports drag'n'drop, false otherwise. 10148 */ 10149 browserSupport: function() { 10150 var supports = false, div = document.createElement('div'); 10151 10152 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 10153 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 10154 return supports; 10155 }, 10156 10157 isDraggingFile: function( event ) { 10158 if ( this.draggingFile !== null ) { 10159 return this.draggingFile; 10160 } 10161 10162 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 10163 return false; 10164 } 10165 10166 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 10167 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 10168 10169 return this.draggingFile; 10170 }, 10171 10172 refresh: function( e ) { 10173 var dropzone_id; 10174 for ( dropzone_id in this.dropzones ) { 10175 // Hide the dropzones only if dragging has left the screen. 10176 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 10177 } 10178 10179 if ( ! _.isUndefined( e ) ) { 10180 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 10181 } 10182 10183 if ( ! this.overContainer && ! this.overDropzone ) { 10184 this.draggingFile = null; 10185 } 10186 10187 return this; 10188 }, 10189 10190 render: function() { 10191 if ( ! this.initialized ) { 10192 return this; 10193 } 10194 10195 View.prototype.render.apply( this, arguments ); 10196 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 10197 return this; 10198 }, 10199 10200 attach: function( index, editor ) { 10201 // Attach a dropzone to an editor. 10202 var dropzone = this.$el.clone(); 10203 this.dropzones.push( dropzone ); 10204 $( editor ).append( dropzone ); 10205 return this; 10206 }, 10207 10208 /** 10209 * When a file is dropped on the editor uploader, open up an editor media workflow 10210 * and upload the file immediately. 10211 * 10212 * @param {jQuery.Event} event The 'drop' event. 10213 * @return {void|boolean} False to prevent default behavior. 10214 */ 10215 drop: function( event ) { 10216 var $wrap, uploadView; 10217 10218 this.containerDragleave( event ); 10219 this.dropzoneDragleave( event ); 10220 10221 this.files = event.originalEvent.dataTransfer.files; 10222 if ( this.files.length < 1 ) { 10223 return; 10224 } 10225 10226 // Set the active editor to the drop target. 10227 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 10228 if ( $wrap.length > 0 && $wrap[0].id ) { 10229 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 10230 } 10231 10232 if ( ! this.workflow ) { 10233 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 10234 frame: 'post', 10235 state: 'insert', 10236 title: l10n.addMedia, 10237 multiple: true 10238 }); 10239 10240 uploadView = this.workflow.uploader; 10241 10242 if ( uploadView.uploader && uploadView.uploader.ready ) { 10243 this.addFiles.apply( this ); 10244 } else { 10245 this.workflow.on( 'uploader:ready', this.addFiles, this ); 10246 } 10247 } else { 10248 this.workflow.state().reset(); 10249 this.addFiles.apply( this ); 10250 this.workflow.open(); 10251 } 10252 10253 return false; 10254 }, 10255 10256 /** 10257 * Add the files to the uploader. 10258 * 10259 * @return {wp.media.view.EditorUploader} Chainable. 10260 */ 10261 addFiles: function() { 10262 if ( this.files.length ) { 10263 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 10264 this.files = []; 10265 } 10266 return this; 10267 }, 10268 10269 containerDragover: function( event ) { 10270 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 10271 return; 10272 } 10273 10274 this.overContainer = true; 10275 this.refresh(); 10276 }, 10277 10278 containerDragleave: function() { 10279 this.overContainer = false; 10280 10281 // Throttle dragleave because it's called when bouncing from some elements to others. 10282 _.delay( _.bind( this.refresh, this ), 50 ); 10283 }, 10284 10285 dropzoneDragover: function( event ) { 10286 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 10287 return; 10288 } 10289 10290 this.overDropzone = true; 10291 this.refresh( event ); 10292 return false; 10293 }, 10294 10295 dropzoneDragleave: function( e ) { 10296 this.overDropzone = false; 10297 _.delay( _.bind( this.refresh, this, e ), 50 ); 10298 }, 10299 10300 click: function( e ) { 10301 // In the rare case where the dropzone gets stuck, hide it on click. 10302 this.containerDragleave( e ); 10303 this.dropzoneDragleave( e ); 10304 this.localDrag = false; 10305 } 10306 }); 10307 10308 module.exports = EditorUploader; 10309 10310 10311 /***/ }, 10312 10313 /***/ 1753 10314 (module) { 10315 10316 var View = wp.media.View, 10317 UploaderInline; 10318 10319 /** 10320 * wp.media.view.UploaderInline 10321 * 10322 * The inline uploader that shows up in the 'Upload Files' tab. 10323 * 10324 * @memberOf wp.media.view 10325 * 10326 * @class 10327 * @augments wp.media.View 10328 * @augments wp.Backbone.View 10329 * @augments Backbone.View 10330 */ 10331 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 10332 tagName: 'div', 10333 className: 'uploader-inline', 10334 template: wp.template('uploader-inline'), 10335 10336 events: { 10337 'click .close': 'hide' 10338 }, 10339 10340 initialize: function() { 10341 _.defaults( this.options, { 10342 message: '', 10343 status: true, 10344 canClose: false 10345 }); 10346 10347 if ( ! this.options.$browser && this.controller.uploader ) { 10348 this.options.$browser = this.controller.uploader.$browser; 10349 } 10350 10351 if ( _.isUndefined( this.options.postId ) ) { 10352 this.options.postId = wp.media.view.settings.post.id; 10353 } 10354 10355 if ( this.options.status ) { 10356 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 10357 controller: this.controller 10358 }) ); 10359 } 10360 }, 10361 10362 prepare: function() { 10363 var suggestedWidth = this.controller.state().get('suggestedWidth'), 10364 suggestedHeight = this.controller.state().get('suggestedHeight'), 10365 data = {}; 10366 10367 data.message = this.options.message; 10368 data.canClose = this.options.canClose; 10369 10370 if ( suggestedWidth && suggestedHeight ) { 10371 data.suggestedWidth = suggestedWidth; 10372 data.suggestedHeight = suggestedHeight; 10373 } 10374 10375 return data; 10376 }, 10377 /** 10378 * Disposes of the inline uploader and its associated views. 10379 * 10380 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 10381 */ 10382 dispose: function() { 10383 if ( this.disposing ) { 10384 /* 10385 * call 'dispose' directly on the parent class 10386 */ 10387 return View.prototype.dispose.apply( this, arguments ); 10388 } 10389 10390 /* 10391 * Run remove on `dispose`, so we can be sure to refresh the 10392 * uploader with a view-less DOM. Track whether we're disposing 10393 * so we don't trigger an infinite loop. 10394 */ 10395 this.disposing = true; 10396 return this.remove(); 10397 }, 10398 /** 10399 * Disposes of the inline uploader and its associated views. 10400 * 10401 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 10402 */ 10403 remove: function() { 10404 /* 10405 * call 'remove' directly on the parent class 10406 */ 10407 var result = View.prototype.remove.apply( this, arguments ); 10408 10409 _.defer( _.bind( this.refresh, this ) ); 10410 return result; 10411 }, 10412 10413 refresh: function() { 10414 var uploader = this.controller.uploader; 10415 10416 if ( uploader ) { 10417 uploader.refresh(); 10418 } 10419 }, 10420 /** 10421 * Replaces the placeholder with the uploader browser and refreshes the uploader. 10422 * 10423 * @return {void|wp.media.view.UploaderInline} Returns itself to allow chaining. 10424 */ 10425 ready: function() { 10426 var $browser = this.options.$browser, 10427 $placeholder; 10428 10429 if ( this.controller.uploader ) { 10430 $placeholder = this.$('.browser'); 10431 10432 // Check if we've already replaced the placeholder. 10433 if ( $placeholder[0] === $browser[0] ) { 10434 return; 10435 } 10436 10437 $browser.detach().text( $placeholder.text() ); 10438 $browser[0].className = $placeholder[0].className; 10439 $browser[0].setAttribute( 'aria-describedby', $placeholder[0].getAttribute('aria-describedby') ); 10440 $placeholder.replaceWith( $browser.show() ); 10441 } 10442 10443 this.refresh(); 10444 return this; 10445 }, 10446 show: function() { 10447 this.$el.removeClass( 'hidden' ); 10448 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 10449 this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' ); 10450 } 10451 }, 10452 hide: function() { 10453 this.$el.addClass( 'hidden' ); 10454 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 10455 this.controller.$uploaderToggler 10456 .attr( 'aria-expanded', 'false' ) 10457 // Move focus back to the toggle button when closing the uploader. 10458 .trigger( 'focus' ); 10459 } 10460 } 10461 10462 }); 10463 10464 module.exports = UploaderInline; 10465 10466 10467 /***/ }, 10468 10469 /***/ 6442 10470 (module) { 10471 10472 /** 10473 * wp.media.view.UploaderStatusError 10474 * 10475 * @memberOf wp.media.view 10476 * 10477 * @class 10478 * @augments wp.media.View 10479 * @augments wp.Backbone.View 10480 * @augments Backbone.View 10481 */ 10482 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{ 10483 className: 'upload-error', 10484 template: wp.template('uploader-status-error') 10485 }); 10486 10487 module.exports = UploaderStatusError; 10488 10489 10490 /***/ }, 10491 10492 /***/ 8197 10493 (module) { 10494 10495 var View = wp.media.View, 10496 UploaderStatus; 10497 10498 /** 10499 * wp.media.view.UploaderStatus 10500 * 10501 * An uploader status for on-going uploads. 10502 * 10503 * @memberOf wp.media.view 10504 * 10505 * @class 10506 * @augments wp.media.View 10507 * @augments wp.Backbone.View 10508 * @augments Backbone.View 10509 */ 10510 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{ 10511 className: 'media-uploader-status', 10512 template: wp.template('uploader-status'), 10513 10514 events: { 10515 'click .upload-dismiss-errors': 'dismiss' 10516 }, 10517 10518 initialize: function() { 10519 this.queue = wp.Uploader.queue; 10520 this.queue.on( 'add remove reset', this.visibility, this ); 10521 this.queue.on( 'add remove reset change:percent', this.progress, this ); 10522 this.queue.on( 'add remove reset change:uploading', this.info, this ); 10523 10524 this.errors = wp.Uploader.errors; 10525 this.errors.reset(); 10526 this.errors.on( 'add remove reset', this.visibility, this ); 10527 this.errors.on( 'add', this.error, this ); 10528 }, 10529 /** 10530 * Disposes of the uploader status and its associated views. 10531 * 10532 * @return {wp.media.view.UploaderStatus} Returns the instance of the UploaderStatus view. 10533 */ 10534 dispose: function() { 10535 wp.Uploader.queue.off( null, null, this ); 10536 /* 10537 * call 'dispose' directly on the parent class 10538 */ 10539 View.prototype.dispose.apply( this, arguments ); 10540 return this; 10541 }, 10542 10543 visibility: function() { 10544 this.$el.toggleClass( 'uploading', !! this.queue.length ); 10545 this.$el.toggleClass( 'errors', !! this.errors.length ); 10546 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 10547 }, 10548 10549 ready: function() { 10550 _.each({ 10551 '$bar': '.media-progress-bar div', 10552 '$index': '.upload-index', 10553 '$total': '.upload-total', 10554 '$filename': '.upload-filename' 10555 }, function( selector, key ) { 10556 this[ key ] = this.$( selector ); 10557 }, this ); 10558 10559 this.visibility(); 10560 this.progress(); 10561 this.info(); 10562 }, 10563 10564 progress: function() { 10565 var queue = this.queue, 10566 $bar = this.$bar; 10567 10568 if ( ! $bar || ! queue.length ) { 10569 return; 10570 } 10571 10572 $bar.width( ( queue.reduce( function( memo, attachment ) { 10573 if ( ! attachment.get('uploading') ) { 10574 return memo + 100; 10575 } 10576 10577 var percent = attachment.get('percent'); 10578 return memo + ( _.isNumber( percent ) ? percent : 100 ); 10579 }, 0 ) / queue.length ) + '%' ); 10580 }, 10581 10582 info: function() { 10583 var queue = this.queue, 10584 index = 0, active; 10585 10586 if ( ! queue.length ) { 10587 return; 10588 } 10589 10590 active = this.queue.find( function( attachment, i ) { 10591 index = i; 10592 return attachment.get('uploading'); 10593 }); 10594 10595 if ( this.$index && this.$total && this.$filename ) { 10596 this.$index.text( index + 1 ); 10597 this.$total.text( queue.length ); 10598 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 10599 } 10600 }, 10601 /** 10602 * Escapes the filename to prevent XSS attacks. 10603 * 10604 * @param {string} filename The name of the file to be escaped. 10605 * @return {string} Escaped filename. 10606 */ 10607 filename: function( filename ) { 10608 return _.escape( filename ); 10609 }, 10610 /** 10611 * Handles an error event from the uploader queue. 10612 * 10613 * @param {Backbone.Model} error The error model from the uploader queue. 10614 * @return {void} 10615 */ 10616 error: function( error ) { 10617 var statusError = new wp.media.view.UploaderStatusError( { 10618 filename: this.filename( error.get( 'file' ).name ), 10619 message: error.get( 'message' ) 10620 } ); 10621 10622 var buttonClose = this.$el.find( 'button' ); 10623 10624 // Can show additional info here while retrying to create image sub-sizes. 10625 this.views.add( '.upload-errors', statusError, { at: 0 } ); 10626 _.delay( function() { 10627 buttonClose.trigger( 'focus' ); 10628 }, 1000 ); 10629 10630 _.delay( function() { 10631 wp.a11y.speak( error.get( 'message' ) ); 10632 }, 1500 ); 10633 }, 10634 10635 /** 10636 * Dismisses the error messages and resets the uploader errors. 10637 */ 10638 dismiss: function() { 10639 var errors = this.views.get('.upload-errors'); 10640 10641 if ( errors ) { 10642 _.invoke( errors, 'remove' ); 10643 } 10644 wp.Uploader.errors.reset(); 10645 wp.a11y.speak( wp.i18n.__( 'Error dismissed.' ) ); 10646 // Move focus to the modal after the dismiss button gets removed from the DOM. 10647 if ( this.controller.modal ) { 10648 this.controller.modal.focusManager.focus(); 10649 } 10650 } 10651 }); 10652 10653 module.exports = UploaderStatus; 10654 10655 10656 /***/ }, 10657 10658 /***/ 8291 10659 (module) { 10660 10661 var $ = jQuery, 10662 UploaderWindow; 10663 10664 /** 10665 * wp.media.view.UploaderWindow 10666 * 10667 * An uploader window that allows for dragging and dropping media. 10668 * 10669 * @memberOf wp.media.view 10670 * 10671 * @class 10672 * @augments wp.media.View 10673 * @augments wp.Backbone.View 10674 * @augments Backbone.View 10675 * 10676 * @param {Object} [options] Options hash passed to the view. 10677 * @param {Object} [options.uploader] Uploader properties. 10678 * @param {jQuery} [options.uploader.browser] 10679 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 10680 * @param {Object} [options.uploader.params] 10681 */ 10682 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{ 10683 tagName: 'div', 10684 className: 'uploader-window', 10685 template: wp.template('uploader-window'), 10686 10687 initialize: function() { 10688 var uploader; 10689 10690 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 10691 10692 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 10693 dropzone: this.$el, 10694 browser: this.$browser, 10695 params: {} 10696 }); 10697 10698 // Ensure the dropzone is a jQuery collection. 10699 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 10700 uploader.dropzone = $( uploader.dropzone ); 10701 } 10702 10703 this.controller.on( 'activate', this.refresh, this ); 10704 10705 this.controller.on( 'detach', function() { 10706 this.$browser.remove(); 10707 }, this ); 10708 }, 10709 10710 refresh: function() { 10711 if ( this.uploader ) { 10712 this.uploader.refresh(); 10713 } 10714 }, 10715 10716 ready: function() { 10717 var postId = wp.media.view.settings.post.id, 10718 dropzone; 10719 10720 // If the uploader already exists, bail. 10721 if ( this.uploader ) { 10722 return; 10723 } 10724 10725 if ( postId ) { 10726 this.options.uploader.params.post_id = postId; 10727 } 10728 this.uploader = new wp.Uploader( this.options.uploader ); 10729 10730 dropzone = this.uploader.dropzone; 10731 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 10732 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 10733 10734 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 10735 }, 10736 10737 _ready: function() { 10738 this.controller.trigger( 'uploader:ready' ); 10739 }, 10740 10741 show: function() { 10742 var $el = this.$el.show(); 10743 10744 // Ensure that the animation is triggered by waiting until 10745 // the transparent element is painted into the DOM. 10746 _.defer( function() { 10747 $el.css({ opacity: 1 }); 10748 }); 10749 }, 10750 10751 hide: function() { 10752 var $el = this.$el.css({ opacity: 0 }); 10753 10754 wp.media.transition( $el ).done( function() { 10755 // Transition end events are subject to race conditions. 10756 // Make sure that the value is set as intended. 10757 if ( '0' === $el.css('opacity') ) { 10758 $el.hide(); 10759 } 10760 }); 10761 10762 // https://core.trac.wordpress.org/ticket/27341 10763 _.delay( function() { 10764 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 10765 $el.hide(); 10766 } 10767 }, 500 ); 10768 } 10769 }); 10770 10771 module.exports = UploaderWindow; 10772 10773 10774 /***/ }, 10775 10776 /***/ 4747 10777 (module) { 10778 10779 /** 10780 * wp.media.View 10781 * 10782 * The base view class for media. 10783 * 10784 * Undelegating events, removing events from the model, and 10785 * removing events from the controller mirror the code for 10786 * `Backbone.View.dispose` in Backbone 0.9.8 development. 10787 * 10788 * This behavior has since been removed, and should not be used 10789 * outside of the media manager. 10790 * 10791 * @memberOf wp.media 10792 * 10793 * @class 10794 * @augments wp.Backbone.View 10795 * @augments Backbone.View 10796 */ 10797 var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{ 10798 /** 10799 * Constructs the media view. 10800 * 10801 * @param {Object} [options] Options for the view. 10802 */ 10803 constructor: function( options ) { 10804 if ( options && options.controller ) { 10805 this.controller = options.controller; 10806 } 10807 wp.Backbone.View.apply( this, arguments ); 10808 }, 10809 /** 10810 * Disposes of the media view. 10811 * 10812 * @todo The internal comment mentions this might have been a stop-gap 10813 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 10814 * care of this in Backbone.View now. 10815 * 10816 * @return {wp.media.View} Returns itself to allow chaining. 10817 */ 10818 dispose: function() { 10819 /* 10820 * Undelegating events, removing events from the model, and 10821 * removing events from the controller mirror the code for 10822 * `Backbone.View.dispose` in Backbone 0.9.8 development. 10823 */ 10824 this.undelegateEvents(); 10825 10826 if ( this.model && this.model.off ) { 10827 this.model.off( null, null, this ); 10828 } 10829 10830 if ( this.collection && this.collection.off ) { 10831 this.collection.off( null, null, this ); 10832 } 10833 10834 // Unbind controller events. 10835 if ( this.controller && this.controller.off ) { 10836 this.controller.off( null, null, this ); 10837 } 10838 10839 return this; 10840 }, 10841 /** 10842 * Removes the media view. 10843 * 10844 * @return {wp.media.View} Returns itself to allow chaining. 10845 */ 10846 remove: function() { 10847 this.dispose(); 10848 /** 10849 * call 'remove' directly on the parent class 10850 */ 10851 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 10852 } 10853 }); 10854 10855 module.exports = View; 10856 10857 10858 /***/ } 10859 10860 /******/ }); 10861 /************************************************************************/ 10862 /******/ // The module cache 10863 /******/ const __webpack_module_cache__ = {}; 10864 /******/ 10865 /******/ // The require function 10866 /******/ function __webpack_require__(moduleId) { 10867 /******/ // Check if module is in cache 10868 /******/ const cachedModule = __webpack_module_cache__[moduleId]; 10869 /******/ if (cachedModule !== undefined) { 10870 /******/ return cachedModule.exports; 10871 /******/ } 10872 /******/ // Create a new module (and put it into the cache) 10873 /******/ const module = __webpack_module_cache__[moduleId] = { 10874 /******/ // no module.id needed 10875 /******/ // no module.loaded needed 10876 /******/ exports: {} 10877 /******/ }; 10878 /******/ 10879 /******/ // Execute the module function 10880 /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 10881 /******/ 10882 /******/ // Return the exports of the module 10883 /******/ return module.exports; 10884 /******/ } 10885 /******/ 10886 /************************************************************************/ 10887 /** 10888 * @output wp-includes/js/media-views.js 10889 */ 10890 10891 var media = wp.media, 10892 $ = jQuery, 10893 l10n; 10894 10895 media.isTouchDevice = ( 'ontouchend' in document ); 10896 10897 // Link any localized strings. 10898 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 10899 10900 // Link any settings. 10901 media.view.settings = l10n.settings || {}; 10902 delete l10n.settings; 10903 10904 // Copy the `post` setting over to the model settings. 10905 media.model.settings.post = media.view.settings.post; 10906 10907 // Check if the browser supports CSS 3.0 transitions. 10908 $.support.transition = (function(){ 10909 var style = document.documentElement.style, 10910 transitions = { 10911 WebkitTransition: 'webkitTransitionEnd', 10912 MozTransition: 'transitionend', 10913 OTransition: 'oTransitionEnd otransitionend', 10914 transition: 'transitionend' 10915 }, transition; 10916 10917 transition = _.find( _.keys( transitions ), function( transition ) { 10918 return ! _.isUndefined( style[ transition ] ); 10919 }); 10920 10921 return transition && { 10922 end: transitions[ transition ] 10923 }; 10924 }()); 10925 10926 /** 10927 * A shared event bus used to provide events into 10928 * the media workflows that 3rd-party devs can use to hook 10929 * in. 10930 */ 10931 media.events = _.extend( {}, Backbone.Events ); 10932 10933 /** 10934 * Makes it easier to bind events using transitions. 10935 * 10936 * @param {string} selector The CSS selector for the element to bind the transition event to. 10937 * @param {number} sensitivity The maximum time to wait for the transition event before resolving the promise. 10938 * @return {Promise} A promise that resolves when the transition has completed. 10939 */ 10940 media.transition = function( selector, sensitivity ) { 10941 var deferred = $.Deferred(); 10942 10943 sensitivity = sensitivity || 2000; 10944 10945 if ( $.support.transition ) { 10946 if ( ! (selector instanceof $) ) { 10947 selector = $( selector ); 10948 } 10949 10950 // Resolve the deferred when the first element finishes animating. 10951 selector.first().one( $.support.transition.end, deferred.resolve ); 10952 10953 // Just in case the event doesn't trigger, fire a callback. 10954 _.delay( deferred.resolve, sensitivity ); 10955 10956 // Otherwise, execute on the spot. 10957 } else { 10958 deferred.resolve(); 10959 } 10960 10961 return deferred.promise(); 10962 }; 10963 10964 media.controller.Region = __webpack_require__( 9875 ); 10965 media.controller.StateMachine = __webpack_require__( 6150 ); 10966 media.controller.State = __webpack_require__( 5694 ); 10967 10968 media.selectionSync = __webpack_require__( 4181 ); 10969 media.controller.Library = __webpack_require__( 472 ); 10970 media.controller.ImageDetails = __webpack_require__( 705 ); 10971 media.controller.GalleryEdit = __webpack_require__( 2038 ); 10972 media.controller.GalleryAdd = __webpack_require__( 7127 ); 10973 media.controller.CollectionEdit = __webpack_require__( 8612 ); 10974 media.controller.CollectionAdd = __webpack_require__( 7145 ); 10975 media.controller.FeaturedImage = __webpack_require__( 1169 ); 10976 media.controller.ReplaceImage = __webpack_require__( 2275 ); 10977 media.controller.EditImage = __webpack_require__( 5663 ); 10978 media.controller.MediaLibrary = __webpack_require__( 8065 ); 10979 media.controller.Embed = __webpack_require__( 4910 ); 10980 media.controller.Cropper = __webpack_require__( 5422 ); 10981 media.controller.CustomizeImageCropper = __webpack_require__( 9660 ); 10982 media.controller.SiteIconCropper = __webpack_require__( 6172 ); 10983 10984 media.View = __webpack_require__( 4747 ); 10985 media.view.Frame = __webpack_require__( 1061 ); 10986 media.view.MediaFrame = __webpack_require__( 2836 ); 10987 media.view.MediaFrame.Select = __webpack_require__( 455 ); 10988 media.view.MediaFrame.Post = __webpack_require__( 4274 ); 10989 media.view.MediaFrame.ImageDetails = __webpack_require__( 5424 ); 10990 media.view.Modal = __webpack_require__( 2621 ); 10991 media.view.FocusManager = __webpack_require__( 718 ); 10992 media.view.UploaderWindow = __webpack_require__( 8291 ); 10993 media.view.EditorUploader = __webpack_require__( 3674 ); 10994 media.view.UploaderInline = __webpack_require__( 1753 ); 10995 media.view.UploaderStatus = __webpack_require__( 8197 ); 10996 media.view.UploaderStatusError = __webpack_require__( 6442 ); 10997 media.view.Toolbar = __webpack_require__( 5275 ); 10998 media.view.Toolbar.Select = __webpack_require__( 9458 ); 10999 media.view.Toolbar.Embed = __webpack_require__( 397 ); 11000 media.view.Button = __webpack_require__( 846 ); 11001 media.view.ButtonGroup = __webpack_require__( 168 ); 11002 media.view.PriorityList = __webpack_require__( 8815 ); 11003 media.view.MenuItem = __webpack_require__( 9013 ); 11004 media.view.Menu = __webpack_require__( 1 ); 11005 media.view.RouterItem = __webpack_require__( 6327 ); 11006 media.view.Router = __webpack_require__( 4783 ); 11007 media.view.Sidebar = __webpack_require__( 1992 ); 11008 media.view.Attachment = __webpack_require__( 4075 ); 11009 media.view.Attachment.Library = __webpack_require__( 3443 ); 11010 media.view.Attachment.EditLibrary = __webpack_require__( 5232 ); 11011 media.view.Attachments = __webpack_require__( 8142 ); 11012 media.view.Search = __webpack_require__( 2102 ); 11013 media.view.AttachmentFilters = __webpack_require__( 7709 ); 11014 media.view.DateFilter = __webpack_require__( 6472 ); 11015 media.view.AttachmentFilters.Uploaded = __webpack_require__( 1368 ); 11016 media.view.AttachmentFilters.All = __webpack_require__( 7349 ); 11017 media.view.AttachmentsBrowser = __webpack_require__( 6829 ); 11018 media.view.Selection = __webpack_require__( 8282 ); 11019 media.view.Attachment.Selection = __webpack_require__( 3962 ); 11020 media.view.Attachments.Selection = __webpack_require__( 3479 ); 11021 media.view.Attachment.EditSelection = __webpack_require__( 4593 ); 11022 media.view.Settings = __webpack_require__( 1915 ); 11023 media.view.Settings.AttachmentDisplay = __webpack_require__( 7656 ); 11024 media.view.Settings.Gallery = __webpack_require__( 7266 ); 11025 media.view.Settings.Playlist = __webpack_require__( 2356 ); 11026 media.view.Attachment.Details = __webpack_require__( 6090 ); 11027 media.view.AttachmentCompat = __webpack_require__( 2982 ); 11028 media.view.Iframe = __webpack_require__( 1982 ); 11029 media.view.Embed = __webpack_require__( 5741 ); 11030 media.view.Label = __webpack_require__( 4338 ); 11031 media.view.EmbedUrl = __webpack_require__( 7327 ); 11032 media.view.EmbedLink = __webpack_require__( 8232 ); 11033 media.view.EmbedImage = __webpack_require__( 2395 ); 11034 media.view.ImageDetails = __webpack_require__( 2650 ); 11035 media.view.Cropper = __webpack_require__( 7637 ); 11036 media.view.SiteIconCropper = __webpack_require__( 443 ); 11037 media.view.SiteIconPreview = __webpack_require__( 7810 ); 11038 media.view.EditImage = __webpack_require__( 6126 ); 11039 media.view.Spinner = __webpack_require__( 9141 ); 11040 media.view.Heading = __webpack_require__( 170 ); 11041 11042 /******/ })() 11043 ;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Sep 24 08:20:34 2026 | Cross-referenced by PHPXref |