Switch code editor to Monaco (#11366)

* Switch code editor to Monaco

This switches out CodeMirror for Monaco which is based on the same code
base as VS code and should work pretty similar to it.

It does add a few async chunks, totalling around 10MB to our build. It
currently supports around 65 languages and in the default configuration,
each language would emit one ugly [number].js chunk, so I opted to
combine them all into a single file for now.

CodeMirror is still being used under the hood by SimpleMDE so it can not
be removed yet.

* inline editorconfig, fix diff, use for markdown, remove more dead code

* refactors, remove jquery usage

* use tab_width

* fix intellisense

* rename function for clarity

* misc tweaks, enable webpack progress display

* only use --progress on dev build

* remove useless borders in arc-green

* fix typo

* remove obsolete comment

* small refactor

* fix file creation and various refactors

* unset useTabStops too when no editorconfig

* small refactor

* disable webpack's [big] warnings

* remove useless await

* fix dark theme check

* rename chunk to 'monaco'

* add to .gitignore and delete webpack dest before build

* increase editor height

* support more editorconfig properties

* remove empty element filter

* rename

Co-authored-by: John Olheiser <john.olheiser@gmail.com>
This commit is contained in:
silverwind 2020-05-14 18:06:01 +02:00 committed by GitHub
parent 984ee0113b
commit 9249c810b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 304 additions and 197 deletions

View file

@ -20,6 +20,7 @@ import createDropzone from './features/dropzone.js';
import highlight from './features/highlight.js';
import ActivityTopAuthors from './components/ActivityTopAuthors.vue';
import {initNotificationsTable, initNotificationCount} from './features/notification.js';
import {createCodeEditor} from './features/codeeditor.js';
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
@ -28,9 +29,7 @@ function htmlEncode(text) {
}
let previewFileModes;
let simpleMDEditor;
const commentMDEditors = {};
let codeMirrorEditor;
// Silence fomantic's error logging when tabs are used without a target content element
$.fn.tab.settings.silent = true;
@ -1467,62 +1466,6 @@ $.fn.getCursorPosition = function () {
return pos;
};
function setSimpleMDE($editArea) {
if (codeMirrorEditor) {
codeMirrorEditor.toTextArea();
codeMirrorEditor = null;
}
if (simpleMDEditor) {
return true;
}
simpleMDEditor = new SimpleMDE({
autoDownloadFontAwesome: false,
element: $editArea[0],
forceSync: true,
renderingConfig: {
singleLineBreaks: false
},
indentWithTabs: false,
tabSize: 4,
spellChecker: false,
previewRender(plainText, preview) { // Async method
setTimeout(() => {
// FIXME: still send render request when return back to edit mode
$.post($editArea.data('url'), {
_csrf: csrf,
mode: 'gfm',
context: $editArea.data('context'),
text: plainText
}, (data) => {
preview.innerHTML = `<div class="markdown ui segment">${data}</div>`;
});
}, 0);
return 'Loading...';
},
toolbar: ['bold', 'italic', 'strikethrough', '|',
'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
'code', 'quote', '|',
'unordered-list', 'ordered-list', '|',
'link', 'image', 'table', 'horizontal-rule', '|',
'clean-block', 'preview', 'fullscreen', 'side-by-side', '|',
{
name: 'revert-to-textarea',
action(e) {
e.toTextArea();
},
className: 'fa fa-file',
title: 'Revert to simple textarea',
},
]
});
$(simpleMDEditor.codemirror.getInputField()).addClass('js-quick-submit');
return true;
}
function setCommentSimpleMDE($editArea) {
const simplemde = new SimpleMDE({
autoDownloadFontAwesome: false,
@ -1569,27 +1512,7 @@ function setCommentSimpleMDE($editArea) {
return simplemde;
}
function setCodeMirror($editArea) {
if (simpleMDEditor) {
simpleMDEditor.toTextArea();
simpleMDEditor = null;
}
if (codeMirrorEditor) {
return true;
}
codeMirrorEditor = CodeMirror.fromTextArea($editArea[0], {
lineNumbers: true
});
codeMirrorEditor.on('change', (cm, _change) => {
$editArea.val(cm.getValue());
});
return true;
}
function initEditor() {
async function initEditor() {
$('.js-quick-pull-choice-option').on('change', function () {
if ($(this).val() === 'commit-to-new-branch') {
$('.quick-pull-branch-name').show();
@ -1650,89 +1573,7 @@ function initEditor() {
const $editArea = $('.repository.editor textarea#edit_area');
if (!$editArea.length) return;
const markdownFileExts = $editArea.data('markdown-file-exts').split(',');
const lineWrapExtensions = $editArea.data('line-wrap-extensions').split(',');
$editFilename.on('keyup', () => {
const val = $editFilename.val();
let mode, spec, extension, extWithDot, dataUrl, apiCall;
extension = extWithDot = '';
const m = /.+\.([^.]+)$/.exec(val);
if (m) {
extension = m[1];
extWithDot = `.${extension}`;
}
const info = CodeMirror.findModeByExtension(extension);
const previewLink = $('a[data-tab=preview]');
if (info) {
mode = info.mode;
spec = info.mime;
apiCall = mode;
} else {
apiCall = extension;
}
if (previewLink.length && apiCall && previewFileModes && previewFileModes.length && previewFileModes.includes(apiCall)) {
dataUrl = previewLink.data('url');
previewLink.data('url', dataUrl.replace(/(.*)\/.*/i, `$1/${mode}`));
previewLink.show();
} else {
previewLink.hide();
}
// If this file is a Markdown extensions, we will load that editor and return
if (markdownFileExts.includes(extWithDot)) {
if (setSimpleMDE($editArea)) {
return;
}
}
// Else we are going to use CodeMirror
if (!codeMirrorEditor && !setCodeMirror($editArea)) {
return;
}
if (mode) {
codeMirrorEditor.setOption('mode', spec);
CodeMirror.autoLoadMode(codeMirrorEditor, mode);
}
if (lineWrapExtensions.includes(extWithDot)) {
codeMirrorEditor.setOption('lineWrapping', true);
} else {
codeMirrorEditor.setOption('lineWrapping', false);
}
// get the filename without any folder
let value = $editFilename.val();
if (value.length === 0) {
return;
}
value = value.split('/');
value = value[value.length - 1];
$.getJSON($editFilename.data('ec-url-prefix') + value, (editorconfig) => {
if (editorconfig.indent_style === 'tab') {
codeMirrorEditor.setOption('indentWithTabs', true);
codeMirrorEditor.setOption('extraKeys', {});
} else {
codeMirrorEditor.setOption('indentWithTabs', false);
// required because CodeMirror doesn't seems to use spaces correctly for {"indentWithTabs": false}:
// - https://github.com/codemirror/CodeMirror/issues/988
// - https://codemirror.net/doc/manual.html#keymaps
codeMirrorEditor.setOption('extraKeys', {
Tab(cm) {
const spaces = new Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ');
cm.replaceSelection(spaces);
}
});
}
codeMirrorEditor.setOption('indentUnit', editorconfig.indent_size || 4);
codeMirrorEditor.setOption('tabSize', editorconfig.tab_width || 4);
});
}).trigger('keyup');
await createCodeEditor($editArea[0], $editFilename[0], previewFileModes);
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
// to enable or disable the commit button