The evolution of open source rich text editor technology

pubuzhixing
15 min readJun 14, 2021

My initial impression of the rich text editor may stay on editors such as UEditor and CKEditor, as shown below

UEditor

After joining Worktile, I started to understand the Markdown editor. You only need to learn some simple Markdown format, you can achieve universal article layout.

Markdown is really amazing.

Markdown Editor

Around August 2019, the company decided to develop an online wiki product similar to Confluence. Since when did I start the development of the rich text editor, I feel very lucky to have such an opportunity, and therefore I found the direction that only needs to study and work hard.

PingCode Wiki Editor

Talk about the difficulty of the rich text editor

We all know that developing a rich text editor is harder than heaven.

In summary, there is a point of conflict:

The conflict between backward productivity and people’s growing functional Requirements.

Backward productivity:

  1. The behavior standards for input related events are not uniform.
  2. Browser vendors have different implementations of the same operation or scene, which leads to compatibility issues.
  3. There are too many uncontrollable situations in using HTML DOM to describe rich text content.

Various functional requirements:

  1. Uncertain interaction intentions, such as pressing the Delete key, different focus positions have different situations that need to be considered.
  2. The variety of content input, such as: typing, pasting, dragging, etc., each of which is quite complicated to handle.
  3. A large number of browser default behaviors that need to be blocked and proxied to make sure the data is valid.
  4. Users are increasingly functional requirements the use of editors, such as: merging cells, multi-level nesting of lists, collaborative editing, and version comparison. Everyone thinks this is a basic requirement. In fact, the technical difficulty is beyond everyone’s imagination.

Open source rich text editor technology

Although the standard is not perfect, the editor technology can be precipitated and developed through open source. Here I mainly introduce the changes and development of the editor in the past 10 years from the technical realization and the evolution of programming ideas.

Focus on the following editors:

CKEditor 1–4(2008)

UEditor (2012)

Quill.js(2012)

CKEditor 5(2014)

Prosemirror(2015)

Draft.js(2015)

Slate(2016)

If you want to fully learn each editor, it may take months or even half a year, so here I mainly talk about my understanding of these editors, and introduce their characteristics and the differences between them.

Editor technical level

According to the degree of difficulty, the editor is divided into three levels

Level 0: I don’t know why it starts from 0, the technical architecture at the initial stage, represents the implementation of traditional editors.

Level 1: Developed from the Level 0, introduced some popular programming ideas and Use JSON to abstract rich text content.

Level 2: The third stage, does not rely on the editing capabilities of the browser at all, and independently implements the cursor and typesetting.

When I introduce the editor below, I will try to define the difficulty level of defining them, so that everyone can understand better.

2008 — CKEditor 1–4

CKEditor 1–4 can represent the technical route of traditional editors (UEditor is the main technology of the same type), which mainly depends on the native editing capabilities of the browser. The input of user content is directly processed by the browser. Bold, italic, carriage return, etc. are implemented by capturing browser events to override the default behavior of the browser, as well as directly updating the DOM. Complex processing will be supplemented by some DOM nesting rules (dtd) and complex data input (such as paste) filtering rules to ensure the correctness of the data.

The editable content depends on the contentEditable property of the DOM, and is based on the native execCommand or custom extended execCommand to modify the rich content.

  1. Text input is basically the default behavior of the browser
  2. Complex style or format operations enter the interactive judgment logic. If the default behavior does not meet the requirements (for example, the behavior is inconsistent between different browsers), the developer can customize the processing.

Features

  1. Depend on the native editing ability of the browser (Level 0).
  2. Based on browser execCommand or extended command collection.
  3. DOM-based nesting rules and filtering
  4. The output rich text content is an HTML string

Advantage

  1. Based on the browser’s native editing capabilities, very good input performance.
  2. No headaches for IME (Chinese) issues

Disadvantage

  1. Unpredictable interaction, easy to cause dirty data (drag and drop, copy and paste, delete).
  2. Different browsers may get different results for the same operation (such as basic bold, italic, Enter), and it is not reliable to use HTML to describe rich text.
  3. The specific structure of rich text content (Picture + Caption) is complex to achieve.
  4. Difficulty in supporting collaborative editors (the root cause of CKEditor 5 from the beginning)

Because CKEditor 4 modifies the rich text data directly to modify the HTML DOM, it is set to Level 0, but before this, there should be editors implemented using textarea, such as the code editor Codemirror, which generally belong to the Level 0 .

2012 — Quill.js

The most representative editor in 2012 is Quill.js. Its appearance has brought many new things to the rich text editor. It is currently the most popular open source rich text editor. The number of github stars is as high as 30.1k. Our PingCode Agile also chose Quill.js when selecting the editor technology, and encapsulated an Angular component based on Quill.js.

The overall implementation of Quill.js still depends on the contentEditable feature of the DOM, but Quill abstracts the DOM Tree and data modification operations, which means that editor developers do not directly modify the DOM to complete the editor functions in most scenarios. The operation is completed through the model operation API provided by Quill, and the main body becomes: Delta, Parchment & Blots.

Delta

Quill uses Delta to describe the content of the editor and its changes. Delta has a simple structure but full of expressiveness.

Delta is a subset of JSON and contains only one ops property. Its value is an array of objects. Each array item represents an operation on the editor (based on the initial state of the editor being empty).

The following is a rich text description:

Use Delta to describe as follows:

{
"ops": [
{
"insert": "Hello "
},
{
"attributes": {
"bold": true
},
"insert": "Quill"
},
{
"insert": "!"
}
]
}

Delta has only three actions and one attribute, but it is enough to describe any rich text content and arbitrary content changes.

three actions:

insert retain delete

one attribute:

attributes: format attributes

One feature of Delta is that it only describes changes in content, and the final content is composed of a series of changes.

If you know about collaborative editing, you should be familiar with the Delta data model. Delta is actually an implementation of the OT model, and OT operation is an algorithm for collaborative editing. Quill can be said to be born for collaboration.

Parchment & Blots

For the abstraction of DOM in Quill.js, Parchment is actually the structure corresponding to the DOM tree. Parchment is composed of Blots. Blot corresponds to the Node of the DOM. How the Quill.js document is rendered is completely determined by Blot. Then this layer model is actually Delta. A middle layer between the data and the final UI;

Correspondence:

Editor Container <====> Parchment

DOM Node <====> Blot

With this abstract model, the biggest change is that the content directly modified by the developer has changed from DOM to Parchment & Blots, and the final DOM modification is limited to Blots..

LinkBlot example:

Data form in Delta:

Architecture diagram

Text input is basically the default behavior of the browser. Quill.js will monitor the changes of the DOM (MutationObserver), and finally synchronize the changes of the DOM to the Delta model data.

For complex styles or format operations and other non-browser default behaviors, the Delta model data will be updated directly, and Delta will drive the update of Parchment & Blots, and then finally the UI changes.

Features

  1. Depend on the native editing ability of the browser (Level 1).
  2. The data update subject is Delta, and the DOM update is described by a Parchment & Blots.
  3. The output data can be a string of HTML or a series of operations described by Delta (that is, JSON).
  4. The main body of Quill.js, Parchment, and Delta are all independent github repositories with good architecture.

Because the data model is introduced and the operation of data change is abstracted, Quill.js is defined as Level 1. The editors that came out later have more or less inspiration from Quill.js..

2015 — ProseMirror

The well-known Confluence is developed based on ProseMirror, so no one will doubt the scalability and stability of ProseMirror, because different modules of ProseMirror are independent github repositories, so I can’t accurately know its specific open source time, probably in 2015.

From the implementation principle, ProseMirror also depends on contentEditable, but it is very powerful that ProseMirror applies popular programming concepts to the development of editors, such as using pure JSON data to describe rich text content, immutable data and the concept of Virtual DOM, and Plugins, layering, Schemas, etc, so I feel that ProseMirror is an editor framework with advanced concepts and complete implementation.

JSON describes rich text content

Described in JSON as follows:

Schemas

The following is an example of the todo plugin, using Spec to describe the attributes of the node and how to render the content of this node according to the attributes:

With the definition of the paradigm corresponding to data and data types, the change from JSON data to DOM can be completely taken over by ProseMirror. ProseMirror builds a layer of virtual DOM in the middle to complete the drive update of data to DOM.

attrs: custom attributes of the data node.

content: the schemas of the child element (regular matching)

toDOM: render the definition of DOM based on node data

The main thing I want to talk about is toDOM, which is similar to how React uses JSX to define rendering DOM instructions, but it feels that it should not be as powerful as JSX.

Transform

ProseMirror has a separate module definition document modification, so the content modification is standardized, all content modification will eventually be converted into atomic operations, and the default implementation can be modified in any plugin, such as implementation: record data change operation to achieve Undo and redo etc.

Go to ProseMirror here to have a state diagram:

  1. Introduce the concept of immutable data, define the modification of the content, and change the original way of changing the DOM to the modification of immutable data
  2. Data driven.

Features

  1. Depend on the native editing ability of the browser (Level 1).
  2. Nested document model (different from Delta’s OT model, its document model is the JS object model in the usual sense, and the corresponding model data can be stored directly as a result).
  3. Schemas define model nesting and rendering rules.
  4. One-way data flow, immutable data, and virtual DOM avoid direct manipulation of the DOM.
  5. The output data is pure JSON
  6. I personally think that the only shortcoming of Prosemirror is that it describes the DOM syntax is not universal (not integrated with the front-end framework)

ProseMirror is another masterpiece of the author of CodeMirror. The concept was already very new at the time, and in its implementation, it proxyed most of the browser’s default behaviors, converting interactive behaviors into data transformations, and it was a well-deserved Level 1.

2015 — Draft.js

Draft.js is the first open source work that combines a rich text editor with React. When developers develop an editor, they write React components directly, and it becomes natural to develop an editor. Because Draft.js and React are also open source frameworks of the Facebook team, the concept of Draft.js is very similar to React and also represents popular programming concepts, such as using state management to save rich text data, using Immutable.js libraries, and data The modification basically all proxy the default behavior of the browser, and modify the rich text data through state management.

Of course, it also has certain limitations, because it only serves the rich text editor using the React framework, and it should be very difficult for other frameworks to use it.

Draft example

I opened the demo and tried it, and found that both quote and list are implemented using flat data structures, and block types are distinguished by type, this design is really a bit bad.

It can be seen that although draft.js also abstracts a JSON data model, it does not support nested data structures, and it is difficult to implement complex nested functional plugins.

Features

  1. Depend on the browser’s native editing capabilities (Level 1 Pro)
  2. React as the UI layer
  3. Management of rich text data combined with React (state management)
  4. Undoubtedly, because Draft.js has not been refactored, its stability should have a great advantage over other frameworks (Slate).
  5. Draft’s description of document data is too rigid. For example, a table that requires nested nodes is not so easy to implement. Even if a table can be embedded as a rich text component in the Draft editor, its limitations are also very large (such as in a cell Basic bolding, italics, and links cannot be achieved with the help of the editor), so its data model is not perfect.
  6. Draft.js directly integrates the development of the rich text editor with React. The extension of the editor function is to write React components, which brings the development experience and efficiency improvement. It completely uses the concept of state management to operate and manage rich text data and data flow. Very clear, so define it as Level 1 Pro.

2016 — Slate

Slate is the world’s most powerful editor framework (personal opinion). Compared with the series of editors introduced above, it is the latest to appear, but it also draws on some of the advantages of other editors, and because of the author With high pursuits, Slate’s architecture is constantly being refactored to keep pace with the times.

Slate has borrowed a lot from the advantages of Quill, ProseMirror, and Draft.js since it came out. Although it is relatively late in the mainstream editors, it is a very popular rich text editor framework due to its good architecture and keeping up with the times.

Architecture diagram

It does not provide out of the box functions. It only provides the basic architecture for developing an editor. If you want to implement an editor, you need to write a series of functional plugins.

Features

  1. Rely on the browser’s native editing capabilities (Level 1 Pro)
  2. Shchema defines data constraint rules (Refer to ProseMirror)
  3. Nest Data Model (Refer to ProseMirror)
  4. React as the view layer (Refer to Draft.js)
  5. First-class plugins, and developers have a lot of control over interactions (Refer to Draft.js)
  6. Immutable, standard data change Commands (Refer to Draft.js)

Slate in this period is more of the shadow of other editors, gathering the best of others.

You can look at the original Slate data:

2018 — Slate Core

Extract the view layer, slate core no longer depends on React.

This makes it possible for Angular and Vue frameworks to use Slate, but it also has certain difficulties because you need to develop a view layer yourself.

Slate data at this time:

Compared with the original structure, there have been some optimizations.

Architecture diagram

2019 — Slate Migration

At the end of 2019, Slate completed a major architecture upgrade. This time it is called an overhaul Migrating (0.50.x). There are many highlights. First, it rewrites the original implementation using TypeScript, and secondly, it rewrites the original complex The plugin is simplified, and the model of immutable data is changed to Immer. The view layer is also separated from the core implementation. Although it was a bit unstable at the beginning, the refactoring is still very meaningful.

Architecture diagram

Features

  1. Very simple data model.
  2. Designed a set of very high abstraction editor business hooks, editor developers can rewrite and expand these hooks, it is a higher abstract plugin, it is very easy to understand and debug.
  3. Use Immer as an immutable data model
  4. The code style uses pure functions and interfaces, and the amount of code is greatly reduced

Data for the latest version of Slate:

Although Slate inherits the advantages of many editor frameworks and continues to improve its own architecture, it still depends on the editability of the browser and has to do a lot of careful handling of how to synchronize Slate behavior with the default behavior of the browser. So it’s still Level 1 Pro.

2021 — Slate

After Slate is refactored with TypeScript, the API style and concept are clearer, and it is better for beginners. The community’s response to Slate is also higher. A good one is based on Slate’s plugin library slate-plugins, and Slate itself is also Continuous optimization and improvement, such as improving the support for TypeScript generics, as well as support for multiple editors, iframes, android, collaboration and more special scenarios.

The future of editors

In fact, the future has already arrived. As early as 2010, Google Doc used a new technology to implement a rich text editor, which is Level 2, no longer relying on the editing capabilities of the browser to independently implement the selection cursor and content layout, but at present There is no open source technology based on this architecture.

Although the implementation of typesetting by oneself can solve many existing problems, it is too difficult to implement and small companies cannot afford this cost. Therefore, the contenteditable technical solution based on the browser is still the best choice. And with the unification of content input-related event standards and the development of technology, many existing problems must be solved gradually, so contenteditable is also the future of editors.

Summary

Thanks to open source technology, the practical experience of editors can be continued and developed,there is no absolute good or bad.

Each editor has its own advantages. CKEditor has been open source for the longest time, and its technical route is clear and discoverable. From CKEditor 4 to CKEditor 5, it is completely refactored to solve the problem of collaborative editing. Quill.js is known as the old editor, and many products use it, such as Shimo, ClickUp, etc., and it is very powerful. ProseMirror is a very mature and stable editor, backed by Confluence. Although Slate is the latest open source, it does the hardest. It has been improving its architecture and keeping the API simple. In 2018, it was rewritten with TypeScript. I think it will have a good future.

Finally, use a timeline diagram to review the history of the open source rich text editor

Introduction to slate-angular

I personally like the Slate framework very much. The front-end stack of our team is Angular. In order to be able to use Slate in Angular, we developed the Angular-based view layer slate-angular with reference to the official slate-react. I personally feel that developing the Angular view layer is a great things, there is no implementation of the Slate editor based on Angular in the open source community, so we want to open source slate-angular to give back to the Slate and Angular communities, so that more Angular developers can use Slate to develop rich text editors.

Currently slate-angular has supported the enterprise-level Wiki product PingCode Wiki which has been running stably for more than 1 year. The initial version is based on the Slate@0.47.0 version (JavaScript version Slate), and the second version is based on the latest Slate implementation (TypeScript). The third edition has been prepared since the beginning of the year, including public github respositry, refactoring of the code, unifying the underlying implementation and API style, and building an online Demo.

Github:https://github.com/worktile/slate-angular

Live Demo :http://slate-angular.ngnice.com/

If you have any questions, you are welcome to submit Issues to slate-angular!

--

--