All Articles

Building rich text editor

Rich text editors is one of those things that you see in almost in every application out there but usually the way they work is kind of abstracted away from most developers by some libraries like DraftJs, Trix or stick with good old contentEditable (more on this later). But it is nice to try building it from scratch just to see how it will work inside these editors and not see them as magic.

contentEditable

contentEditable is a property that is supported by most browsers that allow you to make any element editable. Go head open dev tools and run the following snippet

document.getElementsByTagName("body")[0].contentEditable = true

Now the whole page should become editable and even key bindings like ctrl+b, ctrl+i, ctrl+u should work. Now you may think you are done building rich text editor. Here comes the real issue with contentEditable it does not work the same way in all browsers. So when you press enter in Chrome it adds new <br/> tag while in Firefox each adds a paragraph. To make things worse when you paste some text from other websites the style is retained and may result in different DOM elements in different browsers. When you decide to build a mobile app it cannot render this DOM generated by the editor unless you use Webview which might be bad for performance.

The Good parts of contentEditable

contentEditable gives you way to render rich text easily since it is just DOM you can render custom React components inside it (like mentions). Pasting does work (even though it results in different DOM). And it is supported in lots of browsers.

We could take advantages of the good parts of contentEditable leaving alone its bad parts by simply maintaining our own model of the data in Javascript. Leaving rendering to contentEditable. Editors like DraftJs does this internally.

Attempt 1

Let’s model our data in markdown format. First we will build a simple component that has a div that is contentEditable and it will have local state that tracks the current text and the cursor position. We can use onKeyDown on the contentEditable like you would do on any text input in React. We can handle backspace, delete and arrow keys based on current cursor position and the text entered. We should also check if the key pressed is a printable character and omit it if it is not (alt, shift, capslock…).The code for this is here in branch attempt-one. Now that our basic text editor is up next we need to handle pasting.

Handling pasting

For handling paste we pass onPaste attribute to the contentEditable div and we use e.preventDefault(); to prevent default pasting. We can get the plain text omitting style of the text to be pasted using

  e.clipboardData.getData("text/plain");

then we can manually paste it using

  document.execCommand('insertHTML', false, newText);

and we also update our local state that contains text and cursor position. In the next section we will handle basic rich text features (bold, italic, underline…). and also later we will build features like mentions rendering React components inside it.