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