Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
commitb76ae638462ab0f673e5915986070518dd3f9ad3 (patch)
treebdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/editor
parent434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff)
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/editor')
-rw-r--r--app/assets/javascripts/editor/constants.js6
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js159
-rw-r--r--app/assets/javascripts/editor/source_editor.js29
-rw-r--r--app/assets/javascripts/editor/utils.js31
4 files changed, 198 insertions, 27 deletions
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index 849ff91841a..d40d19000fb 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -28,3 +28,9 @@ export const EDITOR_DIFF_INSTANCE_FN = 'createDiffInstance';
// '*.gitlab-ci.yml' regardless of project configuration.
// https://gitlab.com/gitlab-org/gitlab/-/issues/293641
export const EXTENSION_CI_SCHEMA_FILE_NAME_MATCH = '.gitlab-ci.yml';
+
+export const EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS = 'md';
+export const EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS = 'source-editor-preview';
+export const EXTENSION_MARKDOWN_PREVIEW_ACTION_ID = 'markdown-preview';
+export const EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH = 0.5; // 50% of the width
+export const EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY = 250; // ms
diff --git a/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js b/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
index 997503a12f5..76e009164f7 100644
--- a/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
+++ b/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js
@@ -1,6 +1,165 @@
+import { debounce } from 'lodash';
+import { BLOB_PREVIEW_ERROR } from '~/blob_edit/constants';
+import createFlash from '~/flash';
+import { sanitize } from '~/lib/dompurify';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import syntaxHighlight from '~/syntax_highlight';
+import {
+ EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS,
+ EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH,
+ EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS,
+ EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY,
+} from '../constants';
import { SourceEditorExtension } from './source_editor_extension_base';
+const getPreview = (text, projectPath = '') => {
+ let url;
+
+ if (projectPath) {
+ url = `/${projectPath}/preview_markdown`;
+ } else {
+ const { group, project } = document.body.dataset;
+ url = `/${group}/${project}/preview_markdown`;
+ }
+ return axios
+ .post(url, {
+ text,
+ })
+ .then(({ data }) => {
+ return data.body;
+ });
+};
+
+const setupDomElement = ({ injectToEl = null } = {}) => {
+ const previewEl = document.createElement('div');
+ previewEl.classList.add(EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS);
+ previewEl.style.display = 'none';
+ if (injectToEl) {
+ injectToEl.appendChild(previewEl);
+ }
+ return previewEl;
+};
+
export class EditorMarkdownExtension extends SourceEditorExtension {
+ constructor({ instance, projectPath, ...args } = {}) {
+ super({ instance, ...args });
+ Object.assign(instance, {
+ projectPath,
+ preview: {
+ el: undefined,
+ action: undefined,
+ shown: false,
+ modelChangeListener: undefined,
+ },
+ });
+ this.setupPreviewAction.call(instance);
+
+ instance.getModel().onDidChangeLanguage(({ newLanguage, oldLanguage } = {}) => {
+ if (newLanguage === 'markdown' && oldLanguage !== newLanguage) {
+ instance.setupPreviewAction();
+ } else {
+ instance.cleanup();
+ }
+ });
+
+ instance.onDidChangeModel(() => {
+ const model = instance.getModel();
+ if (model) {
+ const { language } = model.getLanguageIdentifier();
+ instance.cleanup();
+ if (language === 'markdown') {
+ instance.setupPreviewAction();
+ }
+ }
+ });
+ }
+
+ static togglePreviewLayout() {
+ const { width, height } = this.getLayoutInfo();
+ const newWidth = this.preview.shown
+ ? width / EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH
+ : width * EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH;
+ this.layout({ width: newWidth, height });
+ }
+
+ static togglePreviewPanel() {
+ const parentEl = this.getDomNode().parentElement;
+ const { el: previewEl } = this.preview;
+ parentEl.classList.toggle(EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS);
+
+ if (previewEl.style.display === 'none') {
+ // Show the preview panel
+ this.fetchPreview();
+ } else {
+ // Hide the preview panel
+ previewEl.style.display = 'none';
+ }
+ }
+
+ cleanup() {
+ if (this.preview.modelChangeListener) {
+ this.preview.modelChangeListener.dispose();
+ }
+ this.preview.action.dispose();
+ if (this.preview.shown) {
+ EditorMarkdownExtension.togglePreviewPanel.call(this);
+ EditorMarkdownExtension.togglePreviewLayout.call(this);
+ }
+ this.preview.shown = false;
+ }
+
+ fetchPreview() {
+ const { el: previewEl } = this.preview;
+ getPreview(this.getValue(), this.projectPath)
+ .then((data) => {
+ previewEl.innerHTML = sanitize(data);
+ syntaxHighlight(previewEl.querySelectorAll('.js-syntax-highlight'));
+ previewEl.style.display = 'block';
+ })
+ .catch(() => createFlash(BLOB_PREVIEW_ERROR));
+ }
+
+ setupPreviewAction() {
+ if (this.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)) return;
+
+ this.preview.action = this.addAction({
+ id: EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ label: __('Preview Markdown'),
+ keybindings: [
+ // eslint-disable-next-line no-bitwise,no-undef
+ monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_P),
+ ],
+ contextMenuGroupId: 'navigation',
+ contextMenuOrder: 1.5,
+
+ // Method that will be executed when the action is triggered.
+ // @param ed The editor instance is passed in as a convenience
+ run(instance) {
+ instance.togglePreview();
+ },
+ });
+ }
+
+ togglePreview() {
+ if (!this.preview?.el) {
+ this.preview.el = setupDomElement({ injectToEl: this.getDomNode().parentElement });
+ }
+ EditorMarkdownExtension.togglePreviewLayout.call(this);
+ EditorMarkdownExtension.togglePreviewPanel.call(this);
+
+ if (!this.preview?.shown) {
+ this.preview.modelChangeListener = this.onDidChangeModelContent(
+ debounce(this.fetchPreview.bind(this), EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY),
+ );
+ } else {
+ this.preview.modelChangeListener.dispose();
+ }
+
+ this.preview.shown = !this.preview?.shown;
+ }
+
getSelectedText(selection = this.getSelection()) {
const { startLineNumber, endLineNumber, startColumn, endColumn } = selection;
const valArray = this.getValue().split('\n');
diff --git a/app/assets/javascripts/editor/source_editor.js b/app/assets/javascripts/editor/source_editor.js
index ee97714824e..81ddf8d77fa 100644
--- a/app/assets/javascripts/editor/source_editor.js
+++ b/app/assets/javascripts/editor/source_editor.js
@@ -1,7 +1,6 @@
-import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
+import { editor as monacoEditor, Uri } from 'monaco-editor';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
import languages from '~/ide/lib/languages';
-import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import { registerLanguages } from '~/ide/utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { uuids } from '~/lib/utils/uuids';
@@ -11,7 +10,7 @@ import {
EDITOR_READY_EVENT,
EDITOR_TYPE_DIFF,
} from './constants';
-import { clearDomElement } from './utils';
+import { clearDomElement, setupEditorTheme, getBlobLanguage } from './utils';
export default class SourceEditor {
constructor(options = {}) {
@@ -22,26 +21,11 @@ export default class SourceEditor {
...options,
};
- SourceEditor.setupMonacoTheme();
+ setupEditorTheme();
registerLanguages(...languages);
}
- static setupMonacoTheme() {
- const themeName = window.gon?.user_color_scheme || DEFAULT_THEME;
- const theme = themes.find((t) => t.name === themeName);
- if (theme) monacoEditor.defineTheme(themeName, theme.data);
- monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME);
- }
-
- static getModelLanguage(path) {
- const ext = `.${path.split('.').pop()}`;
- const language = monacoLanguages
- .getLanguages()
- .find((lang) => lang.extensions.indexOf(ext) !== -1);
- return language ? language.id : 'plaintext';
- }
-
static pushToImportsArray(arr, toImport) {
arr.push(import(toImport));
}
@@ -124,10 +108,7 @@ export default class SourceEditor {
return model;
}
const diffModel = {
- original: monacoEditor.createModel(
- blobOriginalContent,
- SourceEditor.getModelLanguage(model.uri.path),
- ),
+ original: monacoEditor.createModel(blobOriginalContent, getBlobLanguage(model.uri.path)),
modified: model,
};
instance.setModel(diffModel);
@@ -155,7 +136,7 @@ export default class SourceEditor {
};
static instanceUpdateLanguage(inst, path) {
- const lang = SourceEditor.getModelLanguage(path);
+ const lang = getBlobLanguage(path);
const model = inst.getModel();
return monacoEditor.setModelLanguage(model, lang);
}
diff --git a/app/assets/javascripts/editor/utils.js b/app/assets/javascripts/editor/utils.js
index af4473413f4..df9d3f2b9fb 100644
--- a/app/assets/javascripts/editor/utils.js
+++ b/app/assets/javascripts/editor/utils.js
@@ -1,3 +1,6 @@
+import { editor as monacoEditor, languages as monacoLanguages } from 'monaco-editor';
+import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
+
export const clearDomElement = (el) => {
if (!el || !el.firstChild) return;
@@ -6,6 +9,28 @@ export const clearDomElement = (el) => {
}
};
-export default () => ({
- clearDomElement,
-});
+export const setupEditorTheme = () => {
+ const themeName = window.gon?.user_color_scheme || DEFAULT_THEME;
+ const theme = themes.find((t) => t.name === themeName);
+ if (theme) monacoEditor.defineTheme(themeName, theme.data);
+ monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME);
+};
+
+export const getBlobLanguage = (blobPath) => {
+ const defaultLanguage = 'plaintext';
+
+ if (!blobPath) {
+ return defaultLanguage;
+ }
+
+ const ext = `.${blobPath.split('.').pop()}`;
+ const language = monacoLanguages
+ .getLanguages()
+ .find((lang) => lang.extensions.indexOf(ext) !== -1);
+ return language ? language.id : defaultLanguage;
+};
+
+export const setupCodeSnippet = (el) => {
+ monacoEditor.colorizeElement(el);
+ setupEditorTheme();
+};