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
This commit is contained in:
parent
3b359b1629
commit
cc7236e852
8 changed files with 54 additions and 16 deletions
|
@ -623,7 +623,7 @@ func (f *CodeCommentForm) Validate(req *http.Request, errs binding.Errors) bindi
|
||||||
// SubmitReviewForm for submitting a finished code review
|
// SubmitReviewForm for submitting a finished code review
|
||||||
type SubmitReviewForm struct {
|
type SubmitReviewForm struct {
|
||||||
Content string
|
Content string
|
||||||
Type string `binding:"Required;In(approve,comment,reject)"`
|
Type string
|
||||||
CommitID string
|
CommitID string
|
||||||
Files []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)
|
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 {
|
func (f SubmitReviewForm) ReviewType() models.ReviewType {
|
||||||
switch f.Type {
|
switch f.Type {
|
||||||
case "approve":
|
case "approve":
|
||||||
|
@ -643,6 +643,8 @@ func (f SubmitReviewForm) ReviewType() models.ReviewType {
|
||||||
return models.ReviewTypeComment
|
return models.ReviewTypeComment
|
||||||
case "reject":
|
case "reject":
|
||||||
return models.ReviewTypeReject
|
return models.ReviewTypeReject
|
||||||
|
case "":
|
||||||
|
return models.ReviewTypeComment // default to comment when doing quick-submit (Ctrl+Enter) on the review form
|
||||||
default:
|
default:
|
||||||
return models.ReviewTypeUnknown
|
return models.ReviewTypeUnknown
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,7 @@
|
||||||
<a class="preview item" data-url="{{$.Repository.HTMLURL}}/markdown" data-context="{{$.RepoLink}}">{{$.i18n.Tr "preview"}}</a>
|
<a class="preview item" data-url="{{$.Repository.HTMLURL}}/markdown" data-context="{{$.RepoLink}}">{{$.i18n.Tr "preview"}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui bottom attached active write tab segment">
|
<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>
|
||||||
<div class="ui bottom attached tab preview segment markup">
|
<div class="ui bottom attached tab preview segment markup">
|
||||||
{{$.i18n.Tr "loading"}}
|
{{$.i18n.Tr "loading"}}
|
||||||
|
|
|
@ -202,7 +202,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui bottom active tab write">
|
<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>
|
||||||
<div class="ui bottom tab preview markup">
|
<div class="ui bottom tab preview markup">
|
||||||
{{$.i18n.Tr "loading"}}
|
{{$.i18n.Tr "loading"}}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field {{if .Err_Content}}error{{end}}">
|
<div class="field {{if .Err_Content}}error{{end}}">
|
||||||
<label for="content">{{.i18n.Tr "settings.key_content"}}</label>
|
<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>
|
</div>
|
||||||
<input name="type" type="hidden" value="ssh">
|
<input name="type" type="hidden" value="ssh">
|
||||||
<button class="ui green button">
|
<button class="ui green button">
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="signature">{{$.i18n.Tr "settings.ssh_token_signature"}}</label>
|
<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>
|
</div>
|
||||||
<input name="type" type="hidden" value="verify_ssh">
|
<input name="type" type="hidden" value="verify_ssh">
|
||||||
<button class="ui green button">
|
<button class="ui green button">
|
||||||
|
|
|
@ -44,13 +44,28 @@ export function initFootLanguageMenu() {
|
||||||
|
|
||||||
|
|
||||||
export function initGlobalEnterQuickSubmit() {
|
export function initGlobalEnterQuickSubmit() {
|
||||||
$('.js-quick-submit').on('keydown', function (e) {
|
$(document).on('keydown', '.js-quick-submit', (e) => {
|
||||||
if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode === 13 || e.keyCode === 10)) {
|
if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.key === 'Enter')) {
|
||||||
$(this).closest('form').trigger('submit');
|
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() {
|
export function initGlobalButtonClickOnEnter() {
|
||||||
$(document).on('keypress', '.ui.button', (e) => {
|
$(document).on('keypress', '.ui.button', (e) => {
|
||||||
if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar
|
if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import attachTribute from '../tribute.js';
|
import attachTribute from '../tribute.js';
|
||||||
|
import {handleGlobalEnterQuickSubmit} from '../common-global.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {EasyMDE}
|
* @returns {EasyMDE}
|
||||||
|
@ -71,9 +72,12 @@ export async function createCommentEasyMDE(textarea, easyMDEOptions = {}) {
|
||||||
title: 'Revert to simple textarea',
|
title: 'Revert to simple textarea',
|
||||||
},
|
},
|
||||||
], ...easyMDEOptions});
|
], ...easyMDEOptions});
|
||||||
|
|
||||||
const inputField = easyMDE.codemirror.getInputField();
|
const inputField = easyMDE.codemirror.getInputField();
|
||||||
inputField.classList.add('js-quick-submit');
|
|
||||||
easyMDE.codemirror.setOption('extraKeys', {
|
easyMDE.codemirror.setOption('extraKeys', {
|
||||||
|
'Cmd-Enter': codeMirrorQuickSubmit,
|
||||||
|
'Ctrl-Enter': codeMirrorQuickSubmit,
|
||||||
Enter: (cm) => {
|
Enter: (cm) => {
|
||||||
const tributeContainer = document.querySelector('.tribute-container');
|
const tributeContainer = document.querySelector('.tribute-container');
|
||||||
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
||||||
|
@ -149,3 +153,12 @@ export function validateTextareaNonEmpty($textarea) {
|
||||||
$mdeInputField.prop('required', false);
|
$mdeInputField.prop('required', false);
|
||||||
return true;
|
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());
|
||||||
|
}
|
||||||
|
|
|
@ -355,6 +355,11 @@ async function onEditContent(event) {
|
||||||
initEasyMDEImagePaste(easyMDE, $dropzone[0], $dropzone.find('.files'));
|
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', () => {
|
$editContentZone.find('.cancel.button').on('click', () => {
|
||||||
$renderContent.show();
|
$renderContent.show();
|
||||||
$editContentZone.hide();
|
$editContentZone.hide();
|
||||||
|
@ -362,7 +367,8 @@ async function onEditContent(event) {
|
||||||
dz.emit('reload');
|
dz.emit('reload');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$editContentZone.find('.save.button').on('click', () => {
|
|
||||||
|
$saveButton.on('click', () => {
|
||||||
$renderContent.show();
|
$renderContent.show();
|
||||||
$editContentZone.hide();
|
$editContentZone.hide();
|
||||||
const $attachments = $dropzone.find('.files').find('[name=files]').map(function () {
|
const $attachments = $dropzone.find('.files').find('[name=files]').map(function () {
|
||||||
|
@ -400,7 +406,7 @@ async function onEditContent(event) {
|
||||||
initCommentContent();
|
initCommentContent();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else { // use existing form
|
||||||
$textarea = $segment.find('textarea');
|
$textarea = $segment.find('textarea');
|
||||||
easyMDE = getAttachedEasyMDE($textarea);
|
easyMDE = getAttachedEasyMDE($textarea);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import {initMarkupContent} from '../markup/content.js';
|
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';
|
import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
|
||||||
|
|
||||||
const {csrfToken} = window.config;
|
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());
|
attachEasyMDEToElements(easyMDE);
|
||||||
$mdeInputField.addClass('js-quick-submit');
|
|
||||||
|
|
||||||
$form.on('submit', () => {
|
$form.on('submit', () => {
|
||||||
if (!validateTextareaNonEmpty($editArea)) {
|
if (!validateTextareaNonEmpty($editArea)) {
|
||||||
|
|
Loading…
Reference in a new issue