From cc7236e852e60e4dc9c4fb5692ac62697ef0e9e3 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Fri, 20 May 2022 10:26:04 +0800
Subject: Make Ctrl+Enter (quick submit) work for issue comment and wiki editor
 (#19729)

* Make Ctrl+Enter (quick submit) work for issue comment and wiki editor

* Remove the required `SubmitReviewForm.Type`, empty type (triggered by quick submit) means "comment"

* Merge duplicate code
---
 services/forms/repo_form.go            |  6 ++++--
 templates/repo/diff/box.tmpl           |  2 +-
 templates/repo/issue/view_content.tmpl |  2 +-
 templates/user/settings/keys_ssh.tmpl  |  4 ++--
 web_src/js/features/common-global.js   | 21 ++++++++++++++++++---
 web_src/js/features/comp/EasyMDE.js    | 15 ++++++++++++++-
 web_src/js/features/repo-legacy.js     | 10 ++++++++--
 web_src/js/features/repo-wiki.js       | 10 ++++++----
 8 files changed, 54 insertions(+), 16 deletions(-)

diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 18cbac751c..2bcb91f8c3 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -623,7 +623,7 @@ func (f *CodeCommentForm) Validate(req *http.Request, errs binding.Errors) bindi
 // SubmitReviewForm for submitting a finished code review
 type SubmitReviewForm struct {
 	Content  string
-	Type     string `binding:"Required;In(approve,comment,reject)"`
+	Type     string
 	CommitID string
 	Files    []string
 }
@@ -634,7 +634,7 @@ func (f *SubmitReviewForm) Validate(req *http.Request, errs binding.Errors) bind
 	return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 }
 
-// ReviewType will return the corresponding reviewtype for type
+// ReviewType will return the corresponding ReviewType for type
 func (f SubmitReviewForm) ReviewType() models.ReviewType {
 	switch f.Type {
 	case "approve":
@@ -643,6 +643,8 @@ func (f SubmitReviewForm) ReviewType() models.ReviewType {
 		return models.ReviewTypeComment
 	case "reject":
 		return models.ReviewTypeReject
+	case "":
+		return models.ReviewTypeComment // default to comment when doing quick-submit (Ctrl+Enter) on the review form
 	default:
 		return models.ReviewTypeUnknown
 	}
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index 02c7dcd68a..a70532eca9 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -180,7 +180,7 @@
 						<a class="preview item" data-url="{{$.Repository.HTMLURL}}/markdown" data-context="{{$.RepoLink}}">{{$.i18n.Tr "preview"}}</a>
 					</div>
 					<div class="ui bottom attached active write tab segment">
-						<textarea class="review-textarea" tabindex="1" name="content"></textarea>
+						<textarea class="review-textarea js-quick-submit" tabindex="1" name="content"></textarea>
 					</div>
 					<div class="ui bottom attached tab preview segment markup">
 					{{$.i18n.Tr "loading"}}
diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl
index 8d4fc4f8b3..52353d46d9 100644
--- a/templates/repo/issue/view_content.tmpl
+++ b/templates/repo/issue/view_content.tmpl
@@ -202,7 +202,7 @@
 		</div>
 		<div class="field">
 			<div class="ui bottom active tab write">
-				<textarea tabindex="1" name="content"></textarea>
+				<textarea tabindex="1" name="content" class="js-quick-submit"></textarea>
 			</div>
 			<div class="ui bottom tab preview markup">
 				{{$.i18n.Tr "loading"}}
diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl
index 077e40e015..699905ebe2 100644
--- a/templates/user/settings/keys_ssh.tmpl
+++ b/templates/user/settings/keys_ssh.tmpl
@@ -20,7 +20,7 @@
 			</div>
 			<div class="field {{if .Err_Content}}error{{end}}">
 				<label for="content">{{.i18n.Tr "settings.key_content"}}</label>
-				<textarea id="ssh-key-content" name="content" placeholder="{{.i18n.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea>
+				<textarea id="ssh-key-content" name="content" class="js-quick-submit" placeholder="{{.i18n.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea>
 			</div>
 			<input name="type" type="hidden" value="ssh">
 			<button class="ui green button">
@@ -81,7 +81,7 @@
 						</div>
 						<div class="field">
 							<label for="signature">{{$.i18n.Tr "settings.ssh_token_signature"}}</label>
-							<textarea id="ssh-key-signature" name="signature" placeholder="{{$.i18n.Tr "settings.key_signature_ssh_placeholder"}}" required>{{$.signature}}</textarea>
+							<textarea id="ssh-key-signature" name="signature" class="js-quick-submit" placeholder="{{$.i18n.Tr "settings.key_signature_ssh_placeholder"}}" required>{{$.signature}}</textarea>
 						</div>
 						<input name="type" type="hidden" value="verify_ssh">
 						<button class="ui green button">
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
index eb21a4bb93..57fd549019 100644
--- a/web_src/js/features/common-global.js
+++ b/web_src/js/features/common-global.js
@@ -44,13 +44,28 @@ export function initFootLanguageMenu() {
 
 
 export function initGlobalEnterQuickSubmit() {
-  $('.js-quick-submit').on('keydown', function (e) {
-    if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode === 13 || e.keyCode === 10)) {
-      $(this).closest('form').trigger('submit');
+  $(document).on('keydown', '.js-quick-submit', (e) => {
+    if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.key === 'Enter')) {
+      handleGlobalEnterQuickSubmit(e.target);
+      return false;
     }
   });
 }
 
+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
diff --git a/web_src/js/features/comp/EasyMDE.js b/web_src/js/features/comp/EasyMDE.js
index 0327a1e023..61aaf23e89 100644
--- a/web_src/js/features/comp/EasyMDE.js
+++ b/web_src/js/features/comp/EasyMDE.js
@@ -1,5 +1,6 @@
 import $ from 'jquery';
 import attachTribute from '../tribute.js';
+import {handleGlobalEnterQuickSubmit} from '../common-global.js';
 
 /**
  * @returns {EasyMDE}
@@ -71,9 +72,12 @@ export async function createCommentEasyMDE(textarea, easyMDEOptions = {}) {
         title: 'Revert to simple textarea',
       },
     ], ...easyMDEOptions});
+
   const inputField = easyMDE.codemirror.getInputField();
-  inputField.classList.add('js-quick-submit');
+
   easyMDE.codemirror.setOption('extraKeys', {
+    'Cmd-Enter': codeMirrorQuickSubmit,
+    'Ctrl-Enter': codeMirrorQuickSubmit,
     Enter: (cm) => {
       const tributeContainer = document.querySelector('.tribute-container');
       if (!tributeContainer || tributeContainer.style.display === 'none') {
@@ -149,3 +153,12 @@ export function validateTextareaNonEmpty($textarea) {
   $mdeInputField.prop('required', false);
   return true;
 }
+
+/**
+ * there is no guarantee that the CodeMirror object is inside the same form as the textarea,
+ * so can not call handleGlobalEnterQuickSubmit directly.
+ * @param {CodeMirror.EditorFromTextArea} codeMirror
+ */
+export function codeMirrorQuickSubmit(codeMirror) {
+  handleGlobalEnterQuickSubmit(codeMirror.getTextArea());
+}
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index a24d1b974a..53471b30cf 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -355,6 +355,11 @@ async function onEditContent(event) {
       initEasyMDEImagePaste(easyMDE, $dropzone[0], $dropzone.find('.files'));
     }
 
+    const $saveButton = $editContentZone.find('.save.button');
+    $textarea.on('ce-quick-submit', () => {
+      $saveButton.trigger('click');
+    });
+
     $editContentZone.find('.cancel.button').on('click', () => {
       $renderContent.show();
       $editContentZone.hide();
@@ -362,7 +367,8 @@ async function onEditContent(event) {
         dz.emit('reload');
       }
     });
-    $editContentZone.find('.save.button').on('click', () => {
+
+    $saveButton.on('click', () => {
       $renderContent.show();
       $editContentZone.hide();
       const $attachments = $dropzone.find('.files').find('[name=files]').map(function () {
@@ -400,7 +406,7 @@ async function onEditContent(event) {
         initCommentContent();
       });
     });
-  } else {
+  } else { // use existing form
     $textarea = $segment.find('textarea');
     easyMDE = getAttachedEasyMDE($textarea);
   }
diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js
index 0ac88e3f5a..27f44f4e22 100644
--- a/web_src/js/features/repo-wiki.js
+++ b/web_src/js/features/repo-wiki.js
@@ -1,6 +1,6 @@
 import $ from 'jquery';
 import {initMarkupContent} from '../markup/content.js';
-import {attachEasyMDEToElements, importEasyMDE, validateTextareaNonEmpty} from './comp/EasyMDE.js';
+import {attachEasyMDEToElements, codeMirrorQuickSubmit, importEasyMDE, validateTextareaNonEmpty} from './comp/EasyMDE.js';
 import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
 
 const {csrfToken} = window.config;
@@ -122,10 +122,12 @@ async function initRepoWikiFormEditor() {
     ]
   });
 
-  attachEasyMDEToElements(easyMDE);
+  easyMDE.codemirror.setOption('extraKeys', {
+    'Cmd-Enter': codeMirrorQuickSubmit,
+    'Ctrl-Enter': codeMirrorQuickSubmit,
+  });
 
-  const $mdeInputField = $(easyMDE.codemirror.getInputField());
-  $mdeInputField.addClass('js-quick-submit');
+  attachEasyMDEToElements(easyMDE);
 
   $form.on('submit', () => {
     if (!validateTextareaNonEmpty($editArea)) {
-- 
cgit v1.2.3