media-grid.js 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4. /******/
  5. /******/ // The require function
  6. /******/ function __webpack_require__(moduleId) {
  7. /******/
  8. /******/ // Check if module is in cache
  9. /******/ if(installedModules[moduleId]) {
  10. /******/ return installedModules[moduleId].exports;
  11. /******/ }
  12. /******/ // Create a new module (and put it into the cache)
  13. /******/ var module = installedModules[moduleId] = {
  14. /******/ i: moduleId,
  15. /******/ l: false,
  16. /******/ exports: {}
  17. /******/ };
  18. /******/
  19. /******/ // Execute the module function
  20. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  21. /******/
  22. /******/ // Flag the module as loaded
  23. /******/ module.l = true;
  24. /******/
  25. /******/ // Return the exports of the module
  26. /******/ return module.exports;
  27. /******/ }
  28. /******/
  29. /******/
  30. /******/ // expose the modules object (__webpack_modules__)
  31. /******/ __webpack_require__.m = modules;
  32. /******/
  33. /******/ // expose the module cache
  34. /******/ __webpack_require__.c = installedModules;
  35. /******/
  36. /******/ // define getter function for harmony exports
  37. /******/ __webpack_require__.d = function(exports, name, getter) {
  38. /******/ if(!__webpack_require__.o(exports, name)) {
  39. /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
  40. /******/ }
  41. /******/ };
  42. /******/
  43. /******/ // define __esModule on exports
  44. /******/ __webpack_require__.r = function(exports) {
  45. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  46. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  47. /******/ }
  48. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  49. /******/ };
  50. /******/
  51. /******/ // create a fake namespace object
  52. /******/ // mode & 1: value is a module id, require it
  53. /******/ // mode & 2: merge all properties of value into the ns
  54. /******/ // mode & 4: return value when already ns object
  55. /******/ // mode & 8|1: behave like require
  56. /******/ __webpack_require__.t = function(value, mode) {
  57. /******/ if(mode & 1) value = __webpack_require__(value);
  58. /******/ if(mode & 8) return value;
  59. /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
  60. /******/ var ns = Object.create(null);
  61. /******/ __webpack_require__.r(ns);
  62. /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
  63. /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
  64. /******/ return ns;
  65. /******/ };
  66. /******/
  67. /******/ // getDefaultExport function for compatibility with non-harmony modules
  68. /******/ __webpack_require__.n = function(module) {
  69. /******/ var getter = module && module.__esModule ?
  70. /******/ function getDefault() { return module['default']; } :
  71. /******/ function getModuleExports() { return module; };
  72. /******/ __webpack_require__.d(getter, 'a', getter);
  73. /******/ return getter;
  74. /******/ };
  75. /******/
  76. /******/ // Object.prototype.hasOwnProperty.call
  77. /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  78. /******/
  79. /******/ // __webpack_public_path__
  80. /******/ __webpack_require__.p = "";
  81. /******/
  82. /******/
  83. /******/ // Load entry module and return exports
  84. /******/ return __webpack_require__(__webpack_require__.s = 11);
  85. /******/ })
  86. /************************************************************************/
  87. /******/ ([
  88. /* 0 */,
  89. /* 1 */,
  90. /* 2 */,
  91. /* 3 */,
  92. /* 4 */,
  93. /* 5 */,
  94. /* 6 */,
  95. /* 7 */,
  96. /* 8 */,
  97. /* 9 */,
  98. /* 10 */,
  99. /* 11 */
  100. /***/ (function(module, exports, __webpack_require__) {
  101. module.exports = __webpack_require__(12);
  102. /***/ }),
  103. /* 12 */
  104. /***/ (function(module, exports, __webpack_require__) {
  105. /**
  106. * @output wp-includes/js/media-grid.js
  107. */
  108. var media = wp.media;
  109. media.controller.EditAttachmentMetadata = __webpack_require__( 13 );
  110. media.view.MediaFrame.Manage = __webpack_require__( 14 );
  111. media.view.Attachment.Details.TwoColumn = __webpack_require__( 15 );
  112. media.view.MediaFrame.Manage.Router = __webpack_require__( 16 );
  113. media.view.EditImage.Details = __webpack_require__( 17 );
  114. media.view.MediaFrame.EditAttachments = __webpack_require__( 18 );
  115. media.view.SelectModeToggleButton = __webpack_require__( 19 );
  116. media.view.DeleteSelectedButton = __webpack_require__( 20 );
  117. media.view.DeleteSelectedPermanentlyButton = __webpack_require__( 21 );
  118. /***/ }),
  119. /* 13 */
  120. /***/ (function(module, exports) {
  121. var l10n = wp.media.view.l10n,
  122. EditAttachmentMetadata;
  123. /**
  124. * wp.media.controller.EditAttachmentMetadata
  125. *
  126. * A state for editing an attachment's metadata.
  127. *
  128. * @memberOf wp.media.controller
  129. *
  130. * @class
  131. * @augments wp.media.controller.State
  132. * @augments Backbone.Model
  133. */
  134. EditAttachmentMetadata = wp.media.controller.State.extend(/** @lends wp.media.controller.EditAttachmentMetadata.prototype */{
  135. defaults: {
  136. id: 'edit-attachment',
  137. // Title string passed to the frame's title region view.
  138. title: l10n.attachmentDetails,
  139. // Region mode defaults.
  140. content: 'edit-metadata',
  141. menu: false,
  142. toolbar: false,
  143. router: false
  144. }
  145. });
  146. module.exports = EditAttachmentMetadata;
  147. /***/ }),
  148. /* 14 */
  149. /***/ (function(module, exports) {
  150. var MediaFrame = wp.media.view.MediaFrame,
  151. Library = wp.media.controller.Library,
  152. $ = Backbone.$,
  153. Manage;
  154. /**
  155. * wp.media.view.MediaFrame.Manage
  156. *
  157. * A generic management frame workflow.
  158. *
  159. * Used in the media grid view.
  160. *
  161. * @memberOf wp.media.view.MediaFrame
  162. *
  163. * @class
  164. * @augments wp.media.view.MediaFrame
  165. * @augments wp.media.view.Frame
  166. * @augments wp.media.View
  167. * @augments wp.Backbone.View
  168. * @augments Backbone.View
  169. * @mixes wp.media.controller.StateMachine
  170. */
  171. Manage = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Manage.prototype */{
  172. /**
  173. * @constructs
  174. */
  175. initialize: function() {
  176. _.defaults( this.options, {
  177. title: '',
  178. modal: false,
  179. selection: [],
  180. library: {}, // Options hash for the query to the media library.
  181. multiple: 'add',
  182. state: 'library',
  183. uploader: true,
  184. mode: [ 'grid', 'edit' ]
  185. });
  186. this.$body = $( document.body );
  187. this.$window = $( window );
  188. this.$adminBar = $( '#wpadminbar' );
  189. // Store the Add New button for later reuse in wp.media.view.UploaderInline.
  190. this.$uploaderToggler = $( '.page-title-action' )
  191. .attr( 'aria-expanded', 'false' )
  192. .on( 'click', _.bind( this.addNewClickHandler, this ) );
  193. this.$window.on( 'scroll resize', _.debounce( _.bind( this.fixPosition, this ), 15 ) );
  194. // Ensure core and media grid view UI is enabled.
  195. this.$el.addClass('wp-core-ui');
  196. // Force the uploader off if the upload limit has been exceeded or
  197. // if the browser isn't supported.
  198. if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
  199. this.options.uploader = false;
  200. }
  201. // Initialize a window-wide uploader.
  202. if ( this.options.uploader ) {
  203. this.uploader = new wp.media.view.UploaderWindow({
  204. controller: this,
  205. uploader: {
  206. dropzone: document.body,
  207. container: document.body
  208. }
  209. }).render();
  210. this.uploader.ready();
  211. $('body').append( this.uploader.el );
  212. this.options.uploader = false;
  213. }
  214. this.gridRouter = new wp.media.view.MediaFrame.Manage.Router();
  215. // Call 'initialize' directly on the parent class.
  216. MediaFrame.prototype.initialize.apply( this, arguments );
  217. // Append the frame view directly the supplied container.
  218. this.$el.appendTo( this.options.container );
  219. this.createStates();
  220. this.bindRegionModeHandlers();
  221. this.render();
  222. this.bindSearchHandler();
  223. wp.media.frames.browse = this;
  224. },
  225. bindSearchHandler: function() {
  226. var search = this.$( '#media-search-input' ),
  227. searchView = this.browserView.toolbar.get( 'search' ).$el,
  228. listMode = this.$( '.view-list' ),
  229. input = _.throttle( function (e) {
  230. var val = $( e.currentTarget ).val(),
  231. url = '';
  232. if ( val ) {
  233. url += '?search=' + val;
  234. this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
  235. }
  236. }, 1000 );
  237. // Update the URL when entering search string (at most once per second)
  238. search.on( 'input', _.bind( input, this ) );
  239. this.gridRouter
  240. .on( 'route:search', function () {
  241. var href = window.location.href;
  242. if ( href.indexOf( 'mode=' ) > -1 ) {
  243. href = href.replace( /mode=[^&]+/g, 'mode=list' );
  244. } else {
  245. href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
  246. }
  247. href = href.replace( 'search=', 's=' );
  248. listMode.prop( 'href', href );
  249. })
  250. .on( 'route:reset', function() {
  251. searchView.val( '' ).trigger( 'input' );
  252. });
  253. },
  254. /**
  255. * Create the default states for the frame.
  256. */
  257. createStates: function() {
  258. var options = this.options;
  259. if ( this.options.states ) {
  260. return;
  261. }
  262. // Add the default states.
  263. this.states.add([
  264. new Library({
  265. library: wp.media.query( options.library ),
  266. multiple: options.multiple,
  267. title: options.title,
  268. content: 'browse',
  269. toolbar: 'select',
  270. contentUserSetting: false,
  271. filterable: 'all',
  272. autoSelect: false
  273. })
  274. ]);
  275. },
  276. /**
  277. * Bind region mode activation events to proper handlers.
  278. */
  279. bindRegionModeHandlers: function() {
  280. this.on( 'content:create:browse', this.browseContent, this );
  281. // Handle a frame-level event for editing an attachment.
  282. this.on( 'edit:attachment', this.openEditAttachmentModal, this );
  283. this.on( 'select:activate', this.bindKeydown, this );
  284. this.on( 'select:deactivate', this.unbindKeydown, this );
  285. },
  286. handleKeydown: function( e ) {
  287. if ( 27 === e.which ) {
  288. e.preventDefault();
  289. this.deactivateMode( 'select' ).activateMode( 'edit' );
  290. }
  291. },
  292. bindKeydown: function() {
  293. this.$body.on( 'keydown.select', _.bind( this.handleKeydown, this ) );
  294. },
  295. unbindKeydown: function() {
  296. this.$body.off( 'keydown.select' );
  297. },
  298. fixPosition: function() {
  299. var $browser, $toolbar;
  300. if ( ! this.isModeActive( 'select' ) ) {
  301. return;
  302. }
  303. $browser = this.$('.attachments-browser');
  304. $toolbar = $browser.find('.media-toolbar');
  305. // Offset doesn't appear to take top margin into account, hence +16
  306. if ( ( $browser.offset().top + 16 ) < this.$window.scrollTop() + this.$adminBar.height() ) {
  307. $browser.addClass( 'fixed' );
  308. $toolbar.css('width', $browser.width() + 'px');
  309. } else {
  310. $browser.removeClass( 'fixed' );
  311. $toolbar.css('width', '');
  312. }
  313. },
  314. /**
  315. * Click handler for the `Add New` button.
  316. */
  317. addNewClickHandler: function( event ) {
  318. event.preventDefault();
  319. this.trigger( 'toggle:upload:attachment' );
  320. if ( this.uploader ) {
  321. this.uploader.refresh();
  322. }
  323. },
  324. /**
  325. * Open the Edit Attachment modal.
  326. */
  327. openEditAttachmentModal: function( model ) {
  328. // Create a new EditAttachment frame, passing along the library and the attachment model.
  329. if ( wp.media.frames.edit ) {
  330. wp.media.frames.edit.open().trigger( 'refresh', model );
  331. } else {
  332. wp.media.frames.edit = wp.media( {
  333. frame: 'edit-attachments',
  334. controller: this,
  335. library: this.state().get('library'),
  336. model: model
  337. } );
  338. }
  339. },
  340. /**
  341. * Create an attachments browser view within the content region.
  342. *
  343. * @param {Object} contentRegion Basic object with a `view` property, which
  344. * should be set with the proper region view.
  345. * @this wp.media.controller.Region
  346. */
  347. browseContent: function( contentRegion ) {
  348. var state = this.state();
  349. // Browse our library of attachments.
  350. this.browserView = contentRegion.view = new wp.media.view.AttachmentsBrowser({
  351. controller: this,
  352. collection: state.get('library'),
  353. selection: state.get('selection'),
  354. model: state,
  355. sortable: state.get('sortable'),
  356. search: state.get('searchable'),
  357. filters: state.get('filterable'),
  358. date: state.get('date'),
  359. display: state.get('displaySettings'),
  360. dragInfo: state.get('dragInfo'),
  361. sidebar: 'errors',
  362. suggestedWidth: state.get('suggestedWidth'),
  363. suggestedHeight: state.get('suggestedHeight'),
  364. AttachmentView: state.get('AttachmentView'),
  365. scrollElement: document
  366. });
  367. this.browserView.on( 'ready', _.bind( this.bindDeferred, this ) );
  368. this.errors = wp.Uploader.errors;
  369. this.errors.on( 'add remove reset', this.sidebarVisibility, this );
  370. },
  371. sidebarVisibility: function() {
  372. this.browserView.$( '.media-sidebar' ).toggle( !! this.errors.length );
  373. },
  374. bindDeferred: function() {
  375. if ( ! this.browserView.dfd ) {
  376. return;
  377. }
  378. this.browserView.dfd.done( _.bind( this.startHistory, this ) );
  379. },
  380. startHistory: function() {
  381. // Verify pushState support and activate
  382. if ( window.history && window.history.pushState ) {
  383. if ( Backbone.History.started ) {
  384. Backbone.history.stop();
  385. }
  386. Backbone.history.start( {
  387. root: window._wpMediaGridSettings.adminUrl,
  388. pushState: true
  389. } );
  390. }
  391. }
  392. });
  393. module.exports = Manage;
  394. /***/ }),
  395. /* 15 */
  396. /***/ (function(module, exports) {
  397. var Details = wp.media.view.Attachment.Details,
  398. TwoColumn;
  399. /**
  400. * wp.media.view.Attachment.Details.TwoColumn
  401. *
  402. * A similar view to media.view.Attachment.Details
  403. * for use in the Edit Attachment modal.
  404. *
  405. * @memberOf wp.media.view.Attachment.Details
  406. *
  407. * @class
  408. * @augments wp.media.view.Attachment.Details
  409. * @augments wp.media.view.Attachment
  410. * @augments wp.media.View
  411. * @augments wp.Backbone.View
  412. * @augments Backbone.View
  413. */
  414. TwoColumn = Details.extend(/** @lends wp.media.view.Attachment.Details.TowColumn.prototype */{
  415. template: wp.template( 'attachment-details-two-column' ),
  416. initialize: function() {
  417. this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
  418. Details.prototype.initialize.apply( this, arguments );
  419. },
  420. editAttachment: function( event ) {
  421. if ( event ) {
  422. event.preventDefault();
  423. }
  424. this.controller.content.mode( 'edit-image' );
  425. },
  426. /**
  427. * Noop this from parent class, doesn't apply here.
  428. */
  429. toggleSelectionHandler: function() {},
  430. render: function() {
  431. Details.prototype.render.apply( this, arguments );
  432. wp.media.mixin.removeAllPlayers();
  433. this.$( 'audio, video' ).each( function (i, elem) {
  434. var el = wp.media.view.MediaDetails.prepareSrc( elem );
  435. new window.MediaElementPlayer( el, wp.media.mixin.mejsSettings );
  436. } );
  437. }
  438. });
  439. module.exports = TwoColumn;
  440. /***/ }),
  441. /* 16 */
  442. /***/ (function(module, exports) {
  443. /**
  444. * wp.media.view.MediaFrame.Manage.Router
  445. *
  446. * A router for handling the browser history and application state.
  447. *
  448. * @memberOf wp.media.view.MediaFrame.Manage
  449. *
  450. * @class
  451. * @augments Backbone.Router
  452. */
  453. var Router = Backbone.Router.extend(/** @lends wp.media.view.MediaFrame.Manage.Router.prototype */{
  454. routes: {
  455. 'upload.php?item=:slug&mode=edit': 'editItem',
  456. 'upload.php?item=:slug': 'showItem',
  457. 'upload.php?search=:query': 'search',
  458. 'upload.php': 'reset'
  459. },
  460. // Map routes against the page URL
  461. baseUrl: function( url ) {
  462. return 'upload.php' + url;
  463. },
  464. reset: function() {
  465. var frame = wp.media.frames.edit;
  466. if ( frame ) {
  467. frame.close();
  468. }
  469. },
  470. // Respond to the search route by filling the search field and trigggering the input event
  471. search: function( query ) {
  472. jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
  473. },
  474. // Show the modal with a specific item
  475. showItem: function( query ) {
  476. var media = wp.media,
  477. frame = media.frames.browse,
  478. library = frame.state().get('library'),
  479. item;
  480. // Trigger the media frame to open the correct item
  481. item = library.findWhere( { id: parseInt( query, 10 ) } );
  482. item.set( 'skipHistory', true );
  483. if ( item ) {
  484. frame.trigger( 'edit:attachment', item );
  485. } else {
  486. item = media.attachment( query );
  487. frame.listenTo( item, 'change', function( model ) {
  488. frame.stopListening( item );
  489. frame.trigger( 'edit:attachment', model );
  490. } );
  491. item.fetch();
  492. }
  493. },
  494. // Show the modal in edit mode with a specific item.
  495. editItem: function( query ) {
  496. this.showItem( query );
  497. wp.media.frames.edit.content.mode( 'edit-details' );
  498. }
  499. });
  500. module.exports = Router;
  501. /***/ }),
  502. /* 17 */
  503. /***/ (function(module, exports) {
  504. var View = wp.media.View,
  505. EditImage = wp.media.view.EditImage,
  506. Details;
  507. /**
  508. * wp.media.view.EditImage.Details
  509. *
  510. * @memberOf wp.media.view.EditImage
  511. *
  512. * @class
  513. * @augments wp.media.view.EditImage
  514. * @augments wp.media.View
  515. * @augments wp.Backbone.View
  516. * @augments Backbone.View
  517. */
  518. Details = EditImage.extend(/** @lends wp.media.view.EditImage.Details.prototype */{
  519. initialize: function( options ) {
  520. this.editor = window.imageEdit;
  521. this.frame = options.frame;
  522. this.controller = options.controller;
  523. View.prototype.initialize.apply( this, arguments );
  524. },
  525. back: function() {
  526. this.frame.content.mode( 'edit-metadata' );
  527. },
  528. save: function() {
  529. this.model.fetch().done( _.bind( function() {
  530. this.frame.content.mode( 'edit-metadata' );
  531. }, this ) );
  532. }
  533. });
  534. module.exports = Details;
  535. /***/ }),
  536. /* 18 */
  537. /***/ (function(module, exports) {
  538. var Frame = wp.media.view.Frame,
  539. MediaFrame = wp.media.view.MediaFrame,
  540. $ = jQuery,
  541. EditAttachments;
  542. /**
  543. * wp.media.view.MediaFrame.EditAttachments
  544. *
  545. * A frame for editing the details of a specific media item.
  546. *
  547. * Opens in a modal by default.
  548. *
  549. * Requires an attachment model to be passed in the options hash under `model`.
  550. *
  551. * @memberOf wp.media.view.MediaFrame
  552. *
  553. * @class
  554. * @augments wp.media.view.Frame
  555. * @augments wp.media.View
  556. * @augments wp.Backbone.View
  557. * @augments Backbone.View
  558. * @mixes wp.media.controller.StateMachine
  559. */
  560. EditAttachments = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.EditAttachments.prototype */{
  561. className: 'edit-attachment-frame',
  562. template: wp.template( 'edit-attachment-frame' ),
  563. regions: [ 'title', 'content' ],
  564. events: {
  565. 'click .left': 'previousMediaItem',
  566. 'click .right': 'nextMediaItem'
  567. },
  568. initialize: function() {
  569. Frame.prototype.initialize.apply( this, arguments );
  570. _.defaults( this.options, {
  571. modal: true,
  572. state: 'edit-attachment'
  573. });
  574. this.controller = this.options.controller;
  575. this.gridRouter = this.controller.gridRouter;
  576. this.library = this.options.library;
  577. if ( this.options.model ) {
  578. this.model = this.options.model;
  579. }
  580. this.bindHandlers();
  581. this.createStates();
  582. this.createModal();
  583. this.title.mode( 'default' );
  584. this.toggleNav();
  585. },
  586. bindHandlers: function() {
  587. // Bind default title creation.
  588. this.on( 'title:create:default', this.createTitle, this );
  589. this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
  590. this.on( 'content:create:edit-image', this.editImageMode, this );
  591. this.on( 'content:render:edit-image', this.editImageModeRender, this );
  592. this.on( 'refresh', this.rerender, this );
  593. this.on( 'close', this.detach );
  594. this.bindModelHandlers();
  595. this.listenTo( this.gridRouter, 'route:search', this.close, this );
  596. },
  597. bindModelHandlers: function() {
  598. // Close the modal if the attachment is deleted.
  599. this.listenTo( this.model, 'change:status destroy', this.close, this );
  600. },
  601. createModal: function() {
  602. // Initialize modal container view.
  603. if ( this.options.modal ) {
  604. this.modal = new wp.media.view.Modal({
  605. controller: this,
  606. title: this.options.title,
  607. hasCloseButton: false
  608. });
  609. this.modal.on( 'open', _.bind( function () {
  610. $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) );
  611. }, this ) );
  612. // Completely destroy the modal DOM element when closing it.
  613. this.modal.on( 'close', _.bind( function() {
  614. // Remove the keydown event.
  615. $( 'body' ).off( 'keydown.media-modal' );
  616. // Move focus back to the original item in the grid if possible.
  617. $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
  618. this.resetRoute();
  619. }, this ) );
  620. // Set this frame as the modal's content.
  621. this.modal.content( this );
  622. this.modal.open();
  623. }
  624. },
  625. /**
  626. * Add the default states to the frame.
  627. */
  628. createStates: function() {
  629. this.states.add([
  630. new wp.media.controller.EditAttachmentMetadata({
  631. model: this.model,
  632. library: this.library
  633. })
  634. ]);
  635. },
  636. /**
  637. * Content region rendering callback for the `edit-metadata` mode.
  638. *
  639. * @param {Object} contentRegion Basic object with a `view` property, which
  640. * should be set with the proper region view.
  641. */
  642. editMetadataMode: function( contentRegion ) {
  643. contentRegion.view = new wp.media.view.Attachment.Details.TwoColumn({
  644. controller: this,
  645. model: this.model
  646. });
  647. /**
  648. * Attach a subview to display fields added via the
  649. * `attachment_fields_to_edit` filter.
  650. */
  651. contentRegion.view.views.set( '.attachment-compat', new wp.media.view.AttachmentCompat({
  652. controller: this,
  653. model: this.model
  654. }) );
  655. // Update browser url when navigating media details, except on load.
  656. if ( this.model && ! this.model.get( 'skipHistory' ) ) {
  657. this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
  658. }
  659. },
  660. /**
  661. * Render the EditImage view into the frame's content region.
  662. *
  663. * @param {Object} contentRegion Basic object with a `view` property, which
  664. * should be set with the proper region view.
  665. */
  666. editImageMode: function( contentRegion ) {
  667. var editImageController = new wp.media.controller.EditImage( {
  668. model: this.model,
  669. frame: this
  670. } );
  671. // Noop some methods.
  672. editImageController._toolbar = function() {};
  673. editImageController._router = function() {};
  674. editImageController._menu = function() {};
  675. contentRegion.view = new wp.media.view.EditImage.Details( {
  676. model: this.model,
  677. frame: this,
  678. controller: editImageController
  679. } );
  680. this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) );
  681. },
  682. editImageModeRender: function( view ) {
  683. view.on( 'ready', view.loadEditor );
  684. },
  685. toggleNav: function() {
  686. this.$( '.left' ).prop( 'disabled', ! this.hasPrevious() );
  687. this.$( '.right' ).prop( 'disabled', ! this.hasNext() );
  688. },
  689. /**
  690. * Rerender the view.
  691. */
  692. rerender: function( model ) {
  693. this.stopListening( this.model );
  694. this.model = model;
  695. this.bindModelHandlers();
  696. // Only rerender the `content` region.
  697. if ( this.content.mode() !== 'edit-metadata' ) {
  698. this.content.mode( 'edit-metadata' );
  699. } else {
  700. this.content.render();
  701. }
  702. this.toggleNav();
  703. },
  704. /**
  705. * Click handler to switch to the previous media item.
  706. */
  707. previousMediaItem: function() {
  708. if ( ! this.hasPrevious() ) {
  709. return;
  710. }
  711. this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) );
  712. // Move focus to the Previous button. When there are no more items, to the Next button.
  713. this.focusNavButton( this.hasPrevious() ? '.left' : '.right' );
  714. },
  715. /**
  716. * Click handler to switch to the next media item.
  717. */
  718. nextMediaItem: function() {
  719. if ( ! this.hasNext() ) {
  720. return;
  721. }
  722. this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) );
  723. // Move focus to the Next button. When there are no more items, to the Previous button.
  724. this.focusNavButton( this.hasNext() ? '.right' : '.left' );
  725. },
  726. /**
  727. * Set focus to the navigation buttons depending on the browsing direction.
  728. *
  729. * @since 5.3.0
  730. *
  731. * @param {string} which A CSS selector to target the button to focus.
  732. */
  733. focusNavButton: function( which ) {
  734. $( which ).focus();
  735. },
  736. getCurrentIndex: function() {
  737. return this.library.indexOf( this.model );
  738. },
  739. hasNext: function() {
  740. return ( this.getCurrentIndex() + 1 ) < this.library.length;
  741. },
  742. hasPrevious: function() {
  743. return ( this.getCurrentIndex() - 1 ) > -1;
  744. },
  745. /**
  746. * Respond to the keyboard events: right arrow, left arrow, except when
  747. * focus is in a textarea or input field.
  748. */
  749. keyEvent: function( event ) {
  750. if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) {
  751. return;
  752. }
  753. // The right arrow key
  754. if ( 39 === event.keyCode ) {
  755. this.nextMediaItem();
  756. }
  757. // The left arrow key
  758. if ( 37 === event.keyCode ) {
  759. this.previousMediaItem();
  760. }
  761. },
  762. resetRoute: function() {
  763. var searchTerm = this.controller.browserView.toolbar.get( 'search' ).$el.val(),
  764. url = '' !== searchTerm ? '?search=' + searchTerm : '';
  765. this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
  766. }
  767. });
  768. module.exports = EditAttachments;
  769. /***/ }),
  770. /* 19 */
  771. /***/ (function(module, exports) {
  772. var Button = wp.media.view.Button,
  773. l10n = wp.media.view.l10n,
  774. SelectModeToggle;
  775. /**
  776. * wp.media.view.SelectModeToggleButton
  777. *
  778. * @memberOf wp.media.view
  779. *
  780. * @class
  781. * @augments wp.media.view.Button
  782. * @augments wp.media.View
  783. * @augments wp.Backbone.View
  784. * @augments Backbone.View
  785. */
  786. SelectModeToggle = Button.extend(/** @lends wp.media.view.SelectModeToggle.prototype */{
  787. initialize: function() {
  788. _.defaults( this.options, {
  789. size : ''
  790. } );
  791. Button.prototype.initialize.apply( this, arguments );
  792. this.controller.on( 'select:activate select:deactivate', this.toggleBulkEditHandler, this );
  793. this.controller.on( 'selection:action:done', this.back, this );
  794. },
  795. back: function () {
  796. this.controller.deactivateMode( 'select' ).activateMode( 'edit' );
  797. },
  798. click: function() {
  799. Button.prototype.click.apply( this, arguments );
  800. if ( this.controller.isModeActive( 'select' ) ) {
  801. this.back();
  802. } else {
  803. this.controller.deactivateMode( 'edit' ).activateMode( 'select' );
  804. }
  805. },
  806. render: function() {
  807. Button.prototype.render.apply( this, arguments );
  808. this.$el.addClass( 'select-mode-toggle-button' );
  809. return this;
  810. },
  811. toggleBulkEditHandler: function() {
  812. var toolbar = this.controller.content.get().toolbar, children;
  813. children = toolbar.$( '.media-toolbar-secondary > *, .media-toolbar-primary > *' );
  814. // TODO: the Frame should be doing all of this.
  815. if ( this.controller.isModeActive( 'select' ) ) {
  816. this.model.set( {
  817. size: 'large',
  818. text: l10n.cancel
  819. } );
  820. children.not( '.spinner, .media-button' ).hide();
  821. this.$el.show();
  822. toolbar.$el.addClass( 'media-toolbar-mode-select' );
  823. toolbar.$( '.delete-selected-button' ).removeClass( 'hidden' );
  824. } else {
  825. this.model.set( {
  826. size: '',
  827. text: l10n.bulkSelect
  828. } );
  829. this.controller.content.get().$el.removeClass( 'fixed' );
  830. toolbar.$el.css( 'width', '' );
  831. toolbar.$el.removeClass( 'media-toolbar-mode-select' );
  832. toolbar.$( '.delete-selected-button' ).addClass( 'hidden' );
  833. children.not( '.media-button' ).show();
  834. this.controller.state().get( 'selection' ).reset();
  835. }
  836. }
  837. });
  838. module.exports = SelectModeToggle;
  839. /***/ }),
  840. /* 20 */
  841. /***/ (function(module, exports) {
  842. var Button = wp.media.view.Button,
  843. l10n = wp.media.view.l10n,
  844. DeleteSelected;
  845. /**
  846. * wp.media.view.DeleteSelectedButton
  847. *
  848. * A button that handles bulk Delete/Trash logic
  849. *
  850. * @memberOf wp.media.view
  851. *
  852. * @class
  853. * @augments wp.media.view.Button
  854. * @augments wp.media.View
  855. * @augments wp.Backbone.View
  856. * @augments Backbone.View
  857. */
  858. DeleteSelected = Button.extend(/** @lends wp.media.view.DeleteSelectedButton.prototype */{
  859. initialize: function() {
  860. Button.prototype.initialize.apply( this, arguments );
  861. if ( this.options.filters ) {
  862. this.options.filters.model.on( 'change', this.filterChange, this );
  863. }
  864. this.controller.on( 'selection:toggle', this.toggleDisabled, this );
  865. this.controller.on( 'select:activate', this.toggleDisabled, this );
  866. },
  867. filterChange: function( model ) {
  868. if ( 'trash' === model.get( 'status' ) ) {
  869. this.model.set( 'text', l10n.restoreSelected );
  870. } else if ( wp.media.view.settings.mediaTrash ) {
  871. this.model.set( 'text', l10n.trashSelected );
  872. } else {
  873. this.model.set( 'text', l10n.deletePermanently );
  874. }
  875. },
  876. toggleDisabled: function() {
  877. this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
  878. },
  879. render: function() {
  880. Button.prototype.render.apply( this, arguments );
  881. if ( this.controller.isModeActive( 'select' ) ) {
  882. this.$el.addClass( 'delete-selected-button' );
  883. } else {
  884. this.$el.addClass( 'delete-selected-button hidden' );
  885. }
  886. this.toggleDisabled();
  887. return this;
  888. }
  889. });
  890. module.exports = DeleteSelected;
  891. /***/ }),
  892. /* 21 */
  893. /***/ (function(module, exports) {
  894. var Button = wp.media.view.Button,
  895. DeleteSelected = wp.media.view.DeleteSelectedButton,
  896. DeleteSelectedPermanently;
  897. /**
  898. * wp.media.view.DeleteSelectedPermanentlyButton
  899. *
  900. * When MEDIA_TRASH is true, a button that handles bulk Delete Permanently logic
  901. *
  902. * @memberOf wp.media.view
  903. *
  904. * @class
  905. * @augments wp.media.view.DeleteSelectedButton
  906. * @augments wp.media.view.Button
  907. * @augments wp.media.View
  908. * @augments wp.Backbone.View
  909. * @augments Backbone.View
  910. */
  911. DeleteSelectedPermanently = DeleteSelected.extend(/** @lends wp.media.view.DeleteSelectedPermanentlyButton.prototype */{
  912. initialize: function() {
  913. DeleteSelected.prototype.initialize.apply( this, arguments );
  914. this.controller.on( 'select:activate', this.selectActivate, this );
  915. this.controller.on( 'select:deactivate', this.selectDeactivate, this );
  916. },
  917. filterChange: function( model ) {
  918. this.canShow = ( 'trash' === model.get( 'status' ) );
  919. },
  920. selectActivate: function() {
  921. this.toggleDisabled();
  922. this.$el.toggleClass( 'hidden', ! this.canShow );
  923. },
  924. selectDeactivate: function() {
  925. this.toggleDisabled();
  926. this.$el.addClass( 'hidden' );
  927. },
  928. render: function() {
  929. Button.prototype.render.apply( this, arguments );
  930. this.selectActivate();
  931. return this;
  932. }
  933. });
  934. module.exports = DeleteSelectedPermanently;
  935. /***/ })
  936. /******/ ]);