PageRenderTime 89ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/HotelSpaWP/wp-includes/js/media-views.js

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