From 247927a9b51cea88db6f7d2dee25471339feb51b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 21 Nov 2023 10:12:31 +0800 Subject: [PATCH] Use "is-loading" to avoid duplicate form submit for code comment (#28143) When the form is going to be submitted, add the "is-loading" class to show an indicator and avoid user UI events. When the request finishes (success / error), remove the "is-loading" class to make user can interact the UI. --- web_src/js/features/repo-diff.js | 45 +++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 864b28a3b..e74db04d0 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -6,8 +6,9 @@ import {initDiffCommitSelect} from './repo-diff-commitselect.js'; import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js'; import {initImageDiff} from './imagediff.js'; +import {showErrorToast} from '../modules/toast.js'; -const {csrfToken, pageData} = window.config; +const {csrfToken, pageData, i18n} = window.config; function initRepoDiffReviewButton() { const $reviewBox = $('#review-box'); @@ -50,26 +51,34 @@ function initRepoDiffConversationForm() { return; } - const formData = new FormData($form[0]); + if ($form.hasClass('is-loading')) return; + try { + $form.addClass('is-loading'); + const formData = new FormData($form[0]); - // if the form is submitted by a button, append the button's name and value to the form data - const submitter = e.originalEvent?.submitter; - const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit'); - if (isSubmittedByButton && submitter.name) { - formData.append(submitter.name, submitter.value); - } - const formDataString = String(new URLSearchParams(formData)); - const $newConversationHolder = $(await $.post($form.attr('action'), formDataString)); - const {path, side, idx} = $newConversationHolder.data(); + // if the form is submitted by a button, append the button's name and value to the form data + const submitter = e.originalEvent?.submitter; + const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit'); + if (isSubmittedByButton && submitter.name) { + formData.append(submitter.name, submitter.value); + } + const formDataString = String(new URLSearchParams(formData)); + const $newConversationHolder = $(await $.post($form.attr('action'), formDataString)); + const {path, side, idx} = $newConversationHolder.data(); - $form.closest('.conversation-holder').replaceWith($newConversationHolder); - if ($form.closest('tr').data('line-type') === 'same') { - $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('gt-invisible'); - } else { - $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('gt-invisible'); + $form.closest('.conversation-holder').replaceWith($newConversationHolder); + if ($form.closest('tr').data('line-type') === 'same') { + $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('gt-invisible'); + } else { + $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('gt-invisible'); + } + $newConversationHolder.find('.dropdown').dropdown(); + initCompReactionSelector($newConversationHolder); + } catch { // here the caught error might be a jQuery AJAX error (thrown by await $.post), which is not good to use for error message handling + showErrorToast(i18n.network_error); + } finally { + $form.removeClass('is-loading'); } - $newConversationHolder.find('.dropdown').dropdown(); - initCompReactionSelector($newConversationHolder); }); $(document).on('click', '.resolve-conversation', async function (e) {