1. 13
  1. 10

    Just use a textarea. Textareas have predictable behavior, are performant and easy to interact with from a browser extension like GhostText or Firenvim.

    If you really, really need fancy formatting, use an existing editor like ProseMirror or TinyMCE. These editors are built by people who have more than a decade of experience building in-browser editors and will always, without exception, provide a better user experience than what you can do. This also makes the life of browser extension writers easier as they won’t need to figure out how your custom editor works and will be able to re-use the code they already have.

    1. 1

      And if you need some highlighting it may be done with a transient textarea and a div below.

    2. 6

      We also had a similarly painful experience with this. It’s pretty wild how there can be so much effort at complex APIs like WebGL/GPU/RTC, WASM, and all the rest, but basic custom text editing capabilities are a total mess. My guess is that being able to build custom editors doesn’t allow for much “innovative” advertising telemetry. That said, kudos to all the people in the standards community trying to right the ship.

      1. 3

        The new generation of text editors don’t use contenteditable; they use an invisible input element and render the cursor/markup separately. That’s more up front work but you get something far more reliable and portable.

        1. 1

          I have tried this approach as well, and while it certainly gets around all the contenteditable issues (which are also cross-browser issues), it’s still quite complicated. As you say it’s a lot of work. Definitely the way to go, however.

          1. 1

            Yep, this is how Google Docs, Coda, and Figma work. Do you know of any libraries out there that support this model explicitly?

            1. 1

              https://ace.c9.io/ is an editor which does (somewhat code-editing-specific).

              I’m not aware of a library (probably because it’s the most customized part - eg is the underlying model markdown, html, or something else - if html, what do you do with stuff your editor doesn’t understand).

        2. 3

          I seem to be the exception in that I found contenteditable actually…. wasn’t that bad. I did a combination of the built in functions via execCommand and some DOM manipulation on the client, then the server did did a html normalization filter before saving, which allowed for pretty legible code and diffs across revisions.

          The backspace behavior can certainly get weird, but what I did was have a sidebar that updated the dom position so if backspace wasn’t doing it right, you could just remove a child or strip out a tag on the sidebar. Since there were a number of custom elements in this project, presenting the sidebar as editable dom attributes worked really well for those and the users found it easy enough to apply to other parts too. (That btw is why I went custom. Trying to work that element into the ckeditor or tinymce just proved too much trouble.)

          It ended up being… 1800 lines of javascript and then the server-side post-processor about 40 lines. (though that’s cheating a little since it leveraged various library functions I wrote in my dom.d that total to 8000 lines, but still most of it was just loops modifying elements that match various css selectors so i think if it was in javascript it wouldn’t be that much bigger either, aside from the html formatter.)

          I wish the company let me open source this. It is very specialized to their needs so not terribly useful applied somewhere else but I was kinda proud of the code.

          1. 3

            The author didn’t mention my pet peeve: reordering items in a UL or OL. Whether you use cut&paste or drag&drop, the list gets messed up, usually adding nested items. This has been happening since WebKit first added contenteditable, and it’s never been fixed.

            My intuition is that a DOM tree is fundamentally a bad data structure for a text editor. Structural editing quickly turns into weirdly mis-nested elements. By contrast, the Cocoa text engine on Apple platforms (used in pretty much any native app) uses a run-length encoded mapping of attributes to character ranges, and is a joy to use.

            1. 2

              Or at the very least, the structure of the DOM tree made obvious in such an editor (i.e. think structural s-exp editors, or ye olde Netscape Composer’s what you mean mode)

            2. 3

              I’m baffled by this claim:

              The current solutions don’t offer too much control in processing the data from the contenteditable - the output is always dirty HTML [emphasis mine], 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.

              What? There are several extremely high quality ContentEditable libraries out there for editing a custom, DOM-like tree that’s entirely defined by your schema. The output is this well-structured tree data type that you define. Combined with the fact that all browsers recently rolled out input events level 2, and burgeoning interest in CRDTs for collaborative editing, we’re currently in a web editor renaissance.

              Great editor libraries:

              • ProseMirror: this is the highest quality WYSIWYG editor framework. You define a schema that described your tree of elements, and it provides a complete API to handle transformations. Multiplayer is available. Downsides: Steep learning curve, library code is in several repos in vanilla JS which makes the interior hard to follow.
              • TipTap: the trendiest wrapper on top of ProseMirror. Tried to make ProseMirror easier to use. Flavors for UI frameworks like React and Vue. Slick documentation, and clearly for-profit. Downsides: now there’s a deep layering between you and the DOM, and it’s even harder to understand wtf is happening.
              • Slate (beta): a React based editor framework written in Typescript in a single repo. Has the most elegant model and understandable code of the editors I’ve read. However, has earned a reputation for breaking API changes frequently that leave behind major users. The upside is that the API and object model are better than ProseMirror’s in my opinion. Downsides: React focus, less mature than ProseMirror at handling browser bugs, DANGER may change the whole API again.
              • Quill.js: a more character-oriented editor than Slate or ProseMirror which tend to be more tree-oriented. Maintained by Slab, a SaaS doing the whole collaborative wiki thing. Less mature than ProseMirror in terms of browser bug handling, but more mature than Slab. Comes with its own text suitable for building Operational Transforms, called Delta which I quite like. Simpler API than ProseMirror. Downsides: Simpler API than ProseMirror, but code is still a convoluted multi-repo affair in vanilla JS.

              All of the editors above were built ground-up with multiplayer real time collaboration in mind, but unfortunately not batteries-included. That means that they express changes to the document in a clear and explicit way as a “transaction” or “mutation” that you can serialize, store, invert, and transform to either implement your own Operational Transform like system, or bolt on an existing collaborative data library. The leader in this space today is Y.js, a conflict-free replicated data type library with efficient binary storage and the best performance out of the options in the browser. Y.js has support libraries to plug into all the editor frameworks listed above. If you’re a for-profit company who’s core competency is anything in the territory of “wysiwyg wiki”, I suggest betting on ProseMirror + Y.js: you can build the expertise to use the more complex but mature libraries, and collaboration is table stakes in this area - we want local first software with offline editing without waiting for spinners from the cloud!

              1. 1

                This has been my experience as well. We’ve been building atop ProseMirror for about 14 months at story.ai It’s a pretty steep learning curve, but I believe it has handled a lot of the most complicated problems for us out of the box. Biggest downside for me has been the lack of static type checking (the base ProseMirror libs are not TypeScript oriented and fail to help with things like attribute type checking).

                The documentation and user support is also top notch from Marijn.