Rich text editors such as CKEditor are often used in front end development. There are dozens of meters-worth of code in a library, including many features and Settings that you may not need. The latest version of CKEditor’s code repository has nearly 2000 JS files and 300,000 lines of code.

But if all you need is a simple editor, is it really worth introducing such a huge library?

Today we take you through the principles involved in implementing a simple version of the editor.

start

The editor will use Markdown: a language with concise syntax and styling that is far more secure than pure HTML input and output.

First, we need some dependency packages. @TS-stack /markdown and turnDown, @TS-stack /markdown is used to convert markdown syntax into HTML code for display, while Turndown is used to convert HTML code into Markdown language.

Next, create a basic Vue component named WysiWygeitor. Vue, add a div element to the component and set its contenteditable property to true, then add some Tailwind styles to spruse it up.

<! -- WysiwygEditor.vue --> <template> <div> <div @input="onInput" v-html="innerValue" contenteditable="true" class="wysiwyg-output outline-none border-2 p-4 rounded-lg border-gray-300 focus:border-green-300" /> </div> </template>  <script> export default { name: 'WysiwygEditor', props: ['value'], data() { return { innerValue: this.value } }, methods: { onInput(event) { this.$emit('input', event.target.innerHTML) } } } </script>Copy the code

Then use this component:

<! -- Some other component --> <template> <! -... --> <wysiwyg-editor v-model="someText" /> <! -... --> </template> <! -... -->Copy the code

It looks something like this

The div element now looks like a Textarea tag.

Make the text rich

At the top of the editor, there will be some editing buttons with bold, italic, underlined, keywords, lists and other text. And there will be ICONS on the corresponding functions. This can be achieved by installing the Fontawesome Icon. Then make some style changes to the button.

.button {
  @apply border-2;
  @apply border-gray-300;
  @apply rounded-lg;
  @apply px-3 py-1;
  @apply mb-3 mr-3;
}
.button:hover {
  @apply border-green-300;
}
Copy the code

Add these buttons to the mouse click listener method, and we will implement the specific execution of each method later.

<! -- WysiwygEditor.vue --> <template> <! -... --> <div class="flex flex-wrap"> <button @click="applyBold" class="button"> <font-awesome-icon :icon="['fas', 'bold']" /> </button> <button @click="applyItalic" class="button"> <font-awesome-icon :icon="['fas', 'italic']" /> </button> <button @click="applyHeading" class="button"> <font-awesome-icon :icon="['fas', 'heading']" /> </button> <button @click="applyUl" class="button"> <font-awesome-icon :icon="['fas', 'list-ul']" /> </button> <button @click="applyOl" class="button"> <font-awesome-icon :icon="['fas', 'list-ol']" /> </button> <button @click="undo" class="button"> <font-awesome-icon :icon="['fas', 'undo']" /> </button> <button @click="redo" class="button"> <font-awesome-icon :icon="['fas', 'redo']" /> </button> </div> <! -... --> </template> <! -... -->Copy the code

The editor now looks like this

Doesn’t look like it’s getting closer. There is also a lack of implementation methods for button actions. Document. execCommand is used here, and although MDN has announced that it will scrap this feature, most browsers still support it. Let’s stick with that for now.

Let’s use it to implement the applyBold method

methods: {
  // ...

  applyBold() {
    document.execCommand('bold')
  },

  // ...
}
Copy the code

It’s pretty neat, but again, let’s implement the other methods

// ...

  applyItalic() {
    document.execCommand('italic')
  },
  applyHeading() {
    document.execCommand('formatBlock', false, '<h1>')
  },
  applyUl() {
    document.execCommand('insertUnorderedList')
  },
  applyOl() {
    document.execCommand('insertOrderedList')
  },
  undo() {
    document.execCommand('undo')
  },
  redo() {
    document.execCommand('redo')
  }

  // ...
Copy the code

The only thing I need to say here is applyHeading, because I explicitly want to specify the desired elements here. With these commands, you can do some pre-styling of the element labels that you output

.wysiwyg-output h1 {
  @apply text-2xl;
  @apply font-bold;
  @apply pb-4;
}
.wysiwyg-output p {
  @apply pb-4;
}
.wysiwyg-output p {
  @apply pb-4;
}
.wysiwyg-output ul {
  @apply ml-6;
  @apply list-disc;
}
.wysiwyg-output ol {
  @apply ml-6;
  @apply list-decimal;
}
Copy the code

Once you have a style, type something in the input box

To make things more aesthetically pleasing, replace empty lines with empty paragraph tags and group content that ends in carriage returns as a paragraph

 // ...
  data() {
    return {
      innerValue: this.value || '<p><br></p>'
    }
  },

  mounted() {
    document.execCommand('defaultParagraphSeparator', false, 'p')
  },
  // ...
Copy the code

Add MarkDown support

If I want to write markdown syntax directly in the editor, it is not supported yet

# Hello, world!

**Lorem ipsum dolor** _sit amet_

* Some
* Unordered
* List


1. Some
1. Ordered
1. List
Copy the code

It looks something like this

No style at all. Don’t forget that the @TS-Stack/Markdown library was installed earlier and is now ready to use

import { Marked } from '@ts-stack/markdown'

export default {
  name: 'WysiwygEditor',

  props: ['value'],

  data() {
    return {
      innerValue: Marked.parse(this.value) || '<p><br></p>'
    }
  },

// ...
Copy the code

Once we convert the markdown syntax of the input into HTML code, it looks normal

You also need to transform the component as it exits the article editor data, using the turnDown installed earlier

import TurndownService from 'turndown'

export default {

// ...

  methods: {
    onInput(event) {
      const turndown = new TurndownService({
        emDelimiter: '_',
        linkStyle: 'inlined',
        headingStyle: 'atx'
      })

      this.$emit('input', turndown.turndown(event.target.innerHTML))
    },
// ...
Copy the code

Let’s take the markdown syntax text entered in the editor and export it to the page through the template

<! -- Some other component --> <template> <! -... --> <wysiwyg-editor v-model="someText" /> <pre class="p-4 bg-gray-300 mt-12">{{ someText }}</pre> <! -... --> </template>Copy the code

Input and output are synchronized, and the content is consistent without any problems

It looks like everything is working. It’s working the way we want it to. Here is the complete code

<template>
  <div>
    <div class="flex flex-wrap">
      <button @click="applyBold" class="button">
        <font-awesome-icon :icon="['fas', 'bold']" />
      </button>
      <button @click="applyItalic" class="button">
        <font-awesome-icon :icon="['fas', 'italic']" />
      </button>
      <button @click="applyHeading" class="button">
        <font-awesome-icon :icon="['fas', 'heading']" />
      </button>
      <button @click="applyUl" class="button">
        <font-awesome-icon :icon="['fas', 'list-ul']" />
      </button>
      <button @click="applyOl" class="button">
        <font-awesome-icon :icon="['fas', 'list-ol']" />
      </button>
      <button @click="undo" class="button">
        <font-awesome-icon :icon="['fas', 'undo']" />
      </button>
      <button @click="redo" class="button">
        <font-awesome-icon :icon="['fas', 'redo']" />
      </button>
    </div>

    <div
      @input="onInput"
      v-html="innerValue"
      contenteditable="true"
      class="wysiwyg-output outline-none border-2 p-4 rounded-lg border-gray-300 focus:border-green-300"
    />
  </div>
</template>

<script>
import { Marked } from '@ts-stack/markdown'
import TurndownService from 'turndown'

export default {
  name: 'WysiwygEditor',

  props: ['value'],

  data() {
    return {
      innerValue: Marked.parse(this.value) || '<p><br></p>'
    }
  },

  mounted() {
    document.execCommand('defaultParagraphSeparator', false, 'p')
  },

  methods: {
    onInput(event) {
      const turndown = new TurndownService({
        emDelimiter: '_',
        linkStyle: 'inlined',
        headingStyle: 'atx'
      })
      this.$emit('input', turndown.turndown(event.target.innerHTML))
    },
    applyBold() {
      document.execCommand('bold')
    },
    applyItalic() {
      document.execCommand('italic')
    },
    applyHeading() {
      document.execCommand('formatBlock', false, '<h1>')
    },
    applyUl() {
      document.execCommand('insertUnorderedList')
    },
    applyOl() {
      document.execCommand('insertOrderedList')
    },
    undo() {
      document.execCommand('undo')
    },
    redo() {
      document.execCommand('redo')
    }
  }
}
</script>
Copy the code

conclusion

It takes only 87 lines of code to implement a simple rich text editor. The functionality is still too simple, but at least we know the basics behind implementing a rich text editor. It’s not that hard to add functionality later.

Share core programming knowledge, pay attention to “space programming” public account