diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 23:02:30 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 23:02:30 +0300 |
commit | 41fe97390ceddf945f3d967b8fdb3de4c66b7dea (patch) | |
tree | 9c8d89a8624828992f06d892cd2f43818ff5dcc8 /app/assets/javascripts/content_editor | |
parent | 0804d2dc31052fb45a1efecedc8e06ce9bc32862 (diff) |
Add latest changes from gitlab-org/gitlab@14-9-stable-eev14.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/content_editor')
16 files changed, 269 insertions, 101 deletions
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index a8405fe37c7..a942c9f1149 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -1,17 +1,16 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2'; -import { LOADING_CONTENT_EVENT, LOADING_SUCCESS_EVENT, LOADING_ERROR_EVENT } from '../constants'; import { createContentEditor } from '../services/create_content_editor'; import ContentEditorAlert from './content_editor_alert.vue'; 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, @@ -41,7 +40,6 @@ export default { }, data() { return { - isLoadingContent: false, focused: false, }; }, @@ -55,25 +53,14 @@ export default { extensions, serializerConfig, }); - - this.contentEditor.on(LOADING_CONTENT_EVENT, this.displayLoadingIndicator); - this.contentEditor.on(LOADING_SUCCESS_EVENT, this.hideLoadingIndicator); - this.contentEditor.on(LOADING_ERROR_EVENT, this.hideLoadingIndicator); + }, + mounted() { this.$emit('initialized', this.contentEditor); }, beforeDestroy() { this.contentEditor.dispose(); - this.contentEditor.off(LOADING_CONTENT_EVENT, this.displayLoadingIndicator); - this.contentEditor.off(LOADING_SUCCESS_EVENT, this.hideLoadingIndicator); - this.contentEditor.off(LOADING_ERROR_EVENT, this.hideLoadingIndicator); }, methods: { - displayLoadingIndicator() { - this.isLoadingContent = true; - }, - hideLoadingIndicator() { - this.isLoadingContent = false; - }, focus() { this.focused = true; }, @@ -100,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/content_editor_provider.vue b/app/assets/javascripts/content_editor/components/content_editor_provider.vue index 630aff9858f..cba3b627390 100644 --- a/app/assets/javascripts/content_editor/components/content_editor_provider.vue +++ b/app/assets/javascripts/content_editor/components/content_editor_provider.vue @@ -8,6 +8,7 @@ export default { return { contentEditor, + eventHub: contentEditor.eventHub, tiptapEditor: contentEditor.tiptapEditor, }; }, diff --git a/app/assets/javascripts/content_editor/components/editor_state_observer.vue b/app/assets/javascripts/content_editor/components/editor_state_observer.vue index 0604047a953..02de6470cf2 100644 --- a/app/assets/javascripts/content_editor/components/editor_state_observer.vue +++ b/app/assets/javascripts/content_editor/components/editor_state_observer.vue @@ -1,5 +1,11 @@ <script> import { debounce } from 'lodash'; +import { + LOADING_CONTENT_EVENT, + LOADING_SUCCESS_EVENT, + LOADING_ERROR_EVENT, + ALERT_EVENT, +} from '../constants'; export const tiptapToComponentMap = { update: 'docUpdate', @@ -7,30 +13,48 @@ export const tiptapToComponentMap = { transaction: 'transaction', focus: 'focus', blur: 'blur', - alert: 'alert', }; +export const eventHubEvents = [ + ALERT_EVENT, + LOADING_CONTENT_EVENT, + LOADING_SUCCESS_EVENT, + LOADING_ERROR_EVENT, +]; + const getComponentEventName = (tiptapEventName) => tiptapToComponentMap[tiptapEventName]; export default { - inject: ['tiptapEditor'], + inject: ['tiptapEditor', 'eventHub'], created() { this.disposables = []; Object.keys(tiptapToComponentMap).forEach((tiptapEvent) => { - const eventHandler = debounce((params) => this.handleTipTapEvent(tiptapEvent, params), 100); + const eventHandler = debounce( + (params) => this.bubbleEvent(getComponentEventName(tiptapEvent), params), + 100, + ); this.tiptapEditor?.on(tiptapEvent, eventHandler); this.disposables.push(() => this.tiptapEditor?.off(tiptapEvent, eventHandler)); }); + + eventHubEvents.forEach((event) => { + const handler = (...params) => { + this.bubbleEvent(event, ...params); + }; + + this.eventHub.$on(event, handler); + this.disposables.push(() => this.eventHub?.$off(event, handler)); + }); }, beforeDestroy() { this.disposables.forEach((dispose) => dispose()); }, methods: { - handleTipTapEvent(tiptapEvent, params) { - this.$emit(getComponentEventName(tiptapEvent), params); + bubbleEvent(eventHubEvent, params) { + this.$emit(eventHubEvent, params); }, }, render() { 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/constants.js b/app/assets/javascripts/content_editor/constants.js index 5e56078df01..a39a243ec6b 100644 --- a/app/assets/javascripts/content_editor/constants.js +++ b/app/assets/javascripts/content_editor/constants.js @@ -42,9 +42,10 @@ export const TEXT_STYLE_DROPDOWN_ITEMS = [ }, ]; -export const LOADING_CONTENT_EVENT = 'loadingContent'; +export const LOADING_CONTENT_EVENT = 'loading'; export const LOADING_SUCCESS_EVENT = 'loadingSuccess'; export const LOADING_ERROR_EVENT = 'loadingError'; +export const ALERT_EVENT = 'alert'; export const PARSE_HTML_PRIORITY_LOWEST = 1; export const PARSE_HTML_PRIORITY_DEFAULT = 50; @@ -56,3 +57,4 @@ export const EXTENSION_PRIORITY_LOWER = 75; * https://tiptap.dev/guide/custom-extensions/#priority */ export const EXTENSION_PRIORITY_DEFAULT = 100; +export const EXTENSION_PRIORITY_HIGHEST = 200; diff --git a/app/assets/javascripts/content_editor/extensions/attachment.js b/app/assets/javascripts/content_editor/extensions/attachment.js index 72df1d071d1..9634730f637 100644 --- a/app/assets/javascripts/content_editor/extensions/attachment.js +++ b/app/assets/javascripts/content_editor/extensions/attachment.js @@ -9,15 +9,22 @@ export default Extension.create({ return { uploadsPath: null, renderMarkdown: null, + eventHub: null, }; }, addCommands() { return { uploadAttachment: ({ file }) => () => { - const { uploadsPath, renderMarkdown } = this.options; + const { uploadsPath, renderMarkdown, eventHub } = this.options; - return handleFileEvent({ file, uploadsPath, renderMarkdown, editor: this.editor }); + return handleFileEvent({ + file, + uploadsPath, + renderMarkdown, + editor: this.editor, + eventHub, + }); }, }; }, @@ -29,23 +36,25 @@ export default Extension.create({ key: new PluginKey('attachment'), props: { handlePaste: (_, event) => { - const { uploadsPath, renderMarkdown } = this.options; + const { uploadsPath, renderMarkdown, eventHub } = this.options; return handleFileEvent({ editor, file: event.clipboardData.files[0], uploadsPath, renderMarkdown, + eventHub, }); }, handleDrop: (_, event) => { - const { uploadsPath, renderMarkdown } = this.options; + const { uploadsPath, renderMarkdown, eventHub } = this.options; return handleFileEvent({ editor, file: event.dataTransfer.files[0], uploadsPath, renderMarkdown, + eventHub, }); }, }, diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js index 9dc17fcd570..204ac07d401 100644 --- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js +++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js @@ -1,5 +1,5 @@ import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'; -import * as lowlight from 'lowlight'; +import { lowlight } from 'lowlight/lib/all'; const extractLanguage = (element) => element.getAttribute('lang'); 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..c349aa42a62 --- /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(({ document }) => { + if (!document) { + return; + } + + const { state, view } = editor; + const { tr, selection } = state; + + tr.replaceWith(selection.from - 1, selection.to, document.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/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js index a387322bff7..c5638da2daf 100644 --- a/app/assets/javascripts/content_editor/services/content_editor.js +++ b/app/assets/javascripts/content_editor/services/content_editor.js @@ -1,17 +1,23 @@ -import eventHubFactory from '~/helpers/event_hub_factory'; +import { TextSelection } from 'prosemirror-state'; import { LOADING_CONTENT_EVENT, LOADING_SUCCESS_EVENT, LOADING_ERROR_EVENT } from '../constants'; + /* eslint-disable no-underscore-dangle */ export class ContentEditor { - constructor({ tiptapEditor, serializer }) { + constructor({ tiptapEditor, serializer, deserializer, eventHub }) { this._tiptapEditor = tiptapEditor; this._serializer = serializer; - this._eventHub = eventHubFactory(); + this._deserializer = deserializer; + this._eventHub = eventHub; } get tiptapEditor() { return this._tiptapEditor; } + get eventHub() { + return this._eventHub; + } + get empty() { const doc = this.tiptapEditor?.state.doc; @@ -23,39 +29,31 @@ export class ContentEditor { this.tiptapEditor.destroy(); } - once(type, handler) { - this._eventHub.$once(type, handler); - } - - on(type, handler) { - this._eventHub.$on(type, handler); - } - - emit(type, params = {}) { - this._eventHub.$emit(type, params); - } - - off(type, handler) { - this._eventHub.$off(type, handler); - } - disposeAllEvents() { this._eventHub.dispose(); } async setSerializedContent(serializedContent) { - const { _tiptapEditor: editor, _serializer: serializer } = this; + const { _tiptapEditor: editor, _deserializer: deserializer, _eventHub: eventHub } = this; + const { doc, tr } = editor.state; + const selection = TextSelection.create(doc, 0, doc.content.size); try { - this._eventHub.$emit(LOADING_CONTENT_EVENT); - const document = await serializer.deserialize({ + eventHub.$emit(LOADING_CONTENT_EVENT); + const { document } = await deserializer.deserialize({ schema: editor.schema, content: serializedContent, }); - editor.commands.setContent(document); - this._eventHub.$emit(LOADING_SUCCESS_EVENT); + + if (document) { + tr.setSelection(selection) + .replaceSelectionWith(document, false) + .setMeta('preventUpdate', true); + editor.view.dispatch(tr); + } + eventHub.$emit(LOADING_SUCCESS_EVENT); } catch (e) { - this._eventHub.$emit(LOADING_ERROR_EVENT, e); + eventHub.$emit(LOADING_ERROR_EVENT, e); throw e; } } 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 f451357e211..d9d39a387d0 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -1,5 +1,6 @@ import { Editor } from '@tiptap/vue-2'; import { isFunction } from 'lodash'; +import eventHubFactory from '~/helpers/event_hub_factory'; import { PROVIDE_SERIALIZER_OR_RENDERER_ERROR } from '../constants'; import Attachment from '../extensions/attachment'; import Audio from '../extensions/audio'; @@ -38,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'; @@ -54,6 +56,7 @@ import Video from '../extensions/video'; import WordBreak from '../extensions/word_break'; import { ContentEditor } from './content_editor'; import createMarkdownSerializer from './markdown_serializer'; +import createMarkdownDeserializer from './markdown_deserializer'; import trackInputRulesAndShortcuts from './track_input_rules_and_shortcuts'; const createTiptapEditor = ({ extensions = [], ...options } = {}) => @@ -78,8 +81,10 @@ export const createContentEditor = ({ throw new Error(PROVIDE_SERIALIZER_OR_RENDERER_ERROR); } + const eventHub = eventHubFactory(); + const builtInContentEditorExtensions = [ - Attachment.configure({ uploadsPath, renderMarkdown }), + Attachment.configure({ uploadsPath, renderMarkdown, eventHub }), Audio, Blockquote, Bold, @@ -116,6 +121,7 @@ export const createContentEditor = ({ MathInline, OrderedList, Paragraph, + PasteMarkdown.configure({ renderMarkdown, eventHub }), Reference, Strike, Subscript, @@ -135,7 +141,8 @@ export const createContentEditor = ({ const allExtensions = [...builtInContentEditorExtensions, ...extensions]; const trackedExtensions = allExtensions.map(trackInputRulesAndShortcuts); const tiptapEditor = createTiptapEditor({ extensions: trackedExtensions, ...tiptapOptions }); - const serializer = createMarkdownSerializer({ render: renderMarkdown, serializerConfig }); + const serializer = createMarkdownSerializer({ serializerConfig }); + const deserializer = createMarkdownDeserializer({ render: renderMarkdown }); - return new ContentEditor({ tiptapEditor, serializer }); + return new ContentEditor({ tiptapEditor, serializer, eventHub, deserializer }); }; diff --git a/app/assets/javascripts/content_editor/services/markdown_deserializer.js b/app/assets/javascripts/content_editor/services/markdown_deserializer.js new file mode 100644 index 00000000000..cd4863d8eac --- /dev/null +++ b/app/assets/javascripts/content_editor/services/markdown_deserializer.js @@ -0,0 +1,33 @@ +import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model'; + +export default ({ render }) => { + /** + * Converts a Markdown string into a ProseMirror JSONDocument based + * on a ProseMirror schema. + * + * @param {Object} options — The schema and content for deserialization + * @param {ProseMirror.Schema} params.schema A ProseMirror schema that defines + * the types of content supported in the document + * @param {String} params.content An arbitrary markdown string + * + * @returns An object with the following properties: + * - document: A ProseMirror document object generated from the deserialized Markdown + * - dom: The Markdown Deserializer renders Markdown as HTML to generate the ProseMirror + * document. The dom property contains the HTML generated from the Markdown Source. + */ + return { + deserialize: async ({ schema, content }) => { + const html = await render(content); + + if (!html) return {}; + + const parser = new DOMParser(); + const { body } = parser.parseFromString(html, 'text/html'); + + // append original source as a comment that nodes can access + body.append(document.createComment(content)); + + return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body), dom: body }; + }, + }; +}; diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 925b411e51c..eaaf69c3068 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -1,4 +1,3 @@ -import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model'; import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer, @@ -237,31 +236,7 @@ const defaultSerializerConfig = { * that parses the Markdown and converts it into HTML. * @returns a markdown serializer */ -export default ({ render = () => null, serializerConfig = {} } = {}) => ({ - /** - * Converts a Markdown string into a ProseMirror JSONDocument based - * on a ProseMirror schema. - * @param {ProseMirror.Schema} params.schema A ProseMirror schema that defines - * the types of content supported in the document - * @param {String} params.content An arbitrary markdown string - * @returns A ProseMirror JSONDocument - */ - deserialize: async ({ schema, content }) => { - const html = await render(content); - - if (!html) return null; - - const parser = new DOMParser(); - const { body } = parser.parseFromString(html, 'text/html'); - - // append original source as a comment that nodes can access - body.append(document.createComment(content)); - - const state = ProseMirrorDOMParser.fromSchema(schema).parse(body); - - return state.toJSON(); - }, - +export default ({ serializerConfig = {} } = {}) => ({ /** * Converts a ProseMirror JSONDocument based * on a ProseMirror schema into Markdown diff --git a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js index a1199589c9b..4285e04bbab 100644 --- a/app/assets/javascripts/content_editor/services/markdown_sourcemap.js +++ b/app/assets/javascripts/content_editor/services/markdown_sourcemap.js @@ -1,7 +1,9 @@ -const getFullSource = (element) => { +import { isString } from 'lodash'; + +export const getFullSource = (element) => { const commentNode = element.ownerDocument.body.lastChild; - if (commentNode.nodeName === '#comment') { + if (commentNode?.nodeName === '#comment' && isString(commentNode.textContent)) { return commentNode.textContent.split('\n'); } diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index 4d5a54c0347..5fdd294aa96 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -259,11 +259,16 @@ export function renderContent(state, node, forceRenderInline) { } } -export function renderHTMLNode(tagName, forceRenderInline = false) { +export function renderHTMLNode(tagName, forceRenderContentInline = false) { return (state, node) => { renderTagOpen(state, tagName, node.attrs); - renderContent(state, node, forceRenderInline); + renderContent(state, node, forceRenderContentInline); renderTagClose(state, tagName, false); + + if (forceRenderContentInline) { + state.closeBlock(node); + state.flushClose(); + } }; } diff --git a/app/assets/javascripts/content_editor/services/upload_helpers.js b/app/assets/javascripts/content_editor/services/upload_helpers.js index f5bf2742748..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'; @@ -49,7 +50,7 @@ export const uploadFile = async ({ uploadsPath, renderMarkdown, file }) => { return extractAttachmentLinkUrl(rendered); }; -const uploadImage = async ({ editor, file, uploadsPath, renderMarkdown }) => { +const uploadImage = async ({ editor, file, uploadsPath, renderMarkdown, eventHub }) => { const encodedSrc = await readFileAsDataURL(file); const { view } = editor; @@ -72,14 +73,14 @@ const uploadImage = async ({ editor, file, uploadsPath, renderMarkdown }) => { ); } catch (e) { editor.commands.deleteRange({ from: position, to: position + 1 }); - editor.emit('alert', { + eventHub.$emit('alert', { message: __('An error occurred while uploading the image. Please try again.'), - variant: 'danger', + variant: VARIANT_DANGER, }); } }; -const uploadAttachment = async ({ editor, file, uploadsPath, renderMarkdown }) => { +const uploadAttachment = async ({ editor, file, uploadsPath, renderMarkdown, eventHub }) => { await Promise.resolve(); const { view } = editor; @@ -103,23 +104,23 @@ const uploadAttachment = async ({ editor, file, uploadsPath, renderMarkdown }) = ); } catch (e) { editor.commands.deleteRange({ from, to: from + 1 }); - editor.emit('alert', { + eventHub.$emit('alert', { message: __('An error occurred while uploading the file. Please try again.'), - variant: 'danger', + variant: VARIANT_DANGER, }); } }; -export const handleFileEvent = ({ editor, file, uploadsPath, renderMarkdown }) => { +export const handleFileEvent = ({ editor, file, uploadsPath, renderMarkdown, eventHub }) => { if (!file) return false; if (acceptedMimes.image.includes(file?.type)) { - uploadImage({ editor, file, uploadsPath, renderMarkdown }); + uploadImage({ editor, file, uploadsPath, renderMarkdown, eventHub }); return true; } - uploadAttachment({ editor, file, uploadsPath, renderMarkdown }); + uploadAttachment({ editor, file, uploadsPath, renderMarkdown, eventHub }); return true; }; |