From 2969745b0c95c264ef38dc86f055b530b94c9b78 Mon Sep 17 00:00:00 2001
From: Giteabot <teabot@gitea.io>
Date: Tue, 21 Nov 2023 12:02:58 +0800
Subject: [PATCH] Use "is-loading" to avoid duplicate form submit for code
 comment (#28143) (#28147)

Backport #28143 by @wxiaoguang

Compare by ignoring spaces:
https://github.com/go-gitea/gitea/pull/28143/files?diff=split&w=1

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.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit ffab076b72fb5822531f24f9e608f3b49bb2d324)
---
 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 864b28a3bb..e74db04d08 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) {