loading...

Rich text editor for Vue using Tiptap and Vuetify

casualcoder profile image Casual Coder ・3 min read

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 

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(),
        ]
      })
    }
  },

Pass editor as property to editor-content component

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

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>

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>

Here's how it looks ultimately-

Alt Text

Hope this helps

Posted on by:

Discussion

markdown guide
 

Good article. You can also use my package tiptap-vuetify for quick start.

 

I've checked out you github well done)

 

Awesome tool, i was searching something like that! Thanks!!! :)

 

How to get the content of the editor?
Is it in this.editor? This part is confusing me. Because this.editor doesn't contain generated html content.

 

I've found out how to do that
onUpdate: ({ getHTML }) => {
// get new content on update
const newContent = getHTML()
},