main.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import $ from 'jquery';
  2. import Dropzone from 'dropzone';
  3. import { config, translations } from 'grav-form';
  4. // translations
  5. const Dictionary = {
  6. dictCancelUpload: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD,
  7. dictCancelUploadConfirmation: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION,
  8. dictDefaultMessage: translations.PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE,
  9. dictFallbackMessage: translations.PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE,
  10. dictFallbackText: translations.PLUGIN_FORM.DROPZONE_FALLBACK_TEXT,
  11. dictFileTooBig: translations.PLUGIN_FORM.DROPZONE_FILE_TOO_BIG,
  12. dictInvalidFileType: translations.PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE,
  13. dictMaxFilesExceeded: translations.PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED,
  14. dictRemoveFile: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE,
  15. dictRemoveFileConfirmation: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION,
  16. dictResponseError: translations.PLUGIN_FORM.DROPZONE_RESPONSE_ERROR
  17. };
  18. Dropzone.autoDiscover = false;
  19. const DropzoneMediaConfig = {
  20. createImageThumbnails: { thumbnailWidth: 150 },
  21. addRemoveLinks: false,
  22. dictDefaultMessage: Dictionary.dictDefaultMessage,
  23. dictRemoveFileConfirmation: Dictionary.dictRemoveFileConfirmation,
  24. previewTemplate: ''
  25. };
  26. export default class FilesField {
  27. constructor({ container = '.dropzone.files-upload', options = {} } = {}) {
  28. this.container = $(container);
  29. if (!this.container.length) { return; }
  30. this.urls = {};
  31. DropzoneMediaConfig.previewTemplate = $('#dropzone-template').html();
  32. this.options = Object.assign({}, Dictionary, DropzoneMediaConfig, {
  33. klass: this,
  34. url: this.container.data('file-url-add') || config.current_url,
  35. acceptedFiles: this.container.data('media-types'),
  36. init: this.initDropzone
  37. }, this.container.data('dropzone-options'), options);
  38. this.dropzone = new Dropzone(container, this.options);
  39. this.dropzone.on('complete', this.onDropzoneComplete.bind(this));
  40. this.dropzone.on('success', this.onDropzoneSuccess.bind(this));
  41. this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this));
  42. this.dropzone.on('sending', this.onDropzoneSending.bind(this));
  43. this.dropzone.on('error', this.onDropzoneError.bind(this));
  44. }
  45. initDropzone() {
  46. let files = this.options.klass.container.find('[data-file]');
  47. let dropzone = this;
  48. if (!files.length) { return; }
  49. files.each((index, file) => {
  50. file = $(file);
  51. let data = file.data('file');
  52. let mock = {
  53. name: data.name,
  54. size: data.size,
  55. type: data.type,
  56. status: Dropzone.ADDED,
  57. accepted: true,
  58. url: this.options.url,
  59. removeUrl: data.remove
  60. };
  61. dropzone.files.push(mock);
  62. dropzone.options.addedfile.call(dropzone, mock);
  63. if (mock.type.match(/^image\//)) dropzone.options.thumbnail.call(dropzone, mock, data.path);
  64. file.remove();
  65. });
  66. }
  67. getURI() {
  68. return this.container.data('mediaUri') || '';
  69. }
  70. onDropzoneSending(file, xhr, formData) {
  71. formData.append('__form-name__', this.container.closest('form').find('[name="__form-name__"]').val());
  72. formData.append('__form-file-uploader__', 1);
  73. formData.append('name', this.options.dotNotation);
  74. formData.append('form-nonce', config.form_nonce);
  75. formData.append('task', 'filesupload');
  76. formData.append('uri', this.getURI());
  77. }
  78. onDropzoneSuccess(file, response, xhr) {
  79. if (this.options.reloadPage) {
  80. global.location.reload();
  81. }
  82. // store params for removing file from session before it gets saved
  83. if (response.session) {
  84. file.sessionParams = response.session;
  85. file.removeUrl = this.options.url;
  86. // Touch field value to force a mutation detection
  87. const input = this.container.find('[name][type="hidden"]');
  88. const value = input.val();
  89. input.val(value + ' ');
  90. }
  91. return this.handleError({
  92. file,
  93. data: response,
  94. mode: 'removeFile',
  95. msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_UPLOAD} <strong>${file.name}</strong></p>
  96. <pre>${response.message}</pre>`
  97. });
  98. }
  99. onDropzoneComplete(file) {
  100. if (!file.accepted && !file.rejected) {
  101. let data = {
  102. status: 'error',
  103. message: `${translations.PLUGIN_FORM.FILE_UNSUPPORTED}: ${file.name.match(/\..+/).join('')}`
  104. };
  105. return this.handleError({
  106. file,
  107. data,
  108. mode: 'removeFile',
  109. msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_ADD} <strong>${file.name}</strong></p>
  110. <pre>${data.message}</pre>`
  111. });
  112. }
  113. if (this.options.reloadPage) {
  114. global.location.reload();
  115. }
  116. }
  117. onDropzoneRemovedFile(file, ...extra) {
  118. if (!file.accepted || file.rejected) { return; }
  119. let url = file.removeUrl || this.urls.delete || `${location.href}.json`;
  120. let path = (url || '').match(/path:(.*)\//);
  121. let data = new FormData();
  122. data.append('filename', file.name);
  123. data.append('__form-name__', this.container.closest('form').find('[name="__form-name__"]').val());
  124. data.append('name', this.options.dotNotation);
  125. data.append('form-nonce', config.form_nonce);
  126. data.append('uri', this.getURI());
  127. if (file.sessionParams) {
  128. data.append('__form-file-remover__', '1');
  129. data.append('session', file.sessionParams);
  130. }
  131. $.ajax({
  132. url,
  133. data,
  134. method: 'POST',
  135. contentType: false,
  136. processData: false,
  137. success: () => {
  138. if (!path) { return; }
  139. path = global.atob(path[1]);
  140. let input = this.container.find('[name][type="hidden"]');
  141. let data = JSON.parse(input.val() || '{}');
  142. delete data[path];
  143. input.val(JSON.stringify(data));
  144. }
  145. });
  146. }
  147. onDropzoneError(file, response, xhr) {
  148. let message = xhr && response.error ? response.error.message : response;
  149. $(file.previewElement).find('[data-dz-errormessage]').html(message);
  150. return this.handleError({
  151. file,
  152. data: { status: 'error' },
  153. msg: `<pre>${message}</pre>`
  154. });
  155. }
  156. handleError(options) {
  157. return true;
  158. /* let { file, data, mode, msg } = options;
  159. if (data.status !== 'error' && data.status !== 'unauthorized') { return; }
  160. switch (mode) {
  161. case 'addBack':
  162. if (file instanceof File) {
  163. this.dropzone.addFile.call(this.dropzone, file);
  164. } else {
  165. this.dropzone.files.push(file);
  166. this.dropzone.options.addedfile.call(this.dropzone, file);
  167. this.dropzone.options.thumbnail.call(this.dropzone, file, file.extras.url);
  168. }
  169. break;
  170. case 'removeFile':
  171. default:
  172. if (~this.dropzone.files.indexOf(file)) {
  173. file.rejected = true;
  174. this.dropzone.removeFile.call(this.dropzone, file, { silent: true });
  175. }
  176. break;
  177. }
  178. let modal = $('[data-remodal-id="generic"]');
  179. modal.find('.error-content').html(msg);
  180. $.remodal.lookup[modal.data('remodal')].open(); */
  181. }
  182. }
  183. export function UriToMarkdown(uri) {
  184. uri = uri.replace(/@3x|@2x|@1x/, '');
  185. uri = uri.replace(/\(/g, '%28');
  186. uri = uri.replace(/\)/g, '%29');
  187. return uri.match(/\.(jpe?g|png|gif|svg)$/i) ? `![](${uri})` : `[${decodeURI(uri)}](${uri})`;
  188. }
  189. let instances = [];
  190. let cache = $();
  191. const onAddedNodes = (event, target/* , record, instance */) => {
  192. let files = $(target).find('.dropzone.files-upload');
  193. if (!files.length) { return; }
  194. files.each((index, file) => {
  195. file = $(file);
  196. if (!~cache.index(file)) {
  197. addNode(file);
  198. }
  199. });
  200. };
  201. const addNode = (container) => {
  202. container = $(container);
  203. let input = container.find('input[type="file"]');
  204. let settings = container.data('grav-file-settings') || {};
  205. if (settings.accept && ~settings.accept.indexOf('*')) {
  206. settings.accept = [''];
  207. }
  208. let options = {
  209. url: container.data('file-url-add') || (container.closest('form').attr('action') || config.current_url) + '.json',
  210. paramName: settings.paramName || 'file',
  211. dotNotation: settings.name || 'file',
  212. acceptedFiles: settings.accept ? settings.accept.join(',') : input.attr('accept') || container.data('media-types'),
  213. maxFilesize: settings.filesize || 256,
  214. maxFiles: settings.limit || null,
  215. resizeWidth: settings.resizeWidth || null,
  216. resizeHeight: settings.resizeHeight || null,
  217. resizeQuality: settings.resizeQuality || null,
  218. accept: function(file, done) {
  219. const resolution = settings.resolution;
  220. if (!resolution) return done();
  221. setTimeout(() => {
  222. let error = '';
  223. if (resolution.min) {
  224. Object.keys(resolution.min).forEach((attr) => {
  225. if (file[attr] < resolution.min[attr]) {
  226. error += translations.PLUGIN_FORM.RESOLUTION_MIN.replace(/{{attr}}/g, attr).replace(/{{min}}/g, resolution.min[attr]);
  227. }
  228. });
  229. }
  230. if (!(settings.resizeWidth || settings.resizeHeight)) {
  231. if (resolution.max) {
  232. Object.keys(resolution.max).forEach((attr) => {
  233. if (file[attr] > resolution.max[attr]) {
  234. error += translations.PLUGIN_FORM.RESOLUTION_MAX.replace(/{{attr}}/g, attr).replace(/{{max}}/g, resolution.max[attr]);
  235. }
  236. });
  237. }
  238. }
  239. return done(error);
  240. }, 50);
  241. }
  242. };
  243. cache = cache.add(container);
  244. container = container[0];
  245. instances.push(new FilesField({ container, options }));
  246. };
  247. export let Instances = (() => {
  248. $('.dropzone.files-upload').each((i, container) => addNode(container));
  249. $('body').on('mutation._grav', onAddedNodes);
  250. return instances;
  251. })();