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