diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-04-03 18:06:57 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-03 18:06:57 +0800 |
commit | 5cc0801de90d16b4d528e62de11c9b525be5d122 (patch) | |
tree | 7deaaa2ec388cd91b6b072783d2e4524ef9be263 /web_src/js/features/repo-legacy.js | |
parent | d67e40684f43b0eb744cad26e0265002f033dbc3 (diff) | |
download | gitea-5cc0801de90d16b4d528e62de11c9b525be5d122.tar.gz gitea-5cc0801de90d16b4d528e62de11c9b525be5d122.zip |
Introduce GitHub markdown editor, keep EasyMDE as fallback (#23876)
The first step of the plan
* #23290
Thanks to @silverwind for the first try in #15394 . Close #10729 and a
lot of related issues.
The EasyMDE is not removed, now it works as a fallback, users can switch
between these two editors.
Editor list:
* Issue / PR comment
* Issue / PR comment edit
* Issue / PR comment quote reply
* PR diff view, inline comment
* PR diff view, inline comment edit
* PR diff view, inline comment quote reply
* Release editor
* Wiki editor
Some editors have attached dropzone
Screenshots:
<details>
![image](https://user-images.githubusercontent.com/2114189/229363558-7e44dcd4-fb6d-48a0-92f8-bd12f57bb0a0.png)
![image](https://user-images.githubusercontent.com/2114189/229363566-781489c8-5306-4347-9714-d71af5d5b0b1.png)
![image](https://user-images.githubusercontent.com/2114189/229363771-1717bf5c-0f2a-4fc2-ba84-4f5b2a343a11.png)
![image](https://user-images.githubusercontent.com/2114189/229363793-ad362d0f-a045-47bd-8f9d-05a9a842bb39.png)
</details>
---------
Co-authored-by: silverwind <me@silverwind.io>
Diffstat (limited to 'web_src/js/features/repo-legacy.js')
-rw-r--r-- | web_src/js/features/repo-legacy.js | 331 |
1 files changed, 150 insertions, 181 deletions
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 3689c34272..2e39d3762f 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -1,11 +1,8 @@ import $ from 'jquery'; -import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/EasyMDE.js'; -import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; -import {initEasyMDEImagePaste} from './comp/ImagePaste.js'; import { initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete, initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue, - initRepoIssueStatusButton, initRepoIssueTitleEdit, initRepoIssueWipToggle, + initRepoIssueTitleEdit, initRepoIssueWipToggle, initRepoPullRequestUpdate, updateIssuesMeta, handleReply } from './repo-issue.js'; import {initUnicodeEscapeButton} from './repo-unicode-escape.js'; @@ -19,27 +16,27 @@ import { import {initCitationFileCopyContent} from './citation.js'; import {initCompLabelEdit} from './comp/LabelEdit.js'; import {initRepoDiffConversationNav} from './repo-diff.js'; -import {attachTribute} from './tribute.js'; import {createDropzone} from './dropzone.js'; import {initCommentContent, initMarkupContent} from '../markup/content.js'; import {initCompReactionSelector} from './comp/ReactionSelector.js'; import {initRepoSettingBranches} from './repo-settings.js'; import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js'; import {hideElem, showElem} from '../utils/dom.js'; +import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js'; import {attachRefIssueContextPopup} from './contextpopup.js'; const {csrfToken} = window.config; -// if there are draft comments (more than 20 chars), confirm before reloading, to avoid losing comments +// if there are draft comments, confirm before reloading, to avoid losing comments function reloadConfirmDraftComment() { const commentTextareas = [ document.querySelector('.edit-content-zone:not(.gt-hidden) textarea'), - document.querySelector('.edit_area'), + document.querySelector('#comment-form textarea'), ]; for (const textarea of commentTextareas) { - // Most users won't feel too sad if they lose a comment with 10 or 20 chars, they can re-type these in seconds. + // Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds. // But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy. - if (textarea && textarea.value.trim().length > 20) { + if (textarea && textarea.value.trim().length > 10) { textarea.parentElement.scrollIntoView(); if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) { return; @@ -85,25 +82,20 @@ export function initRepoCommentForm() { }); } - (async () => { - const $statusButton = $('#status-button'); - for (const textarea of $commentForm.find('textarea:not(.review-textarea, .no-easymde)')) { - // Don't initialize EasyMDE for the dormant #edit-content-form - if (textarea.closest('#edit-content-form')) { - continue; - } - const easyMDE = await createCommentEasyMDE(textarea, { - 'onChange': () => { - const value = easyMDE?.value().trim(); - $statusButton.text($statusButton.attr(value.length === 0 ? 'data-status' : 'data-status-and-comment')); - }, - }); - initEasyMDEImagePaste(easyMDE, $commentForm.find('.dropzone')); - } - })(); + const $statusButton = $('#status-button'); + $statusButton.on('click', (e) => { + e.preventDefault(); + $('#status').val($statusButton.data('status-val')); + $('#comment-form').trigger('submit'); + }); + + const _promise = initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), { + onContentChanged(editor) { + $statusButton.text($statusButton.attr(editor.value().trim() ? 'data-status-and-comment' : 'data-status')); + }, + }); initBranchSelector(); - initCompMarkupContentPreviewTab($commentForm); // List submits function initListSubmits(selector, outerSelector) { @@ -275,7 +267,7 @@ export function initRepoCommentForm() { } else if (input_id === '#project_id') { icon = svg('octicon-project', 18, 'gt-mr-3'); } else if (input_id === '#assignee_id') { - icon = `<img class="ui avatar image gt-mr-3" src=${$(this).data('avatar')}>`; + icon = `<img class="ui avatar image gt-mr-3" alt="avatar" src=${$(this).data('avatar')}>`; } $list.find('.selected').html(` @@ -322,162 +314,148 @@ async function onEditContent(event) { const $editContentZone = $segment.find('.edit-content-zone'); const $renderContent = $segment.find('.render-content'); const $rawContent = $segment.find('.raw-content'); - let $textarea; - let easyMDE; - // Setup new form - if ($editContentZone.html().length === 0) { - $editContentZone.html($('#edit-content-form').html()); - $textarea = $editContentZone.find('textarea'); - await attachTribute($textarea.get(), {mentions: true, emoji: true}); - - let dz; - const $dropzone = $editContentZone.find('.dropzone'); - if ($dropzone.length === 1) { - $dropzone.data('saved', false); - - const fileUuidDict = {}; - dz = await createDropzone($dropzone[0], { - url: $dropzone.data('upload-url'), - headers: {'X-Csrf-Token': csrfToken}, - maxFiles: $dropzone.data('max-file'), - maxFilesize: $dropzone.data('max-size'), - acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'), - addRemoveLinks: true, - dictDefaultMessage: $dropzone.data('default-message'), - dictInvalidFileType: $dropzone.data('invalid-input-type'), - dictFileTooBig: $dropzone.data('file-too-big'), - dictRemoveFile: $dropzone.data('remove-file'), - timeout: 0, - thumbnailMethod: 'contain', - thumbnailWidth: 480, - thumbnailHeight: 480, - init() { - this.on('success', (file, data) => { - file.uuid = data.uuid; - fileUuidDict[file.uuid] = {submitted: false}; - const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid); - $dropzone.find('.files').append(input); - }); - this.on('removedfile', (file) => { - $(`#${file.uuid}`).remove(); - if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) { - $.post($dropzone.data('remove-url'), { - file: file.uuid, - _csrf: csrfToken, - }); - } - }); - this.on('submit', () => { - $.each(fileUuidDict, (fileUuid) => { - fileUuidDict[fileUuid].submitted = true; + let comboMarkdownEditor; + + const setupDropzone = async ($dropzone) => { + if ($dropzone.length === 0) return null; + $dropzone.data('saved', false); + + const fileUuidDict = {}; + const dz = await createDropzone($dropzone[0], { + url: $dropzone.data('upload-url'), + headers: {'X-Csrf-Token': csrfToken}, + maxFiles: $dropzone.data('max-file'), + maxFilesize: $dropzone.data('max-size'), + acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'), + addRemoveLinks: true, + dictDefaultMessage: $dropzone.data('default-message'), + dictInvalidFileType: $dropzone.data('invalid-input-type'), + dictFileTooBig: $dropzone.data('file-too-big'), + dictRemoveFile: $dropzone.data('remove-file'), + timeout: 0, + thumbnailMethod: 'contain', + thumbnailWidth: 480, + thumbnailHeight: 480, + init() { + this.on('success', (file, data) => { + file.uuid = data.uuid; + fileUuidDict[file.uuid] = {submitted: false}; + const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid); + $dropzone.find('.files').append(input); + }); + this.on('removedfile', (file) => { + $(`#${file.uuid}`).remove(); + if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) { + $.post($dropzone.data('remove-url'), { + file: file.uuid, + _csrf: csrfToken, }); + } + }); + this.on('submit', () => { + $.each(fileUuidDict, (fileUuid) => { + fileUuidDict[fileUuid].submitted = true; }); - this.on('reload', () => { - $.getJSON($editContentZone.data('attachment-url'), (data) => { - dz.removeAllFiles(true); - $dropzone.find('.files').empty(); - $.each(data, function () { - const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`; - dz.emit('addedfile', this); - dz.emit('thumbnail', this, imgSrc); - dz.emit('complete', this); - dz.files.push(this); - fileUuidDict[this.uuid] = {submitted: true}; - $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%'); - const input = $(`<input id="${this.uuid}" name="files" type="hidden">`).val(this.uuid); - $dropzone.find('.files').append(input); - }); + }); + this.on('reload', () => { + $.getJSON($editContentZone.data('attachment-url'), (data) => { + dz.removeAllFiles(true); + $dropzone.find('.files').empty(); + $.each(data, function () { + const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`; + dz.emit('addedfile', this); + dz.emit('thumbnail', this, imgSrc); + dz.emit('complete', this); + dz.files.push(this); + fileUuidDict[this.uuid] = {submitted: true}; + $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%'); + const input = $(`<input id="${this.uuid}" name="files" type="hidden">`).val(this.uuid); + $dropzone.find('.files').append(input); }); }); - }, - }); + }); + }, + }); + dz.emit('reload'); + return dz; + }; + + const cancelAndReset = (dz) => { + showElem($renderContent); + hideElem($editContentZone); + if (dz) { dz.emit('reload'); } - // Give new write/preview data-tab name to distinguish from others - const $editContentForm = $editContentZone.find('.ui.comment.form'); - const $tabMenu = $editContentForm.find('.tabular.menu'); - $tabMenu.attr('data-write', $editContentZone.data('write')); - $tabMenu.attr('data-preview', $editContentZone.data('preview')); - $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write')); - $tabMenu.find('.preview.item').attr('data-tab', $editContentZone.data('preview')); - $editContentForm.find('.write').attr('data-tab', $editContentZone.data('write')); - $editContentForm.find('.preview').attr('data-tab', $editContentZone.data('preview')); - easyMDE = await createCommentEasyMDE($textarea); - - initCompMarkupContentPreviewTab($editContentForm); - initEasyMDEImagePaste(easyMDE, $dropzone); - - const $saveButton = $editContentZone.find('.save.button'); - $textarea.on('ce-quick-submit', () => { - $saveButton.trigger('click'); - }); + }; + + const saveAndRefresh = (dz, $dropzone) => { + showElem($renderContent); + hideElem($editContentZone); + const $attachments = $dropzone.find('.files').find('[name=files]').map(function () { + return $(this).val(); + }).get(); + $.post($editContentZone.data('update-url'), { + _csrf: csrfToken, + content: comboMarkdownEditor.value(), + context: $editContentZone.data('context'), + files: $attachments, + }, (data) => { + if (!data.content) { + $renderContent.html($('#no-content').html()); + $rawContent.text(''); + } else { + $renderContent.html(data.content); + $rawContent.text(comboMarkdownEditor.value()); - $editContentZone.find('.cancel.button').on('click', (e) => { - e.preventDefault(); - showElem($renderContent); - hideElem($editContentZone); + const refIssues = $renderContent.find('p .ref-issue'); + attachRefIssueContextPopup(refIssues); + } + const $content = $segment; + if (!$content.find('.dropzone-attachments').length) { + if (data.attachments !== '') { + $content.append(`<div class="dropzone-attachments"></div>`); + $content.find('.dropzone-attachments').replaceWith(data.attachments); + } + } else if (data.attachments === '') { + $content.find('.dropzone-attachments').remove(); + } else { + $content.find('.dropzone-attachments').replaceWith(data.attachments); + } if (dz) { + dz.emit('submit'); dz.emit('reload'); } + initMarkupContent(); + initCommentContent(); }); + }; - $saveButton.on('click', () => { - showElem($renderContent); - hideElem($editContentZone); - const $attachments = $dropzone.find('.files').find('[name=files]').map(function () { - return $(this).val(); - }).get(); - $.post($editContentZone.data('update-url'), { - _csrf: csrfToken, - content: $textarea.val(), - context: $editContentZone.data('context'), - files: $attachments, - }, (data) => { - if (data.length === 0 || data.content.length === 0) { - $renderContent.html($('#no-content').html()); - $rawContent.text(''); - } else { - $renderContent.html(data.content); - $rawContent.text($textarea.val()); - const refIssues = $renderContent.find('p .ref-issue'); - attachRefIssueContextPopup(refIssues); - } - const $content = $segment; - if (!$content.find('.dropzone-attachments').length) { - if (data.attachments !== '') { - $content.append(`<div class="dropzone-attachments"></div>`); - $content.find('.dropzone-attachments').replaceWith(data.attachments); - } - } else if (data.attachments === '') { - $content.find('.dropzone-attachments').remove(); - } else { - $content.find('.dropzone-attachments').replaceWith(data.attachments); - } - if (dz) { - dz.emit('submit'); - dz.emit('reload'); - } - initMarkupContent(); - initCommentContent(); - }); + if (!$editContentZone.html()) { + $editContentZone.html($('#issue-comment-editor-template').html()); + comboMarkdownEditor = await initComboMarkdownEditor($editContentZone.find('.combo-markdown-editor')); + + const $dropzone = $editContentZone.find('.dropzone'); + const dz = await setupDropzone($dropzone); + $editContentZone.find('.cancel.button').on('click', (e) => { + e.preventDefault(); + cancelAndReset(dz); + }); + $editContentZone.find('.save.button').on('click', (e) => { + e.preventDefault(); + saveAndRefresh(dz, $dropzone); }); - } else { // use existing form - $textarea = $segment.find('textarea'); - easyMDE = getAttachedEasyMDE($textarea); + } else { + comboMarkdownEditor = getComboMarkdownEditor($editContentZone.find('.combo-markdown-editor')); } // Show write/preview tab and copy raw content as needed showElem($editContentZone); hideElem($renderContent); - if ($textarea.val().length === 0) { - $textarea.val($rawContent.text()); - easyMDE.value($rawContent.text()); + if (!comboMarkdownEditor.value()) { + comboMarkdownEditor.value($rawContent.text()); } - requestAnimationFrame(() => { - $textarea.focus(); - easyMDE.codemirror.focus(); - }); + comboMarkdownEditor.focus(); } export function initRepository() { @@ -575,7 +553,6 @@ export function initRepository() { initRepoIssueCommentDelete(); initRepoIssueDependencyDelete(); initRepoIssueCodeCommentCancel(); - initRepoIssueStatusButton(); initRepoPullRequestUpdate(); initCompReactionSelector(); @@ -592,12 +569,6 @@ export function initRepository() { const $form = $repoComparePull.find('.pullrequest-form'); showElem($form); - $form.find('textarea.edit_area').each(function() { - const easyMDE = getAttachedEasyMDE($(this)); - if (easyMDE) { - easyMDE.codemirror.refresh(); - } - }); }); } @@ -614,24 +585,22 @@ function initRepoIssueCommentEdit() { const target = $(this).data('target'); const quote = $(`#${target}`).text().replace(/\n/g, '\n> '); const content = `> ${quote}\n\n`; - let easyMDE; + let editor; if ($(this).hasClass('quote-reply-diff')) { const $replyBtn = $(this).closest('.comment-code-cloud').find('button.comment-form-reply'); - easyMDE = await handleReply($replyBtn); + editor = await handleReply($replyBtn); } else { // for normal issue/comment page - easyMDE = getAttachedEasyMDE($('#comment-form .edit_area')); + editor = getComboMarkdownEditor($('#comment-form .combo-markdown-editor')); } - if (easyMDE) { - if (easyMDE.value() !== '') { - easyMDE.value(`${easyMDE.value()}\n\n${content}`); + if (editor) { + if (editor.value()) { + editor.value(`${editor.value()}\n\n${content}`); } else { - easyMDE.value(`${content}`); + editor.value(content); } - requestAnimationFrame(() => { - easyMDE.codemirror.focus(); - easyMDE.codemirror.setCursor(easyMDE.codemirror.lineCount(), 0); - }); + editor.focus(); + editor.moveCursorToEnd(); } }); } |