PageRenderTime 1859ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/app/assets/javascripts/dropzone_input.js

https://gitlab.com/alexkeramidas/gitlab-ce
JavaScript | 277 lines | 218 code | 36 blank | 23 comment | 20 complexity | 0c71c519021782def40ee410dabb8d49 MD5 | raw file
  1. import $ from 'jquery';
  2. import Dropzone from 'dropzone';
  3. import _ from 'underscore';
  4. import './preview_markdown';
  5. import csrf from './lib/utils/csrf';
  6. import axios from './lib/utils/axios_utils';
  7. Dropzone.autoDiscover = false;
  8. export default function dropzoneInput(form) {
  9. const divHover = '<div class="div-dropzone-hover"></div>';
  10. const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
  11. const $attachButton = form.find('.button-attach-file');
  12. const $attachingFileMessage = form.find('.attaching-file-message');
  13. const $cancelButton = form.find('.button-cancel-uploading-files');
  14. const $retryLink = form.find('.retry-uploading-link');
  15. const $uploadProgress = form.find('.uploading-progress');
  16. const $uploadingErrorContainer = form.find('.uploading-error-container');
  17. const $uploadingErrorMessage = form.find('.uploading-error-message');
  18. const $uploadingProgressContainer = form.find('.uploading-progress-container');
  19. const uploadsPath = window.uploads_path || null;
  20. const maxFileSize = gon.max_file_size || 10;
  21. const formTextarea = form.find('.js-gfm-input');
  22. let handlePaste;
  23. let pasteText;
  24. let addFileToForm;
  25. let updateAttachingMessage;
  26. let isImage;
  27. let getFilename;
  28. let uploadFile;
  29. formTextarea.wrap('<div class="div-dropzone"></div>');
  30. formTextarea.on('paste', event => handlePaste(event));
  31. // Add dropzone area to the form.
  32. const $mdArea = formTextarea.closest('.md-area');
  33. form.setupMarkdownPreview();
  34. const $formDropzone = form.find('.div-dropzone');
  35. $formDropzone.parent().addClass('div-dropzone-wrapper');
  36. $formDropzone.append(divHover);
  37. $formDropzone.find('.div-dropzone-hover').append(iconPaperclip);
  38. if (!uploadsPath) {
  39. $formDropzone.addClass('js-invalid-dropzone');
  40. return;
  41. }
  42. const dropzone = $formDropzone.dropzone({
  43. url: uploadsPath,
  44. dictDefaultMessage: '',
  45. clickable: true,
  46. paramName: 'file',
  47. maxFilesize: maxFileSize,
  48. uploadMultiple: false,
  49. headers: csrf.headers,
  50. previewContainer: false,
  51. processing: () => $('.div-dropzone-alert').alert('close'),
  52. dragover: () => {
  53. $mdArea.addClass('is-dropzone-hover');
  54. form.find('.div-dropzone-hover').css('opacity', 0.7);
  55. },
  56. dragleave: () => {
  57. $mdArea.removeClass('is-dropzone-hover');
  58. form.find('.div-dropzone-hover').css('opacity', 0);
  59. },
  60. drop: () => {
  61. $mdArea.removeClass('is-dropzone-hover');
  62. form.find('.div-dropzone-hover').css('opacity', 0);
  63. formTextarea.focus();
  64. },
  65. success(header, response) {
  66. const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
  67. const shouldPad = processingFileCount >= 1;
  68. pasteText(response.link.markdown, shouldPad);
  69. // Show 'Attach a file' link only when all files have been uploaded.
  70. if (!processingFileCount) $attachButton.removeClass('hide');
  71. addFileToForm(response.link.url);
  72. },
  73. error: (file, errorMessage = 'Attaching the file failed.', xhr) => {
  74. // If 'error' event is fired by dropzone, the second parameter is error message.
  75. // If the 'errorMessage' parameter is empty, the default error message is set.
  76. // If the 'error' event is fired by backend (xhr) error response, the third parameter is
  77. // xhr object (xhr.responseText is error message).
  78. // On error we hide the 'Attach' and 'Cancel' buttons
  79. // and show an error.
  80. // If there's xhr error message, let's show it instead of dropzone's one.
  81. const message = xhr ? xhr.responseText : errorMessage;
  82. $uploadingErrorContainer.removeClass('hide');
  83. $uploadingErrorMessage.html(message);
  84. $attachButton.addClass('hide');
  85. $cancelButton.addClass('hide');
  86. },
  87. totaluploadprogress(totalUploadProgress) {
  88. updateAttachingMessage(this.files, $attachingFileMessage);
  89. $uploadProgress.text(`${Math.round(totalUploadProgress)}%`);
  90. },
  91. sending: () => {
  92. // DOM elements already exist.
  93. // Instead of dynamically generating them,
  94. // we just either hide or show them.
  95. $attachButton.addClass('hide');
  96. $uploadingErrorContainer.addClass('hide');
  97. $uploadingProgressContainer.removeClass('hide');
  98. $cancelButton.removeClass('hide');
  99. },
  100. removedfile: () => {
  101. $attachButton.removeClass('hide');
  102. $cancelButton.addClass('hide');
  103. $uploadingProgressContainer.addClass('hide');
  104. $uploadingErrorContainer.addClass('hide');
  105. },
  106. queuecomplete: () => {
  107. $('.dz-preview').remove();
  108. $('.markdown-area').trigger('input');
  109. $uploadingProgressContainer.addClass('hide');
  110. $cancelButton.addClass('hide');
  111. },
  112. });
  113. const child = $(dropzone[0]).children('textarea');
  114. // removeAllFiles(true) stops uploading files (if any)
  115. // and remove them from dropzone files queue.
  116. $cancelButton.on('click', (e) => {
  117. e.preventDefault();
  118. e.stopPropagation();
  119. Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
  120. });
  121. // If 'error' event is fired, we store a failed files,
  122. // clear dropzone files queue, change status of failed files to undefined,
  123. // and add that files to the dropzone files queue again.
  124. // addFile() adds file to dropzone files queue and upload it.
  125. $retryLink.on('click', (e) => {
  126. const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone'));
  127. const failedFiles = dropzoneInstance.files;
  128. e.preventDefault();
  129. // 'true' parameter of removeAllFiles() cancels
  130. // uploading of files that are being uploaded at the moment.
  131. dropzoneInstance.removeAllFiles(true);
  132. failedFiles.map((failedFile) => {
  133. const file = failedFile;
  134. if (file.status === Dropzone.ERROR) {
  135. file.status = undefined;
  136. file.accepted = undefined;
  137. }
  138. return dropzoneInstance.addFile(file);
  139. });
  140. });
  141. // eslint-disable-next-line consistent-return
  142. handlePaste = (event) => {
  143. const pasteEvent = event.originalEvent;
  144. if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
  145. const image = isImage(pasteEvent);
  146. if (image) {
  147. event.preventDefault();
  148. const filename = getFilename(pasteEvent) || 'image.png';
  149. const text = `{{${filename}}}`;
  150. pasteText(text);
  151. return uploadFile(image.getAsFile(), filename);
  152. }
  153. }
  154. };
  155. isImage = (data) => {
  156. let i = 0;
  157. while (i < data.clipboardData.items.length) {
  158. const item = data.clipboardData.items[i];
  159. if (item.type.indexOf('image') !== -1) {
  160. return item;
  161. }
  162. i += 1;
  163. }
  164. return false;
  165. };
  166. pasteText = (text, shouldPad) => {
  167. let formattedText = text;
  168. if (shouldPad) {
  169. formattedText += '\n\n';
  170. }
  171. const textarea = child.get(0);
  172. const caretStart = textarea.selectionStart;
  173. const caretEnd = textarea.selectionEnd;
  174. const textEnd = $(child).val().length;
  175. const beforeSelection = $(child).val().substring(0, caretStart);
  176. const afterSelection = $(child).val().substring(caretEnd, textEnd);
  177. $(child).val(beforeSelection + formattedText + afterSelection);
  178. textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
  179. textarea.style.height = `${textarea.scrollHeight}px`;
  180. formTextarea.get(0).dispatchEvent(new Event('input'));
  181. return formTextarea.trigger('input');
  182. };
  183. addFileToForm = (path) => {
  184. $(form).append(`<input type="hidden" name="files[]" value="${_.escape(path)}">`);
  185. };
  186. getFilename = (e) => {
  187. let value;
  188. if (window.clipboardData && window.clipboardData.getData) {
  189. value = window.clipboardData.getData('Text');
  190. } else if (e.clipboardData && e.clipboardData.getData) {
  191. value = e.clipboardData.getData('text/plain');
  192. }
  193. value = value.split('\r');
  194. return value[0];
  195. };
  196. const showSpinner = () => $uploadingProgressContainer.removeClass('hide');
  197. const closeSpinner = () => $uploadingProgressContainer.addClass('hide');
  198. const showError = (message) => {
  199. $uploadingErrorContainer.removeClass('hide');
  200. $uploadingErrorMessage.html(message);
  201. };
  202. const closeAlertMessage = () => form.find('.div-dropzone-alert').alert('close');
  203. const insertToTextArea = (filename, url) => {
  204. const $child = $(child);
  205. $child.val((index, val) => val.replace(`{{${filename}}}`, url));
  206. $child.trigger('change');
  207. };
  208. uploadFile = (item, filename) => {
  209. const formData = new FormData();
  210. formData.append('file', item, filename);
  211. showSpinner();
  212. closeAlertMessage();
  213. axios.post(uploadsPath, formData)
  214. .then(({ data }) => {
  215. const md = data.link.markdown;
  216. insertToTextArea(filename, md);
  217. closeSpinner();
  218. })
  219. .catch((e) => {
  220. showError(e.response.data.message);
  221. closeSpinner();
  222. });
  223. };
  224. updateAttachingMessage = (files, messageContainer) => {
  225. let attachingMessage;
  226. const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued').length;
  227. // Dinamycally change uploading files text depending on files number in
  228. // dropzone files queue.
  229. if (filesCount > 1) {
  230. attachingMessage = `Attaching ${filesCount} files -`;
  231. } else {
  232. attachingMessage = 'Attaching a file -';
  233. }
  234. messageContainer.text(attachingMessage);
  235. };
  236. form.find('.markdown-selector').click(function onMarkdownClick(e) {
  237. e.preventDefault();
  238. $(this).closest('.gfm-form').find('.div-dropzone').click();
  239. formTextarea.focus();
  240. });
  241. }