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