aboutsummaryrefslogtreecommitdiffstats
path: root/web_src/js/features/repo-legacy.js
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2023-04-03 18:06:57 +0800
committerGitHub <noreply@github.com>2023-04-03 18:06:57 +0800
commit5cc0801de90d16b4d528e62de11c9b525be5d122 (patch)
tree7deaaa2ec388cd91b6b072783d2e4524ef9be263 /web_src/js/features/repo-legacy.js
parentd67e40684f43b0eb744cad26e0265002f033dbc3 (diff)
downloadgitea-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.js331
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();
}
});
}