Skip to content

Commit e94463c

Browse files
committed
fix
1 parent f3c7c26 commit e94463c

File tree

7 files changed

+107
-75
lines changed

7 files changed

+107
-75
lines changed

web_src/js/features/comp/ComboMarkdownEditor.ts

Lines changed: 55 additions & 24 deletions
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() {
@@ -139,11 +176,11 @@ class ComboMarkdownEditor {
139176

140177
isUploading() {
141178
if (!this.dropzone) return false;
142-
return this.attachedDropzoneInst.getUploadingFiles().length !== 0;
179+
return this.attachedDropzoneInst.getQueuedFiles().length || this.attachedDropzoneInst.getUploadingFiles().length;
143180
}
144181

145182
setupTab() {
146-
const tabs = this.container.querySelectorAll('.tabular.menu > .item');
183+
const tabs = this.container.querySelectorAll<HTMLElement>('.tabular.menu > .item');
147184

148185
// Fomantic Tab requires the "data-tab" to be globally unique.
149186
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
@@ -175,7 +212,7 @@ class ComboMarkdownEditor {
175212
formData.append('mode', this.previewMode);
176213
formData.append('context', this.previewContext);
177214
formData.append('text', this.value());
178-
formData.append('wiki', this.previewWiki);
215+
formData.append('wiki', String(this.previewWiki));
179216
const response = await POST(this.previewUrl, {data: formData});
180217
const data = await response.text();
181218
renderPreviewPanelContent($(panelPreviewer), data);
@@ -242,24 +279,24 @@ class ComboMarkdownEditor {
242279
easyMDEOpt.toolbar = this.parseEasyMDEToolbar(EasyMDE, easyMDEOpt.toolbar ?? this.easyMDEToolbarDefault);
243280

244281
this.easyMDE = new EasyMDE(easyMDEOpt);
245-
this.easyMDE.codemirror.on('change', (...args) => {this.options?.onContentChanged?.(this, ...args)});
282+
this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container));
246283
this.easyMDE.codemirror.setOption('extraKeys', {
247284
'Cmd-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
248285
'Ctrl-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
249286
Enter: (cm) => {
250-
const tributeContainer = document.querySelector('.tribute-container');
287+
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
251288
if (!tributeContainer || tributeContainer.style.display === 'none') {
252289
cm.execCommand('newlineAndIndent');
253290
}
254291
},
255292
Up: (cm) => {
256-
const tributeContainer = document.querySelector('.tribute-container');
293+
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
257294
if (!tributeContainer || tributeContainer.style.display === 'none') {
258295
return cm.execCommand('goLineUp');
259296
}
260297
},
261298
Down: (cm) => {
262-
const tributeContainer = document.querySelector('.tribute-container');
299+
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
263300
if (!tributeContainer || tributeContainer.style.display === 'none') {
264301
return cm.execCommand('goLineDown');
265302
}
@@ -319,13 +356,7 @@ export function getComboMarkdownEditor(el) {
319356
return el?._giteaComboMarkdownEditor;
320357
}
321358

322-
export async function initComboMarkdownEditor(container, options = {}) {
323-
if (container instanceof $) {
324-
if (container.length !== 1) {
325-
throw new Error('initComboMarkdownEditor: container must be a single element');
326-
}
327-
container = container[0];
328-
}
359+
export async function initComboMarkdownEditor(container: HTMLElement, options = {}) {
329360
if (!container) {
330361
throw new Error('initComboMarkdownEditor: container is null');
331362
}

web_src/js/features/comp/EditorMarkdown.ts

Lines changed: 3 additions & 1 deletion
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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,35 @@ import {
77
DropzoneCustomEventUploadDone,
88
generateMarkdownLinkForAttachment,
99
} from '../dropzone.ts';
10+
import type CodeMirror from 'codemirror';
1011

1112
let uploadIdCounter = 0;
1213

13-
function uploadFile(dropzoneEl, file, placeholderCallback) {
14+
export const EventUploadStateChanged = 'ce-upload-state-changed';
15+
16+
export function triggerUploadStateChanged(target) {
17+
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true}));
18+
}
19+
20+
function uploadFile(dropzoneEl, file) {
1421
return new Promise((resolve) => {
1522
const curUploadId = uploadIdCounter++;
1623
file._giteaUploadId = curUploadId;
1724
const dropzoneInst = dropzoneEl.dropzone;
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);
2532
dropzoneInst.handleFiles([file]);
26-
// if there is no setTimeout, then ComboMarkdownEditor.isUploading() does not working correctly
27-
setTimeout(() => placeholderCallback(), 0);
2833
});
2934
}
3035

3136
class TextareaEditor {
37+
editor : HTMLTextAreaElement;
38+
3239
constructor(editor) {
3340
this.editor = editor;
3441
}
@@ -63,6 +70,8 @@ class TextareaEditor {
6370
}
6471

6572
class CodeMirrorEditor {
73+
editor: CodeMirror.EditorFromTextArea;
74+
6675
constructor(editor) {
6776
this.editor = editor;
6877
}
@@ -101,8 +110,8 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) {
101110
const {width, dppx} = await imageInfo(file);
102111
const placeholder = `[${name}](uploading ...)`;
103112

104-
// the "file" will get its "uuid" during the upload
105-
await uploadFile(dropzoneEl, file, () => editor.insertPlaceholder(placeholder));
113+
editor.insertPlaceholder(placeholder);
114+
await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload
106115
editor.replacePlaceholder(placeholder, generateMarkdownLinkForAttachment(file, {width, dppx}));
107116
}
108117
}

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

Lines changed: 9 additions & 15 deletions
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();
@@ -78,21 +79,13 @@ async function onEditContent(event) {
7879

7980
comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
8081
if (!comboMarkdownEditor) {
81-
const opts = {};
82-
opts.onContentChanged = (editor) => {
83-
const saveButton = editContentZone.querySelector('.ui.primary.button');
84-
const isUploading = editor.isUploading();
85-
if (saveButton) {
86-
saveButton.disabled = isUploading;
87-
}
88-
};
89-
9082
editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML;
91-
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'), opts);
92-
83+
const saveButton = editContentZone.querySelector('.ui.primary.button');
84+
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
85+
const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading();
86+
comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
9387
editContentZone.querySelector('.ui.cancel.button').addEventListener('click', cancelAndReset);
94-
editContentZone.querySelector('.ui.primary.button').addEventListener('click', saveAndRefresh);
95-
opts.onContentChanged(comboMarkdownEditor);
88+
saveButton.addEventListener('click', saveAndRefresh);
9689
}
9790

9891
// Show write/preview tab and copy raw content as needed
@@ -104,6 +97,7 @@ async function onEditContent(event) {
10497
}
10598
comboMarkdownEditor.switchTabToEditor();
10699
comboMarkdownEditor.focus();
100+
triggerUploadStateChanged(comboMarkdownEditor.container);
107101
}
108102

109103
export function initRepoIssueCommentEdit() {

web_src/js/features/repo-issue.ts

Lines changed: 22 additions & 26 deletions
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,13 +669,11 @@ 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();
677-
const isUploading = editor.isUploading();
678-
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();
679677
if (statusButton) {
680678
statusButton.textContent = statusButton.getAttribute(editorText ? 'data-status-and-comment' : 'data-status');
681679
statusButton.disabled = isUploading;
@@ -684,25 +682,23 @@ export async function initSingleCommentEditor($commentForm) {
684682
commentButton.disabled = !editorText || isUploading;
685683
}
686684
};
687-
const editor = await initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts);
688-
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();
689688
}
690689

691690
export function initIssueTemplateCommentEditors($commentForm) {
692691
// pages:
693692
// * new issue with issue template
694693
const $comboFields = $commentForm.find('.combo-editor-dropzone');
695694

696-
const initCombo = async ($combo) => {
697-
const $dropzoneContainer = $combo.find('.form-field-dropzone');
698-
const $formField = $combo.find('.form-field-real');
699-
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');
700699

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

707703
$formField.on('focus', async () => {
708704
// deactivate all markdown editors
@@ -712,16 +708,16 @@ export function initIssueTemplateCommentEditors($commentForm) {
712708

713709
// activate this markdown editor
714710
hideElem($formField);
715-
showElem($markdownEditor);
716-
showElem($dropzoneContainer);
711+
showElem(markdownEditor);
712+
showElem(dropzoneContainer);
717713

718714
await editor.switchToUserPreference();
719715
editor.focus();
720716
});
721717
};
722718

723719
for (const el of $comboFields) {
724-
initCombo($(el));
720+
initCombo(el);
725721
}
726722
}
727723

0 commit comments

Comments
 (0)