diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2022-01-05 20:17:25 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-05 13:17:25 +0100 |
commit | a38ba634a4da15fbb2d1b6ac6742cf01c1503ea4 (patch) | |
tree | 9bf892ae0e481e2d3b36b1eaf9a5273152c3c628 /web_src/js/features | |
parent | 0572c78938d3d8287f81f53effc7ffaacd75d3e7 (diff) | |
download | gitea-a38ba634a4da15fbb2d1b6ac6742cf01c1503ea4.tar.gz gitea-a38ba634a4da15fbb2d1b6ac6742cf01c1503ea4.zip |
Load EasyMDE/CodeMirror dynamically, remove RequireEasyMDE (#18069)
This PR makes frontend load EasyMDE/CodeMirror dynamically, and removes `RequireEasyMDE`.
Diffstat (limited to 'web_src/js/features')
-rw-r--r-- | web_src/js/features/comp/EasyMDE.js (renamed from web_src/js/features/comp/CommentEasyMDE.js) | 60 | ||||
-rw-r--r-- | web_src/js/features/repo-diff.js | 2 | ||||
-rw-r--r-- | web_src/js/features/repo-issue.js | 13 | ||||
-rw-r--r-- | web_src/js/features/repo-legacy.js | 5 | ||||
-rw-r--r-- | web_src/js/features/repo-release.js | 18 | ||||
-rw-r--r-- | web_src/js/features/repo-wiki.js | 319 |
6 files changed, 237 insertions, 180 deletions
diff --git a/web_src/js/features/comp/CommentEasyMDE.js b/web_src/js/features/comp/EasyMDE.js index d3447d7ba2..d5c1a4c734 100644 --- a/web_src/js/features/comp/CommentEasyMDE.js +++ b/web_src/js/features/comp/EasyMDE.js @@ -1,11 +1,58 @@ import attachTribute from '../tribute.js'; +const {appSubUrl} = window.config; + +function loadScript(url) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.async = true; + script.addEventListener('load', () => { + resolve(); + }); + script.addEventListener('error', (e) => { + reject(e.error); + }); + script.src = url; + document.body.appendChild(script); + }); +} + +/** + * @returns {EasyMDE} + */ +export async function importEasyMDE() { + // for CodeMirror: the plugins should be loaded dynamically + // https://github.com/codemirror/CodeMirror/issues/5484 + // https://github.com/codemirror/CodeMirror/issues/4838 + + const [{default: EasyMDE}, {default: CodeMirror}] = await Promise.all([ + import(/* webpackChunkName: "easymde" */'easymde'), + import(/* webpackChunkName: "codemirror" */'codemirror'), + import(/* webpackChunkName: "easymde" */'easymde/dist/easymde.min.css'), + ]); + + // CodeMirror plugins must be loaded by a "Plain browser env" + window.CodeMirror = CodeMirror; + await Promise.all([ + loadScript(`${appSubUrl}/assets/vendor/plugins/codemirror/addon/mode/loadmode.js`), + loadScript(`${appSubUrl}/assets/vendor/plugins/codemirror/mode/meta.js`), + ]); + + // the loadmode.js/meta.js would set the modeURL/modeInfo properties, so we check it to make sure our loading works + if (!CodeMirror.modeURL || !CodeMirror.modeInfo) { + throw new Error('failed to load plugins for CodeMirror'); + } + + CodeMirror.modeURL = `${appSubUrl}/assets/vendor/plugins/codemirror/mode/%N/%N.js`; + return EasyMDE; +} + /** * create an EasyMDE editor for comment * @param textarea jQuery or HTMLElement * @returns {null|EasyMDE} */ -export function createCommentEasyMDE(textarea) { +export async function createCommentEasyMDE(textarea) { if (textarea instanceof jQuery) { textarea = textarea[0]; } @@ -13,12 +60,13 @@ export function createCommentEasyMDE(textarea) { return null; } - const easyMDE = new window.EasyMDE({ + const EasyMDE = await importEasyMDE(); + const easyMDE = new EasyMDE({ autoDownloadFontAwesome: false, element: textarea, forceSync: true, renderingConfig: { - singleLineBreaks: false + singleLineBreaks: false, }, indentWithTabs: false, tabSize: 4, @@ -56,7 +104,7 @@ export function createCommentEasyMDE(textarea) { className: 'fa fa-file', title: 'Revert to simple textarea', }, - ] + ], }); const inputField = easyMDE.codemirror.getInputField(); inputField.classList.add('js-quick-submit'); @@ -64,7 +112,7 @@ export function createCommentEasyMDE(textarea) { Enter: () => { const tributeContainer = document.querySelector('.tribute-container'); if (!tributeContainer || tributeContainer.style.display === 'none') { - return CodeMirror.Pass; + return window.CodeMirror.Pass; } }, Backspace: (cm) => { @@ -72,7 +120,7 @@ export function createCommentEasyMDE(textarea) { cm.getInputField().trigger('input'); } cm.execCommand('delCharBefore'); - } + }, }); attachTribute(inputField, {mentions: true, emoji: true}); attachEasyMDEToElements(easyMDE); diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 3378222ae1..3d937bbdb1 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -1,6 +1,6 @@ import {initCompReactionSelector} from './comp/ReactionSelector.js'; import {initRepoIssueContentHistory} from './repo-issue-content.js'; -import {validateTextareaNonEmpty} from './comp/CommentEasyMDE.js'; +import {validateTextareaNonEmpty} from './comp/EasyMDE.js'; const {csrfToken} = window.config; export function initRepoDiffReviewButton() { diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 545f59a4d8..c2b0254a81 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -1,6 +1,6 @@ import {htmlEscape} from 'escape-goat'; import attachTribute from './tribute.js'; -import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/CommentEasyMDE.js'; +import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/EasyMDE.js'; import {initCompImagePaste} from './comp/ImagePaste.js'; import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; @@ -439,16 +439,17 @@ export function initRepoPullRequestReview() { $(`#show-outdated-${id}`).removeClass('hide'); }); - $(document).on('click', 'button.comment-form-reply', function (e) { + $(document).on('click', 'button.comment-form-reply', async function (e) { e.preventDefault(); + $(this).hide(); const form = $(this).closest('.comment-code-cloud').find('.comment-form'); form.removeClass('hide'); const $textarea = form.find('textarea'); let easyMDE = getAttachedEasyMDE($textarea); if (!easyMDE) { - attachTribute($textarea.get(), {mentions: true, emoji: true}); - easyMDE = createCommentEasyMDE($textarea); + await attachTribute($textarea.get(), {mentions: true, emoji: true}); + easyMDE = await createCommentEasyMDE($textarea); } $textarea.focus(); easyMDE.codemirror.focus(); @@ -515,8 +516,8 @@ export function initRepoPullRequestReview() { td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed'); td.find("input[name='path']").val(path); const $textarea = commentCloud.find('textarea'); - attachTribute($textarea.get(), {mentions: true, emoji: true}); - const easyMDE = createCommentEasyMDE($textarea); + await attachTribute($textarea.get(), {mentions: true, emoji: true}); + const easyMDE = await createCommentEasyMDE($textarea); $textarea.focus(); easyMDE.codemirror.focus(); } diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 87d311716a..fccec8ccac 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -1,4 +1,4 @@ -import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/CommentEasyMDE.js'; +import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/EasyMDE.js'; import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; import {initCompImagePaste, initEasyMDEImagePaste} from './comp/ImagePaste.js'; import { @@ -256,6 +256,7 @@ export function initRepoCommentForm() { async function onEditContent(event) { event.preventDefault(); + $(this).closest('.dropdown').find('.menu').toggle('visible'); const $segment = $(this).closest('.header').next(); const $editContentZone = $segment.find('.edit-content-zone'); @@ -341,7 +342,7 @@ async function onEditContent(event) { $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 = createCommentEasyMDE($textarea); + easyMDE = await createCommentEasyMDE($textarea); initCompMarkupContentPreviewTab($editContentForm); if ($dropzone.length === 1) { diff --git a/web_src/js/features/repo-release.js b/web_src/js/features/repo-release.js index f69ce37d6b..915e722546 100644 --- a/web_src/js/features/repo-release.js +++ b/web_src/js/features/repo-release.js @@ -1,7 +1,7 @@ import attachTribute from './tribute.js'; import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; import {initEasyMDEImagePaste} from './comp/ImagePaste.js'; -import {createCommentEasyMDE} from './comp/CommentEasyMDE.js'; +import {createCommentEasyMDE} from './comp/EasyMDE.js'; export function initRepoRelease() { $(document).on('click', '.remove-rel-attach', function() { @@ -19,11 +19,13 @@ export function initRepoReleaseEditor() { return false; } - const $textarea = $editor.find('textarea'); - attachTribute($textarea.get(), {mentions: false, emoji: true}); - const $files = $editor.parent().find('.files'); - const easyMDE = createCommentEasyMDE($textarea); - initCompMarkupContentPreviewTab($editor); - const dropzone = $editor.parent().find('.dropzone')[0]; - initEasyMDEImagePaste(easyMDE, dropzone, $files); + (async () => { + const $textarea = $editor.find('textarea'); + await attachTribute($textarea.get(), {mentions: false, emoji: true}); + const $files = $editor.parent().find('.files'); + const easyMDE = await createCommentEasyMDE($textarea); + initCompMarkupContentPreviewTab($editor); + const dropzone = $editor.parent().find('.dropzone')[0]; + initEasyMDEImagePaste(easyMDE, dropzone, $files); + })(); } diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js index b76dac030e..c0dc8d1b41 100644 --- a/web_src/js/features/repo-wiki.js +++ b/web_src/js/features/repo-wiki.js @@ -1,186 +1,191 @@ import {initMarkupContent} from '../markup/content.js'; -import {attachEasyMDEToElements, validateTextareaNonEmpty} from './comp/CommentEasyMDE.js'; +import {attachEasyMDEToElements, importEasyMDE, validateTextareaNonEmpty} from './comp/EasyMDE.js'; import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; const {csrfToken} = window.config; -export function initRepoWikiForm() { +async function initRepoWikiFormEditor() { const $editArea = $('.repository.wiki textarea#edit_area'); + if (!$editArea.length) return; + let sideBySideChanges = 0; let sideBySideTimeout = null; let hasEasyMDE = true; - if ($editArea.length > 0) { - const $form = $('.repository.wiki.new .ui.form'); - const easyMDE = new window.EasyMDE({ - autoDownloadFontAwesome: false, - element: $editArea[0], - forceSync: true, - previewRender(plainText, preview) { // Async method - // FIXME: still send render request when return back to edit mode - const render = function () { - sideBySideChanges = 0; + const $form = $('.repository.wiki.new .ui.form'); + const EasyMDE = await importEasyMDE(); + const easyMDE = new EasyMDE({ + autoDownloadFontAwesome: false, + element: $editArea[0], + forceSync: true, + previewRender(plainText, preview) { // Async method + // FIXME: still send render request when return back to edit mode + const render = function () { + sideBySideChanges = 0; + if (sideBySideTimeout !== null) { + clearTimeout(sideBySideTimeout); + sideBySideTimeout = null; + } + $.post($editArea.data('url'), { + _csrf: csrfToken, + mode: 'gfm', + context: $editArea.data('context'), + text: plainText, + wiki: true + }, (data) => { + preview.innerHTML = `<div class="markup ui segment">${data}</div>`; + initMarkupContent(); + }); + }; + + setTimeout(() => { + if (!easyMDE.isSideBySideActive()) { + render(); + } else { + // delay preview by keystroke counting + sideBySideChanges++; + if (sideBySideChanges > 10) { + render(); + } + // or delay preview by timeout if (sideBySideTimeout !== null) { clearTimeout(sideBySideTimeout); sideBySideTimeout = null; } - $.post($editArea.data('url'), { - _csrf: csrfToken, - mode: 'gfm', - context: $editArea.data('context'), - text: plainText, - wiki: true - }, (data) => { - preview.innerHTML = `<div class="markup ui segment">${data}</div>`; - initMarkupContent(); - }); - }; - - setTimeout(() => { - if (!easyMDE.isSideBySideActive()) { - render(); - } else { - // delay preview by keystroke counting - sideBySideChanges++; - if (sideBySideChanges > 10) { - render(); - } - // or delay preview by timeout - if (sideBySideTimeout !== null) { - clearTimeout(sideBySideTimeout); - sideBySideTimeout = null; - } - sideBySideTimeout = setTimeout(render, 600); - } - }, 0); - if (!easyMDE.isSideBySideActive()) { - return 'Loading...'; + sideBySideTimeout = setTimeout(render, 600); } - return preview.innerHTML; - }, - renderingConfig: { - singleLineBreaks: false + }, 0); + if (!easyMDE.isSideBySideActive()) { + return 'Loading...'; + } + return preview.innerHTML; + }, + renderingConfig: { + singleLineBreaks: false + }, + indentWithTabs: false, + tabSize: 4, + spellChecker: false, + toolbar: ['bold', 'italic', 'strikethrough', '|', + 'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|', + { + name: 'code-inline', + action(e) { + const cm = e.codemirror; + const selection = cm.getSelection(); + cm.replaceSelection(`\`${selection}\``); + if (!selection) { + const cursorPos = cm.getCursor(); + cm.setCursor(cursorPos.line, cursorPos.ch - 1); + } + cm.focus(); + }, + className: 'fa fa-angle-right', + title: 'Add Inline Code', + }, 'code', 'quote', '|', { + name: 'checkbox-empty', + action(e) { + const cm = e.codemirror; + cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`); + cm.focus(); + }, + className: 'fa fa-square-o', + title: 'Add Checkbox (empty)', }, - indentWithTabs: false, - tabSize: 4, - spellChecker: false, - toolbar: ['bold', 'italic', 'strikethrough', '|', - 'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|', - { - name: 'code-inline', - action(e) { - const cm = e.codemirror; - const selection = cm.getSelection(); - cm.replaceSelection(`\`${selection}\``); - if (!selection) { - const cursorPos = cm.getCursor(); - cm.setCursor(cursorPos.line, cursorPos.ch - 1); - } - cm.focus(); - }, - className: 'fa fa-angle-right', - title: 'Add Inline Code', - }, 'code', 'quote', '|', { - name: 'checkbox-empty', - action(e) { - const cm = e.codemirror; - cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`); - cm.focus(); - }, - className: 'fa fa-square-o', - title: 'Add Checkbox (empty)', + { + name: 'checkbox-checked', + action(e) { + const cm = e.codemirror; + cm.replaceSelection(`\n- [x] ${cm.getSelection()}`); + cm.focus(); }, - { - name: 'checkbox-checked', - action(e) { - const cm = e.codemirror; - cm.replaceSelection(`\n- [x] ${cm.getSelection()}`); - cm.focus(); - }, - className: 'fa fa-check-square-o', - title: 'Add Checkbox (checked)', - }, '|', - 'unordered-list', 'ordered-list', '|', - 'link', 'image', 'table', 'horizontal-rule', '|', - 'clean-block', 'preview', 'fullscreen', 'side-by-side', '|', - { - name: 'revert-to-textarea', - action(e) { - e.toTextArea(); - hasEasyMDE = false; - const $root = $form.find('.field.content'); - const loading = $root.data('loading'); - $root.append(`<div class="ui bottom tab markup" data-tab="preview">${loading}</div>`); - initCompMarkupContentPreviewTab($form); - }, - className: 'fa fa-file', - title: 'Revert to simple textarea', + className: 'fa fa-check-square-o', + title: 'Add Checkbox (checked)', + }, '|', + 'unordered-list', 'ordered-list', '|', + 'link', 'image', 'table', 'horizontal-rule', '|', + 'clean-block', 'preview', 'fullscreen', 'side-by-side', '|', + { + name: 'revert-to-textarea', + action(e) { + e.toTextArea(); + hasEasyMDE = false; + const $root = $form.find('.field.content'); + const loading = $root.data('loading'); + $root.append(`<div class="ui bottom tab markup" data-tab="preview">${loading}</div>`); + initCompMarkupContentPreviewTab($form); }, - ] - }); + className: 'fa fa-file', + title: 'Revert to simple textarea', + }, + ] + }); + + attachEasyMDEToElements(easyMDE); - attachEasyMDEToElements(easyMDE); + const $mdeInputField = $(easyMDE.codemirror.getInputField()); + $mdeInputField.addClass('js-quick-submit'); - const $mdeInputField = $(easyMDE.codemirror.getInputField()); - $mdeInputField.addClass('js-quick-submit'); + $form.on('submit', () => { + if (!validateTextareaNonEmpty($editArea)) { + return false; + } + }); - $form.on('submit', () => { - if (!validateTextareaNonEmpty($editArea)) { + setTimeout(() => { + const $bEdit = $('.repository.wiki.new .previewtabs a[data-tab="write"]'); + const $bPrev = $('.repository.wiki.new .previewtabs a[data-tab="preview"]'); + const $toolbar = $('.editor-toolbar'); + const $bPreview = $('.editor-toolbar button.preview'); + const $bSideBySide = $('.editor-toolbar a.fa-columns'); + $bEdit.on('click', (e) => { + if (!hasEasyMDE) { return false; } - }); + e.stopImmediatePropagation(); + if ($toolbar.hasClass('disabled-for-preview')) { + $bPreview.trigger('click'); + } - setTimeout(() => { - const $bEdit = $('.repository.wiki.new .previewtabs a[data-tab="write"]'); - const $bPrev = $('.repository.wiki.new .previewtabs a[data-tab="preview"]'); - const $toolbar = $('.editor-toolbar'); - const $bPreview = $('.editor-toolbar button.preview'); - const $bSideBySide = $('.editor-toolbar a.fa-columns'); - $bEdit.on('click', (e) => { - if (!hasEasyMDE) { - return false; - } - e.stopImmediatePropagation(); + return false; + }); + $bPrev.on('click', (e) => { + if (!hasEasyMDE) { + return false; + } + e.stopImmediatePropagation(); + if (!$toolbar.hasClass('disabled-for-preview')) { + $bPreview.trigger('click'); + } + return false; + }); + $bPreview.on('click', () => { + setTimeout(() => { if ($toolbar.hasClass('disabled-for-preview')) { - $bPreview.trigger('click'); + if ($bEdit.hasClass('active')) { + $bEdit.removeClass('active'); + } + if (!$bPrev.hasClass('active')) { + $bPrev.addClass('active'); + } + } else { + if (!$bEdit.hasClass('active')) { + $bEdit.addClass('active'); + } + if ($bPrev.hasClass('active')) { + $bPrev.removeClass('active'); + } } + }, 0); - return false; - }); - $bPrev.on('click', (e) => { - if (!hasEasyMDE) { - return false; - } - e.stopImmediatePropagation(); - if (!$toolbar.hasClass('disabled-for-preview')) { - $bPreview.trigger('click'); - } - return false; - }); - $bPreview.on('click', () => { - setTimeout(() => { - if ($toolbar.hasClass('disabled-for-preview')) { - if ($bEdit.hasClass('active')) { - $bEdit.removeClass('active'); - } - if (!$bPrev.hasClass('active')) { - $bPrev.addClass('active'); - } - } else { - if (!$bEdit.hasClass('active')) { - $bEdit.addClass('active'); - } - if ($bPrev.hasClass('active')) { - $bPrev.removeClass('active'); - } - } - }, 0); + return false; + }); + $bSideBySide.on('click', () => { + sideBySideChanges = 10; + }); + }, 0); +} - return false; - }); - $bSideBySide.on('click', () => { - sideBySideChanges = 10; - }); - }, 0); - } +export function initRepoWikiForm() { + initRepoWikiFormEditor(); } |