media.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import $ from 'jquery';
  2. import request from '../../utils/request';
  3. import FilesField, { UriToMarkdown } from '../../forms/fields/files';
  4. import { config, translations } from 'grav-config';
  5. import { Instance as Editor } from '../../forms/fields/editor';
  6. import Sortable from 'sortablejs';
  7. const previewTemplate = `
  8. <div class="dz-preview dz-file-preview">
  9. <div class="dz-details">
  10. <div class="dz-filename"><span data-dz-name></span></div>
  11. <div class="dz-size" data-dz-size></div>
  12. <img data-dz-thumbnail />
  13. </div>
  14. <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
  15. <div class="dz-success-mark"><span>✔</span></div>
  16. <div class="dz-error-mark"><span>✘</span></div>
  17. <div class="dz-error-message"><span data-dz-errormessage></span></div>
  18. <a class="dz-remove" title="${translations.PLUGIN_ADMIN.DELETE}" href="javascript:undefined;" data-dz-remove>${translations.PLUGIN_ADMIN.DELETE}</a>
  19. <a class="dz-metadata" title="${translations.PLUGIN_ADMIN.METADATA}" href="#" target="_blank" data-dz-metadata>${translations.PLUGIN_ADMIN.METADATA}</a>
  20. <a class="dz-view" title="${translations.PLUGIN_ADMIN.VIEW}" href="#" target="_blank" data-dz-view>${translations.PLUGIN_ADMIN.VIEW}</a>
  21. <a class="dz-insert" title="${translations.PLUGIN_ADMIN.INSERT}" href="javascript:undefined;" data-dz-insert>${translations.PLUGIN_ADMIN.INSERT}</a>
  22. </div>`.trim();
  23. export default class PageMedia extends FilesField {
  24. constructor({ container = '#grav-dropzone', options = {} } = {}) {
  25. options = Object.assign(options, { previewTemplate });
  26. super({ container, options });
  27. if (!this.container.length) { return; }
  28. this.urls = {
  29. fetch: `${this.container.data('media-url')}/task${config.param_sep}listmedia`,
  30. add: `${this.container.data('media-url')}/task${config.param_sep}addmedia`,
  31. delete: `${this.container.data('media-url')}/task${config.param_sep}delmedia`
  32. };
  33. this.dropzone.options.url = this.urls.add;
  34. if (typeof this.options.fetchMedia === 'undefined' || this.options.fetchMedia) {
  35. this.fetchMedia();
  36. }
  37. if (typeof this.options.attachDragDrop === 'undefined' || this.options.attachDragDrop) {
  38. this.attachDragDrop();
  39. }
  40. const field = $(`[name="${this.container.data('dropzone-field')}"]`);
  41. if (field.length) {
  42. this.sortable = new Sortable(this.container.get(0), {
  43. animation: 150,
  44. // forceFallback: true,
  45. setData: (dataTransfer, target) => {
  46. target = $(target);
  47. let uri = encodeURI(target.find('.dz-filename').text());
  48. let shortcode = UriToMarkdown(uri);
  49. this.dropzone.disable();
  50. target.addClass('hide-backface');
  51. dataTransfer.effectAllowed = 'copy';
  52. dataTransfer.setData('text', shortcode);
  53. },
  54. onSort: () => {
  55. let names = [];
  56. this.container.find('[data-dz-name]').each((index, file) => {
  57. file = $(file);
  58. const name = file.text().trim();
  59. names.push(name);
  60. });
  61. field.val(names.join(','));
  62. }
  63. });
  64. }
  65. }
  66. fetchMedia() {
  67. const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
  68. const body = { uri: this.getURI(), order };
  69. let url = this.urls.fetch;
  70. request(url, { method: 'post', body }, (response) => {
  71. let results = response.results;
  72. Object.keys(results).forEach((name) => {
  73. let data = results[name];
  74. let mock = { name, size: data.size, accepted: true, extras: data };
  75. this.dropzone.files.push(mock);
  76. this.dropzone.options.addedfile.call(this.dropzone, mock);
  77. this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
  78. });
  79. this.container.find('.dz-preview').prop('draggable', 'true');
  80. });
  81. }
  82. onDropzoneSending(file, xhr, formData) {
  83. /*
  84. // Cannot call super because Safari and IE API don't implement `delete`
  85. super.onDropzoneSending(file, xhr, formData);
  86. formData.delete('task');
  87. */
  88. formData.append('name', this.options.dotNotation || file.name);
  89. formData.append('admin-nonce', config.admin_nonce);
  90. formData.append('uri', this.getURI());
  91. }
  92. onDropzoneComplete(file) {
  93. super.onDropzoneComplete(file);
  94. if (this.sortable) {
  95. this.sortable.options.onSort();
  96. }
  97. // accepted
  98. $('.dz-preview').prop('draggable', 'true');
  99. }
  100. onDropzoneRemovedFile(file, ...extra) {
  101. super.onDropzoneRemovedFile(file, ...extra);
  102. if (this.sortable) {
  103. this.sortable.options.onSort();
  104. }
  105. }
  106. attachDragDrop() {
  107. this.container.delegate('[data-dz-insert]', 'click', (e) => {
  108. let target = $(e.currentTarget).parent('.dz-preview').find('.dz-filename');
  109. let editor = Editor.editors.filter((index, editor) => $(editor).attr('name') === 'data[content]');
  110. if (editor.length) {
  111. editor = editor.data('codemirror');
  112. editor.focus();
  113. let filename = encodeURI(target.text());
  114. let shortcode = UriToMarkdown(filename);
  115. editor.doc.replaceSelection(shortcode);
  116. }
  117. });
  118. this.container.delegate('[data-dz-view]', 'mouseenter', (e) => {
  119. let target = $(e.currentTarget);
  120. let file = target.parent('.dz-preview').find('.dz-filename');
  121. let filename = encodeURI(file.text());
  122. let URL = target.closest('[data-media-path]').data('media-path');
  123. let original = this.dropzone.files.filter((file) => encodeURI(file.name) === filename).shift();
  124. original = original && ((original.extras && original.extras.original) || encodeURI(original.name));
  125. target.attr('href', `${URL}/${original}`);
  126. });
  127. this.container.delegate('[data-dz-metadata]', 'click', (e) => {
  128. e.preventDefault();
  129. const target = $(e.currentTarget);
  130. const file = target.parent('.dz-preview').find('.dz-filename');
  131. const filename = encodeURI(file.text());
  132. let fileObj = this.dropzone.files.filter((file) => file.name === global.decodeURI(filename)).shift() || {};
  133. if (!fileObj.extras) {
  134. fileObj.extras = { metadata: [] };
  135. }
  136. if (Array.isArray(fileObj.extras.metadata) && !fileObj.extras.metadata.length) {
  137. fileObj.extras.metadata = { '': `${global.decodeURI(filename)}.meta.yaml doesn't exist` };
  138. }
  139. fileObj = fileObj.extras;
  140. const modal_element = $('body').find('[data-remodal-id="metadata"]');
  141. const modal = $.remodal.lookup[modal_element.data('remodal')];
  142. modal_element.find('h1 strong').html(filename);
  143. if (fileObj.url) {
  144. modal_element.find('.meta-preview').html(`<img src="${fileObj.url}" />`);
  145. }
  146. const container = modal_element.find('.meta-content').html('<ul />').find('ul');
  147. Object.keys(fileObj.metadata).forEach((meta) => {
  148. container.append(`<li><strong>${meta ? meta + ':' : ''}</strong> ${fileObj.metadata[meta]}</li>`);
  149. });
  150. modal.open();
  151. });
  152. this.container.delegate('.dz-preview', 'dragstart', (e) => {
  153. let target = $(e.currentTarget);
  154. let uri = encodeURI(target.find('.dz-filename').text());
  155. let shortcode = UriToMarkdown(uri);
  156. this.dropzone.disable();
  157. target.addClass('hide-backface');
  158. e.originalEvent.dataTransfer.effectAllowed = 'copy';
  159. e.originalEvent.dataTransfer.setData('text', shortcode);
  160. });
  161. this.container.delegate('.dz-preview', 'dragend', (e) => {
  162. let target = $(e.currentTarget);
  163. this.dropzone.enable();
  164. target.removeClass('hide-backface');
  165. });
  166. }
  167. }
  168. export let Instance = new PageMedia();