diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-16 15:09:17 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-16 15:09:17 +0300 |
commit | 09dff3eec735ccbe001d165293ecebf195452071 (patch) | |
tree | 03c73077d0703edb9452145e7109835da2cd4918 /app/assets/javascripts/editor | |
parent | 78e911431fc575ff4f6c9b7e0f95c02b57a5e926 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/editor')
-rw-r--r-- | app/assets/javascripts/editor/constants.js | 5 | ||||
-rw-r--r-- | app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js | 143 |
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'); |