| [ 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 /** 3008 * Compute the accessible name for an attachment tile. 3009 * 3010 * Factored out of attributes() so render() can refresh the label when a 3011 * model finishes loading after the view was created. See #65852. 3012 * 3013 * @since 7.2.0 3014 * 3015 * @return {string} Accessible name for the attachment. 3016 */ 3017 getAriaLabel: function() { 3018 var ariaLabel = this.model.get( 'title' ); 3019 3020 if ( ! ariaLabel ) { 3021 if ( this.model.get( 'uploading' ) ) { 3022 ariaLabel = wp.i18n.__( 'uploading…' ); 3023 } else { 3024 ariaLabel = wp.i18n.__( '(no title)' ); 3025 } 3026 } 3027 3028 return ariaLabel; 3029 }, 3030 3031 attributes: function() { 3032 return { 3033 'tabIndex': 0, 3034 'role': 'checkbox', 3035 'aria-label': this.getAriaLabel(), 3036 'aria-checked': false, 3037 'data-id': this.model.get( 'id' ) 3038 }; 3039 }, 3040 3041 events: { 3042 'click': 'toggleSelectionHandler', 3043 'change [data-setting]': 'updateSetting', 3044 'change [data-setting] input': 'updateSetting', 3045 'change [data-setting] select': 'updateSetting', 3046 'change [data-setting] textarea': 'updateSetting', 3047 'click .attachment-close': 'removeFromLibrary', 3048 'click .check': 'checkClickHandler', 3049 'keydown': 'toggleSelectionHandler' 3050 }, 3051 3052 buttons: {}, 3053 3054 initialize: function() { 3055 var selection = this.options.selection, 3056 options = _.defaults( this.options, { 3057 rerenderOnModelChange: true 3058 } ); 3059 3060 if ( options.rerenderOnModelChange ) { 3061 this.listenTo( this.model, 'change', this.render ); 3062 } else { 3063 this.listenTo( this.model, 'change:percent', this.progress ); 3064 } 3065 this.listenTo( this.model, 'change:title', this._syncTitle ); 3066 this.listenTo( this.model, 'change:caption', this._syncCaption ); 3067 this.listenTo( this.model, 'change:artist', this._syncArtist ); 3068 this.listenTo( this.model, 'change:album', this._syncAlbum ); 3069 3070 // Update the selection. 3071 this.listenTo( this.model, 'add', this.select ); 3072 this.listenTo( this.model, 'remove', this.deselect ); 3073 if ( selection ) { 3074 selection.on( 'reset', this.updateSelect, this ); 3075 // Update the model's details view. 3076 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 3077 this.details( this.model, this.controller.state().get('selection') ); 3078 } 3079 3080 this.listenTo( this.controller.states, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 3081 }, 3082 /** 3083 * Update the view after the model has been saved. 3084 * 3085 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3086 */ 3087 dispose: function() { 3088 var selection = this.options.selection; 3089 3090 // Make sure all settings are saved before removing the view. 3091 this.updateAll(); 3092 3093 if ( selection ) { 3094 selection.off( null, null, this ); 3095 } 3096 /** 3097 * call 'dispose' directly on the parent class 3098 */ 3099 View.prototype.dispose.apply( this, arguments ); 3100 return this; 3101 }, 3102 /** 3103 * Renders the attachment view. 3104 * 3105 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3106 */ 3107 render: function() { 3108 var options = _.defaults( this.model.toJSON(), { 3109 orientation: 'landscape', 3110 uploading: false, 3111 type: '', 3112 subtype: '', 3113 icon: '', 3114 filename: '', 3115 caption: '', 3116 title: '', 3117 dateFormatted: '', 3118 width: '', 3119 height: '', 3120 compat: false, 3121 alt: '', 3122 description: '' 3123 }, this.options ); 3124 3125 options.buttons = this.buttons; 3126 options.describe = this.controller.state().get('describe'); 3127 3128 if ( 'image' === options.type ) { 3129 options.size = this.imageSize(); 3130 } 3131 3132 options.can = {}; 3133 if ( options.nonces ) { 3134 options.can.remove = !! options.nonces['delete']; 3135 options.can.save = !! options.nonces.update; 3136 } 3137 3138 if ( this.controller.state().get('allowLocalEdits') && ! options.uploading ) { 3139 options.allowLocalEdits = true; 3140 } 3141 3142 if ( options.uploading && ! options.percent ) { 3143 options.percent = 0; 3144 } 3145 3146 this.views.detach(); 3147 this.$el.html( this.template( options ) ); 3148 3149 this.$el.toggleClass( 'uploading', options.uploading ); 3150 3151 if ( options.uploading ) { 3152 this.$bar = this.$('.media-progress-bar div'); 3153 } else { 3154 delete this.$bar; 3155 } 3156 3157 // Check if the model is selected. 3158 this.updateSelect(); 3159 3160 // Update the save status. 3161 this.updateSave(); 3162 3163 this.views.render(); 3164 3165 /* 3166 * Backbone only evaluates attributes() when the element is created. 3167 * When a tile is built for an id-only model that is still fetching, 3168 * the aria-label is computed from empty data and never refreshes. 3169 * Re-apply it here once the model has data. Skip subclasses that 3170 * reset attributes (e.g. Attachment.Details). See #65852, #47458. 3171 */ 3172 if ( undefined !== _.result( this, 'attributes' )['aria-label'] ) { 3173 this.$el.attr( 'aria-label', this.getAriaLabel() ); 3174 } 3175 3176 return this; 3177 }, 3178 3179 progress: function() { 3180 if ( this.$bar && this.$bar.length ) { 3181 this.$bar.width( this.model.get('percent') + '%' ); 3182 } 3183 }, 3184 3185 /** 3186 * Toggles the selection state of the attachment. 3187 * 3188 * @param {JQuery.Event} event The jQuery event object that triggered the selection toggle. 3189 */ 3190 toggleSelectionHandler: function( event ) { 3191 var method; 3192 3193 // Don't do anything inside inputs and on the attachment check and remove buttons. 3194 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 3195 return; 3196 } 3197 3198 // Catch arrow events. 3199 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 3200 this.controller.trigger( 'attachment:keydown:arrow', event ); 3201 return; 3202 } 3203 3204 // Catch enter and space events. 3205 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3206 return; 3207 } 3208 3209 event.preventDefault(); 3210 3211 // In the grid view, bubble up an edit:attachment event to the controller. 3212 if ( this.controller.isModeActive( 'grid' ) ) { 3213 if ( this.controller.isModeActive( 'edit' ) ) { 3214 // Pass the current target to restore focus when closing. 3215 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 3216 return; 3217 } 3218 3219 if ( this.controller.isModeActive( 'select' ) ) { 3220 method = 'toggle'; 3221 } 3222 } 3223 3224 if ( event.shiftKey ) { 3225 method = 'between'; 3226 } else if ( event.ctrlKey || event.metaKey ) { 3227 method = 'toggle'; 3228 } 3229 3230 // Avoid toggles when the command or control key is pressed with the enter key to prevent deselecting the last selected attachment. 3231 if ( ( event.metaKey || event.ctrlKey ) && ( 13 === event.keyCode || 10 === event.keyCode ) ) { 3232 return; 3233 } 3234 3235 this.toggleSelection({ 3236 method: method 3237 }); 3238 3239 this.controller.trigger( 'selection:toggle' ); 3240 }, 3241 /** 3242 * Toggles the selection state of the attachment. 3243 * 3244 * @param {Object} options The options for toggling the selection state of the attachment. 3245 */ 3246 toggleSelection: function( options ) { 3247 var collection = this.collection, 3248 selection = this.options.selection, 3249 model = this.model, 3250 method = options && options.method, 3251 single, models, singleIndex, modelIndex; 3252 3253 if ( ! selection ) { 3254 return; 3255 } 3256 3257 single = selection.single(); 3258 method = _.isUndefined( method ) ? selection.multiple : method; 3259 3260 // If the `method` is set to `between`, select all models that 3261 // exist between the current and the selected model. 3262 if ( 'between' === method && single && selection.multiple ) { 3263 // If the models are the same, short-circuit. 3264 if ( single === model ) { 3265 return; 3266 } 3267 3268 singleIndex = collection.indexOf( single ); 3269 modelIndex = collection.indexOf( this.model ); 3270 3271 if ( singleIndex < modelIndex ) { 3272 models = collection.models.slice( singleIndex, modelIndex + 1 ); 3273 } else { 3274 models = collection.models.slice( modelIndex, singleIndex + 1 ); 3275 } 3276 3277 selection.add( models ); 3278 selection.single( model ); 3279 return; 3280 3281 // If the `method` is set to `toggle`, just flip the selection 3282 // status, regardless of whether the model is the single model. 3283 } else if ( 'toggle' === method ) { 3284 selection[ this.selected() ? 'remove' : 'add' ]( model ); 3285 selection.single( model ); 3286 return; 3287 } else if ( 'add' === method ) { 3288 selection.add( model ); 3289 selection.single( model ); 3290 return; 3291 } 3292 3293 // Fixes bug that loses focus when selecting a featured image. 3294 if ( ! method ) { 3295 method = 'add'; 3296 } 3297 3298 if ( method !== 'add' ) { 3299 method = 'reset'; 3300 } 3301 3302 if ( this.selected() ) { 3303 /* 3304 * If the model is the single model, remove it. 3305 * If it is not the same as the single model, 3306 * it now becomes the single model. 3307 */ 3308 selection[ single === model ? 'remove' : 'single' ]( model ); 3309 } else { 3310 /* 3311 * If the model is not selected, run the `method` on the 3312 * selection. By default, we `reset` the selection, but the 3313 * `method` can be set to `add` the model to the selection. 3314 */ 3315 selection[ method ]( model ); 3316 selection.single( model ); 3317 } 3318 }, 3319 3320 updateSelect: function() { 3321 this[ this.selected() ? 'select' : 'deselect' ](); 3322 }, 3323 /** 3324 * Checks if the model is selected in the selection. 3325 * 3326 * @return {void|boolean} True if the model is selected in the selection, false otherwise. 3327 */ 3328 selected: function() { 3329 var selection = this.options.selection; 3330 if ( selection ) { 3331 return !! selection.get( this.model.cid ); 3332 } 3333 }, 3334 /** 3335 * Selects the model in the selection. 3336 * 3337 * @param {Backbone.Model} model The model to select. 3338 * @param {Backbone.Collection} collection The collection containing the model. 3339 */ 3340 select: function( model, collection ) { 3341 var selection = this.options.selection, 3342 controller = this.controller; 3343 3344 /* 3345 * Check if a selection exists and if it's the collection provided. 3346 * If they're not the same collection, bail; we're in another 3347 * selection's event loop. 3348 */ 3349 if ( ! selection || ( collection && collection !== selection ) ) { 3350 return; 3351 } 3352 3353 // Bail if the model is already selected. 3354 if ( this.$el.hasClass( 'selected' ) ) { 3355 return; 3356 } 3357 3358 // Add 'selected' class to model, set aria-checked to true. 3359 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 3360 // Make the checkbox tabable, except in media grid (bulk select mode). 3361 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 3362 this.$( '.check' ).attr( 'tabindex', '0' ); 3363 } 3364 }, 3365 /** 3366 * Deselects the model in the selection. 3367 * 3368 * @param {Backbone.Model} model The model to deselect. 3369 * @param {Backbone.Collection} collection The collection containing the model. 3370 */ 3371 deselect: function( model, collection ) { 3372 var selection = this.options.selection; 3373 3374 /* 3375 * Check if a selection exists and if it's the collection provided. 3376 * If they're not the same collection, bail; we're in another 3377 * selection's event loop. 3378 */ 3379 if ( ! selection || ( collection && collection !== selection ) ) { 3380 return; 3381 } 3382 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 3383 .find( '.check' ).attr( 'tabindex', '-1' ); 3384 }, 3385 /** 3386 * Updates the view to reflect whether the model is the single model in the selection. 3387 * 3388 * @param {Backbone.Model} model The model to check for details. 3389 * @param {Backbone.Collection} collection The collection containing the model. 3390 */ 3391 details: function( model, collection ) { 3392 var selection = this.options.selection, 3393 details; 3394 3395 if ( selection !== collection ) { 3396 return; 3397 } 3398 3399 details = selection.single(); 3400 this.$el.toggleClass( 'details', details === this.model ); 3401 }, 3402 /** 3403 * Gets the image size object for the specified size. 3404 * 3405 * @param {string} size The desired image size. 3406 * @return {Object} Returns an object containing the image size information. 3407 */ 3408 imageSize: function( size ) { 3409 var sizes = this.model.get('sizes'), matched = false; 3410 3411 size = size || 'medium'; 3412 3413 // Use the provided image size if possible. 3414 if ( sizes ) { 3415 if ( sizes[ size ] ) { 3416 matched = sizes[ size ]; 3417 } else if ( sizes.large ) { 3418 matched = sizes.large; 3419 } else if ( sizes.thumbnail ) { 3420 matched = sizes.thumbnail; 3421 } else if ( sizes.full ) { 3422 matched = sizes.full; 3423 } 3424 3425 if ( matched ) { 3426 return _.clone( matched ); 3427 } 3428 } 3429 3430 return { 3431 url: this.model.get('url'), 3432 width: this.model.get('width'), 3433 height: this.model.get('height'), 3434 orientation: this.model.get('orientation') 3435 }; 3436 }, 3437 /** 3438 * Update the model's setting with the value from the input. 3439 * 3440 * @param {JQuery.Event} event The jQuery event object that triggered the setting update. 3441 */ 3442 updateSetting: function( event ) { 3443 var $setting = $( event.target ).closest('[data-setting]'), 3444 setting, value; 3445 3446 if ( ! $setting.length ) { 3447 return; 3448 } 3449 3450 setting = $setting.data('setting'); 3451 value = event.target.value; 3452 3453 if ( this.model.get( setting ) !== value ) { 3454 this.save( setting, value ); 3455 } 3456 }, 3457 3458 /** 3459 * Pass all the arguments to the model's save method. 3460 * 3461 * Records the aggregate status of all save requests and updates the 3462 * view's classes accordingly. 3463 */ 3464 save: function() { 3465 var view = this, 3466 save = this._save = this._save || { status: 'ready' }, 3467 request = this.model.save.apply( this.model, arguments ), 3468 requests = save.requests ? $.when( request, save.requests ) : request; 3469 3470 // If we're waiting to remove 'Saved.', stop. 3471 if ( save.savedTimer ) { 3472 clearTimeout( save.savedTimer ); 3473 } 3474 3475 this.updateSave('waiting'); 3476 save.requests = requests; 3477 requests.always( function() { 3478 // If we've performed another request since this one, bail. 3479 if ( save.requests !== requests ) { 3480 return; 3481 } 3482 3483 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 3484 save.savedTimer = setTimeout( function() { 3485 view.updateSave('ready'); 3486 delete save.savedTimer; 3487 }, 2000 ); 3488 }); 3489 }, 3490 /** 3491 * Updates the view's save status. 3492 * 3493 * @param {string} status The new save status to apply to the view. 3494 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3495 */ 3496 updateSave: function( status ) { 3497 var save = this._save = this._save || { status: 'ready' }; 3498 3499 if ( status && status !== save.status ) { 3500 this.$el.removeClass( 'save-' + save.status ); 3501 save.status = status; 3502 } 3503 3504 this.$el.addClass( 'save-' + save.status ); 3505 return this; 3506 }, 3507 3508 updateAll: function() { 3509 var $settings = this.$('[data-setting]'), 3510 model = this.model, 3511 changed; 3512 3513 changed = _.chain( $settings ).map( function( el ) { 3514 var $input = $('input, textarea, select, [value]', el ), 3515 setting, value; 3516 3517 if ( ! $input.length ) { 3518 return; 3519 } 3520 3521 setting = $(el).data('setting'); 3522 value = $input.val(); 3523 3524 // Record the value if it changed. 3525 if ( model.get( setting ) !== value ) { 3526 return [ setting, value ]; 3527 } 3528 }).compact().object().value(); 3529 3530 if ( ! _.isEmpty( changed ) ) { 3531 model.save( changed ); 3532 } 3533 }, 3534 /** 3535 * Removes the model from the collection. 3536 * 3537 * @param {JQuery.Event} event The jQuery event object that triggered the removal from the library. 3538 */ 3539 removeFromLibrary: function( event ) { 3540 // Catch enter and space events. 3541 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3542 return; 3543 } 3544 3545 // Stop propagation so the model isn't selected. 3546 event.stopPropagation(); 3547 3548 this.collection.remove( this.model ); 3549 }, 3550 3551 /** 3552 * Adds the model if it isn't in the selection, if it is in the selection, 3553 * removes it. 3554 * 3555 * @param {JQuery.Event} event The jQuery event object that triggered the selection toggle. 3556 * @return {void} 3557 */ 3558 checkClickHandler: function ( event ) { 3559 var selection = this.options.selection; 3560 if ( ! selection ) { 3561 return; 3562 } 3563 event.stopPropagation(); 3564 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 3565 selection.remove( this.model ); 3566 // Move focus back to the attachment tile (from the check). 3567 this.$el.focus(); 3568 } else { 3569 selection.add( this.model ); 3570 } 3571 3572 // Trigger an action button update. 3573 this.controller.trigger( 'selection:toggle' ); 3574 } 3575 }); 3576 3577 // Ensure settings remain in sync between attachment views. 3578 _.each({ 3579 caption: '_syncCaption', 3580 title: '_syncTitle', 3581 artist: '_syncArtist', 3582 album: '_syncAlbum' 3583 }, function( method, setting ) { 3584 /** 3585 * Updates the DOM when the model's caption changes. 3586 * 3587 * @function _syncCaption 3588 * @memberOf wp.media.view.Attachment 3589 * @instance 3590 * 3591 * @param {Backbone.Model} model The model containing the caption. 3592 * @param {string} value The new caption value. 3593 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3594 */ 3595 /** 3596 * Updates the DOM when the model's title changes. 3597 * 3598 * @function _syncTitle 3599 * @memberOf wp.media.view.Attachment 3600 * @instance 3601 * 3602 * @param {Backbone.Model} model The model containing the title. 3603 * @param {string} value The new title value. 3604 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3605 */ 3606 /** 3607 * Updates the DOM when the model's artist changes. 3608 * 3609 * @function _syncArtist 3610 * @memberOf wp.media.view.Attachment 3611 * @instance 3612 * 3613 * @param {Backbone.Model} model The model containing the artist. 3614 * @param {string} value The new artist value. 3615 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3616 */ 3617 /** 3618 * Updates the DOM when the model's album changes. 3619 * 3620 * @function _syncAlbum 3621 * @memberOf wp.media.view.Attachment 3622 * @instance 3623 * 3624 * @param {Backbone.Model} model The model containing the album. 3625 * @param {string} value The new album value. 3626 * @return {wp.media.view.Attachment} Returns itself to allow chaining. 3627 */ 3628 Attachment.prototype[ method ] = function( model, value ) { 3629 var $setting = this.$('[data-setting="' + setting + '"]'); 3630 3631 if ( ! $setting.length ) { 3632 return this; 3633 } 3634 3635 /* 3636 * If the updated value is in sync with the value in the DOM, there 3637 * is no need to re-render. If we're currently editing the value, 3638 * it will automatically be in sync, suppressing the re-render for 3639 * the view we're editing, while updating any others. 3640 */ 3641 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 3642 return this; 3643 } 3644 3645 return this.render(); 3646 }; 3647 }); 3648 3649 module.exports = Attachment; 3650 3651 3652 /***/ }, 3653 3654 /***/ 6090 3655 (module) { 3656 3657 /* global ClipboardJS */ 3658 var Attachment = wp.media.view.Attachment, 3659 l10n = wp.media.view.l10n, 3660 $ = jQuery, 3661 Details, 3662 __ = wp.i18n.__; 3663 3664 Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{ 3665 tagName: 'div', 3666 className: 'attachment-details', 3667 template: wp.template('attachment-details'), 3668 3669 /* 3670 * Reset all the attributes inherited from Attachment including role=checkbox, 3671 * tabindex, etc., as they are inappropriate for this view. See #47458 and [30483] / #30390. 3672 */ 3673 attributes: {}, 3674 3675 events: { 3676 'change [data-setting]': 'updateSetting', 3677 'change [data-setting] input': 'updateSetting', 3678 'change [data-setting] select': 'updateSetting', 3679 'change [data-setting] textarea': 'updateSetting', 3680 'click .delete-attachment': 'deleteAttachment', 3681 'click .trash-attachment': 'trashAttachment', 3682 'click .untrash-attachment': 'untrashAttachment', 3683 'click .edit-attachment': 'editAttachment', 3684 'keydown': 'toggleSelectionHandler' 3685 }, 3686 3687 /** 3688 * Copies the attachment URL to the clipboard. 3689 * 3690 * @since 5.5.0 3691 * 3692 * @return {void} 3693 */ 3694 copyAttachmentDetailsURLClipboard: function() { 3695 var clipboard = new ClipboardJS( '.copy-attachment-url' ), 3696 successTimeout; 3697 3698 clipboard.on( 'success', function( event ) { 3699 var triggerElement = $( event.trigger ), 3700 successElement = $( '.success', triggerElement.closest( '.copy-to-clipboard-container' ) ); 3701 3702 // Clear the selection and move focus back to the trigger. 3703 event.clearSelection(); 3704 3705 // Show success visual feedback. 3706 clearTimeout( successTimeout ); 3707 successElement.removeClass( 'hidden' ); 3708 3709 // Hide success visual feedback after 3 seconds since last success. 3710 successTimeout = setTimeout( function() { 3711 successElement.addClass( 'hidden' ); 3712 }, 3000 ); 3713 3714 // Handle success audible feedback. 3715 wp.a11y.speak( __( 'The file URL has been copied to your clipboard' ) ); 3716 } ); 3717 }, 3718 3719 /** 3720 * Shows the details of an attachment. 3721 * 3722 * @since 3.5.0 3723 * 3724 * @constructs wp.media.view.Attachment.Details 3725 * @augments wp.media.view.Attachment 3726 * 3727 * @return {void} 3728 */ 3729 initialize: function() { 3730 this.options = _.defaults( this.options, { 3731 rerenderOnModelChange: false 3732 }); 3733 3734 // Call 'initialize' directly on the parent class. 3735 Attachment.prototype.initialize.apply( this, arguments ); 3736 3737 this.copyAttachmentDetailsURLClipboard(); 3738 }, 3739 3740 /** 3741 * Gets the focusable elements to move focus to. 3742 * 3743 * @since 5.3.0 3744 */ 3745 getFocusableElements: function() { 3746 var editedAttachment = $( 'li[data-id="' + this.model.id + '"]' ); 3747 3748 this.previousAttachment = editedAttachment.prev(); 3749 this.nextAttachment = editedAttachment.next(); 3750 }, 3751 3752 /** 3753 * Moves focus to the previous or next attachment in the grid. 3754 * Fallbacks to the upload button or media frame when there are no attachments. 3755 * 3756 * @since 5.3.0 3757 */ 3758 moveFocus: function() { 3759 if ( this.previousAttachment.length ) { 3760 this.previousAttachment.trigger( 'focus' ); 3761 return; 3762 } 3763 3764 if ( this.nextAttachment.length ) { 3765 this.nextAttachment.trigger( 'focus' ); 3766 return; 3767 } 3768 3769 // Fallback: move focus to the "Select Files" button in the media modal. 3770 if ( this.controller.uploader && this.controller.uploader.$browser ) { 3771 this.controller.uploader.$browser.trigger( 'focus' ); 3772 return; 3773 } 3774 3775 // Last fallback. 3776 this.moveFocusToLastFallback(); 3777 }, 3778 3779 /** 3780 * Moves focus to the media frame as last fallback. 3781 * 3782 * @since 5.3.0 3783 */ 3784 moveFocusToLastFallback: function() { 3785 // Last fallback: make the frame focusable and move focus to it. 3786 $( '.media-frame' ) 3787 .attr( 'tabindex', '-1' ) 3788 .trigger( 'focus' ); 3789 }, 3790 3791 /** 3792 * Deletes an attachment. 3793 * 3794 * Deletes an attachment after asking for confirmation. After deletion, 3795 * keeps focus in the modal. 3796 * 3797 * @since 3.5.0 3798 * 3799 * @param {MouseEvent} event A click event. 3800 * 3801 * @return {void} 3802 */ 3803 deleteAttachment: function( event ) { 3804 event.preventDefault(); 3805 3806 this.getFocusableElements(); 3807 3808 if ( window.confirm( l10n.warnDelete ) ) { 3809 this.model.destroy( { 3810 wait: true, 3811 error: function() { 3812 window.alert( l10n.errorDeleting ); 3813 } 3814 } ); 3815 3816 this.moveFocus(); 3817 } 3818 }, 3819 3820 /** 3821 * Sets the Trash state on an attachment, or destroys the model itself. 3822 * 3823 * If the mediaTrash setting is set to true, trashes the attachment. 3824 * Otherwise, the model itself is destroyed. 3825 * 3826 * @since 3.9.0 3827 * 3828 * @param {MouseEvent} event A click event. 3829 * 3830 * @return {void} 3831 */ 3832 trashAttachment: function( event ) { 3833 var library = this.controller.library, 3834 self = this; 3835 event.preventDefault(); 3836 3837 this.getFocusableElements(); 3838 3839 // When in the Media Library and the Media Trash is enabled. 3840 if ( wp.media.view.settings.mediaTrash && 3841 'edit-metadata' === this.controller.content.mode() ) { 3842 3843 this.model.set( 'status', 'trash' ); 3844 this.model.save().done( function() { 3845 library._requery( true ); 3846 /* 3847 * @todo We need to move focus back to the previous, next, or first 3848 * attachment but the library gets re-queried and refreshed. 3849 * Thus, the references to the previous attachments are lost. 3850 * We need an alternate method. 3851 */ 3852 self.moveFocusToLastFallback(); 3853 } ); 3854 } else { 3855 this.model.destroy(); 3856 this.moveFocus(); 3857 } 3858 }, 3859 3860 /** 3861 * Untrashes an attachment. 3862 * 3863 * @since 4.0.0 3864 * 3865 * @param {MouseEvent} event A click event. 3866 * 3867 * @return {void} 3868 */ 3869 untrashAttachment: function( event ) { 3870 var library = this.controller.library; 3871 event.preventDefault(); 3872 3873 this.model.set( 'status', 'inherit' ); 3874 this.model.save().done( function() { 3875 library._requery( true ); 3876 } ); 3877 }, 3878 3879 /** 3880 * Opens the edit page for a specific attachment. 3881 * 3882 * @since 3.5.0 3883 * 3884 * @param {MouseEvent} event A click event. 3885 * 3886 * @return {void} 3887 */ 3888 editAttachment: function( event ) { 3889 var editState = this.controller.states.get( 'edit-image' ); 3890 if ( window.imageEdit && editState ) { 3891 event.preventDefault(); 3892 3893 editState.set( 'image', this.model ); 3894 this.controller.setState( 'edit-image' ); 3895 } else { 3896 this.$el.addClass('needs-refresh'); 3897 } 3898 }, 3899 3900 /** 3901 * Triggers an event on the controller when reverse tabbing (shift+tab). 3902 * 3903 * This event can be used to make sure to move the focus correctly. 3904 * 3905 * Fires `attachment:details:shift-tab` and `attachment:keydown:arrow` events on the controller. 3906 * 3907 * @since 4.0.0 3908 * 3909 * @param {KeyboardEvent} event A keyboard event. 3910 * 3911 * @return {boolean|void} Returns false or undefined. 3912 */ 3913 toggleSelectionHandler: function( event ) { 3914 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 3915 this.controller.trigger( 'attachment:details:shift-tab', event ); 3916 return false; 3917 } 3918 }, 3919 3920 render: function() { 3921 Attachment.prototype.render.apply( this, arguments ); 3922 3923 wp.media.mixin.removeAllPlayers(); 3924 this.$( 'audio, video' ).each( function (i, elem) { 3925 var el = wp.media.view.MediaDetails.prepareSrc( elem ); 3926 new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings ); 3927 } ); 3928 } 3929 }); 3930 3931 module.exports = Details; 3932 3933 3934 /***/ }, 3935 3936 /***/ 5232 3937 (module) { 3938 3939 /** 3940 * wp.media.view.Attachment.EditLibrary 3941 * 3942 * @memberOf wp.media.view.Attachment 3943 * 3944 * @class 3945 * @augments wp.media.view.Attachment 3946 * @augments wp.media.View 3947 * @augments wp.Backbone.View 3948 * @augments Backbone.View 3949 */ 3950 var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{ 3951 buttons: { 3952 close: true 3953 } 3954 }); 3955 3956 module.exports = EditLibrary; 3957 3958 3959 /***/ }, 3960 3961 /***/ 4593 3962 (module) { 3963 3964 /** 3965 * wp.media.view.Attachment.EditSelection 3966 * 3967 * @memberOf wp.media.view.Attachment 3968 * 3969 * @class 3970 * @augments wp.media.view.Attachment.Selection 3971 * @augments wp.media.view.Attachment 3972 * @augments wp.media.View 3973 * @augments wp.Backbone.View 3974 * @augments Backbone.View 3975 */ 3976 var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{ 3977 buttons: { 3978 close: true 3979 } 3980 }); 3981 3982 module.exports = EditSelection; 3983 3984 3985 /***/ }, 3986 3987 /***/ 3443 3988 (module) { 3989 3990 /** 3991 * wp.media.view.Attachment.Library 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 Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{ 4002 buttons: { 4003 check: true 4004 } 4005 }); 4006 4007 module.exports = Library; 4008 4009 4010 /***/ }, 4011 4012 /***/ 3962 4013 (module) { 4014 4015 /** 4016 * wp.media.view.Attachment.Selection 4017 * 4018 * @memberOf wp.media.view.Attachment 4019 * 4020 * @class 4021 * @augments wp.media.view.Attachment 4022 * @augments wp.media.View 4023 * @augments wp.Backbone.View 4024 * @augments Backbone.View 4025 */ 4026 var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{ 4027 className: 'attachment selection', 4028 4029 // On click, just select the model, instead of removing the model from 4030 // the selection. 4031 toggleSelection: function() { 4032 this.options.selection.single( this.model ); 4033 } 4034 }); 4035 4036 module.exports = Selection; 4037 4038 4039 /***/ }, 4040 4041 /***/ 8142 4042 (module) { 4043 4044 var View = wp.media.View, 4045 $ = jQuery, 4046 Attachments, 4047 infiniteScrolling = wp.media.view.settings.infiniteScrolling; 4048 4049 Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{ 4050 tagName: 'ul', 4051 className: 'attachments', 4052 4053 attributes: { 4054 role: 'group', 4055 tabIndex: -1 4056 }, 4057 4058 /** 4059 * Represents the overview of attachments in the Media Library. 4060 * 4061 * The constructor binds events to the collection this view represents when 4062 * adding or removing attachments or resetting the entire collection. 4063 * 4064 * Listens for `collection:add`, `collection:remove`, `collection:reset`, 4065 * `controller:library:selection:add`, `scrollElement:scroll`, `this:ready`, 4066 * and `controller:open` events. 4067 * 4068 * @since 3.5.0 4069 * 4070 * @constructs 4071 * @memberof wp.media.view 4072 * 4073 * @augments wp.media.View 4074 */ 4075 initialize: function() { 4076 this.el.id = _.uniqueId('__attachments-view-'); 4077 4078 /** 4079 * @since 5.8.0 Added the `infiniteScrolling` parameter. 4080 * 4081 * @param infiniteScrolling Whether to enable infinite scrolling or use 4082 * the default "load more" button. 4083 * @param refreshSensitivity The time in milliseconds to throttle the scroll 4084 * handler. 4085 * @param refreshThreshold The amount of pixels that should be scrolled before 4086 * loading more attachments from the server. 4087 * @param AttachmentView The view class to be used for models in the 4088 * collection. 4089 * @param sortable A jQuery sortable options object 4090 * ( http://api.jqueryui.com/sortable/ ). 4091 * @param resize A boolean indicating whether or not to listen to 4092 * resize events. 4093 * @param idealColumnWidth The width in pixels which a column should have when 4094 * calculating the total number of columns. 4095 */ 4096 _.defaults( this.options, { 4097 infiniteScrolling: infiniteScrolling || false, 4098 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 4099 refreshThreshold: 3, 4100 AttachmentView: wp.media.view.Attachment, 4101 sortable: false, 4102 resize: true, 4103 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 4104 }); 4105 4106 this._viewsByCid = {}; 4107 this.$window = $( window ); 4108 this.resizeEvent = 'resize.media-modal-columns'; 4109 4110 this.collection.on( 'add', function( attachment ) { 4111 this.views.add( this.createAttachmentView( attachment ), { 4112 at: this.collection.indexOf( attachment ) 4113 }); 4114 }, this ); 4115 4116 /* 4117 * Find the view to be removed, delete it and call the remove function to clear 4118 * any set event handlers. 4119 */ 4120 this.collection.on( 'remove', function( attachment ) { 4121 var view = this._viewsByCid[ attachment.cid ]; 4122 delete this._viewsByCid[ attachment.cid ]; 4123 4124 if ( view ) { 4125 view.remove(); 4126 } 4127 }, this ); 4128 4129 this.collection.on( 'reset', this.render, this ); 4130 4131 this.controller.on( 'library:selection:add', this.attachmentFocus, this ); 4132 4133 if ( this.options.infiniteScrolling ) { 4134 // Throttle the scroll handler and bind this. 4135 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 4136 4137 this.options.scrollElement = this.options.scrollElement || this.el; 4138 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 4139 } 4140 4141 this.initSortable(); 4142 4143 _.bindAll( this, 'setColumns' ); 4144 4145 if ( this.options.resize ) { 4146 this.on( 'ready', this.bindEvents ); 4147 this.controller.on( 'open', this.setColumns ); 4148 4149 /* 4150 * Call this.setColumns() after this view has been rendered in the 4151 * DOM so attachments get proper width applied. 4152 */ 4153 _.defer( this.setColumns, this ); 4154 } 4155 }, 4156 4157 /** 4158 * Listens to the resizeEvent on the window. 4159 * 4160 * Adjusts the amount of columns accordingly. First removes any existing event 4161 * handlers to prevent duplicate listeners. 4162 * 4163 * Listens for the `window:resize` event. 4164 * 4165 * @since 4.0.0 4166 * 4167 * @return {void} 4168 */ 4169 bindEvents: function() { 4170 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 4171 }, 4172 4173 /** 4174 * Focuses the first item in the collection. 4175 * 4176 * @since 4.0.0 4177 * 4178 * @return {void} 4179 */ 4180 attachmentFocus: function() { 4181 /* 4182 * @todo When uploading new attachments, this tries to move focus to 4183 * the attachments grid. Actually, a progress bar gets initially displayed 4184 * and then updated when uploading completes, so focus is lost. 4185 * Additionally: this view is used for both the attachments list and 4186 * the list of selected attachments in the bottom media toolbar. Thus, when 4187 * uploading attachments, it is called twice and returns two different `this`. 4188 * `this.columns` is truthy within the modal. 4189 */ 4190 if ( this.columns ) { 4191 // Move focus to the grid list within the modal. 4192 this.$el.focus(); 4193 } 4194 }, 4195 4196 /** 4197 * Restores focus to the selected item in the collection. 4198 * 4199 * Moves focus back to the first selected attachment in the grid. Used when 4200 * tabbing backwards from the attachment details sidebar. 4201 * See media.view.AttachmentsBrowser. 4202 * 4203 * @since 4.0.0 4204 * 4205 * @return {void} 4206 */ 4207 restoreFocus: function() { 4208 this.$( 'li.selected:first' ).focus(); 4209 }, 4210 4211 /** 4212 * Handles events for arrow key presses. 4213 * 4214 * Focuses the attachment in the direction of the used arrow key if it exists. 4215 * 4216 * @since 4.0.0 4217 * 4218 * @param {KeyboardEvent} event The keyboard event that triggered this function. 4219 * 4220 * @return {void} 4221 */ 4222 arrowEvent: function( event ) { 4223 var attachments = this.$el.children( 'li' ), 4224 perRow = this.columns, 4225 index = attachments.filter( ':focus' ).index(), 4226 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 4227 4228 if ( index === -1 ) { 4229 return; 4230 } 4231 4232 // Left arrow = 37. 4233 if ( 37 === event.keyCode ) { 4234 if ( 0 === index ) { 4235 return; 4236 } 4237 attachments.eq( index - 1 ).focus(); 4238 } 4239 4240 // Up arrow = 38. 4241 if ( 38 === event.keyCode ) { 4242 if ( 1 === row ) { 4243 return; 4244 } 4245 attachments.eq( index - perRow ).focus(); 4246 } 4247 4248 // Right arrow = 39. 4249 if ( 39 === event.keyCode ) { 4250 if ( attachments.length === index ) { 4251 return; 4252 } 4253 attachments.eq( index + 1 ).focus(); 4254 } 4255 4256 // Down arrow = 40. 4257 if ( 40 === event.keyCode ) { 4258 if ( Math.ceil( attachments.length / perRow ) === row ) { 4259 return; 4260 } 4261 attachments.eq( index + perRow ).focus(); 4262 } 4263 }, 4264 4265 /** 4266 * Clears any set event handlers. 4267 * 4268 * @since 3.5.0 4269 * 4270 * @return {void} 4271 */ 4272 dispose: function() { 4273 this.collection.props.off( null, null, this ); 4274 if ( this.options.resize ) { 4275 this.$window.off( this.resizeEvent ); 4276 } 4277 4278 // Call 'dispose' directly on the parent class. 4279 View.prototype.dispose.apply( this, arguments ); 4280 }, 4281 4282 /** 4283 * Calculates the amount of columns. 4284 * 4285 * Calculates the amount of columns and sets it on the data-columns attribute 4286 * of .media-frame-content. 4287 * 4288 * @since 4.0.0 4289 * 4290 * @return {void} 4291 */ 4292 setColumns: function() { 4293 var prev = this.columns, 4294 width = this.$el.width(); 4295 4296 if ( width ) { 4297 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 4298 4299 if ( ! prev || prev !== this.columns ) { 4300 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 4301 } 4302 } 4303 }, 4304 4305 /** 4306 * Initializes jQuery sortable on the attachment list. 4307 * 4308 * Fails gracefully if jQuery sortable doesn't exist or isn't passed 4309 * in the options. 4310 * 4311 * Fires `collection:reset`. 4312 * 4313 * @since 3.5.0 4314 * 4315 * @return {void} 4316 */ 4317 initSortable: function() { 4318 var collection = this.collection; 4319 4320 if ( ! this.options.sortable || ! $.fn.sortable ) { 4321 return; 4322 } 4323 4324 this.$el.sortable( _.extend({ 4325 // If the `collection` has a `comparator`, disable sorting. 4326 disabled: !! collection.comparator, 4327 4328 /* 4329 * Change the position of the attachment as soon as the mouse pointer 4330 * overlaps a thumbnail. 4331 */ 4332 tolerance: 'pointer', 4333 4334 // Record the initial `index` of the dragged model. 4335 start: function( event, ui ) { 4336 ui.item.data('sortableIndexStart', ui.item.index()); 4337 }, 4338 4339 /* 4340 * Update the model's index in the collection. Do so silently, as the view 4341 * is already accurate. 4342 */ 4343 update: function( event, ui ) { 4344 var model = collection.at( ui.item.data('sortableIndexStart') ), 4345 comparator = collection.comparator; 4346 4347 // Temporarily disable the comparator to prevent `add` 4348 // from re-sorting. 4349 delete collection.comparator; 4350 4351 // Silently shift the model to its new index. 4352 collection.remove( model, { 4353 silent: true 4354 }); 4355 collection.add( model, { 4356 silent: true, 4357 at: ui.item.index() 4358 }); 4359 4360 // Restore the comparator. 4361 collection.comparator = comparator; 4362 4363 // Fire the `reset` event to ensure other collections sync. 4364 collection.trigger( 'reset', collection ); 4365 4366 // If the collection is sorted by menu order, update the menu order. 4367 collection.saveMenuOrder(); 4368 } 4369 }, this.options.sortable ) ); 4370 4371 /* 4372 * If the `orderby` property is changed on the `collection`, 4373 * check to see if we have a `comparator`. If so, disable sorting. 4374 */ 4375 collection.props.on( 'change:orderby', function() { 4376 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 4377 }, this ); 4378 4379 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 4380 this.refreshSortable(); 4381 }, 4382 4383 /** 4384 * Disables jQuery sortable if collection has a comparator or collection.orderby 4385 * equals menuOrder. 4386 * 4387 * @since 3.5.0 4388 * 4389 * @return {void} 4390 */ 4391 refreshSortable: function() { 4392 if ( ! this.options.sortable || ! $.fn.sortable ) { 4393 return; 4394 } 4395 4396 var collection = this.collection, 4397 orderby = collection.props.get('orderby'), 4398 enabled = 'menuOrder' === orderby || ! collection.comparator; 4399 4400 this.$el.sortable( 'option', 'disabled', ! enabled ); 4401 }, 4402 4403 /** 4404 * Creates a new view for an attachment and adds it to _viewsByCid. 4405 * 4406 * @since 3.5.0 4407 * 4408 * @param {wp.media.model.Attachment} attachment The attachment model for which to create a view. 4409 * 4410 * @return {wp.media.View} The created view. 4411 */ 4412 createAttachmentView: function( attachment ) { 4413 var view = new this.options.AttachmentView({ 4414 controller: this.controller, 4415 model: attachment, 4416 collection: this.collection, 4417 selection: this.options.selection 4418 }); 4419 4420 return this._viewsByCid[ attachment.cid ] = view; 4421 }, 4422 4423 /** 4424 * Prepares view for display. 4425 * 4426 * Creates views for every attachment in collection if the collection is not 4427 * empty, otherwise clears all views and loads more attachments. 4428 * 4429 * @since 3.5.0 4430 * 4431 * @return {void} 4432 */ 4433 prepare: function() { 4434 if ( this.collection.length ) { 4435 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 4436 } else { 4437 this.views.unset(); 4438 if ( this.options.infiniteScrolling ) { 4439 this.collection.more().done( this.scroll ); 4440 } 4441 } 4442 }, 4443 4444 /** 4445 * Triggers the scroll function to check if we should query for additional 4446 * attachments right away. 4447 * 4448 * @since 3.5.0 4449 * 4450 * @return {void} 4451 */ 4452 ready: function() { 4453 if ( this.options.infiniteScrolling ) { 4454 this.scroll(); 4455 } 4456 }, 4457 4458 /** 4459 * Handles scroll events. 4460 * 4461 * Shows the spinner if we're close to the bottom. Loads more attachments from 4462 * server if we're {refreshThreshold} times away from the bottom. 4463 * 4464 * @since 3.5.0 4465 * 4466 * @return {void} 4467 */ 4468 scroll: function() { 4469 var view = this, 4470 el = this.options.scrollElement, 4471 scrollTop = el.scrollTop, 4472 toolbar; 4473 4474 /* 4475 * The scroll event occurs on the document, but the element that should be 4476 * checked is the document body. 4477 */ 4478 if ( el === document ) { 4479 el = document.body; 4480 scrollTop = $(document).scrollTop(); 4481 } 4482 4483 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 4484 return; 4485 } 4486 4487 toolbar = this.views.parent.toolbar; 4488 4489 // Show the spinner only if we are close to the bottom. 4490 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 4491 toolbar.get('spinner').show(); 4492 } 4493 4494 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 4495 this.collection.more().done(function() { 4496 view.scroll(); 4497 toolbar.get('spinner').hide(); 4498 }); 4499 } 4500 } 4501 }); 4502 4503 module.exports = Attachments; 4504 4505 4506 /***/ }, 4507 4508 /***/ 6829 4509 (module) { 4510 4511 var View = wp.media.View, 4512 mediaTrash = wp.media.view.settings.mediaTrash, 4513 l10n = wp.media.view.l10n, 4514 $ = jQuery, 4515 AttachmentsBrowser, 4516 infiniteScrolling = wp.media.view.settings.infiniteScrolling, 4517 __ = wp.i18n.__, 4518 sprintf = wp.i18n.sprintf; 4519 4520 /** 4521 * wp.media.view.AttachmentsBrowser 4522 * 4523 * @memberOf wp.media.view 4524 * 4525 * @class 4526 * @augments wp.media.View 4527 * @augments wp.Backbone.View 4528 * @augments Backbone.View 4529 * 4530 * @param {Object} [options] The options hash passed to the view. 4531 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar. 4532 * Accepts 'uploaded' and 'all'. 4533 * @param {boolean} [options.search=true] Whether to show the search interface in the 4534 * browser's toolbar. 4535 * @param {boolean} [options.date=true] Whether to show the date filter in the 4536 * browser's toolbar. 4537 * @param {boolean} [options.display=false] Whether to show the attachments display settings 4538 * view in the sidebar. 4539 * @param {boolean|string} [options.sidebar=true] Whether to create a sidebar for the browser. 4540 * Accepts true, false, and 'errors'. 4541 */ 4542 AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{ 4543 tagName: 'div', 4544 className: 'attachments-browser', 4545 4546 /** 4547 * Initializes the AttachmentsBrowser view. 4548 */ 4549 initialize: function() { 4550 _.defaults( this.options, { 4551 filters: false, 4552 search: true, 4553 date: true, 4554 display: false, 4555 sidebar: true, 4556 AttachmentView: wp.media.view.Attachment.Library 4557 }); 4558 4559 this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this ); 4560 this.controller.on( 'edit:selection', this.editSelection ); 4561 4562 // In the Media Library, the sidebar is used to display errors before the attachments grid. 4563 if ( this.options.sidebar && 'errors' === this.options.sidebar ) { 4564 this.createSidebar(); 4565 } 4566 4567 /* 4568 * In the grid mode (the Media Library), place the Inline Uploader before 4569 * other sections so that the visual order and the DOM order match. This way, 4570 * the Inline Uploader in the Media Library is right after the "Add New" 4571 * button, see ticket #37188. 4572 */ 4573 if ( this.controller.isModeActive( 'grid' ) ) { 4574 this.createUploader(); 4575 4576 /* 4577 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library 4578 * and also for other things, for example the "Drag and drop to reorder" and 4579 * "Suggested dimensions" info in the media modal. 4580 */ 4581 this.createToolbar(); 4582 } else { 4583 this.createToolbar(); 4584 this.createUploader(); 4585 } 4586 4587 // Add a heading before the attachments list. 4588 this.createAttachmentsHeading(); 4589 4590 // Create the attachments wrapper view. 4591 this.createAttachmentsWrapperView(); 4592 4593 if ( ! infiniteScrolling ) { 4594 this.$el.addClass( 'has-load-more' ); 4595 this.createLoadMoreView(); 4596 } 4597 4598 // For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909. 4599 if ( this.options.sidebar && 'errors' !== this.options.sidebar ) { 4600 this.createSidebar(); 4601 } 4602 4603 this.updateContent(); 4604 4605 if ( ! infiniteScrolling ) { 4606 this.updateLoadMoreView(); 4607 } 4608 4609 if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) { 4610 this.$el.addClass( 'hide-sidebar' ); 4611 4612 if ( 'errors' === this.options.sidebar ) { 4613 this.$el.addClass( 'sidebar-for-errors' ); 4614 } 4615 } 4616 4617 this.collection.on( 'add remove reset', this.updateContent, this ); 4618 4619 if ( ! infiniteScrolling ) { 4620 this.collection.on( 'add remove reset', this.updateLoadMoreView, this ); 4621 } 4622 4623 // The non-cached or cached attachments query has completed. 4624 this.collection.on( 'attachments:received', this.announceSearchResults, this ); 4625 }, 4626 4627 /** 4628 * Updates the `wp.a11y.speak()` ARIA live region with a message to communicate 4629 * the number of search results to screen reader users. This function is 4630 * debounced because the collection updates multiple times. 4631 * 4632 * @since 5.3.0 4633 * 4634 * @return {void} 4635 */ 4636 announceSearchResults: _.debounce( function() { 4637 var count, 4638 /* translators: Accessibility text. %d: Number of attachments found in a search. */ 4639 mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Click load more for more results.' ); 4640 4641 if ( infiniteScrolling ) { 4642 /* translators: Accessibility text. %d: Number of attachments found in a search. */ 4643 mediaFoundHasMoreResultsMessage = __( 'Number of media items displayed: %d. Scroll the page for more results.' ); 4644 } 4645 4646 if ( this.collection.mirroring && this.collection.mirroring.args.s ) { 4647 count = this.collection.length; 4648 4649 if ( 0 === count ) { 4650 wp.a11y.speak( l10n.noMediaTryNewSearch ); 4651 return; 4652 } 4653 4654 if ( this.collection.hasMore() ) { 4655 wp.a11y.speak( mediaFoundHasMoreResultsMessage.replace( '%d', count ) ); 4656 return; 4657 } 4658 4659 wp.a11y.speak( l10n.mediaFound.replace( '%d', count ) ); 4660 } 4661 }, 200 ), 4662 4663 /** 4664 * Edits the selection in the modal. This is used when the user clicks the "Edit" button in the modal. 4665 * 4666 * @param {wp.media.view.Modal} modal The modal view. 4667 */ 4668 editSelection: function( modal ) { 4669 // When editing a selection, move focus to the "Go to library" button. 4670 modal.$( '.media-button-backToLibrary' ).focus(); 4671 }, 4672 4673 /** 4674 * Disposes of the view and its children. 4675 * 4676 * @return {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining. 4677 */ 4678 dispose: function() { 4679 this.options.selection.off( null, null, this ); 4680 View.prototype.dispose.apply( this, arguments ); 4681 return this; 4682 }, 4683 4684 /** 4685 * Creates the toolbar view. 4686 */ 4687 createToolbar: function() { 4688 var LibraryViewSwitcher, Filters, toolbarOptions, 4689 showFilterByType = -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ); 4690 4691 toolbarOptions = { 4692 controller: this.controller 4693 }; 4694 4695 if ( this.controller.isModeActive( 'grid' ) ) { 4696 toolbarOptions.className = 'media-toolbar wp-filter'; 4697 } 4698 4699 /** 4700 * @member {wp.media.view.Toolbar} 4701 */ 4702 this.toolbar = new wp.media.view.Toolbar( toolbarOptions ); 4703 4704 this.views.add( this.toolbar ); 4705 4706 this.toolbar.set( 'spinner', new wp.media.view.Spinner({ 4707 priority: -20 4708 }) ); 4709 4710 if ( showFilterByType || this.options.date ) { 4711 /* 4712 * Create a h2 heading before the select elements that filter attachments. 4713 * This heading is visible in the modal and visually hidden in the grid. 4714 */ 4715 this.toolbar.set( 'filters-heading', new wp.media.view.Heading( { 4716 priority: -100, 4717 text: l10n.filterAttachments, 4718 level: 'h2', 4719 className: 'media-attachments-filter-heading screen-reader-text' 4720 }).render() ); 4721 } 4722 4723 if ( showFilterByType ) { 4724 // "Filters" is a <select>, a label element needs to be rendered before. 4725 this.toolbar.set( 'filtersLabel', new wp.media.view.Label({ 4726 value: l10n.filterByType, 4727 attributes: { 4728 'for': 'media-attachment-filters' 4729 }, 4730 priority: -80 4731 }).render() ); 4732 4733 if ( 'uploaded' === this.options.filters ) { 4734 this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({ 4735 controller: this.controller, 4736 model: this.collection.props, 4737 priority: -80 4738 }).render() ); 4739 } else { 4740 Filters = new wp.media.view.AttachmentFilters.All({ 4741 controller: this.controller, 4742 model: this.collection.props, 4743 priority: -80 4744 }); 4745 4746 this.toolbar.set( 'filters', Filters.render() ); 4747 } 4748 } 4749 4750 /* 4751 * Feels odd to bring the global media library switcher into the Attachment browser view. 4752 * Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar ); 4753 * which the controller can tap into and add this view? 4754 */ 4755 if ( this.controller.isModeActive( 'grid' ) ) { 4756 LibraryViewSwitcher = View.extend({ 4757 className: 'view-switch media-grid-view-switch', 4758 template: wp.template( 'media-library-view-switcher') 4759 }); 4760 4761 this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({ 4762 controller: this.controller, 4763 priority: -90 4764 }).render() ); 4765 4766 // DateFilter is a <select>, a label element needs to be rendered before. 4767 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 4768 value: l10n.filterByDate, 4769 attributes: { 4770 'for': 'media-attachment-date-filters' 4771 }, 4772 priority: -75 4773 }).render() ); 4774 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 4775 controller: this.controller, 4776 model: this.collection.props, 4777 priority: -75, 4778 }).render() ); 4779 4780 // BulkSelection is a <div> with subviews, including screen reader text. 4781 this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({ 4782 text: l10n.bulkSelect, 4783 controller: this.controller, 4784 priority: -70 4785 }).render() ); 4786 4787 this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({ 4788 filters: Filters, 4789 style: 'primary', 4790 disabled: true, 4791 text: mediaTrash ? l10n.trashSelected : l10n.deletePermanently, 4792 controller: this.controller, 4793 priority: -80, 4794 click: function() { 4795 var changed = [], removed = [], 4796 selection = this.controller.state().get( 'selection' ), 4797 library = this.controller.state().get( 'library' ); 4798 4799 if ( ! selection.length ) { 4800 return; 4801 } 4802 4803 if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) { 4804 return; 4805 } 4806 4807 if ( mediaTrash && 4808 'trash' !== selection.at( 0 ).get( 'status' ) && 4809 ! window.confirm( l10n.warnBulkTrash ) ) { 4810 4811 return; 4812 } 4813 4814 selection.each( function( model ) { 4815 if ( ! model.get( 'nonces' )['delete'] ) { 4816 removed.push( model ); 4817 return; 4818 } 4819 4820 if ( mediaTrash && 'trash' === model.get( 'status' ) ) { 4821 model.set( 'status', 'inherit' ); 4822 changed.push( model.save() ); 4823 removed.push( model ); 4824 } else if ( mediaTrash ) { 4825 model.set( 'status', 'trash' ); 4826 changed.push( model.save() ); 4827 removed.push( model ); 4828 } else { 4829 model.destroy({wait: true}); 4830 } 4831 } ); 4832 4833 if ( changed.length ) { 4834 selection.remove( removed ); 4835 4836 $.when.apply( null, changed ).then( _.bind( function() { 4837 library._requery( true ); 4838 this.controller.trigger( 'selection:action:done' ); 4839 }, this ) ); 4840 } else { 4841 this.controller.trigger( 'selection:action:done' ); 4842 } 4843 } 4844 }).render() ); 4845 4846 if ( mediaTrash ) { 4847 this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({ 4848 filters: Filters, 4849 style: 'link button-link-delete', 4850 disabled: true, 4851 text: l10n.deletePermanently, 4852 controller: this.controller, 4853 priority: -55, 4854 size: '', 4855 click: function() { 4856 var removed = [], 4857 destroy = [], 4858 selection = this.controller.state().get( 'selection' ); 4859 4860 if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) { 4861 return; 4862 } 4863 4864 selection.each( function( model ) { 4865 if ( ! model.get( 'nonces' )['delete'] ) { 4866 removed.push( model ); 4867 return; 4868 } 4869 4870 destroy.push( model ); 4871 } ); 4872 4873 if ( removed.length ) { 4874 selection.remove( removed ); 4875 } 4876 4877 if ( destroy.length ) { 4878 $.when.apply( null, destroy.map( function (item) { 4879 return item.destroy(); 4880 } ) ).then( _.bind( function() { 4881 this.controller.trigger( 'selection:action:done' ); 4882 }, this ) ); 4883 } 4884 } 4885 }).render() ); 4886 } 4887 4888 } else if ( this.options.date ) { 4889 // DateFilter is a <select>, a label element needs to be rendered before. 4890 this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ 4891 value: l10n.filterByDate, 4892 attributes: { 4893 'for': 'media-attachment-date-filters' 4894 }, 4895 priority: -75 4896 }).render() ); 4897 this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({ 4898 controller: this.controller, 4899 model: this.collection.props, 4900 priority: -75 4901 }).render() ); 4902 } 4903 4904 if ( this.options.search ) { 4905 // Search is an input, a label element needs to be rendered before. 4906 this.toolbar.set( 'searchLabel', new wp.media.view.Label({ 4907 value: l10n.searchLabel, 4908 className: 'media-search-input-label', 4909 attributes: { 4910 'for': 'media-search-input' 4911 }, 4912 priority: 60 4913 }).render() ); 4914 this.toolbar.set( 'search', new wp.media.view.Search({ 4915 controller: this.controller, 4916 model: this.collection.props, 4917 priority: 60 4918 }).render() ); 4919 } 4920 4921 if ( this.options.dragInfo ) { 4922 this.toolbar.set( 'dragInfo', new View({ 4923 el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0], 4924 priority: -40 4925 }) ); 4926 } 4927 4928 if ( this.options.suggestedWidth && this.options.suggestedHeight ) { 4929 this.toolbar.set( 'suggestedDimensions', new View({ 4930 el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0], 4931 priority: -40 4932 }) ); 4933 } 4934 }, 4935 4936 /** 4937 * Updates the content of the attachments browser. 4938 */ 4939 updateContent: function() { 4940 var view = this, 4941 noItemsView; 4942 4943 if ( this.controller.isModeActive( 'grid' ) ) { 4944 // Usually the media library. 4945 noItemsView = view.attachmentsNoResults; 4946 } else { 4947 // Usually the media modal. 4948 noItemsView = view.uploader; 4949 } 4950 4951 if ( ! this.collection.length ) { 4952 this.toolbar.get( 'spinner' ).show(); 4953 this.toolbar.$( '.media-bg-overlay' ).show(); 4954 this.dfd = this.collection.more().done( function() { 4955 if ( ! view.collection.length ) { 4956 noItemsView.$el.removeClass( 'hidden' ); 4957 } else { 4958 noItemsView.$el.addClass( 'hidden' ); 4959 } 4960 view.toolbar.get( 'spinner' ).hide(); 4961 view.toolbar.$( '.media-bg-overlay' ).hide(); 4962 } ); 4963 } else { 4964 noItemsView.$el.addClass( 'hidden' ); 4965 view.toolbar.get( 'spinner' ).hide(); 4966 this.toolbar.$( '.media-bg-overlay' ).hide(); 4967 } 4968 }, 4969 4970 /** 4971 * Creates the uploader view. 4972 */ 4973 createUploader: function() { 4974 this.uploader = new wp.media.view.UploaderInline({ 4975 controller: this.controller, 4976 status: false, 4977 message: this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound, 4978 canClose: this.controller.isModeActive( 'grid' ) 4979 }); 4980 4981 this.uploader.$el.addClass( 'hidden' ); 4982 this.views.add( this.uploader ); 4983 }, 4984 4985 /** 4986 * Toggles the uploader view. 4987 */ 4988 toggleUploader: function() { 4989 if ( this.uploader.$el.hasClass( 'hidden' ) ) { 4990 this.uploader.show(); 4991 } else { 4992 this.uploader.hide(); 4993 } 4994 }, 4995 4996 /** 4997 * Creates the Attachments wrapper view. 4998 * 4999 * @since 5.8.0 5000 * 5001 * @return {void} 5002 */ 5003 createAttachmentsWrapperView: function() { 5004 this.attachmentsWrapper = new wp.media.View( { 5005 className: 'attachments-wrapper' 5006 } ); 5007 5008 // Create the list of attachments. 5009 this.views.add( this.attachmentsWrapper ); 5010 this.createAttachments(); 5011 }, 5012 5013 /** 5014 * Creates the attachments view. 5015 */ 5016 createAttachments: function() { 5017 this.attachments = new wp.media.view.Attachments({ 5018 controller: this.controller, 5019 collection: this.collection, 5020 selection: this.options.selection, 5021 model: this.model, 5022 sortable: this.options.sortable, 5023 scrollElement: this.options.scrollElement, 5024 idealColumnWidth: this.options.idealColumnWidth, 5025 5026 // The single `Attachment` view to be used in the `Attachments` view. 5027 AttachmentView: this.options.AttachmentView 5028 }); 5029 5030 // Add keydown listener to the instance of the Attachments view. 5031 this.controller.on( 'attachment:keydown:arrow', _.bind( this.attachments.arrowEvent, this.attachments ) ); 5032 this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) ); 5033 5034 this.views.add( '.attachments-wrapper', this.attachments ); 5035 5036 if ( this.controller.isModeActive( 'grid' ) ) { 5037 this.attachmentsNoResults = new View({ 5038 controller: this.controller, 5039 tagName: 'p' 5040 }); 5041 5042 this.attachmentsNoResults.$el.addClass( 'hidden no-media' ); 5043 this.attachmentsNoResults.$el.html( l10n.noMedia ); 5044 5045 this.views.add( this.attachmentsNoResults ); 5046 } 5047 }, 5048 5049 /** 5050 * Creates the load more button and attachments counter view. 5051 * 5052 * @since 5.8.0 5053 * 5054 * @return {void} 5055 */ 5056 createLoadMoreView: function() { 5057 var view = this; 5058 5059 this.loadMoreWrapper = new View( { 5060 controller: this.controller, 5061 className: 'load-more-wrapper' 5062 } ); 5063 5064 this.loadMoreCount = new View( { 5065 controller: this.controller, 5066 tagName: 'p', 5067 className: 'load-more-count hidden' 5068 } ); 5069 5070 this.loadMoreButton = new wp.media.view.Button( { 5071 text: __( 'Load more' ), 5072 className: 'load-more hidden', 5073 style: 'primary', 5074 size: '', 5075 click: function() { 5076 view.loadMoreAttachments(); 5077 } 5078 } ); 5079 5080 this.loadMoreSpinner = new wp.media.view.Spinner(); 5081 5082 this.loadMoreJumpToFirst = new wp.media.view.Button( { 5083 text: __( 'Jump to first loaded item' ), 5084 className: 'load-more-jump hidden', 5085 size: '', 5086 click: function() { 5087 view.jumpToFirstAddedItem(); 5088 } 5089 } ); 5090 5091 this.views.add( '.attachments-wrapper', this.loadMoreWrapper ); 5092 this.views.add( '.load-more-wrapper', this.loadMoreSpinner ); 5093 this.views.add( '.load-more-wrapper', this.loadMoreCount ); 5094 this.views.add( '.load-more-wrapper', this.loadMoreButton ); 5095 this.views.add( '.load-more-wrapper', this.loadMoreJumpToFirst ); 5096 }, 5097 5098 /** 5099 * Updates the Load More view. This function is debounced because the 5100 * collection updates multiple times at the add, remove, and reset events. 5101 * We need it to run only once, after all attachments are added or removed. 5102 * 5103 * @since 5.8.0 5104 * 5105 * @return {void} 5106 */ 5107 updateLoadMoreView: _.debounce( function() { 5108 // Ensure the load more view elements are initially hidden at each update. 5109 this.loadMoreButton.$el.addClass( 'hidden' ); 5110 this.loadMoreCount.$el.addClass( 'hidden' ); 5111 this.loadMoreJumpToFirst.$el.addClass( 'hidden' ).prop( 'disabled', true ); 5112 5113 if ( ! this.collection.getTotalAttachments() ) { 5114 return; 5115 } 5116 5117 if ( this.collection.length ) { 5118 this.loadMoreCount.$el.text( 5119 /* translators: 1: Number of displayed attachments, 2: Number of total attachments. */ 5120 sprintf( 5121 __( 'Showing %1$s of %2$s media items' ), 5122 this.collection.length, 5123 this.collection.getTotalAttachments() 5124 ) 5125 ); 5126 5127 this.loadMoreCount.$el.removeClass( 'hidden' ); 5128 } 5129 5130 /* 5131 * Notice that while the collection updates multiple times hasMore() may 5132 * return true when it's actually not true. 5133 */ 5134 if ( this.collection.hasMore() ) { 5135 this.loadMoreButton.$el.removeClass( 'hidden' ); 5136 } 5137 5138 // Find the media item to move focus to. The jQuery `eq()` index is zero-based. 5139 this.firstAddedMediaItem = this.$el.find( '.attachment' ).eq( this.firstAddedMediaItemIndex ); 5140 5141 // If there's a media item to move focus to, make the "Jump to" button available. 5142 if ( this.firstAddedMediaItem.length ) { 5143 this.firstAddedMediaItem.addClass( 'new-media' ); 5144 this.loadMoreJumpToFirst.$el.removeClass( 'hidden' ).prop( 'disabled', false ); 5145 } 5146 5147 // If there are new items added, but no more to be added, move focus to Jump button. 5148 if ( this.firstAddedMediaItem.length && ! this.collection.hasMore() ) { 5149 this.loadMoreJumpToFirst.$el.trigger( 'focus' ); 5150 } 5151 }, 10 ), 5152 5153 /** 5154 * Loads more attachments. 5155 * 5156 * @since 5.8.0 5157 * 5158 * @return {void} 5159 */ 5160 loadMoreAttachments: function() { 5161 var view = this; 5162 5163 if ( ! this.collection.hasMore() ) { 5164 return; 5165 } 5166 5167 /* 5168 * The collection index is zero-based while the length counts the actual 5169 * amount of items. Thus the length is equivalent to the position of the 5170 * first added item. 5171 */ 5172 this.firstAddedMediaItemIndex = this.collection.length; 5173 5174 this.$el.addClass( 'more-loaded' ); 5175 this.collection.each( function( attachment ) { 5176 var attach_id = attachment.attributes.id; 5177 $( '[data-id="' + attach_id + '"]' ).addClass( 'found-media' ); 5178 }); 5179 5180 view.loadMoreSpinner.show(); 5181 this.collection.once( 'attachments:received', function() { 5182 view.loadMoreSpinner.hide(); 5183 } ); 5184 this.collection.more(); 5185 }, 5186 5187 /** 5188 * Moves focus to the first new added item. . 5189 * 5190 * @since 5.8.0 5191 * 5192 * @return {void} 5193 */ 5194 jumpToFirstAddedItem: function() { 5195 // Set focus on first added item. 5196 this.firstAddedMediaItem.focus(); 5197 }, 5198 5199 /** 5200 * Creates the attachments heading view. 5201 */ 5202 createAttachmentsHeading: function() { 5203 this.attachmentsHeading = new wp.media.view.Heading( { 5204 text: l10n.attachmentsList, 5205 level: 'h2', 5206 className: 'media-views-heading screen-reader-text' 5207 } ); 5208 this.views.add( this.attachmentsHeading ); 5209 }, 5210 5211 /** 5212 * Creates the sidebar view. 5213 */ 5214 createSidebar: function() { 5215 var options = this.options, 5216 selection = options.selection, 5217 sidebar = this.sidebar = new wp.media.view.Sidebar({ 5218 controller: this.controller 5219 }); 5220 5221 this.views.add( sidebar ); 5222 5223 if ( this.controller.uploader ) { 5224 sidebar.set( 'uploads', new wp.media.view.UploaderStatus({ 5225 controller: this.controller, 5226 priority: 40 5227 }) ); 5228 } 5229 5230 selection.on( 'selection:single', this.createSingle, this ); 5231 selection.on( 'selection:unsingle', this.disposeSingle, this ); 5232 5233 if ( selection.single() ) { 5234 this.createSingle(); 5235 } 5236 }, 5237 5238 /** 5239 * Creates the single attachment view. 5240 */ 5241 createSingle: function() { 5242 var sidebar = this.sidebar, 5243 single = this.options.selection.single(); 5244 5245 sidebar.set( 'details', new wp.media.view.Attachment.Details({ 5246 controller: this.controller, 5247 model: single, 5248 priority: 80 5249 }) ); 5250 5251 sidebar.set( 'compat', new wp.media.view.AttachmentCompat({ 5252 controller: this.controller, 5253 model: single, 5254 priority: 120 5255 }) ); 5256 5257 if ( this.options.display ) { 5258 sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({ 5259 controller: this.controller, 5260 model: this.model.display( single ), 5261 attachment: single, 5262 priority: 160, 5263 userSettings: this.model.get('displayUserSettings') 5264 }) ); 5265 } 5266 5267 // Show the sidebar on mobile. 5268 if ( this.model.id === 'insert' ) { 5269 sidebar.$el.addClass( 'visible' ); 5270 } 5271 }, 5272 5273 /** 5274 * Disposes of the single attachment view. 5275 */ 5276 disposeSingle: function() { 5277 var sidebar = this.sidebar; 5278 sidebar.unset('details'); 5279 sidebar.unset('compat'); 5280 sidebar.unset('display'); 5281 // Hide the sidebar on mobile. 5282 sidebar.$el.removeClass( 'visible' ); 5283 } 5284 }); 5285 5286 module.exports = AttachmentsBrowser; 5287 5288 5289 /***/ }, 5290 5291 /***/ 3479 5292 (module) { 5293 5294 var Attachments = wp.media.view.Attachments, 5295 Selection; 5296 5297 /** 5298 * wp.media.view.Attachments.Selection 5299 * 5300 * @memberOf wp.media.view.Attachments 5301 * 5302 * @class 5303 * @augments wp.media.view.Attachments 5304 * @augments wp.media.View 5305 * @augments wp.Backbone.View 5306 * @augments Backbone.View 5307 */ 5308 Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{ 5309 events: {}, 5310 initialize: function() { 5311 _.defaults( this.options, { 5312 sortable: false, 5313 resize: false, 5314 5315 // The single `Attachment` view to be used in the `Attachments` view. 5316 AttachmentView: wp.media.view.Attachment.Selection 5317 }); 5318 // Call 'initialize' directly on the parent class. 5319 return Attachments.prototype.initialize.apply( this, arguments ); 5320 } 5321 }); 5322 5323 module.exports = Selection; 5324 5325 5326 /***/ }, 5327 5328 /***/ 168 5329 (module) { 5330 5331 var $ = Backbone.$, 5332 ButtonGroup; 5333 5334 /** 5335 * wp.media.view.ButtonGroup 5336 * 5337 * @memberOf wp.media.view 5338 * 5339 * @class 5340 * @augments wp.media.View 5341 * @augments wp.Backbone.View 5342 * @augments Backbone.View 5343 */ 5344 ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{ 5345 tagName: 'div', 5346 className: 'button-group button-large media-button-group', 5347 5348 initialize: function() { 5349 /** 5350 * @member {wp.media.view.Button[]} 5351 */ 5352 this.buttons = _.map( this.options.buttons || [], function( button ) { 5353 if ( button instanceof Backbone.View ) { 5354 return button; 5355 } else { 5356 return new wp.media.view.Button( button ).render(); 5357 } 5358 }); 5359 5360 delete this.options.buttons; 5361 5362 if ( this.options.classes ) { 5363 this.$el.addClass( this.options.classes ); 5364 } 5365 }, 5366 5367 /** 5368 * Renders the button group. 5369 * 5370 * @return {wp.media.view.ButtonGroup} The button group. 5371 */ 5372 render: function() { 5373 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 5374 return this; 5375 } 5376 }); 5377 5378 module.exports = ButtonGroup; 5379 5380 5381 /***/ }, 5382 5383 /***/ 846 5384 (module) { 5385 5386 /** 5387 * wp.media.view.Button 5388 * 5389 * @memberOf wp.media.view 5390 * 5391 * @class 5392 * @augments wp.media.View 5393 * @augments wp.Backbone.View 5394 * @augments Backbone.View 5395 */ 5396 var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{ 5397 tagName: 'button', 5398 className: 'media-button', 5399 attributes: { type: 'button' }, 5400 5401 events: { 5402 'click': 'click' 5403 }, 5404 5405 defaults: { 5406 text: '', 5407 style: '', 5408 size: 'large', 5409 disabled: false 5410 }, 5411 5412 initialize: function() { 5413 /** 5414 * Create a model with the provided `defaults`. 5415 * 5416 * @member {Backbone.Model} 5417 */ 5418 this.model = new Backbone.Model( this.defaults ); 5419 5420 // If any of the `options` have a key from `defaults`, apply its 5421 // value to the `model` and remove it from the `options` object. 5422 _.each( this.defaults, function( def, key ) { 5423 var value = this.options[ key ]; 5424 if ( _.isUndefined( value ) ) { 5425 return; 5426 } 5427 5428 this.model.set( key, value ); 5429 delete this.options[ key ]; 5430 }, this ); 5431 5432 this.listenTo( this.model, 'change', this.render ); 5433 }, 5434 /** 5435 * Renders the button. 5436 * 5437 * @return {wp.media.view.Button} Returns itself to allow chaining. 5438 */ 5439 render: function() { 5440 var classes = [ 'button', this.className ], 5441 model = this.model.toJSON(); 5442 5443 if ( model.style ) { 5444 classes.push( 'button-' + model.style ); 5445 } 5446 5447 if ( model.size ) { 5448 classes.push( 'button-' + model.size ); 5449 } 5450 5451 classes = _.uniq( classes.concat( this.options.classes ) ); 5452 this.el.className = classes.join(' '); 5453 5454 this.$el.prop( 'disabled', model.disabled ); 5455 this.$el.text( this.model.get('text') ); 5456 5457 return this; 5458 }, 5459 /** 5460 * Handles the click event. 5461 * 5462 * @param {JQuery.Event} event The jQuery event object for the click action. 5463 */ 5464 click: function( event ) { 5465 if ( '#' === this.attributes.href ) { 5466 event.preventDefault(); 5467 } 5468 5469 if ( this.options.click && ! this.model.get('disabled') ) { 5470 this.options.click.apply( this, arguments ); 5471 } 5472 } 5473 }); 5474 5475 module.exports = Button; 5476 5477 5478 /***/ }, 5479 5480 /***/ 7637 5481 (module) { 5482 5483 var View = wp.media.View, 5484 UploaderStatus = wp.media.view.UploaderStatus, 5485 l10n = wp.media.view.l10n, 5486 $ = jQuery, 5487 Cropper; 5488 5489 /** 5490 * wp.media.view.Cropper 5491 * 5492 * Uses the imgAreaSelect plugin to allow a user to crop an image. 5493 * 5494 * Takes imgAreaSelect options from 5495 * wp.customize.HeaderControl.calculateImageSelectOptions via 5496 * wp.customize.HeaderControl.openMM. 5497 * 5498 * @memberOf wp.media.view 5499 * 5500 * @class 5501 * @augments wp.media.View 5502 * @augments wp.Backbone.View 5503 * @augments Backbone.View 5504 */ 5505 Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{ 5506 className: 'crop-content', 5507 template: wp.template('crop-content'), 5508 initialize: function() { 5509 _.bindAll(this, 'onImageLoad'); 5510 }, 5511 ready: function() { 5512 this.controller.frame.on('content:error:crop', this.onError, this); 5513 this.$image = this.$el.find('.crop-image'); 5514 this.$image.on('load', this.onImageLoad); 5515 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 5516 }, 5517 remove: function() { 5518 $(window).off('resize.cropper'); 5519 this.$el.remove(); 5520 this.$el.off(); 5521 View.prototype.remove.apply(this, arguments); 5522 }, 5523 prepare: function() { 5524 return { 5525 title: l10n.cropYourImage, 5526 url: this.options.attachment.get('url') 5527 }; 5528 }, 5529 onImageLoad: function() { 5530 var imgOptions = this.controller.get('imgSelectOptions'), 5531 imgSelect; 5532 5533 if (typeof imgOptions === 'function') { 5534 imgOptions = imgOptions(this.options.attachment, this.controller); 5535 } 5536 5537 imgOptions = _.extend(imgOptions, { 5538 parent: this.$el, 5539 onInit: function() { 5540 5541 // Store the set ratio. 5542 var setRatio = imgSelect.getOptions().aspectRatio; 5543 5544 // On mousedown, if no ratio is set and the Shift key is down, use a 1:1 ratio. 5545 this.parent.children().on( 'mousedown touchstart', function( e ) { 5546 5547 // If no ratio is set and the shift key is down, use a 1:1 ratio. 5548 if ( ! setRatio && e.shiftKey ) { 5549 imgSelect.setOptions( { 5550 aspectRatio: '1:1' 5551 } ); 5552 } 5553 } ); 5554 5555 this.parent.children().on( 'mouseup touchend', function() { 5556 5557 // Restore the set ratio. 5558 imgSelect.setOptions( { 5559 aspectRatio: setRatio ? setRatio : false 5560 } ); 5561 } ); 5562 } 5563 } ); 5564 this.trigger('image-loaded'); 5565 imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 5566 }, 5567 onError: function() { 5568 var filename = this.options.attachment.get('filename'); 5569 5570 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 5571 filename: UploaderStatus.prototype.filename(filename), 5572 message: window._wpMediaViewsL10n.cropError 5573 }), { at: 0 }); 5574 } 5575 }); 5576 5577 module.exports = Cropper; 5578 5579 5580 /***/ }, 5581 5582 /***/ 6126 5583 (module) { 5584 5585 var View = wp.media.View, 5586 EditImage; 5587 5588 /** 5589 * wp.media.view.EditImage 5590 * 5591 * @memberOf wp.media.view 5592 * 5593 * @class 5594 * @augments wp.media.View 5595 * @augments wp.Backbone.View 5596 * @augments Backbone.View 5597 */ 5598 EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{ 5599 className: 'image-editor', 5600 template: wp.template('image-editor'), 5601 5602 initialize: function( options ) { 5603 this.editor = window.imageEdit; 5604 this.controller = options.controller; 5605 View.prototype.initialize.apply( this, arguments ); 5606 }, 5607 5608 prepare: function() { 5609 return this.model.toJSON(); 5610 }, 5611 5612 loadEditor: function() { 5613 this.editor.open( this.model.get( 'id' ), this.model.get( 'nonces' ).edit, this ); 5614 }, 5615 5616 back: function() { 5617 var lastState = this.controller.lastState(); 5618 this.controller.setState( lastState ); 5619 }, 5620 5621 refresh: function() { 5622 this.model.fetch(); 5623 }, 5624 5625 save: function() { 5626 var lastState = this.controller.lastState(); 5627 5628 this.model.fetch().done( _.bind( function() { 5629 this.controller.setState( lastState ); 5630 }, this ) ); 5631 } 5632 5633 }); 5634 5635 module.exports = EditImage; 5636 5637 5638 /***/ }, 5639 5640 /***/ 5741 5641 (module) { 5642 5643 /** 5644 * wp.media.view.Embed 5645 * 5646 * @memberOf wp.media.view 5647 * 5648 * @class 5649 * @augments wp.media.View 5650 * @augments wp.Backbone.View 5651 * @augments Backbone.View 5652 */ 5653 var Embed = wp.media.View.extend(/** @lends wp.media.view.Embed.prototype */{ 5654 className: 'media-embed', 5655 5656 /** 5657 * Initializes the embed view. 5658 */ 5659 initialize: function() { 5660 /** 5661 * @member {wp.media.view.EmbedUrl} 5662 */ 5663 this.url = new wp.media.view.EmbedUrl({ 5664 controller: this.controller, 5665 model: this.model.props 5666 }).render(); 5667 5668 this.views.set([ this.url ]); 5669 this.refresh(); 5670 this.listenTo( this.model, 'change:type', this.refresh ); 5671 this.listenTo( this.model, 'change:loading', this.loading ); 5672 }, 5673 5674 /** 5675 * Sets the settings for the embed view. 5676 * 5677 * @param {Object} view The view object to be set as the settings for the embed view. 5678 */ 5679 settings: function( view ) { 5680 if ( this._settings ) { 5681 this._settings.remove(); 5682 } 5683 this._settings = view; 5684 this.views.add( view ); 5685 }, 5686 5687 /** 5688 * Refreshes the embed view based on the type of embed. 5689 */ 5690 refresh: function() { 5691 var type = this.model.get('type'), 5692 constructor; 5693 5694 if ( 'image' === type ) { 5695 constructor = wp.media.view.EmbedImage; 5696 } else if ( 'link' === type ) { 5697 constructor = wp.media.view.EmbedLink; 5698 } else { 5699 return; 5700 } 5701 5702 this.settings( new constructor({ 5703 controller: this.controller, 5704 model: this.model.props, 5705 priority: 40 5706 }) ); 5707 }, 5708 5709 /** 5710 * Toggles the loading state of the embed view. 5711 */ 5712 loading: function() { 5713 this.$el.toggleClass( 'embed-loading', this.model.get('loading') ); 5714 } 5715 }); 5716 5717 module.exports = Embed; 5718 5719 5720 /***/ }, 5721 5722 /***/ 2395 5723 (module) { 5724 5725 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 5726 EmbedImage; 5727 5728 /** 5729 * wp.media.view.EmbedImage 5730 * 5731 * @memberOf wp.media.view 5732 * 5733 * @class 5734 * @augments wp.media.view.Settings.AttachmentDisplay 5735 * @augments wp.media.view.Settings 5736 * @augments wp.media.View 5737 * @augments wp.Backbone.View 5738 * @augments Backbone.View 5739 */ 5740 EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{ 5741 className: 'embed-media-settings', 5742 template: wp.template('embed-image-settings'), 5743 5744 initialize: function() { 5745 /** 5746 * Call `initialize` directly on parent class with passed arguments 5747 */ 5748 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 5749 this.listenTo( this.model, 'change:url', this.updateImage ); 5750 }, 5751 5752 updateImage: function() { 5753 this.$('img').attr( 'src', this.model.get('url') ); 5754 } 5755 }); 5756 5757 module.exports = EmbedImage; 5758 5759 5760 /***/ }, 5761 5762 /***/ 8232 5763 (module) { 5764 5765 var $ = jQuery, 5766 EmbedLink; 5767 5768 /** 5769 * wp.media.view.EmbedLink 5770 * 5771 * @memberOf wp.media.view 5772 * 5773 * @class 5774 * @augments wp.media.view.Settings 5775 * @augments wp.media.View 5776 * @augments wp.Backbone.View 5777 * @augments Backbone.View 5778 */ 5779 EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{ 5780 className: 'embed-link-settings', 5781 template: wp.template('embed-link-settings'), 5782 5783 initialize: function() { 5784 this.listenTo( this.model, 'change:url', this.updateoEmbed ); 5785 }, 5786 5787 updateoEmbed: _.debounce( function() { 5788 var url = this.model.get( 'url' ); 5789 5790 // Clear out previous results. 5791 this.$('.embed-container').hide().find('.embed-preview').empty(); 5792 this.$( '.setting' ).hide(); 5793 5794 // Only proceed with embed if the field contains more than 11 characters. 5795 // Example: http://a.io is 11 chars 5796 if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) { 5797 return; 5798 } 5799 5800 this.fetch(); 5801 }, wp.media.controller.Embed.sensitivity ), 5802 5803 fetch: function() { 5804 var url = this.model.get( 'url' ), re, youTubeEmbedMatch; 5805 5806 // Check if they haven't typed in 500 ms. 5807 if ( $('#embed-url-field').val() !== url ) { 5808 return; 5809 } 5810 5811 if ( this.dfd && 'pending' === this.dfd.state() ) { 5812 this.dfd.abort(); 5813 } 5814 5815 // Support YouTube embed urls, since they work once in the editor. 5816 re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; 5817 youTubeEmbedMatch = re.exec( url ); 5818 if ( youTubeEmbedMatch ) { 5819 url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; 5820 } 5821 5822 this.dfd = wp.apiRequest({ 5823 url: wp.media.view.settings.oEmbedProxyUrl, 5824 data: { 5825 url: url, 5826 maxwidth: this.model.get( 'width' ), 5827 maxheight: this.model.get( 'height' ) 5828 }, 5829 type: 'GET', 5830 dataType: 'json', 5831 context: this 5832 }) 5833 .done( function( response ) { 5834 this.renderoEmbed( { 5835 data: { 5836 body: response.html || '' 5837 } 5838 } ); 5839 } ) 5840 .fail( this.renderFail ); 5841 }, 5842 5843 renderFail: function ( response, status ) { 5844 if ( 'abort' === status ) { 5845 return; 5846 } 5847 this.$( '.link-text' ).show(); 5848 }, 5849 5850 renderoEmbed: function( response ) { 5851 var html = ( response && response.data && response.data.body ) || ''; 5852 5853 if ( html ) { 5854 this.$('.embed-container').show().find('.embed-preview').html( html ); 5855 } else { 5856 this.renderFail(); 5857 } 5858 } 5859 }); 5860 5861 module.exports = EmbedLink; 5862 5863 5864 /***/ }, 5865 5866 /***/ 7327 5867 (module) { 5868 5869 var View = wp.media.View, 5870 $ = jQuery, 5871 l10n = wp.media.view.l10n, 5872 EmbedUrl; 5873 5874 /** 5875 * wp.media.view.EmbedUrl 5876 * 5877 * @memberOf wp.media.view 5878 * 5879 * @class 5880 * @augments wp.media.View 5881 * @augments wp.Backbone.View 5882 * @augments Backbone.View 5883 */ 5884 EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{ 5885 tagName: 'span', 5886 className: 'embed-url', 5887 5888 events: { 5889 'input': 'url' 5890 }, 5891 5892 initialize: function() { 5893 this.$input = $( '<input id="embed-url-field" type="url" />' ) 5894 .attr( 'aria-label', l10n.insertFromUrlTitle ) 5895 .val( this.model.get('url') ); 5896 this.input = this.$input[0]; 5897 5898 this.spinner = $('<span class="spinner" />')[0]; 5899 this.$el.append([ this.input, this.spinner ]); 5900 5901 this.listenTo( this.model, 'change:url', this.render ); 5902 5903 if ( this.model.get( 'url' ) ) { 5904 _.delay( _.bind( function () { 5905 this.model.trigger( 'change:url' ); 5906 }, this ), 500 ); 5907 } 5908 }, 5909 /** 5910 * Renders the view. 5911 * 5912 * @return {void|wp.media.view.EmbedUrl} Returns itself to allow chaining. 5913 */ 5914 render: function() { 5915 var $input = this.$input; 5916 5917 if ( $input.is(':focus') ) { 5918 return; 5919 } 5920 5921 if ( this.model.get( 'url' ) ) { 5922 this.input.value = this.model.get('url'); 5923 } else { 5924 this.input.setAttribute( 'placeholder', 'https://' ); 5925 } 5926 5927 /** 5928 * Call `render` directly on parent class with passed arguments 5929 */ 5930 View.prototype.render.apply( this, arguments ); 5931 return this; 5932 }, 5933 5934 url: function( event ) { 5935 var url = event.target.value || ''; 5936 this.model.set( 'url', url.trim() ); 5937 } 5938 }); 5939 5940 module.exports = EmbedUrl; 5941 5942 5943 /***/ }, 5944 5945 /***/ 718 5946 (module) { 5947 5948 var $ = jQuery; 5949 5950 /** 5951 * wp.media.view.FocusManager 5952 * 5953 * @memberOf wp.media.view 5954 * 5955 * @class 5956 * @augments wp.media.View 5957 * @augments wp.Backbone.View 5958 * @augments Backbone.View 5959 */ 5960 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 5961 5962 events: { 5963 'keydown': 'focusManagementMode' 5964 }, 5965 5966 /** 5967 * Initializes the Focus Manager. 5968 * 5969 * @param {Object} options The Focus Manager options. 5970 * 5971 * @since 5.3.0 5972 * 5973 * @return {void} 5974 */ 5975 initialize: function( options ) { 5976 this.mode = options.mode || 'constrainTabbing'; 5977 this.tabsAutomaticActivation = options.tabsAutomaticActivation || false; 5978 }, 5979 5980 /** 5981 * Determines which focus management mode to use. 5982 * 5983 * @since 5.3.0 5984 * 5985 * @param {Object} event jQuery event object. 5986 * 5987 * @return {void} 5988 */ 5989 focusManagementMode: function( event ) { 5990 if ( this.mode === 'constrainTabbing' ) { 5991 this.constrainTabbing( event ); 5992 } 5993 5994 if ( this.mode === 'tabsNavigation' ) { 5995 this.tabsNavigation( event ); 5996 } 5997 }, 5998 5999 /** 6000 * Gets all the tabbable elements. 6001 * 6002 * @since 5.3.0 6003 * 6004 * @return {Object} A jQuery collection of tabbable elements. 6005 */ 6006 getTabbables: function() { 6007 // Skip the file input added by Plupload. 6008 return this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 6009 }, 6010 6011 /** 6012 * Moves focus to the modal dialog. 6013 * 6014 * @since 3.5.0 6015 * 6016 * @return {void} 6017 */ 6018 focus: function() { 6019 this.$( '.media-modal' ).trigger( 'focus' ); 6020 }, 6021 6022 /** 6023 * Constrains navigation with the Tab key within the media view element. 6024 * 6025 * @since 4.0.0 6026 * 6027 * @param {Object} event A keydown jQuery event. 6028 * 6029 * @return {void} 6030 */ 6031 constrainTabbing: function( event ) { 6032 var tabbables; 6033 6034 // Look for the tab key. 6035 if ( 9 !== event.keyCode ) { 6036 return; 6037 } 6038 6039 tabbables = this.getTabbables(); 6040 6041 // Keep tab focus within media modal while it's open. 6042 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 6043 tabbables.first().focus(); 6044 return false; 6045 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 6046 tabbables.last().focus(); 6047 return false; 6048 } 6049 }, 6050 6051 /** 6052 * Hides from assistive technologies all the body children. 6053 * 6054 * Sets an `aria-hidden="true"` attribute on all the body children except 6055 * the provided element and other elements that should not be hidden. 6056 * 6057 * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy 6058 * in Safari 11.1 and support is spotty in other browsers. Also, `aria-modal="true"` 6059 * prevents the `wp.a11y.speak()` ARIA live regions to work as they're outside 6060 * of the modal dialog and get hidden from assistive technologies. 6061 * 6062 * @since 5.2.3 6063 * 6064 * @param {Object} visibleElement The jQuery object representing the element that should not be hidden. 6065 * 6066 * @return {void} 6067 */ 6068 setAriaHiddenOnBodyChildren: function( visibleElement ) { 6069 var bodyChildren, 6070 self = this; 6071 6072 if ( this.isBodyAriaHidden ) { 6073 return; 6074 } 6075 6076 // Get all the body children. 6077 bodyChildren = document.body.children; 6078 6079 // Loop through the body children and hide the ones that should be hidden. 6080 _.each( bodyChildren, function( element ) { 6081 // Don't hide the modal element. 6082 if ( element === visibleElement[0] ) { 6083 return; 6084 } 6085 6086 // Determine the body children to hide. 6087 if ( self.elementShouldBeHidden( element ) ) { 6088 element.setAttribute( 'aria-hidden', 'true' ); 6089 // Store the hidden elements. 6090 self.ariaHiddenElements.push( element ); 6091 } 6092 } ); 6093 6094 this.isBodyAriaHidden = true; 6095 }, 6096 6097 /** 6098 * Unhides from assistive technologies all the body children. 6099 * 6100 * Makes visible again to assistive technologies all the body children 6101 * previously hidden and stored in this.ariaHiddenElements. 6102 * 6103 * @since 5.2.3 6104 * 6105 * @return {void} 6106 */ 6107 removeAriaHiddenFromBodyChildren: function() { 6108 _.each( this.ariaHiddenElements, function( element ) { 6109 element.removeAttribute( 'aria-hidden' ); 6110 } ); 6111 6112 this.ariaHiddenElements = []; 6113 this.isBodyAriaHidden = false; 6114 }, 6115 6116 /** 6117 * Determines if the passed element should not be hidden from assistive technologies. 6118 * 6119 * @since 5.2.3 6120 * 6121 * @param {Object} element The DOM element that should be checked. 6122 * 6123 * @return {boolean} Whether the element should not be hidden from assistive technologies. 6124 */ 6125 elementShouldBeHidden: function( element ) { 6126 var role = element.getAttribute( 'role' ), 6127 liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ]; 6128 6129 /* 6130 * Don't hide scripts, elements that already have `aria-hidden`, and 6131 * ARIA live regions. 6132 */ 6133 return ! ( 6134 element.tagName === 'SCRIPT' || 6135 element.hasAttribute( 'aria-hidden' ) || 6136 element.hasAttribute( 'aria-live' ) || 6137 liveRegionsRoles.indexOf( role ) !== -1 6138 ); 6139 }, 6140 6141 /** 6142 * Whether the body children are hidden from assistive technologies. 6143 * 6144 * @since 5.2.3 6145 */ 6146 isBodyAriaHidden: false, 6147 6148 /** 6149 * Stores an array of DOM elements that should be hidden from assistive 6150 * technologies, for example when the media modal dialog opens. 6151 * 6152 * @since 5.2.3 6153 */ 6154 ariaHiddenElements: [], 6155 6156 /** 6157 * Holds the jQuery collection of ARIA tabs. 6158 * 6159 * @since 5.3.0 6160 */ 6161 tabs: $(), 6162 6163 /** 6164 * Sets up tabs in an ARIA tabbed interface. 6165 * 6166 * @since 5.3.0 6167 * 6168 * @return {void} 6169 */ 6170 setupAriaTabs: function() { 6171 this.tabs = this.$( '[role="tab"]' ); 6172 6173 // Set up initial attributes. 6174 this.tabs.attr( { 6175 'aria-selected': 'false', 6176 tabIndex: '-1' 6177 } ); 6178 6179 // Set up attributes on the initially active tab. 6180 this.tabs.filter( '.active' ) 6181 .removeAttr( 'tabindex' ) 6182 .attr( 'aria-selected', 'true' ); 6183 }, 6184 6185 /** 6186 * Enables arrows navigation within the ARIA tabbed interface. 6187 * 6188 * @since 5.3.0 6189 * 6190 * @param {Object} event jQuery event object. 6191 * 6192 * @return {void} 6193 */ 6194 tabsNavigation: function( event ) { 6195 var orientation = 'horizontal', 6196 keys = [ 32, 35, 36, 37, 38, 39, 40 ]; 6197 6198 // Return if not Spacebar, End, Home, or Arrow keys. 6199 if ( keys.indexOf( event.which ) === -1 ) { 6200 return; 6201 } 6202 6203 // Determine navigation direction. 6204 if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) { 6205 orientation = 'vertical'; 6206 } 6207 6208 // Make Up and Down arrow keys do nothing with horizontal tabs. 6209 if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { 6210 return; 6211 } 6212 6213 // Make Left and Right arrow keys do nothing with vertical tabs. 6214 if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { 6215 return; 6216 } 6217 6218 this.switchTabs( event, this.tabs ); 6219 }, 6220 6221 /** 6222 * Switches tabs in the ARIA tabbed interface. 6223 * 6224 * @since 5.3.0 6225 * 6226 * @param {Object} event jQuery event object. 6227 * 6228 * @return {void} 6229 */ 6230 switchTabs: function( event ) { 6231 var key = event.which, 6232 index = this.tabs.index( $( event.target ) ), 6233 newIndex; 6234 6235 switch ( key ) { 6236 // Space bar: Activate current targeted tab. 6237 case 32: { 6238 this.activateTab( this.tabs[ index ] ); 6239 break; 6240 } 6241 // End key: Activate last tab. 6242 case 35: { 6243 event.preventDefault(); 6244 this.activateTab( this.tabs[ this.tabs.length - 1 ] ); 6245 break; 6246 } 6247 // Home key: Activate first tab. 6248 case 36: { 6249 event.preventDefault(); 6250 this.activateTab( this.tabs[ 0 ] ); 6251 break; 6252 } 6253 // Left and up keys: Activate previous tab. 6254 case 37: 6255 case 38: { 6256 event.preventDefault(); 6257 newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1; 6258 this.activateTab( this.tabs[ newIndex ] ); 6259 break; 6260 } 6261 // Right and down keys: Activate next tab. 6262 case 39: 6263 case 40: { 6264 event.preventDefault(); 6265 newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1; 6266 this.activateTab( this.tabs[ newIndex ] ); 6267 break; 6268 } 6269 } 6270 }, 6271 6272 /** 6273 * Sets a single tab to be focusable and semantically selected. 6274 * 6275 * @since 5.3.0 6276 * 6277 * @param {Object} tab The tab DOM element. 6278 * 6279 * @return {void} 6280 */ 6281 activateTab: function( tab ) { 6282 if ( ! tab ) { 6283 return; 6284 } 6285 6286 // The tab is a DOM element: no need for jQuery methods. 6287 tab.focus(); 6288 6289 // Handle automatic activation. 6290 if ( this.tabsAutomaticActivation ) { 6291 tab.removeAttribute( 'tabindex' ); 6292 tab.setAttribute( 'aria-selected', 'true' ); 6293 tab.click(); 6294 6295 return; 6296 } 6297 6298 // Handle manual activation. 6299 $( tab ).on( 'click', function() { 6300 tab.removeAttribute( 'tabindex' ); 6301 tab.setAttribute( 'aria-selected', 'true' ); 6302 } ); 6303 } 6304 }); 6305 6306 module.exports = FocusManager; 6307 6308 6309 /***/ }, 6310 6311 /***/ 1061 6312 (module) { 6313 6314 /** 6315 * wp.media.view.Frame 6316 * 6317 * A frame is a composite view consisting of one or more regions and one or more 6318 * states. 6319 * 6320 * @memberOf wp.media.view 6321 * 6322 * @see wp.media.controller.State 6323 * @see wp.media.controller.Region 6324 * 6325 * @class 6326 * @augments wp.media.View 6327 * @augments wp.Backbone.View 6328 * @augments Backbone.View 6329 * @mixes wp.media.controller.StateMachine 6330 */ 6331 var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{ 6332 initialize: function() { 6333 _.defaults( this.options, { 6334 mode: [ 'select' ] 6335 }); 6336 this._createRegions(); 6337 this._createStates(); 6338 this._createModes(); 6339 }, 6340 6341 _createRegions: function() { 6342 // Clone the regions array. 6343 this.regions = this.regions ? this.regions.slice() : []; 6344 6345 // Initialize regions. 6346 _.each( this.regions, function( region ) { 6347 this[ region ] = new wp.media.controller.Region({ 6348 view: this, 6349 id: region, 6350 selector: '.media-frame-' + region 6351 }); 6352 }, this ); 6353 }, 6354 /** 6355 * Create the frame's states. 6356 * 6357 * @see wp.media.controller.State 6358 * @see wp.media.controller.StateMachine 6359 * 6360 * @fires wp.media.controller.State#ready 6361 */ 6362 _createStates: function() { 6363 // Create the default `states` collection. 6364 this.states = new Backbone.Collection( null, { 6365 model: wp.media.controller.State 6366 }); 6367 6368 // Ensure states have a reference to the frame. 6369 this.states.on( 'add', function( model ) { 6370 model.frame = this; 6371 model.trigger('ready'); 6372 }, this ); 6373 6374 if ( this.options.states ) { 6375 this.states.add( this.options.states ); 6376 } 6377 }, 6378 6379 /** 6380 * A frame can be in a mode or multiple modes at one time. 6381 * 6382 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 6383 */ 6384 _createModes: function() { 6385 // Store active "modes" that the frame is in. Unrelated to region modes. 6386 this.activeModes = new Backbone.Collection(); 6387 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 6388 6389 _.each( this.options.mode, function( mode ) { 6390 this.activateMode( mode ); 6391 }, this ); 6392 }, 6393 /** 6394 * Reset all states on the frame to their defaults. 6395 * 6396 * @return {wp.media.view.Frame} Returns itself to allow chaining. 6397 */ 6398 reset: function() { 6399 this.states.invoke( 'trigger', 'reset' ); 6400 return this; 6401 }, 6402 /** 6403 * Map activeMode collection events to the frame. 6404 * 6405 * @param {Backbone.Model} model The model that triggered the event. 6406 * @param {Backbone.Collection} collection The collection to which the model belongs. 6407 * @param {Object} options The options object associated with the event. 6408 */ 6409 triggerModeEvents: function( model, collection, options ) { 6410 var collectionEvent, 6411 modeEventMap = { 6412 add: 'activate', 6413 remove: 'deactivate' 6414 }, 6415 eventToTrigger; 6416 // Probably a better way to do this. 6417 _.each( options, function( value, key ) { 6418 if ( value ) { 6419 collectionEvent = key; 6420 } 6421 } ); 6422 6423 if ( ! _.has( modeEventMap, collectionEvent ) ) { 6424 return; 6425 } 6426 6427 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 6428 this.trigger( eventToTrigger ); 6429 }, 6430 /** 6431 * Activate a mode on the frame. 6432 * 6433 * @param {string} mode Mode ID. 6434 * @return {void|this} Returns itself to allow chaining. 6435 */ 6436 activateMode: function( mode ) { 6437 // Bail if the mode is already active. 6438 if ( this.isModeActive( mode ) ) { 6439 return; 6440 } 6441 this.activeModes.add( [ { id: mode } ] ); 6442 // Add a CSS class to the frame so elements can be styled for the mode. 6443 this.$el.addClass( 'mode-' + mode ); 6444 6445 return this; 6446 }, 6447 /** 6448 * Deactivate a mode on the frame. 6449 * 6450 * @param {string} mode Mode ID. 6451 * @return {this} Returns itself to allow chaining. 6452 */ 6453 deactivateMode: function( mode ) { 6454 // Bail if the mode isn't active. 6455 if ( ! this.isModeActive( mode ) ) { 6456 return this; 6457 } 6458 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 6459 this.$el.removeClass( 'mode-' + mode ); 6460 6461 // Fires a `#{mode}:deactivate` event on the frame. 6462 this.trigger( mode + ':deactivate' ); 6463 6464 return this; 6465 }, 6466 /** 6467 * Check if a mode is enabled on the frame. 6468 * 6469 * @param {string} mode Mode ID. 6470 * @return {boolean} True if the mode is active, false otherwise. 6471 */ 6472 isModeActive: function( mode ) { 6473 return Boolean( this.activeModes.where( { id: mode } ).length ); 6474 } 6475 }); 6476 6477 // Make the `Frame` a `StateMachine`. 6478 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 6479 6480 module.exports = Frame; 6481 6482 6483 /***/ }, 6484 6485 /***/ 5424 6486 (module) { 6487 6488 var Select = wp.media.view.MediaFrame.Select, 6489 l10n = wp.media.view.l10n, 6490 ImageDetails; 6491 6492 /** 6493 * wp.media.view.MediaFrame.ImageDetails 6494 * 6495 * A media frame for manipulating an image that's already been inserted 6496 * into a post. 6497 * 6498 * @memberOf wp.media.view.MediaFrame 6499 * 6500 * @class 6501 * @augments wp.media.view.MediaFrame.Select 6502 * @augments wp.media.view.MediaFrame 6503 * @augments wp.media.view.Frame 6504 * @augments wp.media.View 6505 * @augments wp.Backbone.View 6506 * @augments Backbone.View 6507 * @mixes wp.media.controller.StateMachine 6508 */ 6509 ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{ 6510 defaults: { 6511 id: 'image', 6512 url: '', 6513 menu: 'image-details', 6514 content: 'image-details', 6515 toolbar: 'image-details', 6516 type: 'link', 6517 title: l10n.imageDetailsTitle, 6518 priority: 120 6519 }, 6520 6521 initialize: function( options ) { 6522 this.image = new wp.media.model.PostImage( options.metadata ); 6523 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 6524 Select.prototype.initialize.apply( this, arguments ); 6525 }, 6526 6527 bindHandlers: function() { 6528 Select.prototype.bindHandlers.apply( this, arguments ); 6529 this.on( 'menu:create:image-details', this.createMenu, this ); 6530 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 6531 this.on( 'content:render:edit-image', this.editImageContent, this ); 6532 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 6533 // Override the select toolbar. 6534 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 6535 }, 6536 6537 createStates: function() { 6538 this.states.add([ 6539 new wp.media.controller.ImageDetails({ 6540 image: this.image, 6541 editable: false 6542 }), 6543 new wp.media.controller.ReplaceImage({ 6544 id: 'replace-image', 6545 library: wp.media.query( { type: 'image' } ), 6546 image: this.image, 6547 multiple: false, 6548 title: l10n.imageReplaceTitle, 6549 toolbar: 'replace', 6550 priority: 80, 6551 displaySettings: true 6552 }), 6553 new wp.media.controller.EditImage( { 6554 image: this.image, 6555 selection: this.options.selection 6556 } ) 6557 ]); 6558 }, 6559 6560 imageDetailsContent: function( options ) { 6561 options.view = new wp.media.view.ImageDetails({ 6562 controller: this, 6563 model: this.state().image, 6564 attachment: this.state().image.attachment 6565 }); 6566 }, 6567 6568 editImageContent: function() { 6569 var state = this.state(), 6570 model = state.get('image'), 6571 view; 6572 6573 if ( ! model ) { 6574 return; 6575 } 6576 6577 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 6578 6579 this.content.set( view ); 6580 6581 // After bringing in the frame, load the actual editor via an Ajax call. 6582 view.loadEditor(); 6583 6584 }, 6585 6586 renderImageDetailsToolbar: function() { 6587 this.toolbar.set( new wp.media.view.Toolbar({ 6588 controller: this, 6589 items: { 6590 select: { 6591 style: 'primary', 6592 text: l10n.update, 6593 priority: 80, 6594 6595 click: function() { 6596 var controller = this.controller, 6597 state = controller.state(); 6598 6599 controller.close(); 6600 6601 // Not sure if we want to use wp.media.string.image which will create a shortcode or 6602 // perhaps wp.html.string to at least to build the <img />. 6603 state.trigger( 'update', controller.image.toJSON() ); 6604 6605 // Restore and reset the default state. 6606 controller.setState( controller.options.state ); 6607 controller.reset(); 6608 } 6609 } 6610 } 6611 }) ); 6612 }, 6613 6614 renderReplaceImageToolbar: function() { 6615 var frame = this, 6616 lastState = frame.lastState(), 6617 previous = lastState && lastState.id; 6618 6619 this.toolbar.set( new wp.media.view.Toolbar({ 6620 controller: this, 6621 items: { 6622 back: { 6623 text: l10n.back, 6624 priority: 80, 6625 click: function() { 6626 if ( previous ) { 6627 frame.setState( previous ); 6628 } else { 6629 frame.close(); 6630 } 6631 } 6632 }, 6633 6634 replace: { 6635 style: 'primary', 6636 text: l10n.replace, 6637 priority: 20, 6638 requires: { selection: true }, 6639 6640 click: function() { 6641 var controller = this.controller, 6642 state = controller.state(), 6643 selection = state.get( 'selection' ), 6644 attachment = selection.single(); 6645 6646 controller.close(); 6647 6648 controller.image.changeAttachment( attachment, state.display( attachment ) ); 6649 6650 // Not sure if we want to use wp.media.string.image which will create a shortcode or 6651 // perhaps wp.html.string to at least to build the <img />. 6652 state.trigger( 'replace', controller.image.toJSON() ); 6653 6654 // Restore and reset the default state. 6655 controller.setState( controller.options.state ); 6656 controller.reset(); 6657 } 6658 } 6659 } 6660 }) ); 6661 } 6662 6663 }); 6664 6665 module.exports = ImageDetails; 6666 6667 6668 /***/ }, 6669 6670 /***/ 4274 6671 (module) { 6672 6673 var Select = wp.media.view.MediaFrame.Select, 6674 Library = wp.media.controller.Library, 6675 l10n = wp.media.view.l10n, 6676 Post; 6677 6678 /** 6679 * wp.media.view.MediaFrame.Post 6680 * 6681 * The frame for manipulating media on the Edit Post page. 6682 * 6683 * @memberOf wp.media.view.MediaFrame 6684 * 6685 * @class 6686 * @augments wp.media.view.MediaFrame.Select 6687 * @augments wp.media.view.MediaFrame 6688 * @augments wp.media.view.Frame 6689 * @augments wp.media.View 6690 * @augments wp.Backbone.View 6691 * @augments Backbone.View 6692 * @mixes wp.media.controller.StateMachine 6693 */ 6694 Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{ 6695 initialize: function() { 6696 this.counts = { 6697 audio: { 6698 count: wp.media.view.settings.attachmentCounts.audio, 6699 state: 'playlist' 6700 }, 6701 video: { 6702 count: wp.media.view.settings.attachmentCounts.video, 6703 state: 'video-playlist' 6704 } 6705 }; 6706 6707 _.defaults( this.options, { 6708 multiple: true, 6709 editing: false, 6710 state: 'insert', 6711 metadata: {} 6712 }); 6713 6714 // Call 'initialize' directly on the parent class. 6715 Select.prototype.initialize.apply( this, arguments ); 6716 this.createIframeStates(); 6717 6718 }, 6719 6720 /** 6721 * Create the default states. 6722 */ 6723 createStates: function() { 6724 var options = this.options; 6725 6726 this.states.add([ 6727 // Main states. 6728 new Library({ 6729 id: 'insert', 6730 title: l10n.insertMediaTitle, 6731 priority: 20, 6732 toolbar: 'main-insert', 6733 filterable: 'all', 6734 library: wp.media.query( options.library ), 6735 multiple: options.multiple ? 'reset' : false, 6736 editable: true, 6737 6738 // If the user isn't allowed to edit fields, 6739 // can they still edit it locally? 6740 allowLocalEdits: true, 6741 6742 // Show the attachment display settings. 6743 displaySettings: true, 6744 // Update user settings when users adjust the 6745 // attachment display settings. 6746 displayUserSettings: true 6747 }), 6748 6749 new Library({ 6750 id: 'gallery', 6751 title: l10n.createGalleryTitle, 6752 priority: 40, 6753 toolbar: 'main-gallery', 6754 filterable: 'uploaded', 6755 multiple: 'add', 6756 editable: false, 6757 6758 library: wp.media.query( _.defaults({ 6759 type: 'image' 6760 }, options.library ) ) 6761 }), 6762 6763 // Embed states. 6764 new wp.media.controller.Embed( { metadata: options.metadata } ), 6765 6766 new wp.media.controller.EditImage( { model: options.editImage } ), 6767 6768 // Gallery states. 6769 new wp.media.controller.GalleryEdit({ 6770 library: options.selection, 6771 editing: options.editing, 6772 menu: 'gallery' 6773 }), 6774 6775 new wp.media.controller.GalleryAdd(), 6776 6777 new Library({ 6778 id: 'playlist', 6779 title: l10n.createPlaylistTitle, 6780 priority: 60, 6781 toolbar: 'main-playlist', 6782 filterable: 'uploaded', 6783 multiple: 'add', 6784 editable: false, 6785 6786 library: wp.media.query( _.defaults({ 6787 type: 'audio' 6788 }, options.library ) ) 6789 }), 6790 6791 // Playlist states. 6792 new wp.media.controller.CollectionEdit({ 6793 type: 'audio', 6794 collectionType: 'playlist', 6795 title: l10n.editPlaylistTitle, 6796 SettingsView: wp.media.view.Settings.Playlist, 6797 library: options.selection, 6798 editing: options.editing, 6799 menu: 'playlist', 6800 dragInfoText: l10n.playlistDragInfo, 6801 dragInfo: false 6802 }), 6803 6804 new wp.media.controller.CollectionAdd({ 6805 type: 'audio', 6806 collectionType: 'playlist', 6807 title: l10n.addToPlaylistTitle 6808 }), 6809 6810 new Library({ 6811 id: 'video-playlist', 6812 title: l10n.createVideoPlaylistTitle, 6813 priority: 60, 6814 toolbar: 'main-video-playlist', 6815 filterable: 'uploaded', 6816 multiple: 'add', 6817 editable: false, 6818 6819 library: wp.media.query( _.defaults({ 6820 type: 'video' 6821 }, options.library ) ) 6822 }), 6823 6824 new wp.media.controller.CollectionEdit({ 6825 type: 'video', 6826 collectionType: 'playlist', 6827 title: l10n.editVideoPlaylistTitle, 6828 SettingsView: wp.media.view.Settings.Playlist, 6829 library: options.selection, 6830 editing: options.editing, 6831 menu: 'video-playlist', 6832 dragInfoText: l10n.videoPlaylistDragInfo, 6833 dragInfo: false 6834 }), 6835 6836 new wp.media.controller.CollectionAdd({ 6837 type: 'video', 6838 collectionType: 'playlist', 6839 title: l10n.addToVideoPlaylistTitle 6840 }) 6841 ]); 6842 6843 if ( wp.media.view.settings.post.featuredImageId ) { 6844 this.states.add( new wp.media.controller.FeaturedImage() ); 6845 } 6846 }, 6847 6848 bindHandlers: function() { 6849 var handlers, checkCounts; 6850 6851 Select.prototype.bindHandlers.apply( this, arguments ); 6852 6853 this.on( 'activate', this.activate, this ); 6854 6855 // Only bother checking media type counts if one of the counts is zero. 6856 checkCounts = _.find( this.counts, function( type ) { 6857 return type.count === 0; 6858 } ); 6859 6860 if ( typeof checkCounts !== 'undefined' ) { 6861 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 6862 } 6863 6864 this.on( 'menu:create:gallery', this.createMenu, this ); 6865 this.on( 'menu:create:playlist', this.createMenu, this ); 6866 this.on( 'menu:create:video-playlist', this.createMenu, this ); 6867 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 6868 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 6869 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 6870 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 6871 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 6872 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 6873 6874 handlers = { 6875 menu: { 6876 'default': 'mainMenu', 6877 'gallery': 'galleryMenu', 6878 'playlist': 'playlistMenu', 6879 'video-playlist': 'videoPlaylistMenu' 6880 }, 6881 6882 content: { 6883 'embed': 'embedContent', 6884 'edit-image': 'editImageContent', 6885 'edit-selection': 'editSelectionContent' 6886 }, 6887 6888 toolbar: { 6889 'main-insert': 'mainInsertToolbar', 6890 'main-gallery': 'mainGalleryToolbar', 6891 'gallery-edit': 'galleryEditToolbar', 6892 'gallery-add': 'galleryAddToolbar', 6893 'main-playlist': 'mainPlaylistToolbar', 6894 'playlist-edit': 'playlistEditToolbar', 6895 'playlist-add': 'playlistAddToolbar', 6896 'main-video-playlist': 'mainVideoPlaylistToolbar', 6897 'video-playlist-edit': 'videoPlaylistEditToolbar', 6898 'video-playlist-add': 'videoPlaylistAddToolbar' 6899 } 6900 }; 6901 6902 _.each( handlers, function( regionHandlers, region ) { 6903 _.each( regionHandlers, function( callback, handler ) { 6904 this.on( region + ':render:' + handler, this[ callback ], this ); 6905 }, this ); 6906 }, this ); 6907 }, 6908 6909 /** 6910 * Activates the frame. 6911 */ 6912 activate: function() { 6913 // Hide menu items for states tied to particular media types if there are no items. 6914 _.each( this.counts, function( type ) { 6915 if ( type.count < 1 ) { 6916 this.menuItemVisibility( type.state, 'hide' ); 6917 } 6918 }, this ); 6919 }, 6920 6921 /** 6922 * Handles the counts of media types. 6923 * 6924 * @param {wp.media.model.Attachments} model The attachment model that changed. 6925 * @param {string} attr The attribute that changed on the model. 6926 */ 6927 mediaTypeCounts: function( model, attr ) { 6928 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 6929 this.counts[ attr ].count++; 6930 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 6931 } 6932 }, 6933 6934 // Menus. 6935 /** 6936 * Handles the main menu for the frame. 6937 * 6938 * @param {wp.Backbone.View} view The menu view for the main menu. 6939 */ 6940 mainMenu: function( view ) { 6941 view.set({ 6942 'library-separator': new wp.media.View({ 6943 className: 'separator', 6944 priority: 100, 6945 attributes: { 6946 role: 'presentation' 6947 } 6948 }) 6949 }); 6950 }, 6951 6952 /** 6953 * Handles the visibility of menu items for the frame. 6954 * 6955 * @param {string} state The state to show or hide. 6956 * @param {string} visibility The visibility of the menu item, either 'show' or 'hide'. 6957 */ 6958 menuItemVisibility: function( state, visibility ) { 6959 var menu = this.menu.get(); 6960 if ( visibility === 'hide' ) { 6961 menu.hide( state ); 6962 } else if ( visibility === 'show' ) { 6963 menu.show( state ); 6964 } 6965 }, 6966 /** 6967 * Handles the gallery menu for the frame. 6968 * 6969 * @param {wp.Backbone.View} view The menu view for the gallery menu. 6970 */ 6971 galleryMenu: function( view ) { 6972 var lastState = this.lastState(), 6973 previous = lastState && lastState.id, 6974 frame = this; 6975 6976 view.set({ 6977 cancel: { 6978 text: l10n.cancelGalleryTitle, 6979 priority: 20, 6980 click: function() { 6981 if ( previous ) { 6982 frame.setState( previous ); 6983 } else { 6984 frame.close(); 6985 } 6986 6987 // Move focus to the modal after canceling a Gallery. 6988 this.controller.modal.focusManager.focus(); 6989 } 6990 }, 6991 separateCancel: new wp.media.View({ 6992 className: 'separator', 6993 priority: 40 6994 }) 6995 }); 6996 }, 6997 6998 /** 6999 * Handles the playlist menu for the frame. 7000 * 7001 * @param {wp.Backbone.View} view The menu view for the playlist menu. 7002 */ 7003 playlistMenu: function( view ) { 7004 var lastState = this.lastState(), 7005 previous = lastState && lastState.id, 7006 frame = this; 7007 7008 view.set({ 7009 cancel: { 7010 text: l10n.cancelPlaylistTitle, 7011 priority: 20, 7012 click: function() { 7013 if ( previous ) { 7014 frame.setState( previous ); 7015 } else { 7016 frame.close(); 7017 } 7018 7019 // Move focus to the modal after canceling an Audio Playlist. 7020 this.controller.modal.focusManager.focus(); 7021 } 7022 }, 7023 separateCancel: new wp.media.View({ 7024 className: 'separator', 7025 priority: 40 7026 }) 7027 }); 7028 }, 7029 7030 /** 7031 * Handles the video playlist menu for the frame. 7032 * 7033 * @param {wp.Backbone.View} view The menu view. 7034 */ 7035 videoPlaylistMenu: function( view ) { 7036 var lastState = this.lastState(), 7037 previous = lastState && lastState.id, 7038 frame = this; 7039 7040 view.set({ 7041 cancel: { 7042 text: l10n.cancelVideoPlaylistTitle, 7043 priority: 20, 7044 click: function() { 7045 if ( previous ) { 7046 frame.setState( previous ); 7047 } else { 7048 frame.close(); 7049 } 7050 7051 // Move focus to the modal after canceling a Video Playlist. 7052 this.controller.modal.focusManager.focus(); 7053 } 7054 }, 7055 separateCancel: new wp.media.View({ 7056 className: 'separator', 7057 priority: 40 7058 }) 7059 }); 7060 }, 7061 7062 // Content. 7063 /** 7064 * Handles the embed content for the frame. 7065 */ 7066 embedContent: function() { 7067 var view = new wp.media.view.Embed({ 7068 controller: this, 7069 model: this.state() 7070 }).render(); 7071 7072 this.content.set( view ); 7073 }, 7074 7075 /** 7076 * Handles the edit selection content for the frame. 7077 */ 7078 editSelectionContent: function() { 7079 var state = this.state(), 7080 selection = state.get('selection'), 7081 view; 7082 7083 view = new wp.media.view.AttachmentsBrowser({ 7084 controller: this, 7085 collection: selection, 7086 selection: selection, 7087 model: state, 7088 sortable: true, 7089 search: false, 7090 date: false, 7091 dragInfo: true, 7092 7093 AttachmentView: wp.media.view.Attachments.EditSelection 7094 }).render(); 7095 7096 view.toolbar.set( 'backToLibrary', { 7097 text: l10n.returnToLibrary, 7098 priority: -100, 7099 7100 click: function() { 7101 this.controller.content.mode('browse'); 7102 // Move focus to the modal when jumping back from Edit Selection to Add Media view. 7103 this.controller.modal.focusManager.focus(); 7104 } 7105 }); 7106 7107 // Browse our library of attachments. 7108 this.content.set( view ); 7109 7110 // Trigger the controller to set focus. 7111 this.trigger( 'edit:selection', this ); 7112 }, 7113 7114 /** 7115 * Handles the edit image content for the frame. 7116 */ 7117 editImageContent: function() { 7118 var image = this.state().get('image'), 7119 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 7120 7121 this.content.set( view ); 7122 7123 // After creating the wrapper view, load the actual editor via an Ajax call. 7124 view.loadEditor(); 7125 7126 }, 7127 7128 // Toolbars. 7129 7130 /** 7131 * Handles the selection status toolbar for the frame 7132 * 7133 * @param {wp.Backbone.View} view The toolbar view. 7134 */ 7135 selectionStatusToolbar: function( view ) { 7136 var editable = this.state().get('editable'); 7137 7138 view.set( 'selection', new wp.media.view.Selection({ 7139 controller: this, 7140 collection: this.state().get('selection'), 7141 priority: -40, 7142 7143 // If the selection is editable, pass the callback to 7144 // switch the content mode. 7145 editable: editable && function() { 7146 this.controller.content.mode('edit-selection'); 7147 } 7148 }).render() ); 7149 }, 7150 7151 /** 7152 * Handles the main insert toolbar for the frame. 7153 * 7154 * @param {wp.Backbone.View} view The toolbar view. 7155 */ 7156 mainInsertToolbar: function( view ) { 7157 var controller = this; 7158 7159 this.selectionStatusToolbar( view ); 7160 7161 view.set( 'insert', { 7162 style: 'primary', 7163 priority: 80, 7164 text: l10n.insertIntoPost, 7165 requires: { selection: true }, 7166 7167 /** 7168 * @ignore 7169 * 7170 * @fires wp.media.controller.State#insert 7171 */ 7172 click: function() { 7173 var state = controller.state(), 7174 selection = state.get('selection'); 7175 7176 controller.close(); 7177 state.trigger( 'insert', selection ).reset(); 7178 } 7179 }); 7180 }, 7181 7182 /** 7183 * Handles the main gallery toolbar for the frame. 7184 * 7185 * @param {wp.Backbone.View} view The toolbar view. 7186 */ 7187 mainGalleryToolbar: function( view ) { 7188 var controller = this; 7189 7190 this.selectionStatusToolbar( view ); 7191 7192 view.set( 'gallery', { 7193 style: 'primary', 7194 text: l10n.createNewGallery, 7195 priority: 60, 7196 requires: { selection: true }, 7197 7198 click: function() { 7199 var selection = controller.state().get('selection'), 7200 edit = controller.state('gallery-edit'), 7201 models = selection.where({ type: 'image' }); 7202 7203 edit.set( 'library', new wp.media.model.Selection( models, { 7204 props: selection.props.toJSON(), 7205 multiple: true 7206 }) ); 7207 7208 // Jump to Edit Gallery view. 7209 this.controller.setState( 'gallery-edit' ); 7210 7211 // Move focus to the modal after jumping to Edit Gallery view. 7212 this.controller.modal.focusManager.focus(); 7213 } 7214 }); 7215 }, 7216 7217 /** 7218 * Handles the main playlist toolbar for the frame. 7219 * 7220 * @param {wp.Backbone.View} view The toolbar view. 7221 */ 7222 mainPlaylistToolbar: function( view ) { 7223 var controller = this; 7224 7225 this.selectionStatusToolbar( view ); 7226 7227 view.set( 'playlist', { 7228 style: 'primary', 7229 text: l10n.createNewPlaylist, 7230 priority: 100, 7231 requires: { selection: true }, 7232 7233 click: function() { 7234 var selection = controller.state().get('selection'), 7235 edit = controller.state('playlist-edit'), 7236 models = selection.where({ type: 'audio' }); 7237 7238 edit.set( 'library', new wp.media.model.Selection( models, { 7239 props: selection.props.toJSON(), 7240 multiple: true 7241 }) ); 7242 7243 // Jump to Edit Audio Playlist view. 7244 this.controller.setState( 'playlist-edit' ); 7245 7246 // Move focus to the modal after jumping to Edit Audio Playlist view. 7247 this.controller.modal.focusManager.focus(); 7248 } 7249 }); 7250 }, 7251 7252 /** 7253 * Handles the main video playlist toolbar for the frame. 7254 * 7255 * @param {wp.Backbone.View} view The toolbar view. 7256 */ 7257 mainVideoPlaylistToolbar: function( view ) { 7258 var controller = this; 7259 7260 this.selectionStatusToolbar( view ); 7261 7262 view.set( 'video-playlist', { 7263 style: 'primary', 7264 text: l10n.createNewVideoPlaylist, 7265 priority: 100, 7266 requires: { selection: true }, 7267 7268 click: function() { 7269 var selection = controller.state().get('selection'), 7270 edit = controller.state('video-playlist-edit'), 7271 models = selection.where({ type: 'video' }); 7272 7273 edit.set( 'library', new wp.media.model.Selection( models, { 7274 props: selection.props.toJSON(), 7275 multiple: true 7276 }) ); 7277 7278 // Jump to Edit Video Playlist view. 7279 this.controller.setState( 'video-playlist-edit' ); 7280 7281 // Move focus to the modal after jumping to Edit Video Playlist view. 7282 this.controller.modal.focusManager.focus(); 7283 } 7284 }); 7285 }, 7286 7287 /** 7288 * Handles the featured image toolbar for the frame. 7289 * 7290 * @param {wp.media.view.Toolbar} toolbar The toolbar view. 7291 */ 7292 featuredImageToolbar: function( toolbar ) { 7293 this.createSelectToolbar( toolbar, { 7294 text: l10n.setFeaturedImage, 7295 state: this.options.state 7296 }); 7297 }, 7298 7299 /** 7300 * Handles the main embed toolbar for the frame. 7301 * 7302 * @param {wp.media.view.Toolbar} toolbar The toolbar view. 7303 */ 7304 mainEmbedToolbar: function( toolbar ) { 7305 toolbar.view = new wp.media.view.Toolbar.Embed({ 7306 controller: this 7307 }); 7308 }, 7309 7310 /** 7311 * Handles the edit image toolbar for the frame. 7312 */ 7313 galleryEditToolbar: function() { 7314 var editing = this.state().get('editing'); 7315 this.toolbar.set( new wp.media.view.Toolbar({ 7316 controller: this, 7317 items: { 7318 insert: { 7319 style: 'primary', 7320 text: editing ? l10n.updateGallery : l10n.insertGallery, 7321 priority: 80, 7322 requires: { library: true, uploadingComplete: true }, 7323 7324 /** 7325 * Handles the click event for the insert button. 7326 * 7327 * @fires wp.media.controller.State#update 7328 */ 7329 click: function() { 7330 var controller = this.controller, 7331 state = controller.state(); 7332 7333 controller.close(); 7334 state.trigger( 'update', state.get('library') ); 7335 7336 // Restore and reset the default state. 7337 controller.setState( controller.options.state ); 7338 controller.reset(); 7339 } 7340 } 7341 } 7342 }) ); 7343 }, 7344 7345 /** 7346 * Handles the add to gallery toolbar for the frame. 7347 */ 7348 galleryAddToolbar: function() { 7349 this.toolbar.set( new wp.media.view.Toolbar({ 7350 controller: this, 7351 items: { 7352 insert: { 7353 style: 'primary', 7354 text: l10n.addToGallery, 7355 priority: 80, 7356 requires: { selection: true }, 7357 7358 /** 7359 * Handles the click event for the insert button. 7360 * 7361 * @fires wp.media.controller.State#reset 7362 */ 7363 click: function() { 7364 var controller = this.controller, 7365 state = controller.state(), 7366 edit = controller.state('gallery-edit'); 7367 7368 edit.get('library').add( state.get('selection').models ); 7369 state.trigger('reset'); 7370 controller.setState('gallery-edit'); 7371 // Move focus to the modal when jumping back from Add to Gallery to Edit Gallery view. 7372 this.controller.modal.focusManager.focus(); 7373 } 7374 } 7375 } 7376 }) ); 7377 }, 7378 7379 /** 7380 * Handles the edit playlist toolbar for the frame. 7381 */ 7382 playlistEditToolbar: function() { 7383 var editing = this.state().get('editing'); 7384 this.toolbar.set( new wp.media.view.Toolbar({ 7385 controller: this, 7386 items: { 7387 insert: { 7388 style: 'primary', 7389 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 7390 priority: 80, 7391 requires: { library: true }, 7392 7393 /** 7394 * Handles the click event for the insert button. 7395 * 7396 * @fires wp.media.controller.State#update 7397 */ 7398 click: function() { 7399 var controller = this.controller, 7400 state = controller.state(); 7401 7402 controller.close(); 7403 state.trigger( 'update', state.get('library') ); 7404 7405 // Restore and reset the default state. 7406 controller.setState( controller.options.state ); 7407 controller.reset(); 7408 } 7409 } 7410 } 7411 }) ); 7412 }, 7413 7414 /** 7415 * Handles the add to playlist toolbar for the frame. 7416 */ 7417 playlistAddToolbar: function() { 7418 this.toolbar.set( new wp.media.view.Toolbar({ 7419 controller: this, 7420 items: { 7421 insert: { 7422 style: 'primary', 7423 text: l10n.addToPlaylist, 7424 priority: 80, 7425 requires: { selection: true }, 7426 7427 /** 7428 * Handles the click event for the insert button. 7429 * 7430 * @fires wp.media.controller.State#reset 7431 */ 7432 click: function() { 7433 var controller = this.controller, 7434 state = controller.state(), 7435 edit = controller.state('playlist-edit'); 7436 7437 edit.get('library').add( state.get('selection').models ); 7438 state.trigger('reset'); 7439 controller.setState('playlist-edit'); 7440 // Move focus to the modal when jumping back from Add to Audio Playlist to Edit Audio Playlist view. 7441 this.controller.modal.focusManager.focus(); 7442 } 7443 } 7444 } 7445 }) ); 7446 }, 7447 7448 /** 7449 * Handles the edit video playlist toolbar for the frame. 7450 */ 7451 videoPlaylistEditToolbar: function() { 7452 var editing = this.state().get('editing'); 7453 this.toolbar.set( new wp.media.view.Toolbar({ 7454 controller: this, 7455 items: { 7456 insert: { 7457 style: 'primary', 7458 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 7459 priority: 140, 7460 requires: { library: true }, 7461 7462 click: function() { 7463 var controller = this.controller, 7464 state = controller.state(), 7465 library = state.get('library'); 7466 7467 library.type = 'video'; 7468 7469 controller.close(); 7470 state.trigger( 'update', library ); 7471 7472 // Restore and reset the default state. 7473 controller.setState( controller.options.state ); 7474 controller.reset(); 7475 } 7476 } 7477 } 7478 }) ); 7479 }, 7480 7481 /** 7482 * Handles the add to video playlist toolbar for the frame. 7483 */ 7484 videoPlaylistAddToolbar: function() { 7485 this.toolbar.set( new wp.media.view.Toolbar({ 7486 controller: this, 7487 items: { 7488 insert: { 7489 style: 'primary', 7490 text: l10n.addToVideoPlaylist, 7491 priority: 140, 7492 requires: { selection: true }, 7493 7494 click: function() { 7495 var controller = this.controller, 7496 state = controller.state(), 7497 edit = controller.state('video-playlist-edit'); 7498 7499 edit.get('library').add( state.get('selection').models ); 7500 state.trigger('reset'); 7501 controller.setState('video-playlist-edit'); 7502 // Move focus to the modal when jumping back from Add to Video Playlist to Edit Video Playlist view. 7503 this.controller.modal.focusManager.focus(); 7504 } 7505 } 7506 } 7507 }) ); 7508 } 7509 }); 7510 7511 module.exports = Post; 7512 7513 7514 /***/ }, 7515 7516 /***/ 455 7517 (module) { 7518 7519 var MediaFrame = wp.media.view.MediaFrame, 7520 l10n = wp.media.view.l10n, 7521 Select; 7522 7523 /** 7524 * wp.media.view.MediaFrame.Select 7525 * 7526 * A frame for selecting an item or items from the media library. 7527 * 7528 * @memberOf wp.media.view.MediaFrame 7529 * 7530 * @class 7531 * @augments wp.media.view.MediaFrame 7532 * @augments wp.media.view.Frame 7533 * @augments wp.media.View 7534 * @augments wp.Backbone.View 7535 * @augments Backbone.View 7536 * @mixes wp.media.controller.StateMachine 7537 */ 7538 Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{ 7539 initialize: function() { 7540 // Call 'initialize' directly on the parent class. 7541 MediaFrame.prototype.initialize.apply( this, arguments ); 7542 7543 _.defaults( this.options, { 7544 selection: [], 7545 library: {}, 7546 multiple: false, 7547 state: 'library' 7548 }); 7549 7550 this.createSelection(); 7551 this.createStates(); 7552 this.bindHandlers(); 7553 }, 7554 7555 /** 7556 * Attach a selection collection to the frame. 7557 * 7558 * A selection is a collection of attachments used for a specific purpose 7559 * by a media frame. e.g. Selecting an attachment (or many) to insert into 7560 * post content. 7561 * 7562 * @see media.model.Selection 7563 */ 7564 createSelection: function() { 7565 var selection = this.options.selection; 7566 7567 if ( ! (selection instanceof wp.media.model.Selection) ) { 7568 this.options.selection = new wp.media.model.Selection( selection, { 7569 multiple: this.options.multiple 7570 }); 7571 } 7572 7573 this._selection = { 7574 attachments: new wp.media.model.Attachments(), 7575 difference: [] 7576 }; 7577 }, 7578 7579 editImageContent: function() { 7580 var image = this.state().get('image'), 7581 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 7582 7583 this.content.set( view ); 7584 7585 // After creating the wrapper view, load the actual editor via an Ajax call. 7586 view.loadEditor(); 7587 }, 7588 7589 /** 7590 * Create the default states on the frame. 7591 */ 7592 createStates: function() { 7593 var options = this.options; 7594 7595 if ( this.options.states ) { 7596 return; 7597 } 7598 7599 // Add the default states. 7600 this.states.add([ 7601 // Main states. 7602 new wp.media.controller.Library({ 7603 library: wp.media.query( options.library ), 7604 multiple: options.multiple, 7605 title: options.title, 7606 priority: 20 7607 }), 7608 new wp.media.controller.EditImage( { model: options.editImage } ) 7609 ]); 7610 }, 7611 7612 /** 7613 * Bind region mode event callbacks. 7614 * 7615 * @see media.controller.Region.render 7616 */ 7617 bindHandlers: function() { 7618 this.on( 'router:create:browse', this.createRouter, this ); 7619 this.on( 'router:render:browse', this.browseRouter, this ); 7620 this.on( 'content:create:browse', this.browseContent, this ); 7621 this.on( 'content:render:upload', this.uploadContent, this ); 7622 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 7623 this.on( 'content:render:edit-image', this.editImageContent, this ); 7624 }, 7625 7626 /** 7627 * Render callback for the router region in the `browse` mode. 7628 * 7629 * @param {wp.media.view.Router} routerView The router view for the `browse` mode. 7630 */ 7631 browseRouter: function( routerView ) { 7632 routerView.set({ 7633 upload: { 7634 text: l10n.uploadFilesTitle, 7635 priority: 20 7636 }, 7637 browse: { 7638 text: l10n.mediaLibraryTitle, 7639 priority: 40 7640 } 7641 }); 7642 }, 7643 7644 /** 7645 * Render callback for the content region in the `browse` mode. 7646 * 7647 * @param {wp.media.controller.Region} contentRegion The content region for the `browse` mode. 7648 */ 7649 browseContent: function( contentRegion ) { 7650 var state = this.state(); 7651 7652 this.$el.removeClass('hide-toolbar'); 7653 7654 // Browse our library of attachments. 7655 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 7656 controller: this, 7657 collection: state.get('library'), 7658 selection: state.get('selection'), 7659 model: state, 7660 sortable: state.get('sortable'), 7661 search: state.get('searchable'), 7662 filters: state.get('filterable'), 7663 date: state.get('date'), 7664 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 7665 dragInfo: state.get('dragInfo'), 7666 7667 idealColumnWidth: state.get('idealColumnWidth'), 7668 suggestedWidth: state.get('suggestedWidth'), 7669 suggestedHeight: state.get('suggestedHeight'), 7670 7671 AttachmentView: state.get('AttachmentView') 7672 }); 7673 }, 7674 7675 /** 7676 * Render callback for the content region in the `upload` mode. 7677 */ 7678 uploadContent: function() { 7679 this.$el.removeClass( 'hide-toolbar' ); 7680 this.content.set( new wp.media.view.UploaderInline({ 7681 controller: this 7682 }) ); 7683 }, 7684 7685 /** 7686 * Toolbars 7687 * 7688 * @param {Object} toolbar The toolbar object for the `select` mode. 7689 * @param {Object} [options={}] The options for creating the select toolbar view. 7690 * @this {wp.media.controller.Region} 7691 */ 7692 createSelectToolbar: function( toolbar, options ) { 7693 options = options || this.options.button || {}; 7694 options.controller = this; 7695 7696 toolbar.view = new wp.media.view.Toolbar.Select( options ); 7697 } 7698 }); 7699 7700 module.exports = Select; 7701 7702 7703 /***/ }, 7704 7705 /***/ 170 7706 (module) { 7707 7708 /** 7709 * wp.media.view.Heading 7710 * 7711 * A reusable heading component for the media library 7712 * 7713 * Used to add accessibility friendly headers in the media library/modal. 7714 * 7715 * @class 7716 * @augments wp.media.View 7717 * @augments wp.Backbone.View 7718 * @augments Backbone.View 7719 */ 7720 var Heading = wp.media.View.extend( { 7721 tagName: function() { 7722 return this.options.level || 'h1'; 7723 }, 7724 className: 'media-views-heading', 7725 7726 initialize: function() { 7727 7728 if ( this.options.className ) { 7729 this.$el.addClass( this.options.className ); 7730 } 7731 7732 this.text = this.options.text; 7733 }, 7734 7735 render: function() { 7736 this.$el.html( this.text ); 7737 return this; 7738 } 7739 } ); 7740 7741 module.exports = Heading; 7742 7743 7744 /***/ }, 7745 7746 /***/ 1982 7747 (module) { 7748 7749 /** 7750 * wp.media.view.Iframe 7751 * 7752 * @memberOf wp.media.view 7753 * 7754 * @class 7755 * @augments wp.media.View 7756 * @augments wp.Backbone.View 7757 * @augments Backbone.View 7758 */ 7759 var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{ 7760 className: 'media-iframe', 7761 /** 7762 * Renders the iframe view. 7763 * 7764 * @return {wp.media.view.Iframe} Returns itself to allow chaining. 7765 */ 7766 render: function() { 7767 this.views.detach(); 7768 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 7769 this.views.render(); 7770 return this; 7771 } 7772 }); 7773 7774 module.exports = Iframe; 7775 7776 7777 /***/ }, 7778 7779 /***/ 2650 7780 (module) { 7781 7782 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 7783 $ = jQuery, 7784 ImageDetails; 7785 7786 /** 7787 * wp.media.view.ImageDetails 7788 * 7789 * @memberOf wp.media.view 7790 * 7791 * @class 7792 * @augments wp.media.view.Settings.AttachmentDisplay 7793 * @augments wp.media.view.Settings 7794 * @augments wp.media.View 7795 * @augments wp.Backbone.View 7796 * @augments Backbone.View 7797 */ 7798 ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{ 7799 className: 'image-details', 7800 template: wp.template('image-details'), 7801 events: _.defaults( AttachmentDisplay.prototype.events, { 7802 'click .edit-attachment': 'editAttachment', 7803 'click .replace-attachment': 'replaceAttachment', 7804 'click .advanced-toggle': 'onToggleAdvanced', 7805 'change [data-setting="customWidth"]': 'onCustomSize', 7806 'change [data-setting="customHeight"]': 'onCustomSize', 7807 'keyup [data-setting="customWidth"]': 'onCustomSize', 7808 'keyup [data-setting="customHeight"]': 'onCustomSize' 7809 } ), 7810 initialize: function() { 7811 // Used in AttachmentDisplay.prototype.updateLinkTo. 7812 this.options.attachment = this.model.attachment; 7813 this.listenTo( this.model, 'change:url', this.updateUrl ); 7814 this.listenTo( this.model, 'change:link', this.toggleLinkSettings ); 7815 this.listenTo( this.model, 'change:size', this.toggleCustomSize ); 7816 7817 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 7818 }, 7819 7820 prepare: function() { 7821 var attachment = false; 7822 7823 if ( this.model.attachment ) { 7824 attachment = this.model.attachment.toJSON(); 7825 } 7826 return _.defaults({ 7827 model: this.model.toJSON(), 7828 attachment: attachment 7829 }, this.options ); 7830 }, 7831 7832 render: function() { 7833 var args = arguments; 7834 7835 if ( this.model.attachment && 'pending' === this.model.dfd.state() ) { 7836 this.model.dfd 7837 .done( _.bind( function() { 7838 AttachmentDisplay.prototype.render.apply( this, args ); 7839 this.postRender(); 7840 }, this ) ) 7841 .fail( _.bind( function() { 7842 this.model.attachment = false; 7843 AttachmentDisplay.prototype.render.apply( this, args ); 7844 this.postRender(); 7845 }, this ) ); 7846 } else { 7847 AttachmentDisplay.prototype.render.apply( this, arguments ); 7848 this.postRender(); 7849 } 7850 7851 return this; 7852 }, 7853 7854 postRender: function() { 7855 setTimeout( _.bind( this.scrollToTop, this ), 10 ); 7856 this.toggleLinkSettings(); 7857 if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) { 7858 this.toggleAdvanced( true ); 7859 } 7860 this.trigger( 'post-render' ); 7861 }, 7862 7863 scrollToTop: function() { 7864 this.$( '.embed-media-settings' ).scrollTop( 0 ); 7865 }, 7866 7867 updateUrl: function() { 7868 this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) ); 7869 this.$( '.url' ).val( this.model.get( 'url' ) ); 7870 }, 7871 7872 toggleLinkSettings: function() { 7873 if ( this.model.get( 'link' ) === 'none' ) { 7874 this.$( '.link-settings' ).addClass('hidden'); 7875 } else { 7876 this.$( '.link-settings' ).removeClass('hidden'); 7877 } 7878 }, 7879 7880 toggleCustomSize: function() { 7881 if ( this.model.get( 'size' ) !== 'custom' ) { 7882 this.$( '.custom-size' ).addClass('hidden'); 7883 } else { 7884 this.$( '.custom-size' ).removeClass('hidden'); 7885 } 7886 }, 7887 7888 onCustomSize: function( event ) { 7889 var dimension = $( event.target ).data('setting'), 7890 num = $( event.target ).val(), 7891 value; 7892 7893 // Ignore bogus input. 7894 if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) { 7895 event.preventDefault(); 7896 return; 7897 } 7898 7899 if ( dimension === 'customWidth' ) { 7900 value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num ); 7901 this.model.set( 'customHeight', value, { silent: true } ); 7902 this.$( '[data-setting="customHeight"]' ).val( value ); 7903 } else { 7904 value = Math.round( this.model.get( 'aspectRatio' ) * num ); 7905 this.model.set( 'customWidth', value, { silent: true } ); 7906 this.$( '[data-setting="customWidth"]' ).val( value ); 7907 } 7908 }, 7909 7910 onToggleAdvanced: function( event ) { 7911 event.preventDefault(); 7912 this.toggleAdvanced(); 7913 }, 7914 7915 toggleAdvanced: function( show ) { 7916 var $advanced = this.$el.find( '.advanced-section' ), 7917 mode; 7918 7919 if ( $advanced.hasClass('advanced-visible') || show === false ) { 7920 $advanced.removeClass('advanced-visible'); 7921 $advanced.find('.advanced-settings').addClass('hidden'); 7922 mode = 'hide'; 7923 } else { 7924 $advanced.addClass('advanced-visible'); 7925 $advanced.find('.advanced-settings').removeClass('hidden'); 7926 mode = 'show'; 7927 } 7928 7929 window.setUserSetting( 'advImgDetails', mode ); 7930 }, 7931 7932 editAttachment: function( event ) { 7933 var editState = this.controller.states.get( 'edit-image' ); 7934 7935 if ( window.imageEdit && editState ) { 7936 event.preventDefault(); 7937 editState.set( 'image', this.model.attachment ); 7938 this.controller.setState( 'edit-image' ); 7939 } 7940 }, 7941 7942 replaceAttachment: function( event ) { 7943 event.preventDefault(); 7944 this.controller.setState( 'replace-image' ); 7945 } 7946 }); 7947 7948 module.exports = ImageDetails; 7949 7950 7951 /***/ }, 7952 7953 /***/ 4338 7954 (module) { 7955 7956 /** 7957 * wp.media.view.Label 7958 * 7959 * @memberOf wp.media.view 7960 * 7961 * @class 7962 * @augments wp.media.View 7963 * @augments wp.Backbone.View 7964 * @augments Backbone.View 7965 */ 7966 var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{ 7967 tagName: 'label', 7968 7969 initialize: function() { 7970 this.value = this.options.value; 7971 }, 7972 7973 render: function() { 7974 this.$el.html( this.value ); 7975 7976 return this; 7977 } 7978 }); 7979 7980 module.exports = Label; 7981 7982 7983 /***/ }, 7984 7985 /***/ 2836 7986 (module) { 7987 7988 var Frame = wp.media.view.Frame, 7989 l10n = wp.media.view.l10n, 7990 $ = jQuery, 7991 MediaFrame; 7992 7993 /** 7994 * wp.media.view.MediaFrame 7995 * 7996 * The frame used to create the media modal. 7997 * 7998 * @memberOf wp.media.view 7999 * 8000 * @class 8001 * @augments wp.media.view.Frame 8002 * @augments wp.media.View 8003 * @augments wp.Backbone.View 8004 * @augments Backbone.View 8005 * @mixes wp.media.controller.StateMachine 8006 */ 8007 MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{ 8008 className: 'media-frame', 8009 template: wp.template('media-frame'), 8010 regions: ['menu','title','content','toolbar','router'], 8011 8012 events: { 8013 'click .media-frame-menu-toggle': 'toggleMenu' 8014 }, 8015 8016 /** 8017 * Initializes the media frame. 8018 * 8019 * @constructs 8020 */ 8021 initialize: function() { 8022 Frame.prototype.initialize.apply( this, arguments ); 8023 8024 _.defaults( this.options, { 8025 title: l10n.mediaFrameDefaultTitle, 8026 modal: true, 8027 uploader: true 8028 }); 8029 8030 // Ensure core UI is enabled. 8031 this.$el.addClass('wp-core-ui'); 8032 8033 // Initialize modal container view. 8034 if ( this.options.modal ) { 8035 this.modal = new wp.media.view.Modal({ 8036 controller: this, 8037 title: this.options.title 8038 }); 8039 8040 this.modal.content( this ); 8041 } 8042 8043 // Force the uploader off if the upload limit has been exceeded or 8044 // if the browser isn't supported. 8045 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 8046 this.options.uploader = false; 8047 } 8048 8049 // Initialize window-wide uploader. 8050 if ( this.options.uploader ) { 8051 this.uploader = new wp.media.view.UploaderWindow({ 8052 controller: this, 8053 uploader: { 8054 dropzone: this.modal ? this.modal.$el : this.$el, 8055 container: this.$el 8056 } 8057 }); 8058 this.views.set( '.media-frame-uploader', this.uploader ); 8059 } 8060 8061 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 8062 8063 // Bind default title creation. 8064 this.on( 'title:create:default', this.createTitle, this ); 8065 this.title.mode('default'); 8066 8067 // Bind default menu. 8068 this.on( 'menu:create:default', this.createMenu, this ); 8069 8070 // Set the menu ARIA tab panel attributes when the modal opens. 8071 this.on( 'open', this.setMenuTabPanelAriaAttributes, this ); 8072 // Set the router ARIA tab panel attributes when the modal opens. 8073 this.on( 'open', this.setRouterTabPanelAriaAttributes, this ); 8074 8075 // Update the menu ARIA tab panel attributes when the content updates. 8076 this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this ); 8077 // Update the router ARIA tab panel attributes when the content updates. 8078 this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this ); 8079 }, 8080 8081 /** 8082 * Sets the attributes to be used on the menu ARIA tab panel. 8083 * 8084 * @since 5.3.0 8085 * 8086 * @return {void} 8087 */ 8088 setMenuTabPanelAriaAttributes: function() { 8089 var stateId = this.state().get( 'id' ), 8090 tabPanelEl = this.$el.find( '.media-frame-tab-panel' ), 8091 ariaLabelledby; 8092 8093 tabPanelEl.removeAttr( 'role aria-labelledby' ); 8094 8095 if ( this.state().get( 'menu' ) && this.menuView && this.menuView.isVisible ) { 8096 ariaLabelledby = 'menu-item-' + stateId; 8097 8098 // Set the tab panel attributes only if the tabs are visible. 8099 tabPanelEl 8100 .attr( { 8101 role: 'tabpanel', 8102 'aria-labelledby': ariaLabelledby, 8103 } ); 8104 } 8105 }, 8106 8107 /** 8108 * Sets the attributes to be used on the router ARIA tab panel. 8109 * 8110 * @since 5.3.0 8111 * 8112 * @return {void} 8113 */ 8114 setRouterTabPanelAriaAttributes: function() { 8115 var tabPanelEl = this.$el.find( '.media-frame-content' ), 8116 ariaLabelledby; 8117 8118 tabPanelEl.removeAttr( 'role aria-labelledby' ); 8119 8120 // Set the tab panel attributes only if the tabs are visible. 8121 if ( this.state().get( 'router' ) && this.routerView && this.routerView.isVisible && this.content._mode ) { 8122 ariaLabelledby = 'menu-item-' + this.content._mode; 8123 8124 tabPanelEl 8125 .attr( { 8126 role: 'tabpanel', 8127 'aria-labelledby': ariaLabelledby, 8128 } ); 8129 } 8130 }, 8131 8132 /** 8133 * Renders the media frame. 8134 * 8135 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8136 */ 8137 render: function() { 8138 // Activate the default state if no active state exists. 8139 if ( ! this.state() && this.options.state ) { 8140 this.setState( this.options.state ); 8141 } 8142 /** 8143 * call 'render' directly on the parent class 8144 */ 8145 return Frame.prototype.render.apply( this, arguments ); 8146 }, 8147 /** 8148 * Creates the title view. 8149 * 8150 * @param {Object} title The title object for creating the title view. 8151 * @this {wp.media.controller.Region} 8152 */ 8153 createTitle: function( title ) { 8154 // A modal's dialog element points `aria-labelledby` at its frame heading. 8155 title.view = new wp.media.View({ 8156 controller: this, 8157 tagName: 'h1', 8158 attributes: this.modal ? { id: this.modal.titleId } : {} 8159 }); 8160 }, 8161 /** 8162 * Creates the menu view. 8163 * 8164 * @param {Object} menu The menu object for creating the menu view. 8165 * @this {wp.media.controller.Region} 8166 */ 8167 createMenu: function( menu ) { 8168 menu.view = new wp.media.view.Menu({ 8169 controller: this, 8170 8171 attributes: { 8172 role: 'tablist', 8173 'aria-orientation': 'vertical' 8174 } 8175 }); 8176 8177 this.menuView = menu.view; 8178 }, 8179 8180 /** 8181 * Toggles the menu visibility. 8182 * 8183 * @param {JQuery.Event} event The click event. 8184 */ 8185 toggleMenu: function( event ) { 8186 var menu = this.$el.find( '.media-menu' ); 8187 8188 menu.toggleClass( 'visible' ); 8189 $( event.target ).attr( 'aria-expanded', menu.hasClass( 'visible' ) ); 8190 }, 8191 8192 /** 8193 * Creates the toolbar view. 8194 * 8195 * @param {Object} toolbar The toolbar object for creating the toolbar view. 8196 * @this {wp.media.controller.Region} 8197 */ 8198 createToolbar: function( toolbar ) { 8199 toolbar.view = new wp.media.view.Toolbar({ 8200 controller: this 8201 }); 8202 }, 8203 /** 8204 * Creates the router view. 8205 * 8206 * @param {Object} router The router object for creating the router view. 8207 * @this {wp.media.controller.Region} 8208 */ 8209 createRouter: function( router ) { 8210 router.view = new wp.media.view.Router({ 8211 controller: this, 8212 8213 attributes: { 8214 role: 'tablist', 8215 'aria-orientation': 'horizontal' 8216 } 8217 }); 8218 8219 this.routerView = router.view; 8220 }, 8221 /** 8222 * Creates the iframe states. 8223 * 8224 * @param {Object} options The options for creating the iframe states. 8225 */ 8226 createIframeStates: function( options ) { 8227 var settings = wp.media.view.settings, 8228 tabs = settings.tabs, 8229 tabUrl = settings.tabUrl, 8230 $postId; 8231 8232 if ( ! tabs || ! tabUrl ) { 8233 return; 8234 } 8235 8236 // Add the post ID to the tab URL if it exists. 8237 $postId = $('#post_ID'); 8238 if ( $postId.length ) { 8239 tabUrl += '&post_id=' + $postId.val(); 8240 } 8241 8242 // Generate the tab states. 8243 _.each( tabs, function( title, id ) { 8244 this.state( 'iframe:' + id ).set( _.defaults({ 8245 tab: id, 8246 src: tabUrl + '&tab=' + id, 8247 title: title, 8248 content: 'iframe', 8249 menu: 'default' 8250 }, options ) ); 8251 }, this ); 8252 8253 this.on( 'content:create:iframe', this.iframeContent, this ); 8254 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 8255 this.on( 'menu:render:default', this.iframeMenu, this ); 8256 this.on( 'open', this.hijackThickbox, this ); 8257 this.on( 'close', this.restoreThickbox, this ); 8258 }, 8259 8260 /** 8261 * Creates the iframe content view. 8262 * 8263 * @param {Object} content The content object for creating the iframe content view. 8264 * @this {wp.media.controller.Region} 8265 */ 8266 iframeContent: function( content ) { 8267 this.$el.addClass('hide-toolbar'); 8268 content.view = new wp.media.view.Iframe({ 8269 controller: this 8270 }); 8271 }, 8272 8273 /** 8274 * Cleans up the iframe content view. 8275 */ 8276 iframeContentCleanup: function() { 8277 this.$el.removeClass('hide-toolbar'); 8278 }, 8279 8280 /** 8281 * Creates the iframe menu. 8282 * 8283 * @param {wp.media.view.Menu} view The menu view. 8284 */ 8285 iframeMenu: function( view ) { 8286 var views = {}; 8287 8288 if ( ! view ) { 8289 return; 8290 } 8291 8292 _.each( wp.media.view.settings.tabs, function( title, id ) { 8293 views[ 'iframe:' + id ] = { 8294 text: this.state( 'iframe:' + id ).get('title'), 8295 priority: 200 8296 }; 8297 }, this ); 8298 8299 view.set( views ); 8300 }, 8301 8302 /** 8303 * Hijacks the Thickbox close function to close the media modal. 8304 */ 8305 hijackThickbox: function() { 8306 var frame = this; 8307 8308 if ( ! window.tb_remove || this._tb_remove ) { 8309 return; 8310 } 8311 8312 this._tb_remove = window.tb_remove; 8313 window.tb_remove = function() { 8314 frame.close(); 8315 frame.reset(); 8316 frame.setState( frame.options.state ); 8317 frame._tb_remove.call( window ); 8318 }; 8319 }, 8320 8321 /** 8322 * Restores the Thickbox close function. 8323 */ 8324 restoreThickbox: function() { 8325 if ( ! this._tb_remove ) { 8326 return; 8327 } 8328 8329 window.tb_remove = this._tb_remove; 8330 delete this._tb_remove; 8331 } 8332 }); 8333 8334 // Map some of the modal's methods to the frame. 8335 _.each(['open','close','attach','detach','escape'], function( method ) { 8336 /** 8337 * Opens the media frame modal. 8338 * 8339 * @function open 8340 * @memberOf wp.media.view.MediaFrame 8341 * @instance 8342 * 8343 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8344 */ 8345 /** 8346 * Closes the media frame modal. 8347 * 8348 * @function close 8349 * @memberOf wp.media.view.MediaFrame 8350 * @instance 8351 * 8352 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8353 */ 8354 /** 8355 * Attaches the media frame to the DOM. 8356 * 8357 * @function attach 8358 * @memberOf wp.media.view.MediaFrame 8359 * @instance 8360 * 8361 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8362 */ 8363 /** 8364 * Detaches the media frame from the DOM. 8365 * 8366 * @function detach 8367 * @memberOf wp.media.view.MediaFrame 8368 * @instance 8369 * 8370 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8371 */ 8372 /** 8373 * Triggers the escape action on the media frame modal. 8374 * 8375 * @function escape 8376 * @memberOf wp.media.view.MediaFrame 8377 * @instance 8378 * 8379 * @return {wp.media.view.MediaFrame} Returns itself to allow chaining. 8380 */ 8381 MediaFrame.prototype[ method ] = function() { 8382 if ( this.modal ) { 8383 this.modal[ method ].apply( this.modal, arguments ); 8384 } 8385 return this; 8386 }; 8387 }); 8388 8389 module.exports = MediaFrame; 8390 8391 8392 /***/ }, 8393 8394 /***/ 9013 8395 (module) { 8396 8397 var MenuItem; 8398 8399 /** 8400 * wp.media.view.MenuItem 8401 * 8402 * @memberOf wp.media.view 8403 * 8404 * @class 8405 * @augments wp.media.View 8406 * @augments wp.Backbone.View 8407 * @augments Backbone.View 8408 */ 8409 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{ 8410 tagName: 'button', 8411 className: 'media-menu-item', 8412 8413 attributes: { 8414 type: 'button', 8415 role: 'tab' 8416 }, 8417 8418 events: { 8419 'click': '_click' 8420 }, 8421 8422 /** 8423 * Allows to override the click event. 8424 */ 8425 _click: function() { 8426 var clickOverride = this.options.click; 8427 8428 if ( clickOverride ) { 8429 clickOverride.call( this ); 8430 } else { 8431 this.click(); 8432 } 8433 }, 8434 8435 /** 8436 * Handles the click event. 8437 */ 8438 click: function() { 8439 var state = this.options.state; 8440 8441 if ( state ) { 8442 this.controller.setState( state ); 8443 // Toggle the menu visibility in the responsive view. 8444 this.views.parent.$el.removeClass( 'visible' ); // @todo Or hide on any click, see below. 8445 } 8446 }, 8447 8448 /** 8449 * Renders the menu item. 8450 * 8451 * @return {wp.media.view.MenuItem} returns itself to allow chaining. 8452 */ 8453 render: function() { 8454 var options = this.options, 8455 menuProperty = options.state || options.contentMode; 8456 8457 if ( options.text ) { 8458 this.$el.text( options.text ); 8459 } else if ( options.html ) { 8460 this.$el.html( options.html ); 8461 } 8462 8463 // Set the menu item ID based on the frame state associated to the menu item. 8464 this.$el.attr( 'id', 'menu-item-' + menuProperty ); 8465 8466 return this; 8467 } 8468 }); 8469 8470 module.exports = MenuItem; 8471 8472 8473 /***/ }, 8474 8475 /***/ 1 8476 (module) { 8477 8478 var MenuItem = wp.media.view.MenuItem, 8479 PriorityList = wp.media.view.PriorityList, 8480 Menu; 8481 8482 /** 8483 * wp.media.view.Menu 8484 * 8485 * @memberOf wp.media.view 8486 * 8487 * @class 8488 * @augments wp.media.view.PriorityList 8489 * @augments wp.media.View 8490 * @augments wp.Backbone.View 8491 * @augments Backbone.View 8492 */ 8493 Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{ 8494 tagName: 'div', 8495 className: 'media-menu', 8496 property: 'state', 8497 ItemView: MenuItem, 8498 region: 'menu', 8499 8500 attributes: { 8501 role: 'tablist', 8502 'aria-orientation': 'horizontal' 8503 }, 8504 8505 /** 8506 * Initializes the menu view. 8507 */ 8508 initialize: function() { 8509 this._views = {}; 8510 8511 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 8512 delete this.options.views; 8513 8514 if ( ! this.options.silent ) { 8515 this.render(); 8516 } 8517 8518 // Initialize the Focus Manager. 8519 this.focusManager = new wp.media.view.FocusManager( { 8520 el: this.el, 8521 mode: 'tabsNavigation' 8522 } ); 8523 8524 // The menu is always rendered and can be visible or hidden on some frames. 8525 this.isVisible = true; 8526 }, 8527 8528 /** 8529 * Creates a view for the given options and id. 8530 * 8531 * @param {Object} options The options for creating the view. 8532 * @param {string} id The id of the view to create. 8533 * @return {wp.media.View} The view instance. 8534 */ 8535 toView: function( options, id ) { 8536 options = options || {}; 8537 options[ this.property ] = options[ this.property ] || id; 8538 return new this.ItemView( options ).render(); 8539 }, 8540 8541 /** 8542 * Updates the menu when the state changes. 8543 */ 8544 ready: function() { 8545 /** 8546 * call 'ready' directly on the parent class 8547 */ 8548 PriorityList.prototype.ready.apply( this, arguments ); 8549 this.visibility(); 8550 8551 // Set up aria tabs initial attributes. 8552 this.focusManager.setupAriaTabs(); 8553 }, 8554 8555 /** 8556 * Sets the menu items. 8557 */ 8558 set: function() { 8559 /** 8560 * call 'set' directly on the parent class 8561 */ 8562 PriorityList.prototype.set.apply( this, arguments ); 8563 this.visibility(); 8564 }, 8565 8566 /** 8567 * Unsets the menu items. 8568 */ 8569 unset: function() { 8570 /** 8571 * call 'unset' directly on the parent class 8572 */ 8573 PriorityList.prototype.unset.apply( this, arguments ); 8574 this.visibility(); 8575 }, 8576 8577 /** 8578 * Updates the menu visibility. 8579 */ 8580 visibility: function() { 8581 var region = this.region, 8582 view = this.controller[ region ].get(), 8583 views = this.views.get(), 8584 hide = ! views || views.length < 2; 8585 8586 if ( this === view ) { 8587 // Flag this menu as hidden or visible. 8588 this.isVisible = ! hide; 8589 // Set or remove a CSS class to hide the menu. 8590 this.controller.$el.toggleClass( 'hide-' + region, hide ); 8591 } 8592 }, 8593 /** 8594 * Selects the menu item with the given id. 8595 * 8596 * @param {string} id The menu item id. 8597 */ 8598 select: function( id ) { 8599 var view = this.get( id ); 8600 8601 if ( ! view ) { 8602 return; 8603 } 8604 8605 this.deselect(); 8606 view.$el.addClass('active'); 8607 8608 // Set up again the aria tabs initial attributes after the menu updates. 8609 this.focusManager.setupAriaTabs(); 8610 }, 8611 8612 /** 8613 * Deselects the menu items. 8614 */ 8615 deselect: function() { 8616 this.$el.children().removeClass('active'); 8617 }, 8618 8619 /** 8620 * Hides the menu item with the given id. 8621 * 8622 * @param {string} id The menu item id. 8623 */ 8624 hide: function( id ) { 8625 var view = this.get( id ); 8626 8627 if ( ! view ) { 8628 return; 8629 } 8630 8631 view.$el.addClass('hidden'); 8632 }, 8633 8634 /** 8635 * Shows the menu item with the given id. 8636 * 8637 * @param {string} id The menu item id. 8638 */ 8639 show: function( id ) { 8640 var view = this.get( id ); 8641 8642 if ( ! view ) { 8643 return; 8644 } 8645 8646 view.$el.removeClass('hidden'); 8647 } 8648 }); 8649 8650 module.exports = Menu; 8651 8652 8653 /***/ }, 8654 8655 /***/ 2621 8656 (module) { 8657 8658 var $ = jQuery, 8659 Modal; 8660 8661 /** 8662 * wp.media.view.Modal 8663 * 8664 * A modal view, which the media modal uses as its default container. 8665 * 8666 * @memberOf wp.media.view 8667 * 8668 * @class 8669 * @augments wp.media.View 8670 * @augments wp.Backbone.View 8671 * @augments Backbone.View 8672 */ 8673 Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{ 8674 tagName: 'div', 8675 template: wp.template('media-modal'), 8676 8677 events: { 8678 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 8679 'keydown': 'keydown' 8680 }, 8681 8682 clickedOpenerEl: null, 8683 8684 initialize: function() { 8685 _.defaults( this.options, { 8686 container: document.body, 8687 title: '', 8688 propagate: true, 8689 hasCloseButton: true 8690 }); 8691 8692 this.titleId = _.uniqueId( 'media-frame-title-' ); 8693 8694 this.focusManager = new wp.media.view.FocusManager({ 8695 el: this.el 8696 }); 8697 }, 8698 /** 8699 * Prepares the data for the modal template. 8700 * 8701 * @since 7.2.0 Added the `titleId` property. 8702 * 8703 * @return {Object} The prepared data. 8704 */ 8705 prepare: function() { 8706 return { 8707 title: this.options.title, 8708 hasCloseButton: this.options.hasCloseButton, 8709 titleId: this.titleId 8710 }; 8711 }, 8712 8713 /** 8714 * Attaches the modal to the DOM and triggers the ready event. 8715 * 8716 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8717 */ 8718 attach: function() { 8719 if ( this.views.attached ) { 8720 return this; 8721 } 8722 8723 if ( ! this.views.rendered ) { 8724 this.render(); 8725 } 8726 8727 this.$el.appendTo( this.options.container ); 8728 8729 // Manually mark the view as attached and trigger ready. 8730 this.views.attached = true; 8731 this.views.ready(); 8732 8733 return this.propagate('attach'); 8734 }, 8735 8736 /** 8737 * Detaches the modal from the DOM and triggers the detach event. 8738 * 8739 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8740 */ 8741 detach: function() { 8742 if ( this.$el.is(':visible') ) { 8743 this.close(); 8744 } 8745 8746 this.$el.detach(); 8747 this.views.attached = false; 8748 return this.propagate('detach'); 8749 }, 8750 8751 /** 8752 * Opens the modal and triggers the open event. 8753 * 8754 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8755 */ 8756 open: function() { 8757 var $el = this.$el, 8758 mceEditor; 8759 8760 if ( $el.is(':visible') ) { 8761 return this; 8762 } 8763 8764 this.clickedOpenerEl = document.activeElement; 8765 8766 if ( ! this.views.attached ) { 8767 this.attach(); 8768 } 8769 8770 // Disable page scrolling. 8771 $( 'body' ).addClass( 'modal-open' ); 8772 8773 $el.show(); 8774 8775 // Try to close the onscreen keyboard. 8776 if ( 'ontouchend' in document ) { 8777 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 8778 mceEditor.iframeElement.focus(); 8779 mceEditor.iframeElement.blur(); 8780 8781 setTimeout( function() { 8782 mceEditor.iframeElement.blur(); 8783 }, 100 ); 8784 } 8785 } 8786 8787 // Set initial focus on the content instead of this view element, to avoid page scrolling. 8788 this.$( '.media-modal' ).trigger( 'focus' ); 8789 8790 // Hide the page content from assistive technologies. 8791 this.focusManager.setAriaHiddenOnBodyChildren( $el ); 8792 8793 return this.propagate('open'); 8794 }, 8795 8796 /** 8797 * Closes the modal and triggers the close event. 8798 * 8799 * @param {Object} [options={}] The options for closing the modal. 8800 * @param {boolean} [options.escape] Whether the modal is being closed due to the escape key. 8801 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8802 */ 8803 close: function( options ) { 8804 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 8805 return this; 8806 } 8807 8808 // Pause current audio/video even after closing the modal. 8809 $( '.mejs-pause button' ).trigger( 'click' ); 8810 8811 // Enable page scrolling. 8812 $( 'body' ).removeClass( 'modal-open' ); 8813 8814 // Hide the modal element by adding display:none. 8815 this.$el.hide(); 8816 8817 /* 8818 * Make visible again to assistive technologies all body children that 8819 * have been made hidden when the modal opened. 8820 */ 8821 this.focusManager.removeAriaHiddenFromBodyChildren(); 8822 8823 // Move focus back in useful location once modal is closed. 8824 if ( null !== this.clickedOpenerEl ) { 8825 // Move focus back to the element that opened the modal. 8826 this.clickedOpenerEl.focus(); 8827 } else { 8828 // Fallback to the admin page main element. 8829 $( '#wpbody-content' ) 8830 .attr( 'tabindex', '-1' ) 8831 .trigger( 'focus' ); 8832 } 8833 8834 this.propagate('close'); 8835 8836 if ( options && options.escape ) { 8837 this.propagate('escape'); 8838 } 8839 8840 return this; 8841 }, 8842 /** 8843 * Closes the modal and triggers the escape event. 8844 * 8845 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8846 */ 8847 escape: function() { 8848 return this.close({ escape: true }); 8849 }, 8850 /** 8851 * Handles the escape key press event to close the modal. 8852 * 8853 * @param {JQuery.Event} event The jQuery event object. 8854 */ 8855 escapeHandler: function( event ) { 8856 event.preventDefault(); 8857 this.escape(); 8858 }, 8859 8860 /** 8861 * Handles the selection of attachments when the command or control key is pressed with the enter key. 8862 * 8863 * @since 6.7 8864 * 8865 * @param {JQuery.Event} event The jQuery event object. 8866 */ 8867 selectHandler: function( event ) { 8868 var selection = this.controller.state().get( 'selection' ); 8869 8870 if ( selection.length <= 0 ) { 8871 return; 8872 } 8873 8874 if ( 'insert' === this.controller.options.state ) { 8875 this.controller.trigger( 'insert', selection ); 8876 } else { 8877 this.controller.trigger( 'select', selection ); 8878 event.preventDefault(); 8879 this.escape(); 8880 } 8881 }, 8882 8883 /** 8884 * Sets the content of the modal by registering views to the '.media-modal-content' selector. 8885 * 8886 * @param {Array|Object} content Views to register to '.media-modal-content' 8887 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8888 */ 8889 content: function( content ) { 8890 this.views.set( '.media-modal-content', content ); 8891 return this; 8892 }, 8893 8894 /** 8895 * Triggers a modal event and if the `propagate` option is set, 8896 * forwards events to the modal's controller. 8897 * 8898 * @param {string} id The ID of the event to propagate. 8899 * @return {wp.media.view.Modal} Returns itself to allow chaining. 8900 */ 8901 propagate: function( id ) { 8902 this.trigger( id ); 8903 8904 if ( this.options.propagate ) { 8905 this.controller.trigger( id ); 8906 } 8907 8908 return this; 8909 }, 8910 /** 8911 * Handles keydown events within the modal. 8912 * 8913 * @param {JQuery.Event} event The jQuery keydown event object. 8914 */ 8915 keydown: function( event ) { 8916 // Close the modal when escape is pressed. 8917 if ( 27 === event.which && this.$el.is(':visible') ) { 8918 this.escape(); 8919 event.stopImmediatePropagation(); 8920 } 8921 8922 // Select the attachment when command or control and enter are pressed. 8923 if ( ( 13 === event.which || 10 === event.which ) && ( event.metaKey || event.ctrlKey ) ) { 8924 this.selectHandler( event ); 8925 event.stopImmediatePropagation(); 8926 } 8927 8928 } 8929 }); 8930 8931 module.exports = Modal; 8932 8933 8934 /***/ }, 8935 8936 /***/ 8815 8937 (module) { 8938 8939 /** 8940 * wp.media.view.PriorityList 8941 * 8942 * @memberOf wp.media.view 8943 * 8944 * @class 8945 * @augments wp.media.View 8946 * @augments wp.Backbone.View 8947 * @augments Backbone.View 8948 */ 8949 var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{ 8950 tagName: 'div', 8951 8952 initialize: function() { 8953 this._views = {}; 8954 8955 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 8956 delete this.options.views; 8957 8958 if ( ! this.options.silent ) { 8959 this.render(); 8960 } 8961 }, 8962 /** 8963 * Adds a view to the list, sorted by its priority. 8964 * 8965 * @param {string} id The ID of the view to set. 8966 * @param {wp.media.View|Object} view The view to set. 8967 * @param {Object} options The options for setting the view. 8968 * @return {wp.media.view.PriorityList} Returns itself to allow chaining. 8969 */ 8970 set: function( id, view, options ) { 8971 var priority, views, index; 8972 8973 options = options || {}; 8974 8975 // Accept an object with an `id` : `view` mapping. 8976 if ( _.isObject( id ) ) { 8977 _.each( id, function( view, id ) { 8978 this.set( id, view ); 8979 }, this ); 8980 return this; 8981 } 8982 8983 if ( ! (view instanceof Backbone.View) ) { 8984 view = this.toView( view, id, options ); 8985 } 8986 view.controller = view.controller || this.controller; 8987 8988 this.unset( id ); 8989 8990 priority = view.options.priority || 10; 8991 views = this.views.get() || []; 8992 8993 _.find( views, function( existing, i ) { 8994 if ( existing.options.priority > priority ) { 8995 index = i; 8996 return true; 8997 } 8998 }); 8999 9000 this._views[ id ] = view; 9001 this.views.add( view, { 9002 at: _.isNumber( index ) ? index : views.length || 0 9003 }); 9004 9005 return this; 9006 }, 9007 /** 9008 * Retrieves a view by its ID. 9009 * 9010 * @param {string} id The ID of the view to retrieve. 9011 * @return {wp.media.View} Returns the view if found, otherwise undefined. 9012 */ 9013 get: function( id ) { 9014 return this._views[ id ]; 9015 }, 9016 /** 9017 * Removes a view by its ID. 9018 * 9019 * @param {string} id The ID of the view to remove. 9020 * @return {wp.media.view.PriorityList} Returns itself to allow chaining. 9021 */ 9022 unset: function( id ) { 9023 var view = this.get( id ); 9024 9025 if ( view ) { 9026 view.remove(); 9027 } 9028 9029 delete this._views[ id ]; 9030 return this; 9031 }, 9032 /** 9033 * Creates a view from an object of options. 9034 * 9035 * @param {Object} options The options for creating the view. 9036 * @return {wp.media.View} Returns the created view. 9037 */ 9038 toView: function( options ) { 9039 return new wp.media.View( options ); 9040 } 9041 }); 9042 9043 module.exports = PriorityList; 9044 9045 9046 /***/ }, 9047 9048 /***/ 6327 9049 (module) { 9050 9051 /** 9052 * wp.media.view.RouterItem 9053 * 9054 * @memberOf wp.media.view 9055 * 9056 * @class 9057 * @augments wp.media.view.MenuItem 9058 * @augments wp.media.View 9059 * @augments wp.Backbone.View 9060 * @augments Backbone.View 9061 */ 9062 var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{ 9063 /** 9064 * On click handler to activate the content region's corresponding mode. 9065 */ 9066 click: function() { 9067 var contentMode = this.options.contentMode; 9068 if ( contentMode ) { 9069 this.controller.content.mode( contentMode ); 9070 } 9071 } 9072 }); 9073 9074 module.exports = RouterItem; 9075 9076 9077 /***/ }, 9078 9079 /***/ 4783 9080 (module) { 9081 9082 var Menu = wp.media.view.Menu, 9083 Router; 9084 9085 /** 9086 * wp.media.view.Router 9087 * 9088 * @memberOf wp.media.view 9089 * 9090 * @class 9091 * @augments wp.media.view.Menu 9092 * @augments wp.media.view.PriorityList 9093 * @augments wp.media.View 9094 * @augments wp.Backbone.View 9095 * @augments Backbone.View 9096 */ 9097 Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{ 9098 tagName: 'div', 9099 className: 'media-router', 9100 property: 'contentMode', 9101 ItemView: wp.media.view.RouterItem, 9102 region: 'router', 9103 9104 attributes: { 9105 role: 'tablist', 9106 'aria-orientation': 'horizontal' 9107 }, 9108 9109 initialize: function() { 9110 this.controller.on( 'content:render', this.update, this ); 9111 // Call 'initialize' directly on the parent class. 9112 Menu.prototype.initialize.apply( this, arguments ); 9113 }, 9114 9115 update: function() { 9116 var mode = this.controller.content.mode(); 9117 if ( mode ) { 9118 this.select( mode ); 9119 } 9120 } 9121 }); 9122 9123 module.exports = Router; 9124 9125 9126 /***/ }, 9127 9128 /***/ 2102 9129 (module) { 9130 9131 var Search; 9132 9133 /** 9134 * wp.media.view.Search 9135 * 9136 * @memberOf wp.media.view 9137 * 9138 * @class 9139 * @augments wp.media.View 9140 * @augments wp.Backbone.View 9141 * @augments Backbone.View 9142 */ 9143 Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{ 9144 tagName: 'input', 9145 className: 'search', 9146 id: 'media-search-input', 9147 9148 attributes: { 9149 type: 'search' 9150 }, 9151 9152 events: { 9153 'input': 'search' 9154 }, 9155 9156 /** 9157 * Renders the search input. 9158 * 9159 * @return {wp.media.view.Search} Returns itself to allow chaining. 9160 */ 9161 render: function() { 9162 this.el.value = this.model.escape('search'); 9163 return this; 9164 }, 9165 9166 /** 9167 * Searches the media library. 9168 * 9169 * @param {JQuery.Event} event The input event. 9170 */ 9171 search: _.debounce( function( event ) { 9172 var searchTerm = event.target.value.trim(); 9173 9174 // Trigger the search only after 2 ASCII characters. 9175 if ( searchTerm && searchTerm.length > 1 ) { 9176 this.model.set( 'search', searchTerm ); 9177 } else { 9178 this.model.unset( 'search' ); 9179 } 9180 }, 500 ) 9181 }); 9182 9183 module.exports = Search; 9184 9185 9186 /***/ }, 9187 9188 /***/ 8282 9189 (module) { 9190 9191 var _n = wp.i18n._n, 9192 sprintf = wp.i18n.sprintf, 9193 Selection; 9194 9195 /** 9196 * wp.media.view.Selection 9197 * 9198 * @memberOf wp.media.view 9199 * 9200 * @class 9201 * @augments wp.media.View 9202 * @augments wp.Backbone.View 9203 * @augments Backbone.View 9204 */ 9205 Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{ 9206 tagName: 'div', 9207 className: 'media-selection', 9208 template: wp.template('media-selection'), 9209 9210 events: { 9211 'click .edit-selection': 'edit', 9212 'click .clear-selection': 'clear' 9213 }, 9214 9215 initialize: function() { 9216 _.defaults( this.options, { 9217 editable: false, 9218 clearable: true 9219 }); 9220 9221 /** 9222 * @member {wp.media.view.Attachments.Selection} 9223 */ 9224 this.attachments = new wp.media.view.Attachments.Selection({ 9225 controller: this.controller, 9226 collection: this.collection, 9227 selection: this.collection, 9228 model: new Backbone.Model() 9229 }); 9230 9231 this.views.set( '.selection-view', this.attachments ); 9232 this.collection.on( 'add remove reset', this.refresh, this ); 9233 this.controller.on( 'content:activate', this.refresh, this ); 9234 }, 9235 9236 ready: function() { 9237 this.refresh(); 9238 }, 9239 9240 refresh: function() { 9241 // If the selection hasn't been rendered, bail. 9242 if ( ! this.$el.children().length ) { 9243 return; 9244 } 9245 9246 var collection = this.collection, 9247 editing = 'edit-selection' === this.controller.content.mode(); 9248 9249 // If nothing is selected, display nothing. 9250 this.$el.toggleClass( 'empty', ! collection.length ); 9251 this.$el.toggleClass( 'one', 1 === collection.length ); 9252 this.$el.toggleClass( 'editing', editing ); 9253 9254 this.$( '.count' ).text( 9255 /* translators: %s: Number of selected media attachments. */ 9256 sprintf( _n( '%s item selected', '%s items selected', collection.length ), collection.length ) 9257 ); 9258 }, 9259 9260 edit: function( event ) { 9261 event.preventDefault(); 9262 if ( this.options.editable ) { 9263 this.options.editable.call( this, this.collection ); 9264 } 9265 }, 9266 9267 clear: function( event ) { 9268 event.preventDefault(); 9269 this.collection.reset(); 9270 9271 // Move focus to the modal. 9272 this.controller.modal.focusManager.focus(); 9273 } 9274 }); 9275 9276 module.exports = Selection; 9277 9278 9279 /***/ }, 9280 9281 /***/ 1915 9282 (module) { 9283 9284 var View = wp.media.View, 9285 $ = Backbone.$, 9286 Settings; 9287 9288 /** 9289 * wp.media.view.Settings 9290 * 9291 * @memberOf wp.media.view 9292 * 9293 * @class 9294 * @augments wp.media.View 9295 * @augments wp.Backbone.View 9296 * @augments Backbone.View 9297 */ 9298 Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{ 9299 events: { 9300 'click button': 'updateHandler', 9301 'change input': 'updateHandler', 9302 'change select': 'updateHandler', 9303 'change textarea': 'updateHandler' 9304 }, 9305 9306 /** 9307 * Initializes the settings view. 9308 */ 9309 initialize: function() { 9310 this.model = this.model || new Backbone.Model(); 9311 this.listenTo( this.model, 'change', this.updateChanges ); 9312 }, 9313 9314 /** 9315 * Prepares the data for rendering. 9316 * 9317 * @return {Object} The data to be used in the template. 9318 */ 9319 prepare: function() { 9320 return _.defaults({ 9321 model: this.model.toJSON() 9322 }, this.options ); 9323 }, 9324 /** 9325 * Renders the settings view. 9326 * 9327 * @return {wp.media.view.Settings} Returns itself to allow chaining. 9328 */ 9329 render: function() { 9330 View.prototype.render.apply( this, arguments ); 9331 // Select the correct values. 9332 _( this.model.attributes ).chain().keys().each( this.update, this ); 9333 return this; 9334 }, 9335 /** 9336 * Updates the selected value for a setting. 9337 * 9338 * @param {string} key The key of the setting to update. 9339 */ 9340 update: function( key ) { 9341 var value = this.model.get( key ), 9342 $setting = this.$('[data-setting="' + key + '"]'), 9343 $buttons, $value; 9344 9345 // Bail if we didn't find a matching setting. 9346 if ( ! $setting.length ) { 9347 return; 9348 } 9349 9350 // Attempt to determine how the setting is rendered and update 9351 // the selected value. 9352 9353 // Handle dropdowns. 9354 if ( $setting.is('select') ) { 9355 $value = $setting.find('[value="' + value + '"]'); 9356 9357 if ( $value.length ) { 9358 $setting.find('option').prop( 'selected', false ); 9359 $value.prop( 'selected', true ); 9360 } else { 9361 // If we can't find the desired value, record what *is* selected. 9362 this.model.set( key, $setting.find(':selected').val() ); 9363 } 9364 9365 // Handle button groups. 9366 } else if ( $setting.hasClass('button-group') ) { 9367 $buttons = $setting.find( 'button' ) 9368 .removeClass( 'active' ) 9369 .attr( 'aria-pressed', 'false' ); 9370 $buttons.filter( '[value="' + value + '"]' ) 9371 .addClass( 'active' ) 9372 .attr( 'aria-pressed', 'true' ); 9373 9374 // Handle text inputs and textareas. 9375 } else if ( $setting.is('input[type="text"], textarea') ) { 9376 if ( ! $setting.is(':focus') ) { 9377 $setting.val( value ); 9378 } 9379 // Handle checkboxes. 9380 } else if ( $setting.is('input[type="checkbox"]') ) { 9381 $setting.prop( 'checked', !! value && 'false' !== value ); 9382 } 9383 }, 9384 /** 9385 * Updates the model when a setting is changed. 9386 * 9387 * @param {JQuery.Event} event The jQuery event object associated with the setting change. 9388 */ 9389 updateHandler: function( event ) { 9390 var $setting = $( event.target ).closest('[data-setting]'), 9391 value = event.target.value, 9392 userSetting; 9393 9394 event.preventDefault(); 9395 9396 if ( ! $setting.length ) { 9397 return; 9398 } 9399 9400 // Use the correct value for checkboxes. 9401 if ( $setting.is('input[type="checkbox"]') ) { 9402 value = $setting[0].checked; 9403 } 9404 9405 // Update the corresponding setting. 9406 this.model.set( $setting.data('setting'), value ); 9407 9408 // If the setting has a corresponding user setting, 9409 // update that as well. 9410 userSetting = $setting.data('userSetting'); 9411 if ( userSetting ) { 9412 window.setUserSetting( userSetting, value ); 9413 } 9414 }, 9415 9416 /** 9417 * Updates the view when the model changes. 9418 * 9419 * @param {Backbone.Model} model The model that changed. 9420 */ 9421 updateChanges: function( model ) { 9422 if ( model.hasChanged() ) { 9423 _( model.changed ).chain().keys().each( this.update, this ); 9424 } 9425 } 9426 }); 9427 9428 module.exports = Settings; 9429 9430 9431 /***/ }, 9432 9433 /***/ 7656 9434 (module) { 9435 9436 var Settings = wp.media.view.Settings, 9437 AttachmentDisplay; 9438 9439 /** 9440 * wp.media.view.Settings.AttachmentDisplay 9441 * 9442 * @memberOf wp.media.view.Settings 9443 * 9444 * @class 9445 * @augments wp.media.view.Settings 9446 * @augments wp.media.View 9447 * @augments wp.Backbone.View 9448 * @augments Backbone.View 9449 */ 9450 AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{ 9451 className: 'attachment-display-settings', 9452 template: wp.template('attachment-display-settings'), 9453 9454 /** 9455 * Initializes the attachment display settings view. 9456 */ 9457 initialize: function() { 9458 var attachment = this.options.attachment; 9459 9460 _.defaults( this.options, { 9461 userSettings: false 9462 }); 9463 // Call 'initialize' directly on the parent class. 9464 Settings.prototype.initialize.apply( this, arguments ); 9465 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 9466 9467 if ( attachment ) { 9468 attachment.on( 'change:uploading', this.render, this ); 9469 } 9470 }, 9471 9472 /** 9473 * Disposes of the attachment display settings view. 9474 */ 9475 dispose: function() { 9476 var attachment = this.options.attachment; 9477 if ( attachment ) { 9478 attachment.off( null, null, this ); 9479 } 9480 /** 9481 * call 'dispose' directly on the parent class 9482 */ 9483 Settings.prototype.dispose.apply( this, arguments ); 9484 }, 9485 /** 9486 * Renders the attachment display settings view. 9487 * 9488 * @return {wp.media.view.AttachmentDisplay} Returns itself to allow chaining. 9489 */ 9490 render: function() { 9491 var attachment = this.options.attachment; 9492 if ( attachment ) { 9493 _.extend( this.options, { 9494 sizes: attachment.get('sizes'), 9495 type: attachment.get('type') 9496 }); 9497 } 9498 /** 9499 * call 'render' directly on the parent class 9500 */ 9501 Settings.prototype.render.call( this ); 9502 this.updateLinkTo(); 9503 return this; 9504 }, 9505 9506 /** 9507 * Updates the linkTo setting. 9508 */ 9509 updateLinkTo: function() { 9510 var linkTo = this.model.get('link'), 9511 $input = this.$('.link-to-custom'), 9512 attachment = this.options.attachment; 9513 9514 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 9515 $input.closest( '.setting' ).addClass( 'hidden' ); 9516 return; 9517 } 9518 9519 if ( attachment ) { 9520 if ( 'post' === linkTo ) { 9521 $input.val( attachment.get('link') ); 9522 } else if ( 'file' === linkTo ) { 9523 $input.val( attachment.get('url') ); 9524 } else if ( ! this.model.get('linkUrl') ) { 9525 $input.val('http://'); 9526 } 9527 9528 $input.prop( 'readonly', 'custom' !== linkTo ); 9529 } 9530 9531 $input.closest( '.setting' ).removeClass( 'hidden' ); 9532 if ( $input.length ) { 9533 $input[0].scrollIntoView(); 9534 } 9535 } 9536 }); 9537 9538 module.exports = AttachmentDisplay; 9539 9540 9541 /***/ }, 9542 9543 /***/ 7266 9544 (module) { 9545 9546 /** 9547 * wp.media.view.Settings.Gallery 9548 * 9549 * @memberOf wp.media.view.Settings 9550 * 9551 * @class 9552 * @augments wp.media.view.Settings 9553 * @augments wp.media.View 9554 * @augments wp.Backbone.View 9555 * @augments Backbone.View 9556 */ 9557 var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{ 9558 className: 'collection-settings gallery-settings', 9559 template: wp.template('gallery-settings') 9560 }); 9561 9562 module.exports = Gallery; 9563 9564 9565 /***/ }, 9566 9567 /***/ 2356 9568 (module) { 9569 9570 /** 9571 * wp.media.view.Settings.Playlist 9572 * 9573 * @memberOf wp.media.view.Settings 9574 * 9575 * @class 9576 * @augments wp.media.view.Settings 9577 * @augments wp.media.View 9578 * @augments wp.Backbone.View 9579 * @augments Backbone.View 9580 */ 9581 var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{ 9582 className: 'collection-settings playlist-settings', 9583 template: wp.template('playlist-settings') 9584 }); 9585 9586 module.exports = Playlist; 9587 9588 9589 /***/ }, 9590 9591 /***/ 1992 9592 (module) { 9593 9594 /** 9595 * wp.media.view.Sidebar 9596 * 9597 * @memberOf wp.media.view 9598 * 9599 * @class 9600 * @augments wp.media.view.PriorityList 9601 * @augments wp.media.View 9602 * @augments wp.Backbone.View 9603 * @augments Backbone.View 9604 */ 9605 var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{ 9606 className: 'media-sidebar' 9607 }); 9608 9609 module.exports = Sidebar; 9610 9611 9612 /***/ }, 9613 9614 /***/ 443 9615 (module) { 9616 9617 var View = wp.media.view, 9618 SiteIconCropper; 9619 9620 /** 9621 * wp.media.view.SiteIconCropper 9622 * 9623 * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon. 9624 * 9625 * Takes imgAreaSelect options from 9626 * wp.customize.SiteIconControl.calculateImageSelectOptions. 9627 * 9628 * @memberOf wp.media.view 9629 * 9630 * @class 9631 * @augments wp.media.view.Cropper 9632 * @augments wp.media.View 9633 * @augments wp.Backbone.View 9634 * @augments Backbone.View 9635 */ 9636 SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{ 9637 className: 'crop-content site-icon', 9638 9639 ready: function () { 9640 View.Cropper.prototype.ready.apply( this, arguments ); 9641 9642 this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) ); 9643 }, 9644 9645 addSidebar: function() { 9646 this.sidebar = new wp.media.view.Sidebar({ 9647 controller: this.controller 9648 }); 9649 9650 this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({ 9651 controller: this.controller, 9652 attachment: this.options.attachment 9653 }) ); 9654 9655 this.controller.cropperView.views.add( this.sidebar ); 9656 } 9657 }); 9658 9659 module.exports = SiteIconCropper; 9660 9661 9662 /***/ }, 9663 9664 /***/ 7810 9665 (module) { 9666 9667 var View = wp.media.View, 9668 $ = jQuery, 9669 SiteIconPreview; 9670 9671 /** 9672 * wp.media.view.SiteIconPreview 9673 * 9674 * Shows a preview of the Site Icon as a favicon and app icon while cropping. 9675 * 9676 * @memberOf wp.media.view 9677 * 9678 * @class 9679 * @augments wp.media.View 9680 * @augments wp.Backbone.View 9681 * @augments Backbone.View 9682 */ 9683 SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{ 9684 className: 'site-icon-preview-crop-modal', 9685 template: wp.template( 'site-icon-preview-crop' ), 9686 9687 ready: function() { 9688 this.controller.imgSelect.setOptions({ 9689 onInit: this.updatePreview, 9690 onSelectChange: this.updatePreview 9691 }); 9692 }, 9693 9694 prepare: function() { 9695 return { 9696 url: this.options.attachment.get( 'url' ) 9697 }; 9698 }, 9699 9700 updatePreview: function( img, coords ) { 9701 var rx = 64 / coords.width, 9702 ry = 64 / coords.height, 9703 preview_rx = 24 / coords.width, 9704 preview_ry = 24 / coords.height; 9705 9706 $( '#preview-app-icon' ).css({ 9707 width: Math.round(rx * this.imageWidth ) + 'px', 9708 height: Math.round(ry * this.imageHeight ) + 'px', 9709 marginLeft: '-' + Math.round(rx * coords.x1) + 'px', 9710 marginTop: '-' + Math.round(ry * coords.y1) + 'px' 9711 }); 9712 9713 $( '#preview-favicon' ).css({ 9714 width: Math.round( preview_rx * this.imageWidth ) + 'px', 9715 height: Math.round( preview_ry * this.imageHeight ) + 'px', 9716 marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px', 9717 marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px' 9718 }); 9719 } 9720 }); 9721 9722 module.exports = SiteIconPreview; 9723 9724 9725 /***/ }, 9726 9727 /***/ 9141 9728 (module) { 9729 9730 /** 9731 * wp.media.view.Spinner 9732 * 9733 * Represents a spinner in the Media Library. 9734 * 9735 * @since 3.9.0 9736 * 9737 * @memberOf wp.media.view 9738 * 9739 * @class 9740 * @augments wp.media.View 9741 * @augments wp.Backbone.View 9742 * @augments Backbone.View 9743 */ 9744 var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{ 9745 tagName: 'span', 9746 className: 'spinner', 9747 spinnerTimeout: false, 9748 delay: 400, 9749 9750 /** 9751 * Shows the spinner. Delays the visibility by the configured amount. 9752 * 9753 * @since 3.9.0 9754 * 9755 * @return {wp.media.view.Spinner} The spinner. 9756 */ 9757 show: function() { 9758 if ( ! this.spinnerTimeout ) { 9759 this.spinnerTimeout = _.delay(function( $el ) { 9760 $el.addClass( 'is-active' ); 9761 }, this.delay, this.$el ); 9762 } 9763 9764 return this; 9765 }, 9766 9767 /** 9768 * Hides the spinner. 9769 * 9770 * @since 3.9.0 9771 * 9772 * @return {wp.media.view.Spinner} The spinner. 9773 */ 9774 hide: function() { 9775 this.$el.removeClass( 'is-active' ); 9776 this.spinnerTimeout = clearTimeout( this.spinnerTimeout ); 9777 9778 return this; 9779 } 9780 }); 9781 9782 module.exports = Spinner; 9783 9784 9785 /***/ }, 9786 9787 /***/ 5275 9788 (module) { 9789 9790 var View = wp.media.View, 9791 Toolbar; 9792 9793 /** 9794 * wp.media.view.Toolbar 9795 * 9796 * A toolbar which consists of a primary and a secondary section. Each sections 9797 * can be filled with views. 9798 * 9799 * @memberOf wp.media.view 9800 * 9801 * @class 9802 * @augments wp.media.View 9803 * @augments wp.Backbone.View 9804 * @augments Backbone.View 9805 */ 9806 Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{ 9807 tagName: 'div', 9808 className: 'media-toolbar', 9809 9810 /** 9811 * Initializes the toolbar view. 9812 */ 9813 initialize: function() { 9814 var state = this.controller.state(), 9815 selection = this.selection = state.get('selection'), 9816 library = this.library = state.get('library'); 9817 9818 this._views = {}; 9819 9820 // The toolbar is composed of two `PriorityList` views. 9821 this.primary = new wp.media.view.PriorityList(); 9822 this.secondary = new wp.media.view.PriorityList(); 9823 this.tertiary = new wp.media.view.PriorityList(); 9824 this.primary.$el.addClass('media-toolbar-primary search-form'); 9825 this.secondary.$el.addClass('media-toolbar-secondary'); 9826 this.tertiary.$el.addClass('media-bg-overlay'); 9827 9828 this.views.set([ this.secondary, this.primary, this.tertiary ]); 9829 9830 if ( this.options.items ) { 9831 this.set( this.options.items, { silent: true }); 9832 } 9833 9834 if ( ! this.options.silent ) { 9835 this.render(); 9836 } 9837 9838 if ( selection ) { 9839 selection.on( 'add remove reset', this.refresh, this ); 9840 } 9841 9842 if ( library ) { 9843 library.on( 'add remove reset', this.refresh, this ); 9844 } 9845 }, 9846 /** 9847 * Disposes of the toolbar view. 9848 * 9849 * @return {wp.media.view.Toolbar} Returns itself to allow chaining 9850 */ 9851 dispose: function() { 9852 if ( this.selection ) { 9853 this.selection.off( null, null, this ); 9854 } 9855 9856 if ( this.library ) { 9857 this.library.off( null, null, this ); 9858 } 9859 /** 9860 * call 'dispose' directly on the parent class 9861 */ 9862 return View.prototype.dispose.apply( this, arguments ); 9863 }, 9864 9865 /** 9866 * Prepares the data for rendering. 9867 */ 9868 ready: function() { 9869 this.refresh(); 9870 }, 9871 9872 /** 9873 * Sets a view by its ID. 9874 * 9875 * @param {string} id The ID of the view to set. 9876 * @param {Backbone.View|Object} view The view to set. 9877 * @param {Object} [options={}] The options for setting the view. 9878 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 9879 */ 9880 set: function( id, view, options ) { 9881 var list; 9882 options = options || {}; 9883 9884 // Accept an object with an `id` : `view` mapping. 9885 if ( _.isObject( id ) ) { 9886 _.each( id, function( view, id ) { 9887 this.set( id, view, { silent: true }); 9888 }, this ); 9889 9890 } else { 9891 if ( ! ( view instanceof Backbone.View ) ) { 9892 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 9893 view = new wp.media.view.Button( view ).render(); 9894 } 9895 9896 view.controller = view.controller || this.controller; 9897 9898 this._views[ id ] = view; 9899 9900 list = view.options.priority < 0 ? 'secondary' : 'primary'; 9901 this[ list ].set( id, view, options ); 9902 } 9903 9904 if ( ! options.silent ) { 9905 this.refresh(); 9906 } 9907 9908 return this; 9909 }, 9910 /** 9911 * Retrieves a view by its ID. 9912 * 9913 * @param {string} id The ID of the view to retrieve. 9914 * @return {wp.media.view.Button} The view associated with the given ID, or undefined if no view is found. 9915 */ 9916 get: function( id ) { 9917 return this._views[ id ]; 9918 }, 9919 /** 9920 * Unsets a view by its ID. 9921 * 9922 * @param {string} id The ID of the view to unset. 9923 * @param {Object} [options={}] The options for unsetting the view. 9924 * @return {wp.media.view.Toolbar} Returns itself to allow chaining. 9925 */ 9926 unset: function( id, options ) { 9927 delete this._views[ id ]; 9928 this.primary.unset( id, options ); 9929 this.secondary.unset( id, options ); 9930 this.tertiary.unset( id, options ); 9931 9932 if ( ! options || ! options.silent ) { 9933 this.refresh(); 9934 } 9935 return this; 9936 }, 9937 9938 /** 9939 * Refreshes the toolbar view. 9940 */ 9941 refresh: function() { 9942 var state = this.controller.state(), 9943 library = state.get('library'), 9944 selection = state.get('selection'); 9945 9946 _.each( this._views, function( button ) { 9947 if ( ! button.model || ! button.options || ! button.options.requires ) { 9948 return; 9949 } 9950 9951 var requires = button.options.requires, 9952 disabled = false, 9953 modelsUploading = library && ! _.isEmpty( library.findWhere( { 'uploading': true } ) ); 9954 9955 // Prevent insertion of attachments if any of them are still uploading. 9956 if ( selection && selection.models ) { 9957 disabled = _.some( selection.models, function( attachment ) { 9958 return attachment.get('uploading') === true; 9959 }); 9960 } 9961 if ( requires.uploadingComplete && modelsUploading ) { 9962 disabled = true; 9963 } 9964 9965 if ( requires.selection && selection && ! selection.length ) { 9966 disabled = true; 9967 } else if ( requires.library && library && ! library.length ) { 9968 disabled = true; 9969 } 9970 button.model.set( 'disabled', disabled ); 9971 }); 9972 } 9973 }); 9974 9975 module.exports = Toolbar; 9976 9977 9978 /***/ }, 9979 9980 /***/ 397 9981 (module) { 9982 9983 var Select = wp.media.view.Toolbar.Select, 9984 l10n = wp.media.view.l10n, 9985 Embed; 9986 9987 /** 9988 * wp.media.view.Toolbar.Embed 9989 * 9990 * @memberOf wp.media.view.Toolbar 9991 * 9992 * @class 9993 * @augments wp.media.view.Toolbar.Select 9994 * @augments wp.media.view.Toolbar 9995 * @augments wp.media.View 9996 * @augments wp.Backbone.View 9997 * @augments Backbone.View 9998 */ 9999 Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{ 10000 initialize: function() { 10001 _.defaults( this.options, { 10002 text: l10n.insertIntoPost, 10003 requires: false 10004 }); 10005 // Call 'initialize' directly on the parent class. 10006 Select.prototype.initialize.apply( this, arguments ); 10007 }, 10008 10009 refresh: function() { 10010 var url = this.controller.state().props.get('url'); 10011 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 10012 /** 10013 * call 'refresh' directly on the parent class 10014 */ 10015 Select.prototype.refresh.apply( this, arguments ); 10016 } 10017 }); 10018 10019 module.exports = Embed; 10020 10021 10022 /***/ }, 10023 10024 /***/ 9458 10025 (module) { 10026 10027 var Toolbar = wp.media.view.Toolbar, 10028 l10n = wp.media.view.l10n, 10029 Select; 10030 10031 /** 10032 * wp.media.view.Toolbar.Select 10033 * 10034 * @memberOf wp.media.view.Toolbar 10035 * 10036 * @class 10037 * @augments wp.media.view.Toolbar 10038 * @augments wp.media.View 10039 * @augments wp.Backbone.View 10040 * @augments Backbone.View 10041 */ 10042 Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{ 10043 initialize: function() { 10044 var options = this.options; 10045 10046 _.bindAll( this, 'clickSelect' ); 10047 10048 _.defaults( options, { 10049 event: 'select', 10050 state: false, 10051 reset: true, 10052 close: true, 10053 text: l10n.select, 10054 10055 // Does the button rely on the selection? 10056 requires: { 10057 selection: true 10058 } 10059 }); 10060 10061 options.items = _.defaults( options.items || {}, { 10062 select: { 10063 style: 'primary', 10064 text: options.text, 10065 priority: 80, 10066 click: this.clickSelect, 10067 requires: options.requires 10068 } 10069 }); 10070 // Call 'initialize' directly on the parent class. 10071 Toolbar.prototype.initialize.apply( this, arguments ); 10072 }, 10073 10074 clickSelect: function() { 10075 var options = this.options, 10076 controller = this.controller; 10077 10078 if ( options.close ) { 10079 controller.close(); 10080 } 10081 10082 if ( options.event ) { 10083 controller.state().trigger( options.event ); 10084 } 10085 10086 if ( options.state ) { 10087 controller.setState( options.state ); 10088 } 10089 10090 if ( options.reset ) { 10091 controller.reset(); 10092 } 10093 } 10094 }); 10095 10096 module.exports = Select; 10097 10098 10099 /***/ }, 10100 10101 /***/ 3674 10102 (module) { 10103 10104 var View = wp.media.View, 10105 l10n = wp.media.view.l10n, 10106 $ = jQuery, 10107 EditorUploader; 10108 10109 /** 10110 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 10111 * and relays drag'n'dropped files to a media workflow. 10112 * 10113 * wp.media.view.EditorUploader 10114 * 10115 * @memberOf wp.media.view 10116 * 10117 * @class 10118 * @augments wp.media.View 10119 * @augments wp.Backbone.View 10120 * @augments Backbone.View 10121 */ 10122 EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{ 10123 tagName: 'div', 10124 className: 'uploader-editor', 10125 template: wp.template( 'uploader-editor' ), 10126 10127 localDrag: false, 10128 overContainer: false, 10129 overDropzone: false, 10130 draggingFile: null, 10131 10132 /** 10133 * Bind drag'n'drop events to callbacks. 10134 * 10135 * @return {wp.media.view.EditorUploader} Chainable. 10136 */ 10137 initialize: function() { 10138 this.initialized = false; 10139 10140 // Bail if not enabled or UA does not support drag'n'drop or File API. 10141 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 10142 return this; 10143 } 10144 10145 this.$document = $(document); 10146 this.dropzones = []; 10147 this.files = []; 10148 10149 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 10150 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 10151 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 10152 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 10153 10154 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 10155 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 10156 10157 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 10158 this.localDrag = event.type === 'dragstart'; 10159 10160 if ( event.type === 'drop' ) { 10161 this.containerDragleave(); 10162 } 10163 }, this ) ); 10164 10165 this.initialized = true; 10166 return this; 10167 }, 10168 10169 /** 10170 * Check browser support for drag'n'drop. 10171 * 10172 * @return {boolean} True if the browser supports drag'n'drop, false otherwise. 10173 */ 10174 browserSupport: function() { 10175 var supports = false, div = document.createElement('div'); 10176 10177 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 10178 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 10179 return supports; 10180 }, 10181 10182 isDraggingFile: function( event ) { 10183 if ( this.draggingFile !== null ) { 10184 return this.draggingFile; 10185 } 10186 10187 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 10188 return false; 10189 } 10190 10191 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 10192 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 10193 10194 return this.draggingFile; 10195 }, 10196 10197 refresh: function( e ) { 10198 var dropzone_id; 10199 for ( dropzone_id in this.dropzones ) { 10200 // Hide the dropzones only if dragging has left the screen. 10201 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 10202 } 10203 10204 if ( ! _.isUndefined( e ) ) { 10205 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 10206 } 10207 10208 if ( ! this.overContainer && ! this.overDropzone ) { 10209 this.draggingFile = null; 10210 } 10211 10212 return this; 10213 }, 10214 10215 render: function() { 10216 if ( ! this.initialized ) { 10217 return this; 10218 } 10219 10220 View.prototype.render.apply( this, arguments ); 10221 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 10222 return this; 10223 }, 10224 10225 attach: function( index, editor ) { 10226 // Attach a dropzone to an editor. 10227 var dropzone = this.$el.clone(); 10228 this.dropzones.push( dropzone ); 10229 $( editor ).append( dropzone ); 10230 return this; 10231 }, 10232 10233 /** 10234 * When a file is dropped on the editor uploader, open up an editor media workflow 10235 * and upload the file immediately. 10236 * 10237 * @param {jQuery.Event} event The 'drop' event. 10238 * @return {void|boolean} False to prevent default behavior. 10239 */ 10240 drop: function( event ) { 10241 var $wrap, uploadView; 10242 10243 this.containerDragleave( event ); 10244 this.dropzoneDragleave( event ); 10245 10246 this.files = event.originalEvent.dataTransfer.files; 10247 if ( this.files.length < 1 ) { 10248 return; 10249 } 10250 10251 // Set the active editor to the drop target. 10252 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 10253 if ( $wrap.length > 0 && $wrap[0].id ) { 10254 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 10255 } 10256 10257 if ( ! this.workflow ) { 10258 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 10259 frame: 'post', 10260 state: 'insert', 10261 title: l10n.addMedia, 10262 multiple: true 10263 }); 10264 10265 uploadView = this.workflow.uploader; 10266 10267 if ( uploadView.uploader && uploadView.uploader.ready ) { 10268 this.addFiles.apply( this ); 10269 } else { 10270 this.workflow.on( 'uploader:ready', this.addFiles, this ); 10271 } 10272 } else { 10273 this.workflow.state().reset(); 10274 this.addFiles.apply( this ); 10275 this.workflow.open(); 10276 } 10277 10278 return false; 10279 }, 10280 10281 /** 10282 * Add the files to the uploader. 10283 * 10284 * @return {wp.media.view.EditorUploader} Chainable. 10285 */ 10286 addFiles: function() { 10287 if ( this.files.length ) { 10288 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 10289 this.files = []; 10290 } 10291 return this; 10292 }, 10293 10294 containerDragover: function( event ) { 10295 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 10296 return; 10297 } 10298 10299 this.overContainer = true; 10300 this.refresh(); 10301 }, 10302 10303 containerDragleave: function() { 10304 this.overContainer = false; 10305 10306 // Throttle dragleave because it's called when bouncing from some elements to others. 10307 _.delay( _.bind( this.refresh, this ), 50 ); 10308 }, 10309 10310 dropzoneDragover: function( event ) { 10311 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 10312 return; 10313 } 10314 10315 this.overDropzone = true; 10316 this.refresh( event ); 10317 return false; 10318 }, 10319 10320 dropzoneDragleave: function( e ) { 10321 this.overDropzone = false; 10322 _.delay( _.bind( this.refresh, this, e ), 50 ); 10323 }, 10324 10325 click: function( e ) { 10326 // In the rare case where the dropzone gets stuck, hide it on click. 10327 this.containerDragleave( e ); 10328 this.dropzoneDragleave( e ); 10329 this.localDrag = false; 10330 } 10331 }); 10332 10333 module.exports = EditorUploader; 10334 10335 10336 /***/ }, 10337 10338 /***/ 1753 10339 (module) { 10340 10341 var View = wp.media.View, 10342 UploaderInline; 10343 10344 /** 10345 * wp.media.view.UploaderInline 10346 * 10347 * The inline uploader that shows up in the 'Upload Files' tab. 10348 * 10349 * @memberOf wp.media.view 10350 * 10351 * @class 10352 * @augments wp.media.View 10353 * @augments wp.Backbone.View 10354 * @augments Backbone.View 10355 */ 10356 UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{ 10357 tagName: 'div', 10358 className: 'uploader-inline', 10359 template: wp.template('uploader-inline'), 10360 10361 events: { 10362 'click .close': 'hide' 10363 }, 10364 10365 initialize: function() { 10366 _.defaults( this.options, { 10367 message: '', 10368 status: true, 10369 canClose: false 10370 }); 10371 10372 if ( ! this.options.$browser && this.controller.uploader ) { 10373 this.options.$browser = this.controller.uploader.$browser; 10374 } 10375 10376 if ( _.isUndefined( this.options.postId ) ) { 10377 this.options.postId = wp.media.view.settings.post.id; 10378 } 10379 10380 if ( this.options.status ) { 10381 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 10382 controller: this.controller 10383 }) ); 10384 } 10385 }, 10386 10387 prepare: function() { 10388 var suggestedWidth = this.controller.state().get('suggestedWidth'), 10389 suggestedHeight = this.controller.state().get('suggestedHeight'), 10390 data = {}; 10391 10392 data.message = this.options.message; 10393 data.canClose = this.options.canClose; 10394 10395 if ( suggestedWidth && suggestedHeight ) { 10396 data.suggestedWidth = suggestedWidth; 10397 data.suggestedHeight = suggestedHeight; 10398 } 10399 10400 return data; 10401 }, 10402 /** 10403 * Disposes of the inline uploader and its associated views. 10404 * 10405 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 10406 */ 10407 dispose: function() { 10408 if ( this.disposing ) { 10409 /* 10410 * call 'dispose' directly on the parent class 10411 */ 10412 return View.prototype.dispose.apply( this, arguments ); 10413 } 10414 10415 /* 10416 * Run remove on `dispose`, so we can be sure to refresh the 10417 * uploader with a view-less DOM. Track whether we're disposing 10418 * so we don't trigger an infinite loop. 10419 */ 10420 this.disposing = true; 10421 return this.remove(); 10422 }, 10423 /** 10424 * Disposes of the inline uploader and its associated views. 10425 * 10426 * @return {wp.media.view.UploaderInline} Returns itself to allow chaining. 10427 */ 10428 remove: function() { 10429 /* 10430 * call 'remove' directly on the parent class 10431 */ 10432 var result = View.prototype.remove.apply( this, arguments ); 10433 10434 _.defer( _.bind( this.refresh, this ) ); 10435 return result; 10436 }, 10437 10438 refresh: function() { 10439 var uploader = this.controller.uploader; 10440 10441 if ( uploader ) { 10442 uploader.refresh(); 10443 } 10444 }, 10445 /** 10446 * Replaces the placeholder with the uploader browser and refreshes the uploader. 10447 * 10448 * @return {void|wp.media.view.UploaderInline} Returns itself to allow chaining. 10449 */ 10450 ready: function() { 10451 var $browser = this.options.$browser, 10452 $placeholder; 10453 10454 if ( this.controller.uploader ) { 10455 $placeholder = this.$('.browser'); 10456 10457 // Check if we've already replaced the placeholder. 10458 if ( $placeholder[0] === $browser[0] ) { 10459 return; 10460 } 10461 10462 $browser.detach().text( $placeholder.text() ); 10463 $browser[0].className = $placeholder[0].className; 10464 $browser[0].setAttribute( 'aria-describedby', $placeholder[0].getAttribute('aria-describedby') ); 10465 $placeholder.replaceWith( $browser.show() ); 10466 } 10467 10468 this.refresh(); 10469 return this; 10470 }, 10471 show: function() { 10472 this.$el.removeClass( 'hidden' ); 10473 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 10474 this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' ); 10475 } 10476 }, 10477 hide: function() { 10478 this.$el.addClass( 'hidden' ); 10479 if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) { 10480 this.controller.$uploaderToggler 10481 .attr( 'aria-expanded', 'false' ) 10482 // Move focus back to the toggle button when closing the uploader. 10483 .trigger( 'focus' ); 10484 } 10485 } 10486 10487 }); 10488 10489 module.exports = UploaderInline; 10490 10491 10492 /***/ }, 10493 10494 /***/ 6442 10495 (module) { 10496 10497 /** 10498 * wp.media.view.UploaderStatusError 10499 * 10500 * @memberOf wp.media.view 10501 * 10502 * @class 10503 * @augments wp.media.View 10504 * @augments wp.Backbone.View 10505 * @augments Backbone.View 10506 */ 10507 var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{ 10508 className: 'upload-error', 10509 template: wp.template('uploader-status-error') 10510 }); 10511 10512 module.exports = UploaderStatusError; 10513 10514 10515 /***/ }, 10516 10517 /***/ 8197 10518 (module) { 10519 10520 var View = wp.media.View, 10521 UploaderStatus; 10522 10523 /** 10524 * wp.media.view.UploaderStatus 10525 * 10526 * An uploader status for on-going uploads. 10527 * 10528 * @memberOf wp.media.view 10529 * 10530 * @class 10531 * @augments wp.media.View 10532 * @augments wp.Backbone.View 10533 * @augments Backbone.View 10534 */ 10535 UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{ 10536 className: 'media-uploader-status', 10537 template: wp.template('uploader-status'), 10538 10539 events: { 10540 'click .upload-dismiss-errors': 'dismiss' 10541 }, 10542 10543 initialize: function() { 10544 this.queue = wp.Uploader.queue; 10545 this.queue.on( 'add remove reset', this.visibility, this ); 10546 this.queue.on( 'add remove reset change:percent', this.progress, this ); 10547 this.queue.on( 'add remove reset change:uploading', this.info, this ); 10548 10549 this.errors = wp.Uploader.errors; 10550 this.errors.reset(); 10551 this.errors.on( 'add remove reset', this.visibility, this ); 10552 this.errors.on( 'add', this.error, this ); 10553 }, 10554 /** 10555 * Disposes of the uploader status and its associated views. 10556 * 10557 * @return {wp.media.view.UploaderStatus} Returns the instance of the UploaderStatus view. 10558 */ 10559 dispose: function() { 10560 wp.Uploader.queue.off( null, null, this ); 10561 /* 10562 * call 'dispose' directly on the parent class 10563 */ 10564 View.prototype.dispose.apply( this, arguments ); 10565 return this; 10566 }, 10567 10568 visibility: function() { 10569 this.$el.toggleClass( 'uploading', !! this.queue.length ); 10570 this.$el.toggleClass( 'errors', !! this.errors.length ); 10571 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 10572 }, 10573 10574 ready: function() { 10575 _.each({ 10576 '$bar': '.media-progress-bar div', 10577 '$index': '.upload-index', 10578 '$total': '.upload-total', 10579 '$filename': '.upload-filename' 10580 }, function( selector, key ) { 10581 this[ key ] = this.$( selector ); 10582 }, this ); 10583 10584 this.visibility(); 10585 this.progress(); 10586 this.info(); 10587 }, 10588 10589 progress: function() { 10590 var queue = this.queue, 10591 $bar = this.$bar; 10592 10593 if ( ! $bar || ! queue.length ) { 10594 return; 10595 } 10596 10597 $bar.width( ( queue.reduce( function( memo, attachment ) { 10598 if ( ! attachment.get('uploading') ) { 10599 return memo + 100; 10600 } 10601 10602 var percent = attachment.get('percent'); 10603 return memo + ( _.isNumber( percent ) ? percent : 100 ); 10604 }, 0 ) / queue.length ) + '%' ); 10605 }, 10606 10607 info: function() { 10608 var queue = this.queue, 10609 index = 0, active; 10610 10611 if ( ! queue.length ) { 10612 return; 10613 } 10614 10615 active = this.queue.find( function( attachment, i ) { 10616 index = i; 10617 return attachment.get('uploading'); 10618 }); 10619 10620 if ( this.$index && this.$total && this.$filename ) { 10621 this.$index.text( index + 1 ); 10622 this.$total.text( queue.length ); 10623 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 10624 } 10625 }, 10626 /** 10627 * Escapes the filename to prevent XSS attacks. 10628 * 10629 * @param {string} filename The name of the file to be escaped. 10630 * @return {string} Escaped filename. 10631 */ 10632 filename: function( filename ) { 10633 return _.escape( filename ); 10634 }, 10635 /** 10636 * Handles an error event from the uploader queue. 10637 * 10638 * @param {Backbone.Model} error The error model from the uploader queue. 10639 * @return {void} 10640 */ 10641 error: function( error ) { 10642 var statusError = new wp.media.view.UploaderStatusError( { 10643 filename: this.filename( error.get( 'file' ).name ), 10644 message: error.get( 'message' ) 10645 } ); 10646 10647 var buttonClose = this.$el.find( 'button' ); 10648 10649 // Can show additional info here while retrying to create image sub-sizes. 10650 this.views.add( '.upload-errors', statusError, { at: 0 } ); 10651 _.delay( function() { 10652 buttonClose.trigger( 'focus' ); 10653 }, 1000 ); 10654 10655 _.delay( function() { 10656 wp.a11y.speak( error.get( 'message' ) ); 10657 }, 1500 ); 10658 }, 10659 10660 /** 10661 * Dismisses the error messages and resets the uploader errors. 10662 */ 10663 dismiss: function() { 10664 var errors = this.views.get('.upload-errors'); 10665 10666 if ( errors ) { 10667 _.invoke( errors, 'remove' ); 10668 } 10669 wp.Uploader.errors.reset(); 10670 wp.a11y.speak( wp.i18n.__( 'Error dismissed.' ) ); 10671 // Move focus to the modal after the dismiss button gets removed from the DOM. 10672 if ( this.controller.modal ) { 10673 this.controller.modal.focusManager.focus(); 10674 } 10675 } 10676 }); 10677 10678 module.exports = UploaderStatus; 10679 10680 10681 /***/ }, 10682 10683 /***/ 8291 10684 (module) { 10685 10686 var $ = jQuery, 10687 UploaderWindow; 10688 10689 /** 10690 * wp.media.view.UploaderWindow 10691 * 10692 * An uploader window that allows for dragging and dropping media. 10693 * 10694 * @memberOf wp.media.view 10695 * 10696 * @class 10697 * @augments wp.media.View 10698 * @augments wp.Backbone.View 10699 * @augments Backbone.View 10700 * 10701 * @param {Object} [options] Options hash passed to the view. 10702 * @param {Object} [options.uploader] Uploader properties. 10703 * @param {jQuery} [options.uploader.browser] 10704 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 10705 * @param {Object} [options.uploader.params] 10706 */ 10707 UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{ 10708 tagName: 'div', 10709 className: 'uploader-window', 10710 template: wp.template('uploader-window'), 10711 10712 initialize: function() { 10713 var uploader; 10714 10715 this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' ); 10716 10717 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 10718 dropzone: this.$el, 10719 browser: this.$browser, 10720 params: {} 10721 }); 10722 10723 // Ensure the dropzone is a jQuery collection. 10724 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 10725 uploader.dropzone = $( uploader.dropzone ); 10726 } 10727 10728 this.controller.on( 'activate', this.refresh, this ); 10729 10730 this.controller.on( 'detach', function() { 10731 this.$browser.remove(); 10732 }, this ); 10733 }, 10734 10735 refresh: function() { 10736 if ( this.uploader ) { 10737 this.uploader.refresh(); 10738 } 10739 }, 10740 10741 ready: function() { 10742 var postId = wp.media.view.settings.post.id, 10743 dropzone; 10744 10745 // If the uploader already exists, bail. 10746 if ( this.uploader ) { 10747 return; 10748 } 10749 10750 if ( postId ) { 10751 this.options.uploader.params.post_id = postId; 10752 } 10753 this.uploader = new wp.Uploader( this.options.uploader ); 10754 10755 dropzone = this.uploader.dropzone; 10756 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 10757 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 10758 10759 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 10760 }, 10761 10762 _ready: function() { 10763 this.controller.trigger( 'uploader:ready' ); 10764 }, 10765 10766 show: function() { 10767 var $el = this.$el.show(); 10768 10769 // Ensure that the animation is triggered by waiting until 10770 // the transparent element is painted into the DOM. 10771 _.defer( function() { 10772 $el.css({ opacity: 1 }); 10773 }); 10774 }, 10775 10776 hide: function() { 10777 var $el = this.$el.css({ opacity: 0 }); 10778 10779 wp.media.transition( $el ).done( function() { 10780 // Transition end events are subject to race conditions. 10781 // Make sure that the value is set as intended. 10782 if ( '0' === $el.css('opacity') ) { 10783 $el.hide(); 10784 } 10785 }); 10786 10787 // https://core.trac.wordpress.org/ticket/27341 10788 _.delay( function() { 10789 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 10790 $el.hide(); 10791 } 10792 }, 500 ); 10793 } 10794 }); 10795 10796 module.exports = UploaderWindow; 10797 10798 10799 /***/ }, 10800 10801 /***/ 4747 10802 (module) { 10803 10804 /** 10805 * wp.media.View 10806 * 10807 * The base view class for media. 10808 * 10809 * Undelegating events, removing events from the model, and 10810 * removing events from the controller mirror the code for 10811 * `Backbone.View.dispose` in Backbone 0.9.8 development. 10812 * 10813 * This behavior has since been removed, and should not be used 10814 * outside of the media manager. 10815 * 10816 * @memberOf wp.media 10817 * 10818 * @class 10819 * @augments wp.Backbone.View 10820 * @augments Backbone.View 10821 */ 10822 var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{ 10823 /** 10824 * Constructs the media view. 10825 * 10826 * @param {Object} [options] Options for the view. 10827 */ 10828 constructor: function( options ) { 10829 if ( options && options.controller ) { 10830 this.controller = options.controller; 10831 } 10832 wp.Backbone.View.apply( this, arguments ); 10833 }, 10834 /** 10835 * Disposes of the media view. 10836 * 10837 * @todo The internal comment mentions this might have been a stop-gap 10838 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 10839 * care of this in Backbone.View now. 10840 * 10841 * @return {wp.media.View} Returns itself to allow chaining. 10842 */ 10843 dispose: function() { 10844 /* 10845 * Undelegating events, removing events from the model, and 10846 * removing events from the controller mirror the code for 10847 * `Backbone.View.dispose` in Backbone 0.9.8 development. 10848 */ 10849 this.undelegateEvents(); 10850 10851 if ( this.model && this.model.off ) { 10852 this.model.off( null, null, this ); 10853 } 10854 10855 if ( this.collection && this.collection.off ) { 10856 this.collection.off( null, null, this ); 10857 } 10858 10859 // Unbind controller events. 10860 if ( this.controller && this.controller.off ) { 10861 this.controller.off( null, null, this ); 10862 } 10863 10864 return this; 10865 }, 10866 /** 10867 * Removes the media view. 10868 * 10869 * @return {wp.media.View} Returns itself to allow chaining. 10870 */ 10871 remove: function() { 10872 this.dispose(); 10873 /** 10874 * call 'remove' directly on the parent class 10875 */ 10876 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 10877 } 10878 }); 10879 10880 module.exports = View; 10881 10882 10883 /***/ } 10884 10885 /******/ }); 10886 /************************************************************************/ 10887 /******/ // The module cache 10888 /******/ const __webpack_module_cache__ = {}; 10889 /******/ 10890 /******/ // The require function 10891 /******/ function __webpack_require__(moduleId) { 10892 /******/ // Check if module is in cache 10893 /******/ const cachedModule = __webpack_module_cache__[moduleId]; 10894 /******/ if (cachedModule !== undefined) { 10895 /******/ return cachedModule.exports; 10896 /******/ } 10897 /******/ // Create a new module (and put it into the cache) 10898 /******/ const module = __webpack_module_cache__[moduleId] = { 10899 /******/ // no module.id needed 10900 /******/ // no module.loaded needed 10901 /******/ exports: {} 10902 /******/ }; 10903 /******/ 10904 /******/ // Execute the module function 10905 /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 10906 /******/ 10907 /******/ // Return the exports of the module 10908 /******/ return module.exports; 10909 /******/ } 10910 /******/ 10911 /************************************************************************/ 10912 /** 10913 * @output wp-includes/js/media-views.js 10914 */ 10915 10916 var media = wp.media, 10917 $ = jQuery, 10918 l10n; 10919 10920 media.isTouchDevice = ( 'ontouchend' in document ); 10921 10922 // Link any localized strings. 10923 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 10924 10925 // Link any settings. 10926 media.view.settings = l10n.settings || {}; 10927 delete l10n.settings; 10928 10929 // Copy the `post` setting over to the model settings. 10930 media.model.settings.post = media.view.settings.post; 10931 10932 // Check if the browser supports CSS 3.0 transitions. 10933 $.support.transition = (function(){ 10934 var style = document.documentElement.style, 10935 transitions = { 10936 WebkitTransition: 'webkitTransitionEnd', 10937 MozTransition: 'transitionend', 10938 OTransition: 'oTransitionEnd otransitionend', 10939 transition: 'transitionend' 10940 }, transition; 10941 10942 transition = _.find( _.keys( transitions ), function( transition ) { 10943 return ! _.isUndefined( style[ transition ] ); 10944 }); 10945 10946 return transition && { 10947 end: transitions[ transition ] 10948 }; 10949 }()); 10950 10951 /** 10952 * A shared event bus used to provide events into 10953 * the media workflows that 3rd-party devs can use to hook 10954 * in. 10955 */ 10956 media.events = _.extend( {}, Backbone.Events ); 10957 10958 /** 10959 * Makes it easier to bind events using transitions. 10960 * 10961 * @param {string} selector The CSS selector for the element to bind the transition event to. 10962 * @param {number} sensitivity The maximum time to wait for the transition event before resolving the promise. 10963 * @return {Promise} A promise that resolves when the transition has completed. 10964 */ 10965 media.transition = function( selector, sensitivity ) { 10966 var deferred = $.Deferred(); 10967 10968 sensitivity = sensitivity || 2000; 10969 10970 if ( $.support.transition ) { 10971 if ( ! (selector instanceof $) ) { 10972 selector = $( selector ); 10973 } 10974 10975 // Resolve the deferred when the first element finishes animating. 10976 selector.first().one( $.support.transition.end, deferred.resolve ); 10977 10978 // Just in case the event doesn't trigger, fire a callback. 10979 _.delay( deferred.resolve, sensitivity ); 10980 10981 // Otherwise, execute on the spot. 10982 } else { 10983 deferred.resolve(); 10984 } 10985 10986 return deferred.promise(); 10987 }; 10988 10989 media.controller.Region = __webpack_require__( 9875 ); 10990 media.controller.StateMachine = __webpack_require__( 6150 ); 10991 media.controller.State = __webpack_require__( 5694 ); 10992 10993 media.selectionSync = __webpack_require__( 4181 ); 10994 media.controller.Library = __webpack_require__( 472 ); 10995 media.controller.ImageDetails = __webpack_require__( 705 ); 10996 media.controller.GalleryEdit = __webpack_require__( 2038 ); 10997 media.controller.GalleryAdd = __webpack_require__( 7127 ); 10998 media.controller.CollectionEdit = __webpack_require__( 8612 ); 10999 media.controller.CollectionAdd = __webpack_require__( 7145 ); 11000 media.controller.FeaturedImage = __webpack_require__( 1169 ); 11001 media.controller.ReplaceImage = __webpack_require__( 2275 ); 11002 media.controller.EditImage = __webpack_require__( 5663 ); 11003 media.controller.MediaLibrary = __webpack_require__( 8065 ); 11004 media.controller.Embed = __webpack_require__( 4910 ); 11005 media.controller.Cropper = __webpack_require__( 5422 ); 11006 media.controller.CustomizeImageCropper = __webpack_require__( 9660 ); 11007 media.controller.SiteIconCropper = __webpack_require__( 6172 ); 11008 11009 media.View = __webpack_require__( 4747 ); 11010 media.view.Frame = __webpack_require__( 1061 ); 11011 media.view.MediaFrame = __webpack_require__( 2836 ); 11012 media.view.MediaFrame.Select = __webpack_require__( 455 ); 11013 media.view.MediaFrame.Post = __webpack_require__( 4274 ); 11014 media.view.MediaFrame.ImageDetails = __webpack_require__( 5424 ); 11015 media.view.Modal = __webpack_require__( 2621 ); 11016 media.view.FocusManager = __webpack_require__( 718 ); 11017 media.view.UploaderWindow = __webpack_require__( 8291 ); 11018 media.view.EditorUploader = __webpack_require__( 3674 ); 11019 media.view.UploaderInline = __webpack_require__( 1753 ); 11020 media.view.UploaderStatus = __webpack_require__( 8197 ); 11021 media.view.UploaderStatusError = __webpack_require__( 6442 ); 11022 media.view.Toolbar = __webpack_require__( 5275 ); 11023 media.view.Toolbar.Select = __webpack_require__( 9458 ); 11024 media.view.Toolbar.Embed = __webpack_require__( 397 ); 11025 media.view.Button = __webpack_require__( 846 ); 11026 media.view.ButtonGroup = __webpack_require__( 168 ); 11027 media.view.PriorityList = __webpack_require__( 8815 ); 11028 media.view.MenuItem = __webpack_require__( 9013 ); 11029 media.view.Menu = __webpack_require__( 1 ); 11030 media.view.RouterItem = __webpack_require__( 6327 ); 11031 media.view.Router = __webpack_require__( 4783 ); 11032 media.view.Sidebar = __webpack_require__( 1992 ); 11033 media.view.Attachment = __webpack_require__( 4075 ); 11034 media.view.Attachment.Library = __webpack_require__( 3443 ); 11035 media.view.Attachment.EditLibrary = __webpack_require__( 5232 ); 11036 media.view.Attachments = __webpack_require__( 8142 ); 11037 media.view.Search = __webpack_require__( 2102 ); 11038 media.view.AttachmentFilters = __webpack_require__( 7709 ); 11039 media.view.DateFilter = __webpack_require__( 6472 ); 11040 media.view.AttachmentFilters.Uploaded = __webpack_require__( 1368 ); 11041 media.view.AttachmentFilters.All = __webpack_require__( 7349 ); 11042 media.view.AttachmentsBrowser = __webpack_require__( 6829 ); 11043 media.view.Selection = __webpack_require__( 8282 ); 11044 media.view.Attachment.Selection = __webpack_require__( 3962 ); 11045 media.view.Attachments.Selection = __webpack_require__( 3479 ); 11046 media.view.Attachment.EditSelection = __webpack_require__( 4593 ); 11047 media.view.Settings = __webpack_require__( 1915 ); 11048 media.view.Settings.AttachmentDisplay = __webpack_require__( 7656 ); 11049 media.view.Settings.Gallery = __webpack_require__( 7266 ); 11050 media.view.Settings.Playlist = __webpack_require__( 2356 ); 11051 media.view.Attachment.Details = __webpack_require__( 6090 ); 11052 media.view.AttachmentCompat = __webpack_require__( 2982 ); 11053 media.view.Iframe = __webpack_require__( 1982 ); 11054 media.view.Embed = __webpack_require__( 5741 ); 11055 media.view.Label = __webpack_require__( 4338 ); 11056 media.view.EmbedUrl = __webpack_require__( 7327 ); 11057 media.view.EmbedLink = __webpack_require__( 8232 ); 11058 media.view.EmbedImage = __webpack_require__( 2395 ); 11059 media.view.ImageDetails = __webpack_require__( 2650 ); 11060 media.view.Cropper = __webpack_require__( 7637 ); 11061 media.view.SiteIconCropper = __webpack_require__( 443 ); 11062 media.view.SiteIconPreview = __webpack_require__( 7810 ); 11063 media.view.EditImage = __webpack_require__( 6126 ); 11064 media.view.Spinner = __webpack_require__( 9141 ); 11065 media.view.Heading = __webpack_require__( 170 ); 11066 11067 /******/ })() 11068 ;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Oct 15 08:20:32 2026 | Cross-referenced by PHPXref |