summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/content_editor/components/content_editor.vue
blob: 5b3f4f4ddf264cc9520c5000c0dde406c8048b3d (plain)
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
<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 './formatting_bubble_menu.vue';
import CodeBlockBubbleMenu from './code_block_bubble_menu.vue';
import TopToolbar from './top_toolbar.vue';
import LoadingIndicator from './loading_indicator.vue';

export default {
  components: {
    LoadingIndicator,
    ContentEditorAlert,
    ContentEditorProvider,
    TiptapEditorContent,
    TopToolbar,
    FormattingBubbleMenu,
    CodeBlockBubbleMenu,
    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 />
          <tiptap-editor-content class="md" :editor="contentEditor.tiptapEditor" />
          <loading-indicator />
        </div>
      </div>
    </div>
  </content-editor-provider>
</template>