mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	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 <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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}}"
 | 
			
		||||
 
 | 
			
		||||
@@ -14,13 +14,13 @@
 | 
			
		||||
				{{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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFileBasedOptions(filename, lineWrapExts) {
 | 
			
		||||
  return {
 | 
			
		||||
    wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 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';
 | 
			
		||||
  }
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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(),
 | 
			
		||||
  ]);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user