Why ContentEditable Is Terrible, Or: How the Medium Editor Works (2014)
medium.com
medium.com
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.
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.
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).
https://confluence.atlassian.com/doc/confluence-storage-form...
In retrospect, it was probably the wrong call. While we made created some unique products and technology that I'm still proud of, it was challenging to get writers on board with some of the newer things (like automatic A/B testing of headlines). It was very expensive to build and maintain a technology team and ultimately without editorial buy-in the returns just weren't there (though our marketer loved it).
My last move before leaving the startup was to transition everything to WordPress.
<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.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.
Why? They are irrelevant for that now. It would be standardised through the WHATWG.
Anyone trying to develop for the web constantly feels the pain of trying to get something working everywhere.
With "supports" being common things with extra markup (especially images and links), spans deferring your problems to css classes, and extras deferring your problems to javascript. The element returned by an extra would always be treated as a single black box by the editor.
* Does it allow further markup inside the headings? If so, which tags? strong? emph? span? a?
* Which attributes are allowed on the <h1> etc. tags? class? style? name? id? lang? dir? id?
You see where this is going.
> spans="blinky"
Hell no.
Yes: the wrong direction.
Trying to support every possible thing obviously won't work - we can't replace writing HTML with a clever enough rich text field. But you can still make progress on web usability by solving the most obvious bits and adding some hooks for the easiest ways to extend it.
You're much better off storing an internal representation of documents and then outputting that as HTML where necessary.
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.
Line1
Continuation of Line1
Line2
If you're wrapping text, how else would you be able to do it?It's certainly not a concern for many of the places Markdown gets used - online systems where the primary form of entry is a textarea. Therefore, those places should likely not use Markdown.
Just because something doesn't fit your use-case doesn't mean it's not common.
I never said they couldn't and they most certainly can (I simply find it awkward to work with). I've simply seen it more common to have them hardwrap than it is to see hardwrapping in a GUI editor.
Also, when mixing code, you may want markdown in comments for better formatting of autogenerated docs, and you don't want softwrapping on while code (at least I don't).
You've never used LaTeX then. I almost always put a line break after a full stop when editing in plain text, because it adds semantic meaning.
Moreover, many single-spaced visual styles have unindented paragraphs with an extra line between paragraphs.
(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.)
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?
> I always use a single line break to indicate a single line break.
Markdown isn't all that different from Word in this respect: hard linebreaks are second class citizens. In word, a single <enter> inserts a paragraph break, while <shift-enter> inserts a linebreak. In markdown a single linebreak inserts an (optional) space, while "<space><space><enter>" is translated to a hard linebreak.
It is very rarely that you need linebreaks in article/body text (eg: a book, newspaper article). One exception might be for dialogue:
- "I don't know.", said Tom.
- "Me neither.", I replied.
You'd probably want a break after the periods, no matter if the column broke up on eg: the comma or in the middle of a longer quote. This is all very different with source code, obviously.It's a trade-off: Are most plain documents somewhat sloppily formatted wrt. paragraph/line breaks? I think this is probably true. And for natural language text, what you usually want for good layout, is soft word wrap. Lack of good word-splitting (hyphenation) for <p>-text in browsers have probably set back web layout 20 years -- and chrome is still a hold-out: https://bugs.chromium.org/p/chromium/issues/detail?id=47083
>argumentative essays
>off the cuff
pick one
if the quote is on the same line (like it is in Markdown formatting) the effect of a "choice" is lost, which is why I have to put two line breaks after it
- pest
- cholera
pick one.
I believe the choice to translate single-enter to a space that can be reflowed is actually based on among other things email-formatting: https://tools.ietf.org/html/rfc2646#section-4I suppose the reasoning for not sticking with "<space><cr><lf>" for soft breaks, and "<cr><lf>" for hard breaks is that plain text on *nix uses just <lf>. So either most people would get ugly <cr>-marks and/or inconsistent <cr><lf>s in their README.md-files, or something other than rfc2646 was needed.
The source code [1] is modular and easily readable, so you can make yourself a decent plugin and add it to your code.
For some a blocker might be GNU license, but overall I think it's a great choice saving developer-hours.
1 : https://github.com/tinymce/tinymce/tree/master/js/tinymce/cl...
Quill, for example, is pretty feature-rich and has a similar model to that described here.[0]
The core idea is simple: forget about HTML, instead have your own data structure which represents a document in a saner way. Implement a series of functionally pure actions on that data structure and then map UI activity onto those actions (potentially using ContentEditable fields as an input). Then, React-style, your HTML is a pure function of the internal document structure.
I used a similar technique to build a rich text editor at a startup and we definitely didn't encounter anything which couldn't be done, including fairly complex structures (ex. quizzes with photo options and captions.). It definitely worked well and was a lot less error-prone and hacky than TinyMCE.
That being said, it was also a 6 month effort to get a full feature set in place. If you don't have that kind of time and just want something that works (and you don't need to extend it much), TinyMCE is fine.
Agreed that Markdown is a non-starter for almost anyone non-technical.
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/ [2]: http://afiedler.github.io/draft-js-dm-demo/
Medium's editor side steps this by just rerendering after each input.
Keyboards on Android Chrome used to emit keycode 0 for every character that was inserted and then mutate the DOM directly. And spelling corrections just mutated he DOM without firing events.
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.
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.
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.
Trix : https://github.com/basecamp/trix (From Basecamp)
ProseMirror : https://prosemirror.net/
Draft.js : https://facebook.github.io/draft-js/
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.
It uses ContentEditable. Wonder what your thoughts are?
As others have noted, it's really tough to compete with text editing/rich text applications (e.g., Sublime, Vim, Word, etc.) on the web. So far, the real Medium is the only app that I've used that isn't a pain to write in even though it's contentEditable.
Another promising project is draftjs from Facebook. Check it out: https://facebook.github.io/draft-js/.
It's really just a UI clone of the Medium editor. It doesn't work at all the same way under the hood and instead tries to use ContentEditable as the store of a document.
Any approach which works that way is doomed to failure. ContentEditable works fine as an input but you need to have a pure mapping from all inputs to an internal structure which only your editor controls.
Brilliantly simple.