diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-04 18:16:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-04 18:16:11 +0300 |
commit | 73e15fde38825a490903ef88933d8896585f3008 (patch) | |
tree | 9540de3751be70a2f101185c8f830452d3a2ad1d /app/assets/javascripts/content_editor | |
parent | 7150920cea6cdd82b9409d5757fa26dac66876d1 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor')
6 files changed, 138 insertions, 25 deletions
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index 7e9380ec553..a942c9f1149 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -1,5 +1,4 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2'; import { createContentEditor } from '../services/create_content_editor'; import ContentEditorAlert from './content_editor_alert.vue'; @@ -7,10 +6,11 @@ import ContentEditorProvider from './content_editor_provider.vue'; import EditorStateObserver from './editor_state_observer.vue'; import FormattingBubbleMenu from './formatting_bubble_menu.vue'; import TopToolbar from './top_toolbar.vue'; +import LoadingIndicator from './loading_indicator.vue'; export default { components: { - GlLoadingIcon, + LoadingIndicator, ContentEditorAlert, ContentEditorProvider, TiptapEditorContent, @@ -40,7 +40,6 @@ export default { }, data() { return { - isLoadingContent: false, focused: false, }; }, @@ -62,12 +61,6 @@ export default { this.contentEditor.dispose(); }, methods: { - displayLoadingIndicator() { - this.isLoadingContent = true; - }, - hideLoadingIndicator() { - this.isLoadingContent = false; - }, focus() { this.focused = true; }, @@ -85,14 +78,7 @@ export default { <template> <content-editor-provider :content-editor="contentEditor"> <div> - <editor-state-observer - @loading="displayLoadingIndicator" - @loadingSuccess="hideLoadingIndicator" - @loadingError="hideLoadingIndicator" - @docUpdate="notifyChange" - @focus="focus" - @blur="blur" - /> + <editor-state-observer @docUpdate="notifyChange" @focus="focus" @blur="blur" /> <content-editor-alert /> <div data-testid="content-editor" @@ -101,13 +87,11 @@ export default { :class="{ 'is-focused': focused }" > <top-toolbar ref="toolbar" class="gl-mb-4" /> - <div v-if="isLoadingContent" class="gl-w-full gl-display-flex gl-justify-content-center"> - <gl-loading-icon size="sm" /> - </div> - <template v-else> + <div class="gl-relative"> <formatting-bubble-menu /> <tiptap-editor-content class="md" :editor="contentEditor.tiptapEditor" /> - </template> + <loading-indicator /> + </div> </div> </div> </content-editor-provider> diff --git a/app/assets/javascripts/content_editor/components/loading_indicator.vue b/app/assets/javascripts/content_editor/components/loading_indicator.vue new file mode 100644 index 00000000000..5b9383d6e11 --- /dev/null +++ b/app/assets/javascripts/content_editor/components/loading_indicator.vue @@ -0,0 +1,39 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import EditorStateObserver from './editor_state_observer.vue'; + +export default { + components: { + GlLoadingIcon, + EditorStateObserver, + }, + data() { + return { + isLoading: false, + }; + }, + methods: { + displayLoadingIndicator() { + this.isLoading = true; + }, + hideLoadingIndicator() { + this.isLoading = false; + }, + }, +}; +</script> +<template> + <editor-state-observer + @loading="displayLoadingIndicator" + @loadingSuccess="hideLoadingIndicator" + @loadingError="hideLoadingIndicator" + > + <div + v-if="isLoading" + class="gl-w-full gl-display-flex gl-justify-content-center gl-align-items-center gl-absolute gl-top-0 gl-bottom-0" + > + <div class="gl-bg-white gl-absolute gl-w-full gl-h-full gl-opacity-3"></div> + <gl-loading-icon size="md" /> + </div> + </editor-state-observer> +</template> diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/paste_markdown.js new file mode 100644 index 00000000000..31774e0ec51 --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/paste_markdown.js @@ -0,0 +1,86 @@ +import { Extension } from '@tiptap/core'; +import { Plugin, PluginKey } from 'prosemirror-state'; +import { __ } from '~/locale'; +import { VARIANT_DANGER } from '~/flash'; +import createMarkdownDeserializer from '../services/markdown_deserializer'; +import { + ALERT_EVENT, + LOADING_CONTENT_EVENT, + LOADING_SUCCESS_EVENT, + LOADING_ERROR_EVENT, + EXTENSION_PRIORITY_HIGHEST, +} from '../constants'; + +const TEXT_FORMAT = 'text/plain'; +const HTML_FORMAT = 'text/html'; +const VS_CODE_FORMAT = 'vscode-editor-data'; + +export default Extension.create({ + name: 'pasteMarkdown', + priority: EXTENSION_PRIORITY_HIGHEST, + addOptions() { + return { + renderMarkdown: null, + }; + }, + addCommands() { + return { + pasteMarkdown: (markdown) => () => { + const { editor, options } = this; + const { renderMarkdown, eventHub } = options; + const deserializer = createMarkdownDeserializer({ render: renderMarkdown }); + + eventHub.$emit(LOADING_CONTENT_EVENT); + + deserializer + .deserialize({ schema: editor.schema, content: markdown }) + .then((doc) => { + if (!doc) { + return; + } + + const { state, view } = editor; + const { tr, selection } = state; + + tr.replaceWith(selection.from - 1, selection.to, doc.content); + view.dispatch(tr); + eventHub.$emit(LOADING_SUCCESS_EVENT); + }) + .catch(() => { + eventHub.$emit(ALERT_EVENT, { + message: __('An error occurred while pasting text in the editor. Please try again.'), + variant: VARIANT_DANGER, + }); + eventHub.$emit(LOADING_ERROR_EVENT); + }); + + return true; + }, + }; + }, + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey('pasteMarkdown'), + props: { + handlePaste: (_, event) => { + const { clipboardData } = event; + const content = clipboardData.getData(TEXT_FORMAT); + const hasHTML = clipboardData.types.some((type) => type === HTML_FORMAT); + const hasVsCode = clipboardData.types.some((type) => type === VS_CODE_FORMAT); + const vsCodeMeta = hasVsCode ? JSON.parse(clipboardData.getData(VS_CODE_FORMAT)) : {}; + const language = vsCodeMeta.mode; + + if (!content || (hasHTML && !hasVsCode) || (hasVsCode && language !== 'markdown')) { + return false; + } + + this.editor.commands.pasteMarkdown(content); + + return true; + }, + }, + }), + ]; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/table.js b/app/assets/javascripts/content_editor/extensions/table.js index 004bb8b815c..d7456ab4094 100644 --- a/app/assets/javascripts/content_editor/extensions/table.js +++ b/app/assets/javascripts/content_editor/extensions/table.js @@ -1,5 +1,6 @@ import { Table } from '@tiptap/extension-table'; import { debounce } from 'lodash'; +import { VARIANT_WARNING } from '~/flash'; import { __ } from '~/locale'; import { getMarkdownSource } from '../services/markdown_sourcemap'; import { shouldRenderHTMLTable } from '../services/serialization_helpers'; @@ -14,7 +15,7 @@ const onUpdate = debounce((editor) => { message: __( 'The content editor may change the markdown formatting style of the document, which may not match your original markdown style.', ), - variant: 'warning', + variant: VARIANT_WARNING, }); alertShown = true; diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js index 354244b85ff..d9d39a387d0 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -39,6 +39,7 @@ import Loading from '../extensions/loading'; import MathInline from '../extensions/math_inline'; import OrderedList from '../extensions/ordered_list'; import Paragraph from '../extensions/paragraph'; +import PasteMarkdown from '../extensions/paste_markdown'; import Reference from '../extensions/reference'; import Strike from '../extensions/strike'; import Subscript from '../extensions/subscript'; @@ -120,6 +121,7 @@ export const createContentEditor = ({ MathInline, OrderedList, Paragraph, + PasteMarkdown.configure({ renderMarkdown, eventHub }), Reference, Strike, Subscript, diff --git a/app/assets/javascripts/content_editor/services/upload_helpers.js b/app/assets/javascripts/content_editor/services/upload_helpers.js index 33e9f455ef8..1abecb8f414 100644 --- a/app/assets/javascripts/content_editor/services/upload_helpers.js +++ b/app/assets/javascripts/content_editor/services/upload_helpers.js @@ -1,3 +1,4 @@ +import { VARIANT_DANGER } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import { extractFilename, readFileAsDataURL } from './utils'; @@ -74,7 +75,7 @@ const uploadImage = async ({ editor, file, uploadsPath, renderMarkdown, eventHub editor.commands.deleteRange({ from: position, to: position + 1 }); eventHub.$emit('alert', { message: __('An error occurred while uploading the image. Please try again.'), - variant: 'danger', + variant: VARIANT_DANGER, }); } }; @@ -105,7 +106,7 @@ const uploadAttachment = async ({ editor, file, uploadsPath, renderMarkdown, eve editor.commands.deleteRange({ from, to: from + 1 }); eventHub.$emit('alert', { message: __('An error occurred while uploading the file. Please try again.'), - variant: 'danger', + variant: VARIANT_DANGER, }); } }; |