Editor.vue 5.9 KB
Newer Older
les's avatar
les committed
1
2
<template lang='pug'>
  .editor(:class='{ "with-border": border }')
les's avatar
les committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    editor-menu-bubble(:editor='editor' :keep-in-bounds='true' v-slot='{ commands, isActive, getMarkAttrs, menu }')
      el-button-group.menububble(:class="{ 'is-active': menu.isActive }" :style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`")
        el-popover(trigger='hover' placement='bottom-start')
          el-button.float-left(slot='reference' size='mini') <v-icon name='question'/>
          template
            span This editor supports inline <code>markdown</code>
            div <v-icon name='heading'/> → Title ⇒ Start a line with <code>#</code>
            div <v-icon name='bold'/> → Bold ⇒ <code>ctrl+b</code>
            div <v-icon name='italic'/> → Italic ⇒ <code>ctrl+i</code>
            div <v-icon name='underline'/> → Underline ⇒ <code>ctrl+u</code>
            div <v-icon name='list-ul'/> → List ⇒ Start a line with <code>-</code>
            div <v-icon name='list-ol'/> → Ordered List ⇒ Start a line with <code>1.</code>
            div <v-icon name='quote-right'/> → Quote ⇒ Start a line with <code>&gt;</code>
            div <v-icon name='code'/> → Code ⇒ Use backtick <code>`</code>
            div <v-icon name='link'/> → Link ⇒ Select a word and fill the input
        //- el-button(size='mini' :class='{ "is-active": isActive.heading({level:4})}' @click='commands.heading({level: 4})') <v-icon name='heading'/>
        //- el-button(size='mini' :class='{ "is-active": isActive.bold() }' @click='commands.bold')
          <v-icon name='bold' />
        //- el-button(size='mini' :class='{ "is-active": isActive.italic() }' @click='commands.italic') <v-icon name='italic'/>
        //- el-button(size='mini' :class='{ "is-active": isActive.underline() }' @click='commands.underline') <v-icon name='underline'/>
        el-button(size='mini' :class='{ "is-active": isActive.link() }' @click='commands.link({href: ""}); $refs.link.focus(); linkActive=true') <v-icon name='link'/>
        input(:value='isActive.link() && getMarkAttrs("link") && getMarkAttrs("link").href || ""' ref='link' :class='{ "is-active": isActive.link() || linkActive }'
          placeholder='https://' @keypress.enter='commands.link({ href: $event.target.value})')
les's avatar
les committed
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
          //- el-button(size='mini' :class='{ "is-active": isActive.strike() }' @click='commands.strike') <v-icon name='strikethrough'/>
        //- br
        //- el-button-group
        //-   el-button(size='mini' :class='{ "is-active": isActive.code() }' @click='commands.code') <v-icon size=16 name='code'/>
        //-   el-button(size='mini' :class='{ "is-active": isActive.bullet_list() }' @click='commands.bullet_list') <v-icon name='list-ul'/>
        //-   //- el-button(size='mini' :class='{ "is-active": isActive.ordered_list() }' @click='commands.ordered_list') <v-icon name='list-ol'/>
        //-   el-button(size='mini' :class='{ "is-active": isActive.blockquote() }' @click='commands.blockquote') <v-icon name='quote-right'/>

        //- el-button.float-right(v-if='!noSave' size='mini' type='success' plain icon='el-icon-check'
        //-   @click='$emit("save", editor.getHTML())') {{$t('common.save')}}

    editor-content.content(:editor='editor' spellcheck='false')
</template>
<script>
import _ from 'lodash'
les's avatar
les committed
41
import { Editor, EditorContent, EditorMenuBar, EditorMenuBubble, Extension } from 'tiptap'
les's avatar
les committed
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
import {
  Blockquote,
  BulletList,
  CodeBlock,
  HardBreak,
  Heading,
  ListItem,
  OrderedList,
  Bold,
  Code,
  Italic,
  Link,
  History,
  Strike,
  Underline
} from 'tiptap-extensions'

export default {
  name: 'Editor',
  components: { EditorContent, EditorMenuBar, EditorMenuBubble },
  props: {
    value: { type: String, default: '' },
    border: { type: Boolean, default: false },
    noSave: { type: Boolean, default: false }
  },
  data () {
    return {
      linkActive: false,
les's avatar
les committed
70
71
72
73
74
75
76
77
78
79
80
      editor: null,
      update: false
    }
  },
  watch: {
    value () {
      if (this.update) {
        this.update = false
        return
      }
      this.editor.setContent(this.value)
les's avatar
les committed
81
82
83
84
    }
  },
  mounted () {
    this.editor = new Editor({
les's avatar
les committed
85
86
87
88
      onUpdate: _.debounce(({ getHTML }) => {
        this.update = true
        this.$emit('input', getHTML())
      }, 300),
les's avatar
les committed
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
      content: this.value,
      extensions: [
        new Blockquote(),
        new BulletList(),
        new CodeBlock(),
        new HardBreak(),
        new Heading({ levels: [1, 2, 3, 4, 5, 6] }),
        new OrderedList(),
        new ListItem(),
        new Code(),
        new History(),
        new Link({ openOnClick: false }),
        new Bold(),
        new Italic(),
        new Strike(),
les's avatar
les committed
104
        new Underline()
les's avatar
les committed
105
106
107
108
109
110
111
112
      ]
    })
  },
  beforeDestroy () {
    if (this.editor) { this.editor.destroy() }
  }
}
</script>
les's avatar
les committed
113
<style lang='less'>
les's avatar
les committed
114
115

.editor {
les's avatar
les committed
116
  position: relative;
les's avatar
les committed
117
  overflow-y: auto;
les's avatar
les committed
118
  padding-top: 1.7em;
les's avatar
les committed
119
120
121
122
123
124
125
126
127
128
129
130
131
132
  scrollbar-width: thin;

  &.with-border {
    border: 1px solid #ddd;
    border-radius: 5px;
  }

  .content {
    padding: 0px 5px 0px 5px;
    flex: 1;
    scrollbar-width: thin;
    overflow-y: auto;
  }

les's avatar
les committed
133
134
  .menububble {
    position: absolute;
les's avatar
les committed
135
136
    display: flex;
    overflow: hidden;
les's avatar
les committed
137
    opacity: 0;
les's avatar
les committed
138
    z-index: 1;
les's avatar
les committed
139
    background: #dddddd;
les's avatar
les committed
140
    transform: translateX(-50%);
les's avatar
les committed
141
    border-radius: 3px;
les's avatar
les committed
142
    padding: 0.07rem;
les's avatar
les committed
143
    transition: opacity 0.2s, visibility 0.2s, left .2s, bottom .2s;
les's avatar
les committed
144
    visibility: hidden;
les's avatar
les committed
145

les's avatar
les committed
146
147
148
149
    &.is-active {
      opacity: 1;
      visibility: visible;
    }
les's avatar
les committed
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    input {
      padding: 0;
      margin: 1px;
      display: block;
      border: 0;
      color: #444;
      font-size: .8em;
      border-radius: 3px;
      line-height: 100%;
      transition: width .2s;
      padding-left: 5px;
      flex-grow: 1;
    }

    .fa-icon {
      width: auto;
      font-size: 10px;
      height: 1.4em; /* or any other relative font sizes */
      /* You would have to include the following two lines to make this work in Safari */
les's avatar
les committed
169
      // max-width: 100%;
les's avatar
les committed
170
171
172
173
174
175
176
      max-height: 100%;
    }

  }
}

</style>