[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/ -> media-views.js (source)

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


Generated : Fri Sep 4 08:20:24 2026 Cross-referenced by PHPXref