Illustration from my good friend and teammate Jake Sauer!

Almost one year ago Guru tasked me with ripping out our old markdown editor and replacing it with a brand new, Medium-style, WYSIWYG editor.

Our Design & Experience Lead had already put together a continent-size art board in Sketch, and it was time for me to join forces with him and try to bring it to life. ⭐️

Obviously, the first thing I did was panic. Google turned up few results and it seemed like companies weren’t talking about how they engineered their editors.

After a lot of stumbling, I eventually figured out how to get started. I experimented a bit with DraftJS and, while I’m all about standing on the shoulders of giants, I noticed that Draft didn’t seem like Facebook’s priority at the time. Eventually I stumbled upon SlateJS and decided to run with it because the Lead Developer was very active.

Slate was built with collaborative editing in mind, something that would be very hard to implement with Draft. In retrospect, either of these libraries would of been great starting points (as well as a couple others), but I’m going to stick with what I know and focus on Slate for the rest of this series.

Warning: Slate is in Beta. API related examples in this series may be out of date by the time you find it, so I’m going to try to stick to some higher level concepts that I believe apply to all WYSIWYG designs, and do my best to explain the intent of any code examples.

For the sake of keeping these concepts digestible, I’m going to be breaking each one down into their own parts. For this first part, I’ll give a high-level retrospective on a few problems that innocent, full-of-life me didn’t consider. Here’s the article that I wish I found a year ago.

TLDR; You’re going to need at the very least something to translate to and from HTML, something to handle fixing invalid editor states, and something to handle making changes to the data model.

Me, feeling totally ready to help you build this thing 😬

How are you going to save your content?

Slate gives you a few options. You could serialize your content as JSON rather easily, or you could go a step further and build a HTMLSerializer so that you could save it directly as HTML.

Basically, an HTML serializer does two things. It turns HTML into something Slate can consume, and it turns a Slate Data Model into HTML. We use a combination of both. HTML is much easier for an end-user to consume, and can be useful for light rendering of content. However, there is overhead in converting it to a Slate Data Model, so we cache that for quick start up times.

How are you going to handle users pasting in HTML?

Maybe you could enforce plain text only pasting, but people are going to expect to paste their content into your editor, and for everything to magically work. Trust me, you’re going to have to deal with some truly insane markup from seasoned editors on the scene like Microsoft Word. Check out this beauty that we need to be able to handle. It’s markup for a heading, a small numbered list, and a paragraph, all in the tight digestible package of 1541 lines of code, with a plethora of made up tags. Having a flexible HTML Serializer built from the very beginning, along with entry points for massaging content from other editors, will prevent a lot of future headaches.

How are you going to handle legacy content, and invalid content state?

This is going to happen all the time. Say you want your editor to have a title, and at least one editable block under it. If your user deletes the title how do you fix it? If they delete the last editable block how do you fix it? What happens if they paste a table into a table cell? Or a list into a block quote? What if your editor previously let block quotes have lists, but now it doesn’t and you have a bunch of freaky, deaky, mutant content all over the place? For these scenarios, we are going to need to create a schema for each of our blocks, and enforce them with a Normalizer.

Basically, any time there is a change your editor’s data model, it should check to make sure that it ends up in a valid state. The Normalizer will loop through your data model, and give you hooks to fix things that are invalid. Having a flexible Normalizer is key when trying to build a bug-free editor that can recover from failure and, combined with a powerful HTML Serializer, will allow you to convert formats from other documents into something your editor can understand with minimal friction. 💥

How are you going to handle all the different interactions, and all the different places they can come from?

If a user tries to add a table while there cursor is in a paragraph with text, what should happen? What if it’s empty, should it work the same? What if they are trying to put it inside a list? You’re going to want invest in a flexible Transformer that can interact with your data model based off a variable number of things:

What are the items that you are trying to change? Is your cursor in a list or a table, perhaps? Does it matter if it’s empty or not? What are you trying to do? Insert a table? Insert something below the target? Maybe replace chunk of text entirely? Maybe you want to wrap it in a list? Where is this coming from? Maybe a toolbar? Perhaps a side menu of some sort? We make changes differently if it comes from our slash command for example. (Shown below)

I honestly don’t know what our editor will do most of the time anymore. It has a mind of it’s own.

Invest in a robust and flexible Transformer, and you won’t have catastrophic results when a user hits an edge case that works in another editor. You’ll thank yourself for having all of this logic stored away and global, because you may end up with up to 5 or 6 different ways for a user to do one thing. (Markdown Shortcuts, Keyboard Shortcuts, Toolbar, Highlight Menu, etc…)

If I could go back one year, I would buy myself a beer and say this: