Naturally, when a text editor comes up in a discussion, creating a custom solution is not the first thing that comes to mind. So I went ahead, and I started looking for what reliable WYSIWYG editors I could use. There are quite a few ready solutions on the internet. Some are famous and used by well-known platforms such as WordPress. But many are also abandoned and are left to collect dust in GitHub repositories.
After lots of time searching and testing editors online, it turns out that we have some rather unique requirements. None of the available editors met our criteria, so I was set on an adventure to code a custom WYSIWYG editor.
In my years of frontend experience, this project gave me the wildest ride in terms of rare bugs and unexpected behavior. I've googled so hard that I ended up finding obscure WebKit bugs that aren't closed since a decade ago!
I want to clear out that I have only used modern Chrome and Firefox. Contenteditable in Safari and older browsers is another world that I will cover in future blog posts.
A custom WYSIWYG editor in 2021?
You might ask yourself, do we need an in-house solution when there are hundreds of options out there? And the answer is surprising, yes - we do!
The nature of our services asks for it; we need a custom data type to store and process the contents of a contenteditable for later use in our product line. For example, the content you create through our editor must be searchable, and also it is rendered in all of our products in very diverse styles - such as in the helpdesk widget, search widget, or our knowledge base platform.
The current solutions don't offer too much control in processing the data from the contenteditable - the output is always dirty HTML, and storing and processing HTML generated by a contenteditable in the database is a no-go.
Oh, and we also needed as many elements as possible, including custom ones which not only the current libraries didn't support - but also it's not possible to extend functionality or register new ones unless you fork the whole thing.
Our editor currently supports:
- Bold
- Italic
- Underline
- Headings
- Bulleted List
- URLs
- Inline Code
- Images
- Table
- Horizontal Dividers
- Blockquote
- Code block (with syntax highlights and multi-language support)
- Youtube videos
- and a few more
Starting the development
My thought process was to use the contenteditable attribute to create a straightforward editor first - by following best practices available online. The kind of editor that only supports bold, italic, and underline. And then figure out a way to extend functionality and add custom elements as we need them.
My thinking took that direction because when I see open source projects such as Firefox or Chromium, "inconsistent mess" is not the first thought that goes in my mind. I thought the current contenteditable attribute could handle such a simple editor - but I was wrong.
Pain #1: execCommand is deprecated
To make a text bold with contenteditable, you have to use the JavaScript method execCommand
which, if you look up into the MDN, it's a deprecated method! And there is no other method available. It's the only one you can use to style text in a contenteditable!
Frankly, this is quite a red flag, but I decided to push forward anyway.
Many editors on the planet right now are at risk of simply stopping working in the next version of Chrome or Firefox when the browser vendor decides to drop execCommand
altogether. I haven't seen any editors that don't use the execCommand
method yet.
Pain #2: Backspace merge from hell
Picture this scenario: Your cursor is at the beginning of a paragraph. Just before this paragraph, there is a heading (H2), and you hit backspace. What should happen?
In layman's terms: the paragraph should join the heading. And in more technical terms: the contents of the P tag should go inside the contents of the H2 tag. But this is not what was happening when I looked in dev tools.
<!-- current HTML -->
<h2>My heading</h2>
<p>My very short paragraph.</p>
<!-- expected outcome -->
<h2>My headingMy very short paragraph.</h2>
Upon hitting backspace, the browser decides to convert the P tag into a SPAN - and then it applies some inline CSS to this SPAN to make it look like the H2 tag and then puts this new SPAN element inside the H2 tag.
<!-- actual outcome -->
<h2>My heading<span style="font-size: 20px; line-height: 25px;">My very short paragraph.</span></h2>
To add more to this complexity, if the paragraph has styles such as bold and italic - it will also convert the B and I elements into a SPAN and preserve the text style with inline CSS through the text-style
property.
When I found out that contenteditable is committing sins with HTML elements, I questioned whether I would finish this project. But this is only pain #2, so keep reading to find out more!
Pain #3: Funny line breaks
The most well-known way to add a line break (vertical space) since Firefox 1 is by using the BR tag.
When the cursor is in the middle of a chunk of text, and you hit Enter twice - you would naturally think that chunk of text gets separated in two, and then contenteditable adds two BR tags in between.
Not quite. Contenteditable might add more than two BR tags - or even worse, it may add two P
tags with extra margins to fake a line break. As an example:
<p>Line 1</p>
<p>Line 2</p>
<p>Line 3</p>
The situation is even more complicated when dealing with user selection. A lot of issues are found when handling pasted content - sometimes the user copied content with extra line breaks, and then paste this content into contenteditable. The result is often unclear and unexpected, which means I need to handle this situation manually.
Pain #4: User selection quirks
It turns out that different browsers have different quirks when it comes to user selection.
The most well-known example is the fact that, in Firefox, you need to use document.getSelection
instead of window.getSelection
. This is not well documented, and since it's an inconsistent mess, handling user selection is one of the worst and most time-consuming parts of this project.
Different quirks are quite common in older and modern browsers alike. It will take a lot of time and effort to get the user selection working as expected - and this is often one of the most overlooked problems in WYSIWYG editors.
This is one of the most challenging aspects of using contenteditable, and it has taken a lot of time and effort to get it working correctly.
Conclusion
It's safe to say that building a custom WYSIWYG editor with contenteditable is not a fun ride. It has taken a lot of time and effort to overcome the bugs and inconsistencies that come with contenteditable. However, it has also been a valuable learning experience.
If you're considering building your own WYSIWYG editor, make sure you are aware of the quirks and challenges involved. There are many well-established editors out there, and while they might not fit all your needs, they are often more reliable and easier to use than creating a custom solution from scratch.
In the end, I hope that sharing these experiences can help others who are facing similar challenges. If you have any questions or need help, feel free to reach out!