debian-mirror-gitlab/app/assets/javascripts/dropzone_input.js

301 lines
10 KiB
JavaScript
Raw Normal View History

2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2018-03-17 18:26:18 +05:30
import Dropzone from 'dropzone';
2020-05-24 23:13:21 +05:30
import { escape } from 'lodash';
2018-12-05 23:21:45 +05:30
import './behaviors/preview_markdown';
2020-03-13 15:44:24 +05:30
import PasteMarkdownTable from './behaviors/markdown/paste_markdown_table';
2018-03-17 18:26:18 +05:30
import csrf from './lib/utils/csrf';
import axios from './lib/utils/axios_utils';
2019-09-04 21:01:54 +05:30
import { n__, __ } from '~/locale';
2020-04-22 19:07:51 +05:30
import { getFilename } from '~/lib/utils/file_upload';
2021-01-29 00:20:46 +05:30
import { spriteIcon } from '~/lib/utils/common_utils';
2018-03-17 18:26:18 +05:30
Dropzone.autoDiscover = false;
2018-11-20 20:47:30 +05:30
/**
* Return the error message string from the given response.
*
* @param {String|Object} res
*/
function getErrorMessage(res) {
2020-05-24 23:13:21 +05:30
if (!res || typeof res === 'string') {
2018-11-20 20:47:30 +05:30
return res;
}
return res.message;
}
2020-04-22 19:07:51 +05:30
export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
2018-03-17 18:26:18 +05:30
const divHover = '<div class="div-dropzone-hover"></div>';
2021-01-29 00:20:46 +05:30
const iconPaperclip = spriteIcon('paperclip', 'div-dropzone-icon s24');
2018-03-17 18:26:18 +05:30
const $attachButton = form.find('.button-attach-file');
const $attachingFileMessage = form.find('.attaching-file-message');
const $cancelButton = form.find('.button-cancel-uploading-files');
const $retryLink = form.find('.retry-uploading-link');
const $uploadProgress = form.find('.uploading-progress');
const $uploadingErrorContainer = form.find('.uploading-error-container');
const $uploadingErrorMessage = form.find('.uploading-error-message');
const $uploadingProgressContainer = form.find('.uploading-progress-container');
2018-11-20 20:47:30 +05:30
const uploadsPath = form.data('uploads-path') || window.uploads_path || null;
2018-03-17 18:26:18 +05:30
const maxFileSize = gon.max_file_size || 10;
const formTextarea = form.find('.js-gfm-input');
let handlePaste;
let pasteText;
let addFileToForm;
let updateAttachingMessage;
let isImage;
let uploadFile;
formTextarea.wrap('<div class="div-dropzone"></div>');
formTextarea.on('paste', event => handlePaste(event));
// Add dropzone area to the form.
const $mdArea = formTextarea.closest('.md-area');
form.setupMarkdownPreview();
const $formDropzone = form.find('.div-dropzone');
$formDropzone.parent().addClass('div-dropzone-wrapper');
$formDropzone.append(divHover);
$formDropzone.find('.div-dropzone-hover').append(iconPaperclip);
if (!uploadsPath) {
$formDropzone.addClass('js-invalid-dropzone');
2018-11-20 20:47:30 +05:30
return null;
2018-03-17 18:26:18 +05:30
}
2016-09-13 17:45:13 +05:30
2018-03-17 18:26:18 +05:30
const dropzone = $formDropzone.dropzone({
url: uploadsPath,
dictDefaultMessage: '',
clickable: true,
paramName: 'file',
maxFilesize: maxFileSize,
uploadMultiple: false,
headers: csrf.headers,
previewContainer: false,
2020-04-22 19:07:51 +05:30
...config,
2018-03-17 18:26:18 +05:30
dragover: () => {
$mdArea.addClass('is-dropzone-hover');
form.find('.div-dropzone-hover').css('opacity', 0.7);
},
dragleave: () => {
$mdArea.removeClass('is-dropzone-hover');
form.find('.div-dropzone-hover').css('opacity', 0);
},
drop: () => {
$mdArea.removeClass('is-dropzone-hover');
form.find('.div-dropzone-hover').css('opacity', 0);
formTextarea.focus();
},
success(header, response) {
const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
const shouldPad = processingFileCount >= 1;
pasteText(response.link.markdown, shouldPad);
// Show 'Attach a file' link only when all files have been uploaded.
if (!processingFileCount) $attachButton.removeClass('hide');
addFileToForm(response.link.url);
},
2019-09-04 21:01:54 +05:30
error: (file, errorMessage = __('Attaching the file failed.'), xhr) => {
2018-03-17 18:26:18 +05:30
// If 'error' event is fired by dropzone, the second parameter is error message.
// If the 'errorMessage' parameter is empty, the default error message is set.
// If the 'error' event is fired by backend (xhr) error response, the third parameter is
// xhr object (xhr.responseText is error message).
// On error we hide the 'Attach' and 'Cancel' buttons
// and show an error.
2018-11-20 20:47:30 +05:30
const message = getErrorMessage(errorMessage || xhr.responseText);
2017-09-10 17:25:29 +05:30
$uploadingErrorContainer.removeClass('hide');
$uploadingErrorMessage.html(message);
2018-03-17 18:26:18 +05:30
$attachButton.addClass('hide');
$cancelButton.addClass('hide');
},
totaluploadprogress(totalUploadProgress) {
updateAttachingMessage(this.files, $attachingFileMessage);
$uploadProgress.text(`${Math.round(totalUploadProgress)}%`);
},
sending: () => {
// DOM elements already exist.
// Instead of dynamically generating them,
// we just either hide or show them.
$attachButton.addClass('hide');
$uploadingErrorContainer.addClass('hide');
$uploadingProgressContainer.removeClass('hide');
$cancelButton.removeClass('hide');
},
removedfile: () => {
$attachButton.removeClass('hide');
$cancelButton.addClass('hide');
$uploadingProgressContainer.addClass('hide');
$uploadingErrorContainer.addClass('hide');
},
queuecomplete: () => {
$('.dz-preview').remove();
$('.markdown-area').trigger('input');
$uploadingProgressContainer.addClass('hide');
$cancelButton.addClass('hide');
},
});
const child = $(dropzone[0]).children('textarea');
// removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue.
2018-12-13 13:39:08 +05:30
$cancelButton.on('click', e => {
2018-03-17 18:26:18 +05:30
e.preventDefault();
e.stopPropagation();
Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
});
// If 'error' event is fired, we store a failed files,
// clear dropzone files queue, change status of failed files to undefined,
// and add that files to the dropzone files queue again.
// addFile() adds file to dropzone files queue and upload it.
2018-12-13 13:39:08 +05:30
$retryLink.on('click', e => {
const dropzoneInstance = Dropzone.forElement(
e.target.closest('.js-main-target-form').querySelector('.div-dropzone'),
);
2018-03-17 18:26:18 +05:30
const failedFiles = dropzoneInstance.files;
e.preventDefault();
// 'true' parameter of removeAllFiles() cancels
// uploading of files that are being uploaded at the moment.
dropzoneInstance.removeAllFiles(true);
2018-12-13 13:39:08 +05:30
failedFiles.map(failedFile => {
2018-03-17 18:26:18 +05:30
const file = failedFile;
if (file.status === Dropzone.ERROR) {
file.status = undefined;
file.accepted = undefined;
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
return dropzoneInstance.addFile(file);
2017-08-17 22:00:37 +05:30
});
2018-03-17 18:26:18 +05:30
});
// eslint-disable-next-line consistent-return
2018-12-13 13:39:08 +05:30
handlePaste = event => {
2018-03-17 18:26:18 +05:30
const pasteEvent = event.originalEvent;
2020-03-13 15:44:24 +05:30
const { clipboardData } = pasteEvent;
if (clipboardData && clipboardData.items) {
const converter = new PasteMarkdownTable(clipboardData);
// Apple Numbers copies a table as an image, HTML, and text, so
// we need to check for the presence of a table first.
if (converter.isTable()) {
2018-03-17 18:26:18 +05:30
event.preventDefault();
2020-03-13 15:44:24 +05:30
const text = converter.convertToTableMarkdown();
2018-03-17 18:26:18 +05:30
pasteText(text);
2020-03-13 15:44:24 +05:30
} else {
const image = isImage(pasteEvent);
if (image) {
event.preventDefault();
const filename = getFilename(pasteEvent) || 'image.png';
const text = `{{${filename}}}`;
pasteText(text);
return uploadFile(image.getAsFile(), filename);
}
2018-03-17 18:26:18 +05:30
}
}
};
2018-12-13 13:39:08 +05:30
isImage = data => {
2018-03-17 18:26:18 +05:30
let i = 0;
while (i < data.clipboardData.items.length) {
const item = data.clipboardData.items[i];
if (item.type.indexOf('image') !== -1) {
return item;
}
i += 1;
}
return false;
};
pasteText = (text, shouldPad) => {
let formattedText = text;
if (shouldPad) {
formattedText += '\n\n';
}
const textarea = child.get(0);
const caretStart = textarea.selectionStart;
const caretEnd = textarea.selectionEnd;
const textEnd = $(child).val().length;
2018-12-13 13:39:08 +05:30
const beforeSelection = $(child)
.val()
.substring(0, caretStart);
const afterSelection = $(child)
.val()
.substring(caretEnd, textEnd);
2018-03-17 18:26:18 +05:30
$(child).val(beforeSelection + formattedText + afterSelection);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
formTextarea.get(0).dispatchEvent(new Event('input'));
return formTextarea.trigger('input');
};
2018-12-13 13:39:08 +05:30
addFileToForm = path => {
2020-05-24 23:13:21 +05:30
$(form).append(`<input type="hidden" name="files[]" value="${escape(path)}">`);
2018-03-17 18:26:18 +05:30
};
const showSpinner = () => $uploadingProgressContainer.removeClass('hide');
const closeSpinner = () => $uploadingProgressContainer.addClass('hide');
2018-12-13 13:39:08 +05:30
const showError = message => {
2018-03-17 18:26:18 +05:30
$uploadingErrorContainer.removeClass('hide');
$uploadingErrorMessage.html(message);
};
const insertToTextArea = (filename, url) => {
const $child = $(child);
2020-04-08 14:13:33 +05:30
const textarea = $child.get(0);
const caretStart = textarea.selectionStart;
const caretEnd = textarea.selectionEnd;
const formattedText = `{{${filename}}}`;
$child.val((index, val) => val.replace(formattedText, url));
textarea.setSelectionRange(
caretStart - formattedText.length + url.length,
caretEnd - formattedText.length + url.length,
);
2018-03-17 18:26:18 +05:30
$child.trigger('change');
};
uploadFile = (item, filename) => {
const formData = new FormData();
formData.append('file', item, filename);
showSpinner();
2018-12-13 13:39:08 +05:30
axios
.post(uploadsPath, formData)
2018-03-17 18:26:18 +05:30
.then(({ data }) => {
const md = data.link.markdown;
insertToTextArea(filename, md);
closeSpinner();
})
2018-12-13 13:39:08 +05:30
.catch(e => {
2018-03-17 18:26:18 +05:30
showError(e.response.data.message);
closeSpinner();
});
};
updateAttachingMessage = (files, messageContainer) => {
2018-12-13 13:39:08 +05:30
const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued')
.length;
2019-09-04 21:01:54 +05:30
const attachingMessage = n__('Attaching a file', 'Attaching %d files', filesCount);
2018-03-17 18:26:18 +05:30
2019-09-04 21:01:54 +05:30
messageContainer.text(`${attachingMessage} -`);
2018-03-17 18:26:18 +05:30
};
form.find('.markdown-selector').click(function onMarkdownClick(e) {
e.preventDefault();
2018-12-13 13:39:08 +05:30
$(this)
.closest('.gfm-form')
.find('.div-dropzone')
.click();
2018-03-17 18:26:18 +05:30
formTextarea.focus();
});
2018-11-20 20:47:30 +05:30
2019-12-26 22:10:19 +05:30
return $formDropzone.get(0) ? Dropzone.forElement($formDropzone.get(0)) : null;
2018-03-17 18:26:18 +05:30
}