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