diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 419c5996dc651..444bda5ad1411 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -5,6 +5,9 @@ import createDropzone from './dropzone.js'; import {initCompColorPicker} from './comp/ColorPicker.js'; import {showGlobalErrorMessage} from '../bootstrap.js'; import {attachDropdownAria} from './aria.js'; +import {removeUploadedFileFromEditor} from './comp/ImagePaste.js'; +import {getAttachedEasyMDE} from './comp/EasyMDE.js'; +import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js'; const {appUrl, csrfToken} = window.config; @@ -53,20 +56,6 @@ export function initGlobalEnterQuickSubmit() { }); } -export function handleGlobalEnterQuickSubmit(target) { - const $target = $(target); - const $form = $(target).closest('form'); - if ($form.length) { - // here use the event to trigger the submit event (instead of calling `submit()` method directly) - // otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog - $form.trigger('submit'); - } else { - // if no form, then the editor is for an AJAX request, dispatch an event to the target, let the target's event handler to do the AJAX request. - // the 'ce-' prefix means this is a CustomEvent - $target.trigger('ce-quick-submit'); - } -} - export function initGlobalButtonClickOnEnter() { $(document).on('keypress', '.ui.button', (e) => { if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar @@ -208,6 +197,12 @@ export function initGlobalDropzone() { $.post($dropzone.data('remove-url'), { file: file.uuid, _csrf: csrfToken, + }).then(() => { + // FIXME: the code is still very fragile, in the future, there should be stable relation between dropzone and its editor. + const easyMDE = getAttachedEasyMDE($dropzone.parent().parent().find('textarea')); + if (easyMDE) { + removeUploadedFileFromEditor(easyMDE, file.uuid); + } }); } }); diff --git a/web_src/js/features/comp/EasyMDE.js b/web_src/js/features/comp/EasyMDE.js index 7c1db9a9988dc..f1b4b0efc398d 100644 --- a/web_src/js/features/comp/EasyMDE.js +++ b/web_src/js/features/comp/EasyMDE.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import attachTribute from '../tribute.js'; -import {handleGlobalEnterQuickSubmit} from '../common-global.js'; +import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js'; /** * @returns {EasyMDE} diff --git a/web_src/js/features/comp/ImagePaste.js b/web_src/js/features/comp/ImagePaste.js index da41e7611a620..914808824646a 100644 --- a/web_src/js/features/comp/ImagePaste.js +++ b/web_src/js/features/comp/ImagePaste.js @@ -2,7 +2,7 @@ import $ from 'jquery'; const {csrfToken} = window.config; -async function uploadFile(file, uploadUrl) { +async function uploadFile(file, uploadUrl, dropzone) { const formData = new FormData(); formData.append('file', file, file.name); @@ -11,7 +11,27 @@ async function uploadFile(file, uploadUrl) { headers: {'X-Csrf-Token': csrfToken}, body: formData, }); - return await res.json(); + const data = await res.json(); + const upfile = {name: file.name, size: file.size, uuid: data.uuid}; + dropzone.emit('addedfile', upfile); + dropzone.emit('thumbnail', upfile, `/attachments/${data.uuid}`); + dropzone.emit('complete', upfile); + dropzone.files.push(upfile); + return data; +} + +/** + * @param editor{EasyMDE} + * @param fileUuid + */ +export function removeUploadedFileFromEditor(editor, fileUuid) { + // the raw regexp is: /!\[[^\]]*]\(\/attachments\/{uuid}\)/ + const re = new RegExp(`!\\[[^\\]]*]\\(/attachments/${fileUuid}\\)`); + editor.value(editor.value().replace(re, '')); // at the moment, we assume the editor is an EasyMDE + if (editor.element) { + // when using "simple textarea" mode, the value of the textarea should be replaced too. + editor.element.value = editor.element.value.replace(re, ''); + } } function clipboardPastedImages(e) { @@ -89,6 +109,8 @@ class CodeMirrorEditor { export function initEasyMDEImagePaste(easyMDE, $dropzone) { + if ($dropzone.length !== 1) throw new Error('invalid dropzone binding for editor'); + const uploadUrl = $dropzone.attr('data-upload-url'); const $files = $dropzone.find('.files'); @@ -107,7 +129,7 @@ export function initEasyMDEImagePaste(easyMDE, $dropzone) { const placeholder = `![${name}](uploading ...)`; editor.insertPlaceholder(placeholder); - const data = await uploadFile(img, uploadUrl); + const data = await uploadFile(img, uploadUrl, $dropzone[0].dropzone); editor.replacePlaceholder(placeholder, `![${name}](/attachments/${data.uuid})`); const $input = $(``).attr('id', data.uuid).val(data.uuid); diff --git a/web_src/js/features/comp/QuickSubmit.js b/web_src/js/features/comp/QuickSubmit.js new file mode 100644 index 0000000000000..43424a949f1ec --- /dev/null +++ b/web_src/js/features/comp/QuickSubmit.js @@ -0,0 +1,15 @@ +import $ from 'jquery'; + +export function handleGlobalEnterQuickSubmit(target) { + const $target = $(target); + const $form = $(target).closest('form'); + if ($form.length) { + // here use the event to trigger the submit event (instead of calling `submit()` method directly) + // otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog + $form.trigger('submit'); + } else { + // if no form, then the editor is for an AJAX request, dispatch an event to the target, let the target's event handler to do the AJAX request. + // the 'ce-' prefix means this is a CustomEvent + $target.trigger('ce-quick-submit'); + } +} diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 11c97ccfb011c..672de54d68cdc 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/EasyMDE.js'; import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; -import {initEasyMDEImagePaste} from './comp/ImagePaste.js'; +import {initEasyMDEImagePaste, removeUploadedFileFromEditor} from './comp/ImagePaste.js'; import { initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete, @@ -33,7 +33,7 @@ import initRepoPullRequestMergeForm from './repo-issue-pr-form.js'; const {csrfToken} = window.config; export function initRepoCommentForm() { - const $commentForm = $('.comment.form'); + const $commentForm = $('#comment-form, #new-issue'); // for issues and PRs if ($commentForm.length === 0) { return; } @@ -284,7 +284,8 @@ async function onEditContent(event) { if ($dropzone.length === 1) { $dropzone.data('saved', false); - const fileUuidDict = {}; + let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the removedfile event + let fileUuidDict = {}; // if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone dz = await createDropzone($dropzone[0], { url: $dropzone.data('upload-url'), headers: {'X-Csrf-Token': csrfToken}, @@ -308,12 +309,18 @@ async function onEditContent(event) { $dropzone.find('.files').append(input); }); this.on('removedfile', (file) => { + if (disableRemovedfileEvent) return; $(`#${file.uuid}`).remove(); - if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) { + if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid]?.submitted) { $.post($dropzone.data('remove-url'), { file: file.uuid, _csrf: csrfToken, + }).then(() => { + removeUploadedFileFromEditor(easyMDE, file.uuid); }); + } else { + // for saved comment's attachment's removal, only remove the link in the editor + removeUploadedFileFromEditor(easyMDE, file.uuid); } }); this.on('submit', () => { @@ -323,8 +330,11 @@ async function onEditContent(event) { }); this.on('reload', () => { $.getJSON($editContentZone.data('attachment-url'), (data) => { + disableRemovedfileEvent = true; dz.removeAllFiles(true); + disableRemovedfileEvent = false; $dropzone.find('.files').empty(); + fileUuidDict = {}; $.each(data, function () { const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`; dz.emit('addedfile', this);