If you haven’t been in rich text editing, you’ve probably heard the saying “rich text editing is a sinkhole, don’t touch it.” Yes, rich text editing is a sinkhole, but Slate is a great way to help you. Here’s how rich text editing can be complicated, and how Slate solves it.

background

There is a subtle difference between the rich text editing world and regular front-end development: in this world, the most popular solutions tend to be quite heavy. Why haven’t lightweight editors gone mainstream in the “lighter is better” front-end community? This starts with the implementation of the editor.

In the browser, the principles of rich text editing can be roughly divided into the following three:

  1. in<textarea>On positioning various styles. This is what Facebook’s early comment system used.
  2. Implement your own layout engine, even the blinking cursor is through<div>Control. This is what Google Docs uses.
  3. Use the browser’s native ContentEditable editing mode. This is what most existing rich text editors use.

Among the three schemes, the first one is difficult to support even bold, italic and other operations, and has been basically abandoned; The second is a huge game that only Google and Microsoft, the giants that can build their own browsers, can play well. For the last, if you don’t know about Contenteditable, you can open any web site, add the property to its TAB, and see how it transforms into a gorgeous editor 😀. The browser has taken care of shortcuts, undo stacks, cursors, input methods, compatibility… How thoughtful!

ContentEditable regression of

She was too young then to know that all the gifts of fate had been secretly priced.

— Zweig, The Beheaded Queen

There is no free lunch, and ContentEditable is no exception. The writer of the Medium Editor has written an article about the horrors of ContentEditable. The criticism can be boiled down to the lack of consistency in data structures and behaviour of ContentEditable.

For example, for the sentence “welcome to the 19th CPC National Congress”, the following HTML representations are exactly equivalent:

<! -- -- -- > normal
<p>celebrate<b>the 19th National Congress of the Communist Party of China</b></p>
<! -- Separate B tags -->
<p>celebrate<b>ten</b><b>nine</b></p>
<! -- Nested b tag -->
<p>celebrate<b><b>the 19th National Congress of the Communist Party of China</b></b></p>
<! -- empty b tag -->
<p>celebrate<b>ten</b><b></b><b>nine</b></p>
<! --span instead of b tag -->
<p>celebrate<span style="font-weight: bold">the 19th National Congress of the Communist Party of China</span></p>Copy the code

They may look the same, but there are significant differences in their editing behavior. With ContentEditable, browsers often insert these spam tags automatically.

For another example, a simple line wrap operation for the sentence “Welcome the 19th CPC National Congress” might produce the following result:

<p>celebrate<br/>the 19th National Congress of the Communist Party of China</p> <! Insert br tag -->
<p>celebrate</p></p>the 19th National Congress of the Communist Party of China</p> <! -- Split p tag -->Copy the code

Browsers differ in their behavior for even simple line breaks. As a result, when you edit a document in Chrome, open it up in Firefox and edit it again, you’re more likely to get bugs that aren’t simple style issues, but rather nasty bugs that destroy data structures.

There are a number of so-called ultra-lightweight editors in the community, which are almost a glorified shell for ContentEditable. The editor relies almost entirely on the native behavior of the browser, regardless of the destruction of data structures by ContentEditable, which makes it difficult to implement advanced editing capabilities. If you choose them with the idea that lighter things are more beautiful, think twice before you decide.

Is it an application, a class library, or a framework?

Another awkward problem in rich text editing is the positioning of the editor. Generally speaking, the front-end domain touches on three kinds of projects:

Application of Application

Applications generally refer to projects that contain interfaces and interactive logic, such as various management background systems.

The class Library Library

The class library provides apis that users can call to develop applications, but does not affect the code architecture of the application, such as jQuery and React:

jQuery: The Write Less, Do More, JavaScript Library

React is a JavaScript library for building user interfaces.

React is just a view layer. It needs to be combined with many libraries before it can be used to develop applications.

Framework Framework

The framework also provides an API, but it is very intrusive to the application code, requiring the user to provide the code for the framework to run in the way the framework does. Vue and Angular are typical frameworks:

Vue.js – The Progressive

JavaScript Framework

AngularJS — Superheroic JavaScript MVW Framework

So which of these does a rich text editor fall into? Each Editor project says it is positioned as Editor, but is Editor an application, a class library, or a framework? Many of the most popular out-of-the-box editors have integrated styles and interaction logic so that they are practically an application.

The problem here is that the customization of the application is the worst. As a result, many out-of-the-box editors struggle with simple configuration when it comes to customizing different editing experiences. This often involves using all sorts of clever tricks, or learning another clunky plugin mechanism for the editor itself.

Frameworks like Vue and Angular have a reputation for ease of use. Is there a framework for rich text editing? Yes, and Slate wasn’t the first. For those of you who know something about editors, One such editing framework is Draft.js from Facebook, which lets you customize your own editor using the React technology stack. If draft.js is already great, how does Slate compare to it? How does Slate solve all the problems that ContentEditable has encountered above? Let’s take a look.

Introduction of Slate

Slate is not an editor application, but a powerful framework for manipulating rich text data, built on a solid foundation of React and Immutable. Implementing a rich text editor based on Slate is equivalent to developing a common Web application using React (view layer) + Immutable (data layer). An editor architecture based on Slate is shown below, where the flow of data is straightforward:

editor-arch

The Toolbar Toolbar in the left view layer and the various nodes in the Editor are pure React components, while the model layer on the right uses much of the support provided by Slate. Let’s take a look at some of the key players in this architecture.

Immutable, by far the most desirable data structure

As we know, the properties of JS objects are mutable and can be assigned at will. Immutable data types, by contrast, do not allow arbitrary assignments, and each modification through the Immutable API generates a new reference.

This may not seem like much, and it’s not much different than making a full copy of the data for every change. The power of Immutable, however, is that the same parts are completely shared between references. This means that a complex document tree, Immutable, that changes just one leaf will generate a new tree that shares everything but that leaf.

What does this have to do with rich text editing? As we know, the [undo] function of the editor is actually a very difficult function, and many customized editors with the undo function are prone to inconsistent state before and after the undo. But with Immutable, each edit generates a completely new editor state, making it easy to undo and redo by simply switching between the different states. Also, Immutable fully supports complex nesting to express tree structures of documents. It can be argued that Immutable is naturally suitable for the model layer that implements rich text editing. In Slate and draft.js, rich text data is a layer of encapsulation for Immutable, with built-in support for undo operations that require no additional encoding. In this regard, a major plus for Slate over Draft.js is its support for nested data structures and good support for editing complex content such as tables.

React, by far the most appropriate view layer

Immutable.js is a javascript library for Immutable data, and was introduced by Facebook to improve performance of the React application rather than to cancel it. Immutable and React, so to speak, have an innate understanding.

So why do we need React? Currently, almost all editor solutions other than Slate and draft.js require either the concept of an editor plug-in tightly coupled to the DOM or the built-in functionality of the editor to customize edit nodes such as formulas, charts, and so on. This approach is not optimal in terms of learning cost and efficiency.

Imagine if all the editing content in the React editor could be implemented in the form of the React component (headings, paragraphs, etc.). Would the threshold for rich text editing be so high? Mapping Immutable data to React components is a mature pattern that has been tested in many Web applications. Under such a framework, the daunting problems of ContentEditable can be solved: Just add the contentEditable property for the React component, preventDefault for key and click events, and allow the event to change to Immutable.

In this scenario, the difficulty of implementing an editor is greatly reduced by eliminating the need for DOM experts. Even an average developer like this writer, who is only familiar with React and has a little more than a year of experience with the front end, is capable of developing his own editor. Here to smuggle in a little personal goods:

In rich text editing, React + Immutable, which fully changes state at a global granularity and then updates components as needed, is superior to Vue, which updates components at a fine-grained level based on dependency tracing. The way Vue mutate data directly is not good for undo and rollback in principle, and the API of the functional component VNode is not as intuitive and easy to use as React (Vue 2.5 has improved, but there are still gaps). Currently, no similar framework exists in the Vue community, and this scenario is one of the highlights of the React stack compared to Vue.

However, both draft.js and Slate implement support for React. Slate’s custom node API is a bit more convenient, but that’s not a decisive advantage. So what’s special about Slate?

Slate, the most flexible Controller yet

From the previous introduction, we saw that quite a few innovations came from draft.js. So what’s so special about Slate?

Draft.js has Immutable as a Model and React as a View, but when you use it to implement the editor, you may feel that it is a bit more cumbersome or less than normal application development. Well, this thing is probably the Controller you’re familiar with.

Even in today’s world of front wheels, the MVC architecture of UI applications is not obsolete, but has evolved into MVVM and even M-V-WHATEVER architectures. The editor application is also a UI application, and we also need a mechanism to connect the Model and View.

This is probably not where draft.js shines; its document transformation API is heavy to use, and EditorState changes are more restrictive. Slate provides a more flexible concept to connect Model and View. Let’s take a quick look at what happens when an edit occurs in Slate:

  1. The user presses a key in the Node where the editor cursor is located, triggering an event.
  2. Distribute different changes, such as newline, bold, etc., according to the key value of the key.
  3. Change Changes State to generate a new State.
  4. After the new State is verified by the Schema, it is rendered to the editor and the corresponding Node is updated as needed.

The core mechanism of the whole process can be summarized as a formula: state.change().change(). Change is a very elegant API, and all transformations are implemented through the change object. For example, if the user inserts text and then deletes another paragraph, the changes to the document can be abstracted as:

state.change().insertText().deleteBlock()Copy the code

Every operation is a chain call! In the scenario of collaborative editing, operations from different users can actually be reduced to such chain calls to State, which makes it possible to achieve collaborative editing based on Slate. On the other hand, the API in each Change chain call can be implemented as a pure function and then chain-executed through Slate’s Call API, making it possible to write your own Change and add unit tests.

This elegant approach to editing makes it easier for Slate to connect models to Views and perform complex manipulations on rich text data. In addition, Slate supports custom Schema verification rules for state. You can add verification rules such as “the first node must be Heading node” or “image node must contain SRC attribute”, and filter abnormal data.

Of course, there is no concept of Controller in Slate, but in fact, the rich text editing Change operation written on Slate is a bit like writing Controller logic in a traditional MVC application. Slate, in other words, makes writing complex operational logic as easy as writing a Change function. At this point, Slate’s architecture is easy to use.

conclusion

Slate is an up-and-comer in rich text editing. Within a year of its launch, however, its community of contributors is approaching draft.js and even Vue in the hundreds. Also, its Issue and PR processing is more timely than draft.js, its authors are more open to new ideas, and its iterations are more active.

Slate borrows many of its core features from other good editor projects, such as its Immutable data layer and framework concepts from draft. js, Schema and Change concepts from ProseMirror, and so on. While many of its highlights aren’t unique on their own, they are well-rounded on a macro level (sound like Vue?). . At present, it is still in rapid iteration, and there are many opportunities for those who are interested in participating to become contributors.

Resources

  • The Slate’s official website
  • Slate Github
  • Slate 英 æ–‡ 版