1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
<script>
import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2';
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 './bubble_menus/formatting.vue';
import CodeBlockBubbleMenu from './bubble_menus/code_block.vue';
import LinkBubbleMenu from './bubble_menus/link.vue';
import MediaBubbleMenu from './bubble_menus/media.vue';
import TopToolbar from './top_toolbar.vue';
import LoadingIndicator from './loading_indicator.vue';
export default {
components: {
LoadingIndicator,
ContentEditorAlert,
ContentEditorProvider,
TiptapEditorContent,
TopToolbar,
FormattingBubbleMenu,
CodeBlockBubbleMenu,
LinkBubbleMenu,
MediaBubbleMenu,
EditorStateObserver,
},
props: {
renderMarkdown: {
type: Function,
required: true,
},
uploadsPath: {
type: String,
required: true,
},
extensions: {
type: Array,
required: false,
default: () => [],
},
serializerConfig: {
type: Object,
required: false,
default: () => {},
},
},
data() {
return {
focused: false,
};
},
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,
});
},
mounted() {
this.$emit('initialized', this.contentEditor);
},
beforeDestroy() {
this.contentEditor.dispose();
},
methods: {
focus() {
this.focused = true;
},
blur() {
this.focused = false;
},
notifyChange() {
this.$emit('change', {
empty: this.contentEditor.empty,
});
},
},
};
</script>
<template>
<content-editor-provider :content-editor="contentEditor">
<div>
<editor-state-observer @docUpdate="notifyChange" @focus="focus" @blur="blur" />
<content-editor-alert />
<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" />
<div class="gl-relative">
<formatting-bubble-menu />
<code-block-bubble-menu />
<link-bubble-menu />
<media-bubble-menu />
<tiptap-editor-content class="md" :editor="contentEditor.tiptapEditor" />
<loading-indicator />
</div>
</div>
</div>
</content-editor-provider>
</template>
|