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


Generated : Thu Sep 24 08:20:34 2026 Cross-referenced by PHPXref