
Why ContentEditable Is Terrible, Or: How the Medium Editor Works (2014) - rfreytag
https://medium.com/medium-eng/why-contenteditable-is-terrible-122d8a40e480
======
morgante
This article was very inspirational for me when developing the CMS for a
previous startup and informed that development extensively.

Personally, I found the most beneficial thing to be abandoning HTML entirely.
Instead I developed a JSON data structure for documents which was stored
internally. This is very powerful for a couple of reasons:

1\. Any user input can be mapped as pure functions on top of your internal
document structure. This way you can also enforce business rules easily.
Additionally, any function which you don't support will _never_ occur. (No
more Word garbage!) ContentEditable works fine as an _input_ though.

2\. You can write functionally pure mappings from your internal structure to
other formats. This of course includes HTML, but isn't exclusive to HTML.
Content can also be mapped into native mobile components as well.

3\. You can be flexible with input types. For example, it was fairly trivial
to (for fun) add a Markdown mode which would map any Markdown input onto the
internal document representation and then back to Markdown.

4\. You can introduce new semantic concepts which HTML doesn't have elements
for, like a "quiz." This makes it easy to rejigger the display of such
elements later on, or to have the same semantic element render completely
differently on mobile and desktop (for example). With HTML, you're stuck doing
error-prone tree parsing or Regexes.

~~~
degenerate
Were you able to handle paste-from-word? If so, would you mind sharing that
bit of code on github? I would love to make a custom solution, but 90% of the
content being put into our CMS is copied from Word. To clean it effectively, I
resort to using CKEditor to clean it for me and then storing as HTML. I'd love
to not do that, but CKEditor seems to have figured out all the problems,
especially when pasting tables or adjacently aligned content.

My users won't learn markdown and expect it to just "work". For complex Word
documents, I never came up with a viable solution to parse it.

~~~
morgante
Yes, we were able to handle pasting from Word. We basically gathered a ton of
examples of the HTML which Word generates and then developed mappings from
that HTML to the actions we supported. TDD and unit testing made it fairly
efficient.

Keep in mind that we purposefully did _not_ support the full scope of HTML
which Word would output. Authors couldn't make their headlines 50pt Comic Sans
in red, for example.

I'd love to share the code, but unfortunately it's from an old employer and I
no longer have access (or the right to share the code).

------
Jaruzel
On the basis that browser based WYSIWYG editors have become de-rigour in
almost all big online publishing platforms, it surprises me that the browsers
themselves are not being updated to provide a standard input field
(standardised via w3c of course) whose 'value' attribute outputs basic HTML
mark-up. Something along the lines of:

    
    
      <input type="editor" supports="bold;italic;images;headings"  ... >
    

This would remove the massive amount of work that UI developers still have to
do to hand crank something as simple as a good editor that GUI OSs have
enjoyed for decades.

~~~
Rezo
If the browsers incorporate complex editors, they're likely to be subtly or
even wildly different from each other in behavior. What's the "correct"
behavior after hinting enter in an unordered item list, create a new bullet or
start a new line for the current one? Is there a popup to create links when
you highlight text? Can a heading be split over multiple lines? Some of the
answers depend on the constraints of your underlying storage format like
Markdown vs HTML. On the other hand, if I pick ProseMirror or Draft.js, I can
be reasonably secure in that the behavior is consistent for my users.

If a capability can be implemented outside of the core browser, I think it's a
good idea to do so. It's sort of like putting something into the Linux kernel
vs doing it in a userspace module. The kernel/browser should provide the low-
level hooks for userspace to innovate, not try to incorporate userspace
components. An external project can iterate more rapidly, and there's also a
lower barrier for people to contribute code and fixes. Duplicate projects
"waste" resources, but they're also competing with each other and that
improves quality in the long run.

~~~
esailija
Aka Extensible Web
[https://extensiblewebmanifesto.org/](https://extensiblewebmanifesto.org/)

------
georgestephanis
Yup. Wysiwygs are hard. But just saying "You can edit paragraphs, that's all
you get" is worse. Clients need to be able to do more advanced things in an
editor, and coding a hard limit on never being able to do them feels like pure
folly.

Is TinyMCE perfect? Far from it. But it's stable and reliable across browsers,
with a well-defined API, and it gets the job done.

I know a lot of folks here would just as soon just use Markdown for all
content writing. I like Markdown. Clients hate it. It feels like an obscure
language, when they just want something "like Microsoft Word".

And CMS's can hardly say "Yeah, we know we used to support these greater
markup sets, but now we're just not going to, so don't open up your old posts,
or they're going to lose some content in our bright shiny new editor" \--
that's massively problematic as well for user trust.

If you're starting something new, and want to place a hard cap on what your
users can do markup-wise, sure, give this a shot. But be very aware of the
restrictions that you're imposing.

~~~
iopq
I hate markdown. I always use a single line break to indicate a single line
break. Having to use two breaks the most basic expectation of editing text.

~~~
arundelo
Two Markdown line breaks don't translate to a single line break in the output
but to a paragraph break.

(To output a single line break, precede your source line break with two
spaces. As others have pointed out, these behaviors are to let you manually
wrap your source but have the output be auto-wrapped.)

~~~
andybak
> As others have pointed out, these behaviors are to let you manually wrap
> your source but have the output be auto-wrapped

Ah. That makes sense finally. However - I've never needed this ability whilst
I constantly suffer from the non-intuitive default behaviour. Cure worse than
disease?

------
afiedler
Facebook's draft.js[1] tries to solve a lot of these problems and uses React
under the hood to maintain a mapping between the model of the document that
you are editing and the DOM.

I have only played with it, not used it in production, but if you are using
React it could be a good solution.

The one limitation I've seen is that is doesn't really support documents that
need a hierarchical model (an example of this would be tables, but also things
like "I want a code block within a blockquote").

If you want to see draft.js's document model, I made a tool to do that,
too[2].

[1]: [https://facebook.github.io/draft-js/](https://facebook.github.io/draft-
js/) [2]: [http://afiedler.github.io/draft-js-dm-
demo/](http://afiedler.github.io/draft-js-dm-demo/)

~~~
BasDirks
There are a bunch of bugs immediately apparent. Try typing while making a
selection with mouse. Is that expected behaviour? nah.

~~~
teleclimber
What _is_ the "expected behavior" for simultaneously drag-selecting text and
typing? And why? And who decided that?

I've found that there are many edge cases in rich content-editing. If your
general-purpose editor does something reasonable and constructive with
esoteric inputs it's fine.

Draft.js appears to cancel the selection process and drops the typed character
wherever the mouse happens to be. Reasonable.

------
nickstefan12
My attempt at implementing what medium describes:
[https://github.com/NickStefan/tome-
editor](https://github.com/NickStefan/tome-editor)

My favorite idea was using independent ranges data objects to track styling.
Eg {start: 2, end: 3, range: color, value: blue}. Then only the rendering api
needs to worry about overlapping ranges. Basically avoids manipulating trees.
That's partly what makes content editable suck so much.

~~~
dmix
Could you publish an online demo, even a gif if you don't want to host one?
You're probably missing out on quite a few users who are too lazy to clone and
install, just to bookmark for later use.

~~~
nickstefan12
I've been meaning to do that! I'll definitely move it up the priority list.

I had intended the api to be entirely programmatic and allow others to build
GUIs for how they wanted, say, font size drop downs to look. I could host the
test html page I've been testing it with. It exposes some random styling
buttons and visualizes the data model below the rendered text.

------
rtcoms
I would like to see a comparison between

Trix : [https://github.com/basecamp/trix](https://github.com/basecamp/trix)
(From Basecamp)

ProseMirror : [https://prosemirror.net/](https://prosemirror.net/)

Draft.js : [https://facebook.github.io/draft-
js/](https://facebook.github.io/draft-js/)

~~~
javan
They all use contenteditable for I/O only (no execCommand), and they all
maintain their own internal document model.

~~~
ohfunkyeah
Which is why the interesting part of the comparison would focus on where they
differ.

------
swalsh
I once used ContentEditable combined with Angular JS. It was the single worst
UX development experience in my life. What a terrible control. I was trying to
make an interface that binded an array of models, which had individual
properties that bound to a properties panel on the side. Editing the property
panel would instantly update the text, and updating the text would update the
property panel. (the way i'm describing it you might wonder why do you need it
in two places, but i assure you it made a bunch of sense... and was a super
cool idea).

I ran into the problem though that if you accidentally deleted the text, but
than tried to write it again all the bindings instantly broke. Or conversely
you might start writing some text with the intent of it being outside the
binded area, but it would still be inside... so the analysis that set the
property panel control didn't work as expected.

I eventually got something that worked... but it was far from the original
vision. I never really did figure out how to gracefully work with the control.

~~~
jessaustin
Frankly, it's not surprising that what you describe was difficult. Very few
libraries would have been designed with that sort of "updates in both
directions" architecture in mind.

~~~
underwater
It could me possible if contentEditable had events that corresponded to edits
that were about to happen. Instead it just emits the raw interactions (e.g.
keypress) and then mutates the DOM. It's very easy to get a mutation cannot be
retroactively mapped to an edit.

------
mymmaster
I assume you've seen this: [https://github.com/yabwe/medium-
editor](https://github.com/yabwe/medium-editor)

It uses ContentEditable. Wonder what your thoughts are?

~~~
andy_ppp
I've used it before - about a year ago and found it to be quite buggy in
actual use.

~~~
feiss
I've used it about 3 months ago and it didn't feel so buggy. Quite stable and
nice, BUT not totally stable, I'm afraid :(

------
meric
I've always wondered how web non-contenteditable editors implement WYSIWYG.
This is a good guide.

~~~
Tiquor
The ability tof have your storage format be your own is also an advantage that
non Web editors have.

------
KaoruAoiShiho
So what's a good editor that works this way? I know that quilljs is coming out
soon but it's still not released yet. Trix has a ton of bugs and the 1.0
version is still in development. Is there a stable one.

~~~
Rezo
[https://prosemirror.net](https://prosemirror.net) is nice to use and seems
quite mature. Made by the guy who also did the popular CodeMirror.

~~~
ohfunkyeah
The API and internal code is still expected to change. He very recently
finished a large breaking refactor, so it is not mature. Having said that it
is a nice editor with a lot of promise.

------
twlng
For [http://www.TwLng.com](http://www.TwLng.com) we're using
[http://quilljs.com](http://quilljs.com)

------
douche
I've been pretty happy using TextAngular so far

[http://textangular.com/](http://textangular.com/)

------
frik
ContentEditable (or rich text editor in general) would be very useful. But
browser vendors like Microsoft, Google and Apple all have vast interest to
keep the sad quirky status quo. It would cannibalise their web word processors
of their cloud services. Weird is that Mozilla Firefox shows little interest
as well.

------
whistlerbrk
contenteditable stole 4 months of my life from me. I wish I'd read way way
more before charging a hill so so many before me had died upon. Very happy we
have sensible alternatives today like Medium.js amongst the others people have
mentioned.

------
manigandham
Here's a list of HTML content editors that I put together back when basecamp
released Trix:
[https://news.ycombinator.com/item?id=10413549](https://news.ycombinator.com/item?id=10413549)

------
arxpoetica
His description of the problem is brilliant. One might disagree with the
axioms presented, but the conclusion is spot on: << It’s impossible to build
what ContentEditable wants to be, because they have conflicting requirements.
>>

Brilliantly simple.

------
ilmiont
Remember reading this article when it was first published... very interested
to do so again!

------
Semiapies
Is anyone actually still using bare contenteditable in a serious way?

~~~
RobertKerans
Yeah, I just built an [internal] CSS documentation system; every item has a
fully editable example using CSS in style tags on the page, each with
contenteditable applied to allow users to play around with it; editing within
the style tag immediately alters the styling of the associated example. Works
beautifully, as I have no need to persist anything, and zero JS used. (I get
this is an edge case; I've tried using it a couple of times for persisted
input and, yeah, not a good idea past simple I/O)

