diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-24 18:15:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-24 18:15:02 +0300 |
commit | c4b4a75c35cb2015c01ef0b60f8ad8baaaf889df (patch) | |
tree | 16eabfd63477e1904d7eb5d9f92f3e5a4e4d3e0f /app/assets/javascripts/content_editor | |
parent | e40c68997d44209aed2baf3a8ec6be9ae99fb0b5 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor')
8 files changed, 78 insertions, 51 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..7e9380ec553 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -1,7 +1,6 @@ <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'; @@ -55,17 +54,12 @@ 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() { @@ -91,7 +85,14 @@ export default { <template> <content-editor-provider :content-editor="contentEditor"> <div> - <editor-state-observer @docUpdate="notifyChange" @focus="focus" @blur="blur" /> + <editor-state-observer + @loading="displayLoadingIndicator" + @loadingSuccess="hideLoadingIndicator" + @loadingError="hideLoadingIndicator" + @docUpdate="notifyChange" + @focus="focus" + @blur="blur" + /> <content-editor-alert /> <div data-testid="content-editor" 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/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/services/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js index a387322bff7..1fc05c434ca 100644 --- a/app/assets/javascripts/content_editor/services/content_editor.js +++ b/app/assets/javascripts/content_editor/services/content_editor.js @@ -1,17 +1,20 @@ -import eventHubFactory from '~/helpers/event_hub_factory'; 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, eventHub }) { this._tiptapEditor = tiptapEditor; this._serializer = serializer; - this._eventHub = eventHubFactory(); + this._eventHub = eventHub; } get tiptapEditor() { return this._tiptapEditor; } + get eventHub() { + return this._eventHub; + } + get empty() { const doc = this.tiptapEditor?.state.doc; @@ -23,39 +26,23 @@ 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, _serializer: serializer, _eventHub: eventHub } = this; try { - this._eventHub.$emit(LOADING_CONTENT_EVENT); + eventHub.$emit(LOADING_CONTENT_EVENT); const document = await serializer.deserialize({ schema: editor.schema, content: serializedContent, }); editor.commands.setContent(document); - this._eventHub.$emit(LOADING_SUCCESS_EVENT); + 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..e26fc5ca8c9 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'; @@ -78,8 +79,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, @@ -137,5 +140,5 @@ export const createContentEditor = ({ const tiptapEditor = createTiptapEditor({ extensions: trackedExtensions, ...tiptapOptions }); const serializer = createMarkdownSerializer({ render: renderMarkdown, serializerConfig }); - return new ContentEditor({ tiptapEditor, serializer }); + return new ContentEditor({ tiptapEditor, serializer, eventHub }); }; diff --git a/app/assets/javascripts/content_editor/services/upload_helpers.js b/app/assets/javascripts/content_editor/services/upload_helpers.js index f5bf2742748..33e9f455ef8 100644 --- a/app/assets/javascripts/content_editor/services/upload_helpers.js +++ b/app/assets/javascripts/content_editor/services/upload_helpers.js @@ -49,7 +49,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 +72,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', }); } }; -const uploadAttachment = async ({ editor, file, uploadsPath, renderMarkdown }) => { +const uploadAttachment = async ({ editor, file, uploadsPath, renderMarkdown, eventHub }) => { await Promise.resolve(); const { view } = editor; @@ -103,23 +103,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', }); } }; -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; }; |