]> source.dussan.org Git - gitea.git/commitdiff
Use monaco for the git hook editor (#13552)
authorsilverwind <me@silverwind.io>
Sat, 14 Nov 2020 03:57:34 +0000 (04:57 +0100)
committerGitHub <noreply@github.com>
Sat, 14 Nov 2020 03:57:34 +0000 (22:57 -0500)
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 <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
routers/repo/setting.go
templates/repo/editor/edit.tmpl
templates/repo/settings/githook_edit.tmpl
templates/repo/settings/githooks.tmpl
web_src/js/features/codeeditor.js
web_src/js/index.js
web_src/less/_editor.less

index e4f8adc38fab0cecb8a8c059279528f2f7933763..368879234bcde294bbe99eaefaadd1abaf69f679 100644 (file)
@@ -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)
index 6b88d8859735057a495d5b9e44cefad9ba3f48c4..b991d24242592f3a21cc928f925d812b43fab187 100644 (file)
@@ -36,7 +36,7 @@
                                        {{end}}
                                </div>
                                <div class="ui bottom attached active tab segment" data-tab="write">
-                                       <textarea id="edit_area" name="content" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
+                                       <textarea id="edit_area" name="content" class="hide" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
                                                data-url="{{.Repository.APIURL}}/markdown"
                                                data-context="{{.RepoLink}}"
                                                data-markdown-file-exts="{{.MarkdownFileExts}}"
index 04833cfc58e0f2fd430ec9e13a483d799108bc9a..4d68d9bfb1015ef793ca625e22936758f4efd0ae 100644 (file)
                                {{with .Hook}}
                                        <div class="inline field">
                                                <label>{{$.i18n.Tr "repo.settings.githook_name"}}</label>
-                                               <span>{{.Name}}</span>
+                                               <span class="hook-filename">{{.Name}}</span>
                                        </div>
                                        <div class="field">
                                                <label for="content">{{$.i18n.Tr "repo.settings.githook_content"}}</label>
-                                               <textarea id="content" name="content" rows="20" wrap="off" autofocus>{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
+                                               <textarea id="content" name="content" class="hide">{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
+                                               <div class="editor-loading is-loading"></div>
                                        </div>
-
                                        <div class="inline field">
                                                <button class="ui green button">{{$.i18n.Tr "repo.settings.update_githook"}}</button>
                                        </div>
index 213637ed46afbae826dd3f74c92bea122773478f..522ec75e8316168975bf5872e4ccbdd044ae2723 100644 (file)
@@ -16,7 +16,9 @@
                                        <div class="item">
                                                <span class="text {{if .IsActive}}green{{else}}grey{{end}}">{{svg "octicon-dot-fill"}}</span>
                                                <span>{{.Name}}</span>
-                                               <a class="text blue ui right" href="{{$.RepoLink}}/settings/hooks/git/{{.Name}}"><i class="fa fa-pencil"></i></a>
+                                               <a class="text blue ui right" href="{{$.RepoLink}}/settings/hooks/git/{{.Name}}">
+                                                       {{svg "octicon-pencil"}}
+                                               </a>
                                        </div>
                                {{end}}
                        </div>
index d9ddb58d00ee5fbc9251d67368d94a86bfa7c208..3b6864ffe7188597cd6aad46e5447c167573b58d 100644 (file)
@@ -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;
 }
index 08305043d4170f56627e9cefaeb16d4d782de54a..1d36dcf6b2dc797e70196b801d10a8181abbd342 100644 (file)
@@ -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(),
   ]);
 });
 
index 4ed211a628718d5aca352c8caad735520c7a0585..bb6996064674402ebedb42e12871ca16d2216f2e 100644 (file)
   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;
+}