Rich text editor for Vue using Tiptap and Vuetify

casualcoder

Casual Coder

Posted on October 6, 2019

Rich text editor for Vue using Tiptap and Vuetify

Recently, I wanted to add a rich text editor in one of my pet project. I dabbled with CKEditor for a while but I found it a bit difficult to adopt in Vue (I'm fairly new to front end development)
Other option was Tiptap, It is a renderless editor based on Prosemirror. It was easy to follow, I was able to create a basic editor without any style quickly.

This is an overview of how to use Vuetify for styling of Tiptap editor

Setup

Assuming you alreaday have a Vuetify project-



$ npm add tiptap tiptap-extensions 


Enter fullscreen mode Exit fullscreen mode

Code Overview

Key concepts before going into the code (from docs)

Editor class

This class is a central building block of tiptap. It does most of the heavy lifting of creating a working ProseMirror editor such as creating the EditorView, setting the initial EditorState and so on. The Editor constructor accepts an object of editor options.

EditorContent

This is like an container component which accepts Editor instance as a property.

Extensions

Each editor feature like Headings, Bold, Italics, Images etc. are implemented as extensions. We need to pass instance of each extension in the editor option for each of the feature we want in our editor.

EditorMenuBar

Alt Text

This component holds all the toolbar buttons. The action is performed through commands eg. commands.bold, commands.image, which can be linked to click event of any button.

With this background we can dive into code -

Add Editor instance with Heading, Bold, Underline and Image extensions.



data() {
    return {
      editor: new Editor({
        content: `Type here...
        `,
        extensions:[
            new Heading({levels: [1,2,3]}),
            new Bold(),
            new Underline(),
            new Image(),
        ]
      })
    }
  },



Enter fullscreen mode Exit fullscreen mode

Pass editor as property to editor-content component



 <editor-content class="editor-box" :editor="editor"/>


Enter fullscreen mode Exit fullscreen mode

Create Editor's menubar. It uses slots which I don't fully understand at this point, But all we need to understand is that commands are the actions that we wish to perform like making something Bold, inserting image and isActive is used to check if current line or current selection has the Bold/Italic or not.



<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
            <div>
                <v-btn text icon
                :class="{ 'is-active': isActive.bold() }"
                @click="commands.bold"
                >
                    <v-icon>mdi-format-bold</v-icon>
                </v-btn>
            </div>
</editor-menu-bar>


Enter fullscreen mode Exit fullscreen mode

Here's the full code of the view.



<template>
  <v-container>
      <v-row>
        <editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
            <div >
                <v-btn text icon
                :class="{ 'is-active': isActive.heading({ level: 1 }) }"
                @click="commands.heading({level: 1})"
                >
                   <b> H1 </b>
                </v-btn>
                <v-btn text icon
                :class="{ 'is-active': isActive.bold() }"
                @click="commands.bold"
                >
                    <v-icon>mdi-format-bold</v-icon>
                </v-btn>

                <v-btn text icon
                :class="{ 'is-active': isActive.underline() }"
                @click="commands.underline"
                >
                    <v-icon>mdi-format-underline</v-icon>
                </v-btn>

               <v-btn text icon
               @click="loadImage(commands.image)">
                   <v-icon>mdi-image</v-icon>
               </v-btn>
            </div>            
        </editor-menu-bar>
    </v-row>
    <v-row>
        <v-col cols=12 >
            <editor-content class="editor-box" :editor="editor"/>
        </v-col>
    </v-row>
  </v-container>
</template>


<script>
import { Editor, EditorContent, EditorMenuBar  } from 'tiptap';
import { Heading, 
        Bold, 
        Underline,
        Image } from 'tiptap-extensions';
export default {
components: {
    EditorContent,
    EditorMenuBar,
  },
  data() {
    return {
      editor: new Editor({
        content: `Type here...
        `,
        extensions:[
            new Heading({levels: [1,2,3]}),
            new Bold(),
            new Underline(),
            new Image(),
        ]
      })
    }
  },
  methods:{
      loadImage:function(command){
          command({src: "https://66.media.tumblr.com/dcd3d24b79d78a3ee0f9192246e727f1/tumblr_o00xgqMhPM1qak053o1_400.gif"})
      }
  },
  beforeDestroy() {
    this.editor.destroy()
  },
};
</script>
<style >
.editor-box> * {
    border-color: grey;
    border-style: solid;
    border-width: 1px;
}

.is-active{
    border-color: grey;
    border-style: solid;
    border-width: 1px;
}
 /* *:focus {
    outline: none;
}  */
</style>


Enter fullscreen mode Exit fullscreen mode

Here's how it looks ultimately-

Alt Text

Hope this helps

💖 💪 🙅 🚩
casualcoder
Casual Coder

Posted on October 6, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related