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
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-
Hope this helps
Top comments (8)
Good article. You can also use my package tiptap-vuetify for quick start.
I've checked out you github well done)
wow, what an easy to user plugin
congs man
hi, many thanks for the awesome tool, but please how can I write the content to json file because it give null value when i try to write the content in json file
let jsonData = {discussion:this.discussion}
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()
},
would you please explain to me how to write the content from the text editor to json file because it gives null values