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