Skip to content

Commit 620f196

Browse files
sangcheol12wxiaoguangGiteaBot
authored
Prevent from submitting issue/comment on uploading (#32263)
fix #32262 --------- Co-authored-by: wxiaoguang <[email protected]> Co-authored-by: Giteabot <[email protected]>
1 parent a264c46 commit 620f196

File tree

7 files changed

+109
-56
lines changed

7 files changed

+109
-56
lines changed

web_src/js/features/comp/ComboMarkdownEditor.ts

+59-23
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ import '@github/text-expander-element';
33
import $ from 'jquery';
44
import {attachTribute} from '../tribute.ts';
55
import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.ts';
6-
import {initEasyMDEPaste, initTextareaEvents} from './EditorUpload.ts';
6+
import {
7+
EventUploadStateChanged,
8+
initEasyMDEPaste,
9+
initTextareaEvents,
10+
triggerUploadStateChanged,
11+
} from './EditorUpload.ts';
712
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.ts';
813
import {renderPreviewPanelContent} from '../repo-editor.ts';
914
import {easyMDEToolbarActions} from './EasyMDEToolbarActions.ts';
1015
import {initTextExpander} from './TextExpander.ts';
1116
import {showErrorToast} from '../../modules/toast.ts';
1217
import {POST} from '../../modules/fetch.ts';
13-
import {initTextareaMarkdown} from './EditorMarkdown.ts';
18+
import {EventEditorContentChanged, initTextareaMarkdown, triggerEditorContentChanged} from './EditorMarkdown.ts';
1419
import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
1520

1621
let elementIdCounter = 0;
@@ -37,7 +42,34 @@ export function validateTextareaNonEmpty(textarea) {
3742
return true;
3843
}
3944

40-
class ComboMarkdownEditor {
45+
export class ComboMarkdownEditor {
46+
static EventEditorContentChanged = EventEditorContentChanged;
47+
static EventUploadStateChanged = EventUploadStateChanged;
48+
49+
public container : HTMLElement;
50+
51+
// TODO: use correct types to replace these "any" types
52+
options: any;
53+
54+
tabEditor: HTMLElement;
55+
tabPreviewer: HTMLElement;
56+
57+
easyMDE: any;
58+
easyMDEToolbarActions: any;
59+
easyMDEToolbarDefault: any;
60+
61+
textarea: HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
62+
textareaMarkdownToolbar: HTMLElement;
63+
textareaAutosize: any;
64+
65+
dropzone: HTMLElement;
66+
attachedDropzoneInst: any;
67+
68+
previewUrl: string;
69+
previewContext: string;
70+
previewMode: string;
71+
previewWiki: boolean;
72+
4173
constructor(container, options = {}) {
4274
container._giteaComboMarkdownEditor = this;
4375
this.options = options;
@@ -63,14 +95,13 @@ class ComboMarkdownEditor {
6395

6496
setupContainer() {
6597
initTextExpander(this.container.querySelector('text-expander'));
66-
this.container.addEventListener('ce-editor-content-changed', (e) => this.options?.onContentChanged?.(this, e));
6798
}
6899

69100
setupTextarea() {
70101
this.textarea = this.container.querySelector('.markdown-text-editor');
71102
this.textarea._giteaComboMarkdownEditor = this;
72103
this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`;
73-
this.textarea.addEventListener('input', (e) => this.options?.onContentChanged?.(this, e));
104+
this.textarea.addEventListener('input', () => triggerEditorContentChanged(this.container));
74105
this.applyEditorHeights(this.textarea, this.options.editorHeights);
75106

76107
if (this.textarea.getAttribute('data-disable-autosize') !== 'true') {
@@ -115,15 +146,21 @@ class ComboMarkdownEditor {
115146

116147
async setupDropzone() {
117148
const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
118-
if (dropzoneParentContainer) {
119-
this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone');
120-
if (this.dropzone) this.attachedDropzoneInst = await initDropzone(this.dropzone);
121-
}
149+
if (!dropzoneParentContainer) return;
150+
this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone');
151+
if (!this.dropzone) return;
152+
153+
this.attachedDropzoneInst = await initDropzone(this.dropzone);
154+
// dropzone events
155+
// * "processing" means a file is being uploaded
156+
// * "queuecomplete" means all files have been uploaded
157+
this.attachedDropzoneInst.on('processing', () => triggerUploadStateChanged(this.container));
158+
this.attachedDropzoneInst.on('queuecomplete', () => triggerUploadStateChanged(this.container));
122159
}
123160

124161
dropzoneGetFiles() {
125162
if (!this.dropzone) return null;
126-
return Array.from(this.dropzone.querySelectorAll('.files [name=files]'), (el) => el.value);
163+
return Array.from(this.dropzone.querySelectorAll<HTMLInputElement>('.files [name=files]'), (el) => el.value);
127164
}
128165

129166
dropzoneReloadFiles() {
@@ -137,8 +174,13 @@ class ComboMarkdownEditor {
137174
this.attachedDropzoneInst.emit(DropzoneCustomEventReloadFiles);
138175
}
139176

177+
isUploading() {
178+
if (!this.dropzone) return false;
179+
return this.attachedDropzoneInst.getQueuedFiles().length || this.attachedDropzoneInst.getUploadingFiles().length;
180+
}
181+
140182
setupTab() {
141-
const tabs = this.container.querySelectorAll('.tabular.menu > .item');
183+
const tabs = this.container.querySelectorAll<HTMLElement>('.tabular.menu > .item');
142184

143185
// Fomantic Tab requires the "data-tab" to be globally unique.
144186
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
@@ -170,7 +212,7 @@ class ComboMarkdownEditor {
170212
formData.append('mode', this.previewMode);
171213
formData.append('context', this.previewContext);
172214
formData.append('text', this.value());
173-
formData.append('wiki', this.previewWiki);
215+
formData.append('wiki', String(this.previewWiki));
174216
const response = await POST(this.previewUrl, {data: formData});
175217
const data = await response.text();
176218
renderPreviewPanelContent($(panelPreviewer), data);
@@ -237,24 +279,24 @@ class ComboMarkdownEditor {
237279
easyMDEOpt.toolbar = this.parseEasyMDEToolbar(EasyMDE, easyMDEOpt.toolbar ?? this.easyMDEToolbarDefault);
238280

239281
this.easyMDE = new EasyMDE(easyMDEOpt);
240-
this.easyMDE.codemirror.on('change', (...args) => {this.options?.onContentChanged?.(this, ...args)});
282+
this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container));
241283
this.easyMDE.codemirror.setOption('extraKeys', {
242284
'Cmd-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
243285
'Ctrl-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
244286
Enter: (cm) => {
245-
const tributeContainer = document.querySelector('.tribute-container');
287+
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
246288
if (!tributeContainer || tributeContainer.style.display === 'none') {
247289
cm.execCommand('newlineAndIndent');
248290
}
249291
},
250292
Up: (cm) => {
251-
const tributeContainer = document.querySelector('.tribute-container');
293+
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
252294
if (!tributeContainer || tributeContainer.style.display === 'none') {
253295
return cm.execCommand('goLineUp');
254296
}
255297
},
256298
Down: (cm) => {
257-
const tributeContainer = document.querySelector('.tribute-container');
299+
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
258300
if (!tributeContainer || tributeContainer.style.display === 'none') {
259301
return cm.execCommand('goLineDown');
260302
}
@@ -314,13 +356,7 @@ export function getComboMarkdownEditor(el) {
314356
return el?._giteaComboMarkdownEditor;
315357
}
316358

317-
export async function initComboMarkdownEditor(container, options = {}) {
318-
if (container instanceof $) {
319-
if (container.length !== 1) {
320-
throw new Error('initComboMarkdownEditor: container must be a single element');
321-
}
322-
container = container[0];
323-
}
359+
export async function initComboMarkdownEditor(container: HTMLElement, options = {}) {
324360
if (!container) {
325361
throw new Error('initComboMarkdownEditor: container is null');
326362
}

web_src/js/features/comp/EditorMarkdown.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
export const EventEditorContentChanged = 'ce-editor-content-changed';
2+
13
export function triggerEditorContentChanged(target) {
2-
target.dispatchEvent(new CustomEvent('ce-editor-content-changed', {bubbles: true}));
4+
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
35
}
46

57
function handleIndentSelection(textarea, e) {

web_src/js/features/comp/EditorUpload.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ import {
77
DropzoneCustomEventUploadDone,
88
generateMarkdownLinkForAttachment,
99
} from '../dropzone.ts';
10+
import type CodeMirror from 'codemirror';
1011

1112
let uploadIdCounter = 0;
1213

14+
export const EventUploadStateChanged = 'ce-upload-state-changed';
15+
16+
export function triggerUploadStateChanged(target) {
17+
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true}));
18+
}
19+
1320
function uploadFile(dropzoneEl, file) {
1421
return new Promise((resolve) => {
1522
const curUploadId = uploadIdCounter++;
@@ -18,7 +25,7 @@ function uploadFile(dropzoneEl, file) {
1825
const onUploadDone = ({file}) => {
1926
if (file._giteaUploadId === curUploadId) {
2027
dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone);
21-
resolve();
28+
resolve(file);
2229
}
2330
};
2431
dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone);
@@ -27,6 +34,8 @@ function uploadFile(dropzoneEl, file) {
2734
}
2835

2936
class TextareaEditor {
37+
editor : HTMLTextAreaElement;
38+
3039
constructor(editor) {
3140
this.editor = editor;
3241
}
@@ -61,6 +70,8 @@ class TextareaEditor {
6170
}
6271

6372
class CodeMirrorEditor {
73+
editor: CodeMirror.EditorFromTextArea;
74+
6475
constructor(editor) {
6576
this.editor = editor;
6677
}

web_src/js/features/repo-issue-edit.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import $ from 'jquery';
22
import {handleReply} from './repo-issue.ts';
3-
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
3+
import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
44
import {POST} from '../modules/fetch.ts';
55
import {showErrorToast} from '../modules/toast.ts';
66
import {hideElem, showElem} from '../utils/dom.ts';
77
import {attachRefIssueContextPopup} from './contextpopup.ts';
88
import {initCommentContent, initMarkupContent} from '../markup/content.ts';
9+
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
910

1011
async function onEditContent(event) {
1112
event.preventDefault();
@@ -15,7 +16,7 @@ async function onEditContent(event) {
1516
const renderContent = segment.querySelector('.render-content');
1617
const rawContent = segment.querySelector('.raw-content');
1718

18-
let comboMarkdownEditor;
19+
let comboMarkdownEditor : ComboMarkdownEditor;
1920

2021
const cancelAndReset = (e) => {
2122
e.preventDefault();
@@ -79,9 +80,12 @@ async function onEditContent(event) {
7980
comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
8081
if (!comboMarkdownEditor) {
8182
editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML;
83+
const saveButton = editContentZone.querySelector('.ui.primary.button');
8284
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
85+
const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading();
86+
comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
8387
editContentZone.querySelector('.ui.cancel.button').addEventListener('click', cancelAndReset);
84-
editContentZone.querySelector('.ui.primary.button').addEventListener('click', saveAndRefresh);
88+
saveButton.addEventListener('click', saveAndRefresh);
8589
}
8690

8791
// Show write/preview tab and copy raw content as needed
@@ -93,6 +97,7 @@ async function onEditContent(event) {
9397
}
9498
comboMarkdownEditor.switchTabToEditor();
9599
comboMarkdownEditor.focus();
100+
triggerUploadStateChanged(comboMarkdownEditor.container);
96101
}
97102

98103
export function initRepoIssueCommentEdit() {

web_src/js/features/repo-issue.ts

+24-25
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {htmlEscape} from 'escape-goat';
33
import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts';
44
import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
55
import {setFileFolding} from './file-fold.ts';
6-
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
6+
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
77
import {toAbsoluteUrl} from '../utils.ts';
88
import {GET, POST} from '../modules/fetch.ts';
99
import {showErrorToast} from '../modules/toast.ts';
@@ -483,9 +483,9 @@ export function initRepoPullRequestReview() {
483483
await handleReply(this);
484484
});
485485

486-
const $reviewBox = $('.review-box-panel');
487-
if ($reviewBox.length === 1) {
488-
const _promise = initComboMarkdownEditor($reviewBox.find('.combo-markdown-editor'));
486+
const elReviewBox = document.querySelector('.review-box-panel');
487+
if (elReviewBox) {
488+
initComboMarkdownEditor(elReviewBox.querySelector('.combo-markdown-editor'));
489489
}
490490

491491
// The following part is only for diff views
@@ -548,7 +548,7 @@ export function initRepoPullRequestReview() {
548548
$td.find("input[name='line']").val(idx);
549549
$td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
550550
$td.find("input[name='path']").val(path);
551-
const editor = await initComboMarkdownEditor($td.find('.combo-markdown-editor'));
551+
const editor = await initComboMarkdownEditor($td[0].querySelector('.combo-markdown-editor'));
552552
editor.focus();
553553
} catch (error) {
554554
console.error(error);
@@ -669,37 +669,36 @@ export async function initSingleCommentEditor($commentForm) {
669669
// pages:
670670
// * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
671671
// * issue/pr view page: with comment form, has status-button and comment-button
672-
const opts = {};
673-
const statusButton = document.querySelector('#status-button');
674-
const commentButton = document.querySelector('#comment-button');
675-
opts.onContentChanged = (editor) => {
676-
const editorText = editor.value().trim();
672+
const editor = await initComboMarkdownEditor($commentForm[0].querySelector('.combo-markdown-editor'));
673+
const statusButton = document.querySelector<HTMLButtonElement>('#status-button');
674+
const commentButton = document.querySelector<HTMLButtonElement>('#comment-button');
675+
const syncUiState = () => {
676+
const editorText = editor.value().trim(), isUploading = editor.isUploading();
677677
if (statusButton) {
678678
statusButton.textContent = statusButton.getAttribute(editorText ? 'data-status-and-comment' : 'data-status');
679+
statusButton.disabled = isUploading;
679680
}
680681
if (commentButton) {
681-
commentButton.disabled = !editorText;
682+
commentButton.disabled = !editorText || isUploading;
682683
}
683684
};
684-
const editor = await initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts);
685-
opts.onContentChanged(editor); // sync state of buttons with the initial content
685+
editor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
686+
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, syncUiState);
687+
syncUiState();
686688
}
687689

688690
export function initIssueTemplateCommentEditors($commentForm) {
689691
// pages:
690692
// * new issue with issue template
691693
const $comboFields = $commentForm.find('.combo-editor-dropzone');
692694

693-
const initCombo = async ($combo) => {
694-
const $dropzoneContainer = $combo.find('.form-field-dropzone');
695-
const $formField = $combo.find('.form-field-real');
696-
const $markdownEditor = $combo.find('.combo-markdown-editor');
695+
const initCombo = async (elCombo) => {
696+
const $formField = $(elCombo.querySelector('.form-field-real'));
697+
const dropzoneContainer = elCombo.querySelector('.form-field-dropzone');
698+
const markdownEditor = elCombo.querySelector('.combo-markdown-editor');
697699

698-
const editor = await initComboMarkdownEditor($markdownEditor, {
699-
onContentChanged: (editor) => {
700-
$formField.val(editor.value());
701-
},
702-
});
700+
const editor = await initComboMarkdownEditor(markdownEditor);
701+
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value()));
703702

704703
$formField.on('focus', async () => {
705704
// deactivate all markdown editors
@@ -709,16 +708,16 @@ export function initIssueTemplateCommentEditors($commentForm) {
709708

710709
// activate this markdown editor
711710
hideElem($formField);
712-
showElem($markdownEditor);
713-
showElem($dropzoneContainer);
711+
showElem(markdownEditor);
712+
showElem(dropzoneContainer);
714713

715714
await editor.switchToUserPreference();
716715
editor.focus();
717716
});
718717
};
719718

720719
for (const el of $comboFields) {
721-
initCombo($(el));
720+
initCombo(el);
722721
}
723722
}
724723

web_src/js/features/repo-release.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function initTagNameEditor() {
5050
}
5151

5252
function initRepoReleaseEditor() {
53-
const editor = document.querySelector('.repository.new.release .combo-markdown-editor');
53+
const editor = document.querySelector<HTMLElement>('.repository.new.release .combo-markdown-editor');
5454
if (!editor) {
5555
return;
5656
}

web_src/js/features/repo-wiki.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import {fomanticMobileScreen} from '../modules/fomantic.ts';
44
import {POST} from '../modules/fetch.ts';
55

66
async function initRepoWikiFormEditor() {
7-
const editArea = document.querySelector('.repository.wiki .combo-markdown-editor textarea');
7+
const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea');
88
if (!editArea) return;
99

1010
const form = document.querySelector('.repository.wiki.new .ui.form');
11-
const editorContainer = form.querySelector('.combo-markdown-editor');
11+
const editorContainer = form.querySelector<HTMLElement>('.combo-markdown-editor');
1212
let editor;
1313

1414
let renderRequesting = false;

0 commit comments

Comments
 (0)