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:
Diffstat (limited to 'app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js')
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js159
1 files changed, 159 insertions, 0 deletions
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');