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