Wysiwyg editor with Nuxt.js
Tags: vue, buefy, nuxt
Published: 2022-01-24 15:17:03

I implemented wysiwyg editor with a library named [tiptap](https://tiptap.dev/).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hk7df0cdmj7bosdjfwe1.png)

Although I am almost a beginner in Nuxt.js, it was really easy to create the component.

FYI, the sample code uses `buefy` though I skip about installing and the detail of it.

## Add tiptap to a project

[The official website](https://tiptap.dev/installation/nuxt) shows how to add it.
In my case, using yarn.

```
$ yarn add @tiptap/vue-2 @tiptap/starter-kit
```

## Implement a wysiwyg component

Create `Tiptap.vue` and write the following code as a component.

```typescript
<template>
  <div class="editor">
    <div v-if="editor" class="menu">
      <b-tooltip label="bold" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('bold') }"
          icon-left="format-bold"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleBold().run()"
        />
      </b-tooltip>
      <b-tooltip label="italic" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('italic') }"
          icon-left="format-italic"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleItalic().run()"
        />
      </b-tooltip>
      <b-tooltip label="strike" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('strike') }"
          icon-left="format-strikethrough"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleStrike().run()"
        />
      </b-tooltip>
      <b-tooltip label="code" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('code') }"
          icon-left="code-tags"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleCode().run()"
        />
      </b-tooltip>
      <b-tooltip label="paragraph" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('paragraph') }"
          icon-left="format-paragraph"
          type="is-text"
          size="small"
          @click="editor.chain().focus().setParagraph().run()"
        />
      </b-tooltip>
      <b-tooltip label="header1" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
          icon-left="format-header-1"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
        />
      </b-tooltip>
      <b-tooltip label="header2" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
          icon-left="format-header-2"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
        />
      </b-tooltip>
      <b-tooltip label="header3" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
          icon-left="format-header-3"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
        />
      </b-tooltip>
      <b-tooltip label="bulleted list" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('bulletList') }"
          icon-left="format-list-bulleted"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleBulletList().run()"
        />
      </b-tooltip>
      <b-tooltip label="ordered list" type="is-dark">
        <b-button
          :class="{ 'is-active': editor.isActive('orderedList') }"
          icon-left="format-list-numbered"
          type="is-text"
          size="small"
          @click="editor.chain().focus().toggleOrderedList().run()"
        />
      </b-tooltip>
      <b-tooltip label="undo" type="is-dark">
        <b-button
          icon-left="undo"
          type="is-text"
          size="small"
          @click="editor.chain().focus().undo().run()"
        />
      </b-tooltip>
      <b-tooltip label="redo" type="is-dark">
        <b-button
          icon-left="redo"
          type="is-text"
          size="small"
          @click="editor.chain().focus().redo().run()"
        />
      </b-tooltip>
    </div>
    <div class="input-field">
      <editor-content :editor="editor" />
    </div>
  </div>
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-2';
import StarterKit from '@tiptap/starter-kit';

export default {
  components: {
    EditorContent,
  },

  props: {
    value: {
      type: String,
      default: '',
    },
  },

  data() {
    return {
      editor: null,
    };
  },

  watch: {
    value(value) {
      // HTML
      const isSame = this.editor.getHTML() === value;

      // JSON
      // const isSame = JSON.stringify(this.editor.getJSON()) === JSON.stringify(value)

      if (isSame) {
        return;
      }

      this.editor.commands.setContent(value, false);
    },
  },

  mounted() {
    this.editor = new Editor({
      content: this.value,
      extensions: [StarterKit],
      onUpdate: () => {
        // HTML
        this.$emit('input', this.editor.getHTML());

        // JSON
        // this.$emit('input', this.editor.getJSON())
      },
    });
  },

  beforeDestroy() {
    this.editor.destroy();
  },
};
</script>

<style lang="scss" scoped>
.editor {
  border: 1px solid lightgray;
  border-radius: 5px;
  margin-top: 10px;
  margin-bottom: 10px;
}
.menu {
  border-bottom: 1px solid lightgray;
  padding: 10px;
}
.input-field {
  padding: 10px;
}
</style>
```

The block below represents one of the menu button.
I use `Material icons` so you can find the icons like `format-bold` on [the website](https://fonts.google.com/icons).

```typescript
<b-tooltip label="bold" type="is-dark">
 <b-button
   :class="{ 'is-active': editor.isActive('bold') }"
   icon-left="format-bold"
   type="is-text"
   size="small"
   @click="editor.chain().focus().toggleBold().run()"
 />
</b-tooltip>
```

When you call the component on any other component, you should pass a value like this.

```typescript
<tiptap value="something">
```

`something` must belong to like `data()`.
And when you modify the text on the wysiwyg editor, `something` property is going to be changed.