diff options
Diffstat (limited to 'app/assets/javascripts/content_editor/components/content_editor.vue')
-rw-r--r-- | app/assets/javascripts/content_editor/components/content_editor.vue | 108 |
1 files changed, 87 insertions, 21 deletions
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index 9a51def7075..a372233e543 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -1,45 +1,111 @@ <script> -import { GlAlert } from '@gitlab/ui'; +import { GlLoadingIcon } from '@gitlab/ui'; import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2'; -import { ContentEditor } from '../services/content_editor'; +import { LOADING_CONTENT_EVENT, LOADING_SUCCESS_EVENT, LOADING_ERROR_EVENT } from '../constants'; +import { createContentEditor } from '../services/create_content_editor'; +import ContentEditorError from './content_editor_error.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'; export default { components: { - GlAlert, + GlLoadingIcon, + ContentEditorError, + ContentEditorProvider, TiptapEditorContent, TopToolbar, + FormattingBubbleMenu, + EditorStateObserver, }, props: { - contentEditor: { - type: ContentEditor, + renderMarkdown: { + type: Function, required: true, }, + uploadsPath: { + type: String, + required: true, + }, + extensions: { + type: Array, + required: false, + default: () => [], + }, + serializerConfig: { + type: Object, + required: false, + default: () => {}, + }, }, data() { return { - error: '', + isLoadingContent: false, + focused: false, }; }, - mounted() { - this.contentEditor.tiptapEditor.on('error', (error) => { - this.error = error; + created() { + const { renderMarkdown, uploadsPath, extensions, serializerConfig } = this; + + // This is a non-reactive attribute intentionally since this is a complex object. + this.contentEditor = createContentEditor({ + renderMarkdown, + uploadsPath, + 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); + 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; + }, + blur() { + this.focused = false; + }, + notifyChange() { + this.$emit('change', { + empty: this.contentEditor.empty, + }); + }, }, }; </script> <template> - <div> - <gl-alert v-if="error" class="gl-mb-6" variant="danger" @dismiss="error = ''"> - {{ error }} - </gl-alert> - <div - data-testid="content-editor" - class="md-area" - :class="{ 'is-focused': contentEditor.tiptapEditor.isFocused }" - > - <top-toolbar ref="toolbar" class="gl-mb-4" :content-editor="contentEditor" /> - <tiptap-editor-content class="md" :editor="contentEditor.tiptapEditor" /> + <content-editor-provider :content-editor="contentEditor"> + <div> + <editor-state-observer @docUpdate="notifyChange" @focus="focus" @blur="blur" /> + <content-editor-error /> + <div + data-testid="content-editor" + data-qa-selector="content_editor_container" + class="md-area" + :class="{ 'is-focused': focused }" + > + <top-toolbar ref="toolbar" class="gl-mb-4" /> + <formatting-bubble-menu /> + <div v-if="isLoadingContent" class="gl-w-full gl-display-flex gl-justify-content-center"> + <gl-loading-icon size="sm" /> + </div> + <tiptap-editor-content v-else class="md" :editor="contentEditor.tiptapEditor" /> + </div> </div> - </div> + </content-editor-provider> </template> |