From 374ff60465ace2471d0dde121c6a3e1527ab0bec Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 14 Nov 2020 04:57:34 +0100 Subject: [PATCH] Use monaco for the git hook editor (#13552) Migrate git hook editor to monaco, replacing CodeMirror. Had to do a few refactors to make the monaco instantiation generic enough to be of use. Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick --- routers/repo/setting.go | 1 - templates/repo/editor/edit.tmpl | 2 +- templates/repo/settings/githook_edit.tmpl | 6 +- templates/repo/settings/githooks.tmpl | 4 +- web_src/js/features/codeeditor.js | 97 +++++++++++++---------- web_src/js/index.js | 17 ++-- web_src/less/_editor.less | 9 ++- 7 files changed, 74 insertions(+), 62 deletions(-) diff --git a/routers/repo/setting.go b/routers/repo/setting.go index e4f8adc38..368879234 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -787,7 +787,6 @@ func GitHooks(ctx *context.Context) { func GitHooksEdit(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings.githooks") ctx.Data["PageIsSettingsGitHooks"] = true - ctx.Data["RequireSimpleMDE"] = true name := ctx.Params(":name") hook, err := ctx.Repo.GitRepo.GetHook(name) diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 6b88d8859..b991d2424 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -36,7 +36,7 @@ {{end}}
- + +
-
diff --git a/templates/repo/settings/githooks.tmpl b/templates/repo/settings/githooks.tmpl index 213637ed4..522ec75e8 100644 --- a/templates/repo/settings/githooks.tmpl +++ b/templates/repo/settings/githooks.tmpl @@ -16,7 +16,9 @@
{{svg "octicon-dot-fill"}} {{.Name}} - + + {{svg "octicon-pencil"}} +
{{end}} diff --git a/web_src/js/features/codeeditor.js b/web_src/js/features/codeeditor.js index d9ddb58d0..3b6864ffe 100644 --- a/web_src/js/features/codeeditor.js +++ b/web_src/js/features/codeeditor.js @@ -26,12 +26,11 @@ function getLanguage(filename) { return languagesByFilename[filename] || languagesByExt[extname(filename)] || 'plaintext'; } -function updateEditor(monaco, editor, filenameInput) { - const newFilename = filenameInput.value; - editor.updateOptions(getOptions(filenameInput)); +function updateEditor(monaco, editor, filename, lineWrapExts) { + editor.updateOptions({...getFileBasedOptions(filename, lineWrapExts)}); const model = editor.getModel(); const language = model.getModeId(); - const newLanguage = getLanguage(newFilename); + const newLanguage = getLanguage(filename); if (language !== newLanguage) monaco.editor.setModelLanguage(model, newLanguage); } @@ -41,24 +40,12 @@ function exportEditor(editor) { if (!window.codeEditors.includes(editor)) window.codeEditors.push(editor); } -export async function createCodeEditor(textarea, filenameInput, previewFileModes) { - const filename = basename(filenameInput.value); - const previewLink = document.querySelector('a[data-tab=preview]'); - const markdownExts = (textarea.dataset.markdownFileExts || '').split(','); - const lineWrapExts = (textarea.dataset.lineWrapExtensions || '').split(','); - const isMarkdown = markdownExts.includes(extname(filename)); - - if (previewLink) { - if (isMarkdown && (previewFileModes || []).includes('markdown')) { - previewLink.dataset.url = previewLink.dataset.url.replace(/(.*)\/.*/i, `$1/markdown`); - previewLink.style.display = ''; - } else { - previewLink.style.display = 'none'; - } - } - +export async function createMonaco(textarea, filename, editorOpts) { const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor'); + initLanguages(monaco); + let {language, ...other} = editorOpts; + if (!language) language = getLanguage(filename); const container = document.createElement('div'); container.className = 'monaco-editor-container'; @@ -66,8 +53,9 @@ export async function createCodeEditor(textarea, filenameInput, previewFileModes const editor = monaco.editor.create(container, { value: textarea.value, - language: getLanguage(filename), - ...getOptions(filenameInput, lineWrapExts), + theme: isDarkTheme() ? 'vs-dark' : 'vs', + language, + ...other, }); const model = editor.getModel(); @@ -80,33 +68,60 @@ export async function createCodeEditor(textarea, filenameInput, previewFileModes editor.layout(); }); - filenameInput.addEventListener('keyup', () => { - updateEditor(monaco, editor, filenameInput); - }); + exportEditor(editor); const loading = document.querySelector('.editor-loading'); if (loading) loading.remove(); - exportEditor(editor); + return {monaco, editor}; +} - return editor; +function getFileBasedOptions(filename, lineWrapExts) { + return { + wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off', + }; } -function getOptions(filenameInput, lineWrapExts) { - const ec = getEditorconfig(filenameInput); - const theme = isDarkTheme() ? 'vs-dark' : 'vs'; - const wordWrap = (lineWrapExts || []).includes(extname(filenameInput.value)) ? 'on' : 'off'; - - const opts = {theme, wordWrap}; - if (isObject(ec)) { - opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec); - if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size); - if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize; - if ('max_line_length' in ec) opts.rulers = [Number(ec.max_line_length)]; - opts.trimAutoWhitespace = ec.trim_trailing_whitespace === true; - opts.insertSpaces = ec.indent_style === 'space'; - opts.useTabStops = ec.indent_style === 'tab'; +export async function createCodeEditor(textarea, filenameInput, previewFileModes) { + const filename = basename(filenameInput.value); + const previewLink = document.querySelector('a[data-tab=preview]'); + const markdownExts = (textarea.dataset.markdownFileExts || '').split(','); + const lineWrapExts = (textarea.dataset.lineWrapExtensions || '').split(','); + const isMarkdown = markdownExts.includes(extname(filename)); + const editorConfig = getEditorconfig(filenameInput); + + if (previewLink) { + if (isMarkdown && (previewFileModes || []).includes('markdown')) { + previewLink.dataset.url = previewLink.dataset.url.replace(/(.*)\/.*/i, `$1/markdown`); + previewLink.style.display = ''; + } else { + previewLink.style.display = 'none'; + } } + const {monaco, editor} = await createMonaco(textarea, filename, { + ...getFileBasedOptions(filenameInput.value, lineWrapExts), + ...getEditorConfigOptions(editorConfig), + }); + + filenameInput.addEventListener('keyup', () => { + const filename = filenameInput.value; + updateEditor(monaco, editor, filename, lineWrapExts); + }); + + return editor; +} + +function getEditorConfigOptions(ec) { + if (!isObject(ec)) return {}; + + const opts = {}; + opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec); + if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size); + if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize; + if ('max_line_length' in ec) opts.rulers = [Number(ec.max_line_length)]; + opts.trimAutoWhitespace = ec.trim_trailing_whitespace === true; + opts.insertSpaces = ec.indent_style === 'space'; + opts.useTabStops = ec.indent_style === 'tab'; return opts; } diff --git a/web_src/js/index.js b/web_src/js/index.js index 08305043d..1d36dcf6b 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -23,7 +23,7 @@ import createDropzone from './features/dropzone.js'; import initTableSort from './features/tablesort.js'; import ActivityTopAuthors from './components/ActivityTopAuthors.vue'; import {initNotificationsTable, initNotificationCount} from './features/notification.js'; -import {createCodeEditor} from './features/codeeditor.js'; +import {createCodeEditor, createMonaco} from './features/codeeditor.js'; import {svg, svgs} from './svg.js'; import {stripTags} from './utils.js'; @@ -1732,15 +1732,10 @@ function initUserSettings() { } } -function initGithook() { - if ($('.edit.githook').length === 0) { - return; - } - - CodeMirror.autoLoadMode(CodeMirror.fromTextArea($('#content')[0], { - lineNumbers: true, - mode: 'shell' - }), 'shell'); +async function initGithook() { + if ($('.edit.githook').length === 0) return; + const filename = document.querySelector('.hook-filename').textContent; + await createMonaco($('#content')[0], filename, {language: 'shell'}); } function initWebhook() { @@ -2517,7 +2512,6 @@ $(document).ready(async () => { initEditForm(); initEditor(); initOrganization(); - initGithook(); initWebhook(); initAdmin(); initCodeView(); @@ -2575,6 +2569,7 @@ $(document).ready(async () => { initServiceWorker(), initNotificationCount(), renderMarkdownContent(), + initGithook(), ]); }); diff --git a/web_src/less/_editor.less b/web_src/less/_editor.less index 4ed211a62..bb6996064 100644 --- a/web_src/less/_editor.less +++ b/web_src/less/_editor.less @@ -53,10 +53,6 @@ border-right: 1px solid var(--color-secondary) !important; } -#edit_area { - display: none; -} - .monaco-editor-container { width: 100%; min-height: 200px; @@ -73,3 +69,8 @@ color: transparent !important; background-color: transparent !important; } + +.edit.githook .monaco-editor-container { + border: 1px solid var(--color-secondary); + height: 70vh; +}