Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Build your own WYSIWYG markdown editor for Vue 📝👀
Pascal Thormeier
Pascal Thormeier

Posted on • Edited on

     

Build your own WYSIWYG markdown editor for Vue 📝👀

HTML5 and modern JavaScript make a lot of things a lot easier than they used to be back in the days. Complex things don't require a lot of hacks anymore, many things come out of the box.

There's a lot of off-the-shelf WYSIWYG (What You See Is What You Get, a.k.a. "rich text") editors, likeCKEditor. They offer a ton of features and several guides, features and plugins for all kinds of frameworks, but their code bases are usually huuuge. I mean, the CKEditor 5 repository hasaround 2000 JS files totalling in around 300k lines of code - mind-boggling, isn't it?

And probably unnecessary: Most use cases don't require a PDF or even a Word export, real-time collaboration, maths and chemistry integration, revisions, automatic creation of bibliographies, or afull-blown Excel clone. When you only want some basic text editing - why not build your own WYSIWYG editor once?

In this post, I will explain how to create your own WYSIWYG markdown editor for Vue!

Getting started

This editor will use markdown: It's a simple syntax, can be styled however I want and is a lot safer to persist and output again than plain HTML.

First, I need a few packages. Namely@ts-stack/markdown andturndown.@ts-stack/markdown to display markdown as HTML andturndown to transfer HTML back into markdown.

Next, I create a basic Vue component that supportsv-model and call itWysiwygEditor.vue. I can already use a<div> here with the attributecontenteditable. I also add some Tailwind styling to make it look nice.

<!-- 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>exportdefault{name:'WysiwygEditor',props:['value'],data(){return{innerValue:this.value}},methods:{onInput(event){this.$emit('input',event.target.innerHTML)}}}</script>
Enter fullscreen modeExit fullscreen mode

This component can now be used like this:

<!-- Some other component --><template><!-- ... --><wysiwyg-editorv-model="someText"/><!-- ... --></template><!-- ... -->
Enter fullscreen modeExit fullscreen mode

This would look like this:

WYSIWYG editor, styled with Tailwind

The div now basically behaves like atextarea with a tiny difference: It produces HTML.

Putting the "rich" into "rich text"

You probably know the buttons to make text bold, italic or underlined and to add lists, headings, etc. from programs like Google Docs or Word. Let's add those next. For this I installedfontawesome icons and add the buttons right above the textarea-div. But first: Some styling:

.button{@applyborder-2;@applyborder-gray-300;@applyrounded-lg;@applypx-3py-1;@applymb-3mr-3;}.button:hover{@applyborder-green-300;}
Enter fullscreen modeExit fullscreen mode

I will already add the click listeners and implement the methods used a bit later on.

<!-- WysiwygEditor.vue --><template><!-- ... --><divclass="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><!-- ... -->
Enter fullscreen modeExit fullscreen mode

The editor now looks like this:

WYSIWYG editor, now with buttons.

Amazing. Now I need to add actual functionality to this thing. For this I will usedocument.execCommand, which is more or less made for creating WYSIWYG editors. Even though MDN states that this feature is deprecated, most browser still offer some support for it, so for the most basic functions, it should still work out.

Let's implement theapplyBold method:

methods:{// ...applyBold(){document.execCommand('bold')},// ...}
Enter fullscreen modeExit fullscreen mode

Ok, that's pretty straight forward. Now the rest:

// ...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')}// ...
Enter fullscreen modeExit fullscreen mode

The only method popping out here isapplyHeading, because I explicitly need to specify here which element I want. With these commands in place, I can continue to style the output a bit:

.wysiwyg-outputh1{@applytext-2xl;@applyfont-bold;@applypb-4;}.wysiwyg-outputp{@applypb-4;}.wysiwyg-outputp{@applypb-4;}.wysiwyg-outputul{@applyml-6;@applylist-disc;}.wysiwyg-outputol{@applyml-6;@applylist-decimal;}
Enter fullscreen modeExit fullscreen mode

The finished editor (with some example content looks like this:

Editor with example content

To make things behave a little nicer, I also need to set an empty paragraph as default for empty content and make the default "line break" be a paragraph, too:

// ...data(){return{innerValue:this.value||'<p><br></p>'}},mounted(){document.execCommand('defaultParagraphSeparator',false,'p')},// ...
Enter fullscreen modeExit fullscreen mode

Adding in the markdown

So, I want to put markdowninto the editor and get markdownout of it. I start by defining some markdown string to see what happens:

# Hello, world!**Lorem ipsum dolor** _sit amet_* Some* Unordered* List1. Some1. Ordered1. List
Enter fullscreen modeExit fullscreen mode

WYSIWYG editor with unformatted markdown

Yup, nothing happens. Remember the@ts-stack/markdown lib I installed earlier? Let's use it:

import{Marked}from'@ts-stack/markdown'exportdefault{name:'WysiwygEditor',props:['value'],data(){return{innerValue:Marked.parse(this.value)||'<p><br></p>'}},// ...
Enter fullscreen modeExit fullscreen mode

And now the input will be rendered as HTML:

Formatted markdown in the WYSIWYG editor

Awesome! Now in order to get markdownout of the component, I useturndown:

importTurndownServicefrom'turndown'exportdefault{// ...methods:{onInput(event){constturndown=newTurndownService({emDelimiter:'_',linkStyle:'inlined',headingStyle:'atx'})this.$emit('input',turndown.turndown(event.target.innerHTML))},// ...
Enter fullscreen modeExit fullscreen mode

Let's see if it works by outputting the markdown we receive in a preformatted div:

<!-- Some other component --><template><!-- ... --><wysiwyg-editorv-model="someText"/><preclass="p-4 bg-gray-300 mt-12">{{ someText }}</pre><!-- ... --></template>
Enter fullscreen modeExit fullscreen mode

Awesome! Done! Let's put this thing to the test:

More content added and generated markdown outputted.

Animated version of the markdown editor.

Seems to be working!

For reference, here's the entire component:

<template>
<div>
<divclass="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>

<span>&lt;div</span>  <span>@</span><span>input=</span><span>"onInput"</span>  <span>v-html=</span><span>"innerValue"</span>  <span>contenteditable=</span><span>"true"</span>  <span>class=</span><span>"wysiwyg-output outline-none border-2 p-4 rounded-lg border-gray-300 focus:border-green-300"</span><span>/&gt;</span>
Enter fullscreen modeExit fullscreen mode

</div>
</template>

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

exportdefault{
name:'WysiwygEditor',

props:['value'],

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

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

methods:{
onInput(event){
constturndown=newTurndownService({
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>

Enter fullscreen modeExit fullscreen mode




Takeaway thoughts

That was fun. A WYSIWYG editor in 87 lines of Vue is quite small. The component behaves like an input usingv-model which is more added convenience. In my opinion, for a hobby project, this editor is sufficient for small cases where there's not much content.

In client projects though, I'd rather use an off-the-shelf solution, because of better maintainability, features and support. But building this thing was great learning opportunity, nevertheless!


I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a ❤️or a 🦄! I write tech articles in my free time and like to drink coffee every once in a while.

If you want to support my efforts,buy me a coffeeorfollow me on Twitter 🐦!

Buy me a coffee button

Top comments(27)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
bogdaniel profile image
Bogdan Olteanu
I love coding and creating new apps.
  • Location
    Ploiesti
  • Work
    Web Developer at Zenchron Dynamics
  • Joined

What would be the alternative to document.execCommand since it's deprecated ? :D I've been looking for ideas for quite some time.

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

A very good question. There's a few options, actually, but none are quite as elegant as this. According to caniuse, all major browsersstill offer support for execCommand.

Turning on/off different modes, like bold or italic text can be achieved by creating a tree-like structure where you edit nodes in the back. Theexperimental selection API could help with features that work on selected text, as in "select text and mark as bold" for example. I would probably still usecontenteditable, but try to emulate the features that execCommand uses. There's also the possibility of using a polyfill at some point in the future, but none is available right now from what I can tell.

What have you stumbled upon during your research?

CollapseExpand
 
bogdaniel profile image
Bogdan Olteanu
I love coding and creating new apps.
  • Location
    Ploiesti
  • Work
    Web Developer at Zenchron Dynamics
  • Joined

Selection API is something really nice but as it states is experimental, second option would berange combined with a tree-like structure as you said and contenteditable since it's closest to what we want. prob's textarea would work fine too. The Range API I don't really like it for the moment .. :-) specially if you would want to combine it with a tree structure or maybe I'm missing a few things here and there. :D

Thread Thread
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

To be honest, I would have to read a lot deeper into the range API to really understand it, but it does look promising! I hope that the Selection API will get some more attention and leave the "experimental" state soon, since it has a lot of potential. What's the reason for you not liking the Range API at the moment?

Thread Thread
 
hasnaindev profile image
Muhammad Hasnain
  • Joined

Despite being experimental, the selection API has the same support as the range API. Perhaps an abstraction layer would do the trick but will take a lot of time and careful engineering.

About a year ago I really wanted to create my own WYSIWYG editor. Things I read about document exec were not good. For instance, what happens when someone tries to paste text from outside, how are we going to handle the tags in the pasted text.

Moreover, it is deprecated which is literally a mood killer. Someday I'll create a really simple WYSIWYG editor using range and or selection API. I have general knowledge of trees but working with nodes, correctly inserting and removing nodes is NOT an easy thing.

Thread Thread
 
bogdaniel profile image
Bogdan Olteanu
I love coding and creating new apps.
  • Location
    Ploiesti
  • Work
    Web Developer at Zenchron Dynamics
  • Joined
• Edited on• Edited

Yeap, same here would really love to build my own WYSIWYG even for testing / learning purposes I know a great range of editors but still want to dive in this part and build one from scratch..
document exec is great and easy to use most of the editors these days are based on them except the very new ones. but as you said it's deprecated and you can't count on it + building with exec it's a 20 - 30 minutes jobs to do simple things. Diving into selection and range that's the real stuff to play with.

Thread Thread
 
hasnaindev profile image
Muhammad Hasnain
  • Joined

Indeed. When I was looking into this initially an agency shared a kind of a case study saying that making a WYSIWYG was one of the most difficult things that they had ever done. They even went on to say that it is one of the most complicated thing on the front-end.

Thread Thread
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

Oh yes, it absolutely is. I find it an absolute pity that execCommand got deprecated. This thing shows how simple it could be, but ultimately isn't when you want to have support on all browsers and not rewrite that thing in a year or two.

CollapseExpand
 
ewencodes profile image
ewencodes
  • Joined

So helpful! Love it!

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

Glad you liked it!

CollapseExpand
 
jonnykio profile image
Jonathan Kumar
Software Engineer from Sri Lanka
  • Education
    BCAS
  • Work
    Software Engineer
  • Joined
• Edited on• Edited

Not sure if this is due to Vue 3 or not. But, the emit 'input' doesn't work directly on Vue.js for custom components. You have to useupdate:modelValue to trigger the change and read the prop frommodelValue. You can find out more from herevuejs.org/guide/components/events....

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

You're absolutely right! I've written this post begining of 2021, and Vue3 wasn't the default until January 2022. Since people still find this useful, I'll update it in the coming days.

CollapseExpand
 
atellmer profile image
AlexPlex
  • Joined

Great article

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

Thank you!

CollapseExpand
 
samcodestack profile image
Godwin Samson
Am an hardworking developer
  • Email
  • Location
    Nigeria
  • Education
    OAU, OBAFEMI AWOLOWO UNIVERSITY
  • Work
    Dev at Samcode Web service
  • Joined

That's nice

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

Thank you, so glad you liked it! :)

CollapseExpand
 
simonholdorf profile image
Simon Holdorf
Coder • Freelancer • Creator Cloud • DevOps • Full-Stack • Data • Security • AI YouTube: https://youtube.com/@litwireSite: https://bio.link/simonholdorf Newsletter: https://litwire.substack.com

Awesome, thanks!

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

You're very welcome! I had so much fun writing and building this!

CollapseExpand
 
karthikeyan676 profile image
Karthikeyan
Creating...

document.execCommand - it's deprecated

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

Yup, that's what I wrote in the article as well.🙂 I still think for such an experiment it's sufficient, as most browsers still support a basic version of the commands.

CollapseExpand
 
aneesh777 profile image
aneesh777
  • Location
    India
  • Work
    Junior MEAN stack developer at Loremine
  • Joined

Great

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

Thank you!

CollapseExpand
 
vkbishu profile image
Venkatesh Bishu
Senior Frontend Engineer @ Popup Inc. U.S.
  • Location
    India
  • Work
    Senior Frontend Engineer Popup. Inc. US
  • Joined

Nice article.

CollapseExpand
 
gok9ok profile image
Gokce Tosun
Binge watches. Binge critiques. Obsessed with painting 🎨, future AI overlords 🤖 and emojis ❤️
  • Location
    Warsaw
  • Work
    Senior Marketing Specialist at CKSource
  • Joined

In case you'd like to take on a similar challenge, this article may be helpfulckeditor.com/blog/ContentEditable-...

CollapseExpand
 
thormeier profile image
Pascal Thormeier
Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

Thank you for this article! Very well stated why this shouldn't go beyond hobby projects and experimenting. Off-the-shelf WYSIWYG editors are amazing, and I most probably wouldn't use a self-made one in a larger client project, too, but trying to build one made me understand the challenges editors like Quill and CKEditor are facing, so this was an amazing learning opportunity!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Passionate full stack web developer, course author for Educative, book author for Packt, he/him.Find my work and get to know me on my Linktree: https://linktr.ee/thormeier
  • Location
    Switzerland
  • Education
    BSc FHNW Computer Science (iCompetence)
  • Work
    Senior Software Developer at Liip
  • Joined

More fromPascal Thormeier

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp