From 992b0a0ab9de64ed94d5856f06f5fa106216ccdb Mon Sep 17 00:00:00 2001 From: sangcheol12 Date: Tue, 15 Oct 2024 13:01:22 +0900 Subject: [PATCH 1/8] prevent comment on uploading --- web_src/js/features/comp/EditorUpload.ts | 6 +++++- web_src/js/features/repo-issue.ts | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index 4cc031e5c818d..49365450a7fe2 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -99,7 +99,11 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) { const {width, dppx} = await imageInfo(file); const placeholder = `[${name}](uploading ...)`; - editor.insertPlaceholder(placeholder); + // Placeholders should be inserted on uploading the thumnail. + // If not, the check upload process may fail + dropzoneEl.dropzone.on('thumbnail', () => { + editor.insertPlaceholder(placeholder); + }); await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload editor.replacePlaceholder(placeholder, generateMarkdownLinkForAttachment(file, {width, dppx})); } diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index e450f561c0a85..57d4639d92f4e 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -674,11 +674,13 @@ export async function initSingleCommentEditor($commentForm) { const commentButton = document.querySelector('#comment-button'); opts.onContentChanged = (editor) => { const editorText = editor.value().trim(); + const isUploading = editor.attachedDropzoneInst.getUploadingFiles().length != 0; + if (statusButton) { statusButton.textContent = statusButton.getAttribute(editorText ? 'data-status-and-comment' : 'data-status'); } if (commentButton) { - commentButton.disabled = !editorText; + commentButton.disabled = !editorText || isUploading; } }; const editor = await initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts); From ddbefb7f40fdce956e7721dca643bc93382ec924 Mon Sep 17 00:00:00 2001 From: sangcheol12 Date: Tue, 15 Oct 2024 15:12:29 +0900 Subject: [PATCH 2/8] add isUploding method in ComboMarkdownEditor --- web_src/js/features/comp/ComboMarkdownEditor.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts index 5f1807f3733ad..a3867593f66fb 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.ts +++ b/web_src/js/features/comp/ComboMarkdownEditor.ts @@ -137,6 +137,11 @@ class ComboMarkdownEditor { this.attachedDropzoneInst.emit(DropzoneCustomEventReloadFiles); } + isUploading() { + if (!this.dropzone) return false; + return this.attachedDropzoneInst.getUploadingFiles().length != 0; + } + setupTab() { const tabs = this.container.querySelectorAll('.tabular.menu > .item'); From d88502559a1166f31a744ca662c53fd269ef0028 Mon Sep 17 00:00:00 2001 From: sangcheol12 Date: Tue, 15 Oct 2024 15:12:56 +0900 Subject: [PATCH 3/8] prevent status button on uploading images --- web_src/js/features/repo-issue.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 57d4639d92f4e..0da6cf44ca723 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -674,10 +674,11 @@ export async function initSingleCommentEditor($commentForm) { const commentButton = document.querySelector('#comment-button'); opts.onContentChanged = (editor) => { const editorText = editor.value().trim(); - const isUploading = editor.attachedDropzoneInst.getUploadingFiles().length != 0; + const isUploading = editor.isUploading(); if (statusButton) { statusButton.textContent = statusButton.getAttribute(editorText ? 'data-status-and-comment' : 'data-status'); + statusButton.disabled = isUploading; } if (commentButton) { commentButton.disabled = !editorText || isUploading; From e286eda1b022c28013c098774d106f8130b8aa89 Mon Sep 17 00:00:00 2001 From: sangcheol12 Date: Tue, 15 Oct 2024 15:29:33 +0900 Subject: [PATCH 4/8] remove insert placeholder event handler after upload success --- web_src/js/features/comp/EditorUpload.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index 49365450a7fe2..8e1eaa6165c2f 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -101,10 +101,10 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) { // Placeholders should be inserted on uploading the thumnail. // If not, the check upload process may fail - dropzoneEl.dropzone.on('thumbnail', () => { - editor.insertPlaceholder(placeholder); - }); + const handleInsertPlaceholder = () => editor.insertPlaceholder(placeholder); + dropzoneEl.dropzone.on('thumbnail', handleInsertPlaceholder); await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload + dropzoneEl.dropzone.off('thumbnail', handleInsertPlaceholder); editor.replacePlaceholder(placeholder, generateMarkdownLinkForAttachment(file, {width, dppx})); } } From 7d13422ddcd34bd77180979317aaf8a89cbee7f7 Mon Sep 17 00:00:00 2001 From: sangcheol12 Date: Tue, 15 Oct 2024 16:16:32 +0900 Subject: [PATCH 5/8] Add try-catch to handle potential upload errors in handleUploadFiles --- web_src/js/features/comp/EditorUpload.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index 8e1eaa6165c2f..f2c1174d29952 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -103,8 +103,13 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) { // If not, the check upload process may fail const handleInsertPlaceholder = () => editor.insertPlaceholder(placeholder); dropzoneEl.dropzone.on('thumbnail', handleInsertPlaceholder); - await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload - dropzoneEl.dropzone.off('thumbnail', handleInsertPlaceholder); + try { + await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload + } catch (err) { + throw err; + } finally { + dropzoneEl.dropzone.off('thumbnail', handleInsertPlaceholder); + } editor.replacePlaceholder(placeholder, generateMarkdownLinkForAttachment(file, {width, dppx})); } } From 34cfdaf37a71613cbd77ee83cc429021d9a93f78 Mon Sep 17 00:00:00 2001 From: sangcheol12 Date: Tue, 15 Oct 2024 17:58:40 +0900 Subject: [PATCH 6/8] disable modify button on image uploading --- web_src/js/features/repo-issue-edit.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index 33a7a10923192..ee214094c48ba 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -78,10 +78,21 @@ async function onEditContent(event) { comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); if (!comboMarkdownEditor) { + const opts = {}; + opts.onContentChanged = (editor) => { + const saveButton = editContentZone.querySelector('.ui.primary.button'); + const isUploading = editor.isUploading(); + if (saveButton) { + saveButton.disabled = isUploading; + } + }; + editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML; - comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); + comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'), opts); + editContentZone.querySelector('.ui.cancel.button').addEventListener('click', cancelAndReset); editContentZone.querySelector('.ui.primary.button').addEventListener('click', saveAndRefresh); + opts.onContentChanged(comboMarkdownEditor); } // Show write/preview tab and copy raw content as needed From f3c7c261785373d989b745313e06f4bb3faaf393 Mon Sep 17 00:00:00 2001 From: sangcheol12 Date: Tue, 22 Oct 2024 15:58:32 +0900 Subject: [PATCH 7/8] prevent upload button on uploading with callback --- web_src/js/features/comp/ComboMarkdownEditor.ts | 6 +++--- web_src/js/features/comp/EditorUpload.ts | 17 +++++------------ web_src/js/features/repo-issue-edit.ts | 2 +- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts index a3867593f66fb..0849b1d03b4b5 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.ts +++ b/web_src/js/features/comp/ComboMarkdownEditor.ts @@ -138,10 +138,10 @@ class ComboMarkdownEditor { } isUploading() { - if (!this.dropzone) return false; - return this.attachedDropzoneInst.getUploadingFiles().length != 0; + if (!this.dropzone) return false; + return this.attachedDropzoneInst.getUploadingFiles().length !== 0; } - + setupTab() { const tabs = this.container.querySelectorAll('.tabular.menu > .item'); diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index f2c1174d29952..b9e3f6dbab2d9 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -10,7 +10,7 @@ import { let uploadIdCounter = 0; -function uploadFile(dropzoneEl, file) { +function uploadFile(dropzoneEl, file, placeholderCallback) { return new Promise((resolve) => { const curUploadId = uploadIdCounter++; file._giteaUploadId = curUploadId; @@ -23,6 +23,8 @@ function uploadFile(dropzoneEl, file) { }; dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone); dropzoneInst.handleFiles([file]); + // if there is no setTimeout, then ComboMarkdownEditor.isUploading() does not working correctly + setTimeout(() => placeholderCallback(), 0); }); } @@ -99,17 +101,8 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) { const {width, dppx} = await imageInfo(file); const placeholder = `[${name}](uploading ...)`; - // Placeholders should be inserted on uploading the thumnail. - // If not, the check upload process may fail - const handleInsertPlaceholder = () => editor.insertPlaceholder(placeholder); - dropzoneEl.dropzone.on('thumbnail', handleInsertPlaceholder); - try { - await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload - } catch (err) { - throw err; - } finally { - dropzoneEl.dropzone.off('thumbnail', handleInsertPlaceholder); - } + // the "file" will get its "uuid" during the upload + await uploadFile(dropzoneEl, file, () => editor.insertPlaceholder(placeholder)); editor.replacePlaceholder(placeholder, generateMarkdownLinkForAttachment(file, {width, dppx})); } } diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index ee214094c48ba..aab7d037c8dd1 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -89,7 +89,7 @@ async function onEditContent(event) { editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML; comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'), opts); - + editContentZone.querySelector('.ui.cancel.button').addEventListener('click', cancelAndReset); editContentZone.querySelector('.ui.primary.button').addEventListener('click', saveAndRefresh); opts.onContentChanged(comboMarkdownEditor); From e94463c18a95d36b8bc17dfd74020a2bd0a5d785 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 22 Oct 2024 17:12:59 +0800 Subject: [PATCH 8/8] fix --- .../js/features/comp/ComboMarkdownEditor.ts | 79 +++++++++++++------ web_src/js/features/comp/EditorMarkdown.ts | 4 +- web_src/js/features/comp/EditorUpload.ts | 21 +++-- web_src/js/features/repo-issue-edit.ts | 24 +++--- web_src/js/features/repo-issue.ts | 48 ++++++----- web_src/js/features/repo-release.ts | 2 +- web_src/js/features/repo-wiki.ts | 4 +- 7 files changed, 107 insertions(+), 75 deletions(-) diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts index 0849b1d03b4b5..d0e122c54ab6b 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.ts +++ b/web_src/js/features/comp/ComboMarkdownEditor.ts @@ -3,14 +3,19 @@ import '@github/text-expander-element'; import $ from 'jquery'; import {attachTribute} from '../tribute.ts'; import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.ts'; -import {initEasyMDEPaste, initTextareaEvents} from './EditorUpload.ts'; +import { + EventUploadStateChanged, + initEasyMDEPaste, + initTextareaEvents, + triggerUploadStateChanged, +} from './EditorUpload.ts'; import {handleGlobalEnterQuickSubmit} from './QuickSubmit.ts'; import {renderPreviewPanelContent} from '../repo-editor.ts'; import {easyMDEToolbarActions} from './EasyMDEToolbarActions.ts'; import {initTextExpander} from './TextExpander.ts'; import {showErrorToast} from '../../modules/toast.ts'; import {POST} from '../../modules/fetch.ts'; -import {initTextareaMarkdown} from './EditorMarkdown.ts'; +import {EventEditorContentChanged, initTextareaMarkdown, triggerEditorContentChanged} from './EditorMarkdown.ts'; import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts'; let elementIdCounter = 0; @@ -37,7 +42,34 @@ export function validateTextareaNonEmpty(textarea) { return true; } -class ComboMarkdownEditor { +export class ComboMarkdownEditor { + static EventEditorContentChanged = EventEditorContentChanged; + static EventUploadStateChanged = EventUploadStateChanged; + + public container : HTMLElement; + + // TODO: use correct types to replace these "any" types + options: any; + + tabEditor: HTMLElement; + tabPreviewer: HTMLElement; + + easyMDE: any; + easyMDEToolbarActions: any; + easyMDEToolbarDefault: any; + + textarea: HTMLTextAreaElement & {_giteaComboMarkdownEditor: any}; + textareaMarkdownToolbar: HTMLElement; + textareaAutosize: any; + + dropzone: HTMLElement; + attachedDropzoneInst: any; + + previewUrl: string; + previewContext: string; + previewMode: string; + previewWiki: boolean; + constructor(container, options = {}) { container._giteaComboMarkdownEditor = this; this.options = options; @@ -63,14 +95,13 @@ class ComboMarkdownEditor { setupContainer() { initTextExpander(this.container.querySelector('text-expander')); - this.container.addEventListener('ce-editor-content-changed', (e) => this.options?.onContentChanged?.(this, e)); } setupTextarea() { this.textarea = this.container.querySelector('.markdown-text-editor'); this.textarea._giteaComboMarkdownEditor = this; this.textarea.id = `_combo_markdown_editor_${String(elementIdCounter++)}`; - this.textarea.addEventListener('input', (e) => this.options?.onContentChanged?.(this, e)); + this.textarea.addEventListener('input', () => triggerEditorContentChanged(this.container)); this.applyEditorHeights(this.textarea, this.options.editorHeights); if (this.textarea.getAttribute('data-disable-autosize') !== 'true') { @@ -115,15 +146,21 @@ class ComboMarkdownEditor { async setupDropzone() { const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container'); - if (dropzoneParentContainer) { - this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone'); - if (this.dropzone) this.attachedDropzoneInst = await initDropzone(this.dropzone); - } + if (!dropzoneParentContainer) return; + this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone'); + if (!this.dropzone) return; + + this.attachedDropzoneInst = await initDropzone(this.dropzone); + // dropzone events + // * "processing" means a file is being uploaded + // * "queuecomplete" means all files have been uploaded + this.attachedDropzoneInst.on('processing', () => triggerUploadStateChanged(this.container)); + this.attachedDropzoneInst.on('queuecomplete', () => triggerUploadStateChanged(this.container)); } dropzoneGetFiles() { if (!this.dropzone) return null; - return Array.from(this.dropzone.querySelectorAll('.files [name=files]'), (el) => el.value); + return Array.from(this.dropzone.querySelectorAll('.files [name=files]'), (el) => el.value); } dropzoneReloadFiles() { @@ -139,11 +176,11 @@ class ComboMarkdownEditor { isUploading() { if (!this.dropzone) return false; - return this.attachedDropzoneInst.getUploadingFiles().length !== 0; + return this.attachedDropzoneInst.getQueuedFiles().length || this.attachedDropzoneInst.getUploadingFiles().length; } setupTab() { - const tabs = this.container.querySelectorAll('.tabular.menu > .item'); + const tabs = this.container.querySelectorAll('.tabular.menu > .item'); // Fomantic Tab requires the "data-tab" to be globally unique. // 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 { formData.append('mode', this.previewMode); formData.append('context', this.previewContext); formData.append('text', this.value()); - formData.append('wiki', this.previewWiki); + formData.append('wiki', String(this.previewWiki)); const response = await POST(this.previewUrl, {data: formData}); const data = await response.text(); renderPreviewPanelContent($(panelPreviewer), data); @@ -242,24 +279,24 @@ class ComboMarkdownEditor { easyMDEOpt.toolbar = this.parseEasyMDEToolbar(EasyMDE, easyMDEOpt.toolbar ?? this.easyMDEToolbarDefault); this.easyMDE = new EasyMDE(easyMDEOpt); - this.easyMDE.codemirror.on('change', (...args) => {this.options?.onContentChanged?.(this, ...args)}); + this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container)); this.easyMDE.codemirror.setOption('extraKeys', { 'Cmd-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()), 'Ctrl-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()), Enter: (cm) => { - const tributeContainer = document.querySelector('.tribute-container'); + const tributeContainer = document.querySelector('.tribute-container'); if (!tributeContainer || tributeContainer.style.display === 'none') { cm.execCommand('newlineAndIndent'); } }, Up: (cm) => { - const tributeContainer = document.querySelector('.tribute-container'); + const tributeContainer = document.querySelector('.tribute-container'); if (!tributeContainer || tributeContainer.style.display === 'none') { return cm.execCommand('goLineUp'); } }, Down: (cm) => { - const tributeContainer = document.querySelector('.tribute-container'); + const tributeContainer = document.querySelector('.tribute-container'); if (!tributeContainer || tributeContainer.style.display === 'none') { return cm.execCommand('goLineDown'); } @@ -319,13 +356,7 @@ export function getComboMarkdownEditor(el) { return el?._giteaComboMarkdownEditor; } -export async function initComboMarkdownEditor(container, options = {}) { - if (container instanceof $) { - if (container.length !== 1) { - throw new Error('initComboMarkdownEditor: container must be a single element'); - } - container = container[0]; - } +export async function initComboMarkdownEditor(container: HTMLElement, options = {}) { if (!container) { throw new Error('initComboMarkdownEditor: container is null'); } diff --git a/web_src/js/features/comp/EditorMarkdown.ts b/web_src/js/features/comp/EditorMarkdown.ts index 9ec71aba74447..deee561dabaa9 100644 --- a/web_src/js/features/comp/EditorMarkdown.ts +++ b/web_src/js/features/comp/EditorMarkdown.ts @@ -1,5 +1,7 @@ +export const EventEditorContentChanged = 'ce-editor-content-changed'; + export function triggerEditorContentChanged(target) { - target.dispatchEvent(new CustomEvent('ce-editor-content-changed', {bubbles: true})); + target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true})); } function handleIndentSelection(textarea, e) { diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index b9e3f6dbab2d9..582639a817bf3 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -7,10 +7,17 @@ import { DropzoneCustomEventUploadDone, generateMarkdownLinkForAttachment, } from '../dropzone.ts'; +import type CodeMirror from 'codemirror'; let uploadIdCounter = 0; -function uploadFile(dropzoneEl, file, placeholderCallback) { +export const EventUploadStateChanged = 'ce-upload-state-changed'; + +export function triggerUploadStateChanged(target) { + target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true})); +} + +function uploadFile(dropzoneEl, file) { return new Promise((resolve) => { const curUploadId = uploadIdCounter++; file._giteaUploadId = curUploadId; @@ -18,17 +25,17 @@ function uploadFile(dropzoneEl, file, placeholderCallback) { const onUploadDone = ({file}) => { if (file._giteaUploadId === curUploadId) { dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone); - resolve(); + resolve(file); } }; dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone); dropzoneInst.handleFiles([file]); - // if there is no setTimeout, then ComboMarkdownEditor.isUploading() does not working correctly - setTimeout(() => placeholderCallback(), 0); }); } class TextareaEditor { + editor : HTMLTextAreaElement; + constructor(editor) { this.editor = editor; } @@ -63,6 +70,8 @@ class TextareaEditor { } class CodeMirrorEditor { + editor: CodeMirror.EditorFromTextArea; + constructor(editor) { this.editor = editor; } @@ -101,8 +110,8 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) { const {width, dppx} = await imageInfo(file); const placeholder = `[${name}](uploading ...)`; - // the "file" will get its "uuid" during the upload - await uploadFile(dropzoneEl, file, () => editor.insertPlaceholder(placeholder)); + editor.insertPlaceholder(placeholder); + await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload editor.replacePlaceholder(placeholder, generateMarkdownLinkForAttachment(file, {width, dppx})); } } diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index aab7d037c8dd1..77a76ad3ca697 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -1,11 +1,12 @@ import $ from 'jquery'; import {handleReply} from './repo-issue.ts'; -import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; +import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; import {POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {hideElem, showElem} from '../utils/dom.ts'; import {attachRefIssueContextPopup} from './contextpopup.ts'; import {initCommentContent, initMarkupContent} from '../markup/content.ts'; +import {triggerUploadStateChanged} from './comp/EditorUpload.ts'; async function onEditContent(event) { event.preventDefault(); @@ -15,7 +16,7 @@ async function onEditContent(event) { const renderContent = segment.querySelector('.render-content'); const rawContent = segment.querySelector('.raw-content'); - let comboMarkdownEditor; + let comboMarkdownEditor : ComboMarkdownEditor; const cancelAndReset = (e) => { e.preventDefault(); @@ -78,21 +79,13 @@ async function onEditContent(event) { comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); if (!comboMarkdownEditor) { - const opts = {}; - opts.onContentChanged = (editor) => { - const saveButton = editContentZone.querySelector('.ui.primary.button'); - const isUploading = editor.isUploading(); - if (saveButton) { - saveButton.disabled = isUploading; - } - }; - editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML; - comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'), opts); - + const saveButton = editContentZone.querySelector('.ui.primary.button'); + comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); + const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading(); + comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState); editContentZone.querySelector('.ui.cancel.button').addEventListener('click', cancelAndReset); - editContentZone.querySelector('.ui.primary.button').addEventListener('click', saveAndRefresh); - opts.onContentChanged(comboMarkdownEditor); + saveButton.addEventListener('click', saveAndRefresh); } // Show write/preview tab and copy raw content as needed @@ -104,6 +97,7 @@ async function onEditContent(event) { } comboMarkdownEditor.switchTabToEditor(); comboMarkdownEditor.focus(); + triggerUploadStateChanged(comboMarkdownEditor.container); } export function initRepoIssueCommentEdit() { diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 0da6cf44ca723..5d5e3568b921f 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -3,7 +3,7 @@ import {htmlEscape} from 'escape-goat'; import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts'; import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; import {setFileFolding} from './file-fold.ts'; -import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; +import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; import {toAbsoluteUrl} from '../utils.ts'; import {GET, POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; @@ -483,9 +483,9 @@ export function initRepoPullRequestReview() { await handleReply(this); }); - const $reviewBox = $('.review-box-panel'); - if ($reviewBox.length === 1) { - const _promise = initComboMarkdownEditor($reviewBox.find('.combo-markdown-editor')); + const elReviewBox = document.querySelector('.review-box-panel'); + if (elReviewBox) { + initComboMarkdownEditor(elReviewBox.querySelector('.combo-markdown-editor')); } // The following part is only for diff views @@ -548,7 +548,7 @@ export function initRepoPullRequestReview() { $td.find("input[name='line']").val(idx); $td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed'); $td.find("input[name='path']").val(path); - const editor = await initComboMarkdownEditor($td.find('.combo-markdown-editor')); + const editor = await initComboMarkdownEditor($td[0].querySelector('.combo-markdown-editor')); editor.focus(); } catch (error) { console.error(error); @@ -669,13 +669,11 @@ export async function initSingleCommentEditor($commentForm) { // pages: // * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content) // * issue/pr view page: with comment form, has status-button and comment-button - const opts = {}; - const statusButton = document.querySelector('#status-button'); - const commentButton = document.querySelector('#comment-button'); - opts.onContentChanged = (editor) => { - const editorText = editor.value().trim(); - const isUploading = editor.isUploading(); - + const editor = await initComboMarkdownEditor($commentForm[0].querySelector('.combo-markdown-editor')); + const statusButton = document.querySelector('#status-button'); + const commentButton = document.querySelector('#comment-button'); + const syncUiState = () => { + const editorText = editor.value().trim(), isUploading = editor.isUploading(); if (statusButton) { statusButton.textContent = statusButton.getAttribute(editorText ? 'data-status-and-comment' : 'data-status'); statusButton.disabled = isUploading; @@ -684,8 +682,9 @@ export async function initSingleCommentEditor($commentForm) { commentButton.disabled = !editorText || isUploading; } }; - const editor = await initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts); - opts.onContentChanged(editor); // sync state of buttons with the initial content + editor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState); + editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, syncUiState); + syncUiState(); } export function initIssueTemplateCommentEditors($commentForm) { @@ -693,16 +692,13 @@ export function initIssueTemplateCommentEditors($commentForm) { // * new issue with issue template const $comboFields = $commentForm.find('.combo-editor-dropzone'); - const initCombo = async ($combo) => { - const $dropzoneContainer = $combo.find('.form-field-dropzone'); - const $formField = $combo.find('.form-field-real'); - const $markdownEditor = $combo.find('.combo-markdown-editor'); + const initCombo = async (elCombo) => { + const $formField = $(elCombo.querySelector('.form-field-real')); + const dropzoneContainer = elCombo.querySelector('.form-field-dropzone'); + const markdownEditor = elCombo.querySelector('.combo-markdown-editor'); - const editor = await initComboMarkdownEditor($markdownEditor, { - onContentChanged: (editor) => { - $formField.val(editor.value()); - }, - }); + const editor = await initComboMarkdownEditor(markdownEditor); + editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value())); $formField.on('focus', async () => { // deactivate all markdown editors @@ -712,8 +708,8 @@ export function initIssueTemplateCommentEditors($commentForm) { // activate this markdown editor hideElem($formField); - showElem($markdownEditor); - showElem($dropzoneContainer); + showElem(markdownEditor); + showElem(dropzoneContainer); await editor.switchToUserPreference(); editor.focus(); @@ -721,7 +717,7 @@ export function initIssueTemplateCommentEditors($commentForm) { }; for (const el of $comboFields) { - initCombo($(el)); + initCombo(el); } } diff --git a/web_src/js/features/repo-release.ts b/web_src/js/features/repo-release.ts index 2928376c7fefe..7589c77136355 100644 --- a/web_src/js/features/repo-release.ts +++ b/web_src/js/features/repo-release.ts @@ -50,7 +50,7 @@ function initTagNameEditor() { } function initRepoReleaseEditor() { - const editor = document.querySelector('.repository.new.release .combo-markdown-editor'); + const editor = document.querySelector('.repository.new.release .combo-markdown-editor'); if (!editor) { return; } diff --git a/web_src/js/features/repo-wiki.ts b/web_src/js/features/repo-wiki.ts index 2c7fb1b1b8c91..1a62427b731c2 100644 --- a/web_src/js/features/repo-wiki.ts +++ b/web_src/js/features/repo-wiki.ts @@ -4,11 +4,11 @@ import {fomanticMobileScreen} from '../modules/fomantic.ts'; import {POST} from '../modules/fetch.ts'; async function initRepoWikiFormEditor() { - const editArea = document.querySelector('.repository.wiki .combo-markdown-editor textarea'); + const editArea = document.querySelector('.repository.wiki .combo-markdown-editor textarea'); if (!editArea) return; const form = document.querySelector('.repository.wiki.new .ui.form'); - const editorContainer = form.querySelector('.combo-markdown-editor'); + const editorContainer = form.querySelector('.combo-markdown-editor'); let editor; let renderRequesting = false;