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-16 15:09:17 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-16 15:09:17 +0300
commit09dff3eec735ccbe001d165293ecebf195452071 (patch)
tree03c73077d0703edb9452145e7109835da2cd4918 /app/assets/javascripts/editor
parent78e911431fc575ff4f6c9b7e0f95c02b57a5e926 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/editor')
-rw-r--r--app/assets/javascripts/editor/constants.js5
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js143
2 files changed, 148 insertions, 0 deletions
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index 849ff91841a..dfc57f4966c 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -28,3 +28,8 @@ 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
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..0d60339594c 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,149 @@
+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,
+} 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,
+ },
+ });
+ this.setupPreviewAction.call(instance);
+ }
+
+ 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() {
+ 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.modelChangeListener = this.onDidChangeModelContent(
+ debounce(this.fetchPreview.bind(this), 250),
+ );
+ } else {
+ this.modelChangeListener.dispose();
+ }
+
+ this.preview.shown = !this.preview?.shown;
+
+ this.getModel().onDidChangeLanguage(({ newLanguage, oldLanguage } = {}) => {
+ if (newLanguage === 'markdown' && oldLanguage !== newLanguage) {
+ this.setupPreviewAction();
+ } else {
+ this.cleanup();
+ }
+ });
+ }
+
getSelectedText(selection = this.getSelection()) {
const { startLineNumber, endLineNumber, startColumn, endColumn } = selection;
const valArray = this.getValue().split('\n');