The preface

To begin with, the web rich text editor is a sinkhole with poor browser support and high user demand… . After joining the team, I dug deeper in the rich text editor field. ε=(´ο ‘*))) alas, it all ended in tears (but it seems that every field digs into a hole). Today, I will take wangEditor as an example to tell you what the problems of our products are and some solutions we have researched in these days.

Existing problems

  1. The core code is not separated from the functional code, and we now modify it directly in the project, whether we deal with issues or new features. In the long run, our project will eventually become a monolith project (which is the original intention of plug-ins).
  2. There is a strong coupling between the plug-in and the menu. If the user does not need the menu, after removing the function (editor.config.menus = []), the function will also fail. (actual case: issue#3117)
  3. execCommandIt was the core of the rich text editor, but it was buggy and incompatible with different browsers, so it was officially abandoned. This indicates that the API is untrustworthy.

Corresponding solutions

pluggable

In the last article, I mentioned the idea of replacing core Core + Plugin + Menu with core Core + Plugin, but I didn’t mention the reason. Today, I will talk about the reason for my design like this.

Imagine a scenario where users don’t need your menu bar, just the ability to edit areas and insert images. If the source code is designed as the core core + plugin + menu, then menu is pure soy sauce, no role, and even increase the volume of the project.

So replace it with a core + plugin, which is the idea of core + anything = everything. I’ll give you a few examples:

  • Core + menu-bar plug-in = rich text editor
  • Core + MarkDown plug-in = Markdown editor
  • Core + menu bar plug-in + MarkDown plug-in = rich text + Markdown editor
  • Core + highlight.js + Monaco-Editor = online code editor
  • .

In this way, wangEditor may not be limited to rich text, but as long as our kernel is good and extensible, it can be used for all editor scenarios! (A little fantasy ha ha, Wuhu take off!)

Decouple the relationship between menus

Currently in wangEditor, the editor is enabled as follows:

When a user does not want the Image menu, the Image uploading function is disabled by removing the Image from the editor.config.menus. If a user wants to upload an Image, the Image menu item must be present. (All the code for uploading images goes into the Image constructor.)

In this case, we can decouple the specific function of uploading images from the Image menu. In this way, the upload Image function becomes a separate entity, and the Image menu can rely on the upload Image function:

Users can register plug-in functions directly if they do not need menu items:

The picture is really ugly, you big guy make do with see ^_^

execCommand

To take you through execCommand, MDN is defined as follows:

When an HTML document switches to design mode (which means editable, you can make a DOM editable by setting contenteditable of the DOM to true), the document exposes the execCommand method, which allows commands to be run to manipulate the elements of the editable content area. Most commands affect document selection (bold, italic, etc.), while other commands insert new elements (add links) or affect entire lines (indent). When using contentEditable, calling execCommand() affects the editable elements of the current activity.

ExecCommand affects the contents of the current selection by receiving a command. You can pass in different instructions to produce different effects.

Small science

Here to give you a concept of popular science, or the noun behind may not understand.

In the rich text editor world, there are three levels:

  • L0: Implemented with native Contenteditable and execCommand.
  • English: Implement execCommand yourself with native Contenteditable
  • L2: Implement contenteditable and execCommand yourself

WangEditor is currently L0 and would like to upgrade to L1.

Look at wangEditor with an MVC architecture

MVC is Model, View, Control. What are they in wangEditor?

  • Model: Edit the HTML code in the area
  • View: The content displayed in the edit area on the page
  • Control: Operations performed by the user (e.g. bold, italic, undo, restore, etc.)

As you can see, execCommand is part of Control. It modifies the Model to make the View change accordingly.

Talk about alternatives to the execCommand API

There are many alternatives to execCommand in the community, such as customizing commands to manipulate the DOM with Selection information. But I didn’t feel very good, so I dropped the pass (I felt that frequent DOM manipulation would cause poor performance).

One solution I think is possible: the Model layer uses a document protocol instead of HTML code. This is much better performance than directly modifying THE HTML code before, as many well-known editors do.

Document agreement

So what is a document protocol? As a developer, you’ve probably heard of many protocols (HTTP, TCP, etc.). The document protocol here is a data structure that describes the contents of the edit area. You can think of it as the Fiber Tree in React or the virtual Dom in Vue.

New architecture design based on Redux

Redux is a state management solution, and I think the new kernel of wangEditor can be built on top of the Redux workflow.

The flow chart

Let’s start with a flow chart

The main character

  • Store: A repository for storing state data
  • State: Document protocol (a variety of names on the Internet, let’s call it Schema)
  • Action: Indicates an operation. Action is the only way to change state
  • Reducer: Receive state and action and return a new state

Process analysis

  1. EventEmitter listens to events that trigger an action when it listens to the user manipulating the contents of the edit area.
  2. EnentEmitter gives the generated action to the Store
  3. The Store hands the current state and actions to the Reducer
  4. The Reducer is responsible for applying the action to the current state and returning a new state
  5. Store overwrites the old state with the new state
  6. Store passes state to Render
  7. Render receives state and maps it to the real DOM
  8. Displays the current DOM on the page

The characteristics of

  1. Unidirectional data flow
  2. The state is immutable
  3. The process is clear and the work of each part is clear. (It also shows strong support for plug-ins, which can expose hook in each working node and expand functions)

How is plug-in supported

Plug-ins are used for function extension. In this architecture, there are basically two tasks to achieve function extension:

  1. Design a reasonable action.
  2. Design a Reducer to handle this action so that the action can be applied to the state.

For example, if you want to have bold function:

  1. Register listening events in EventEmitter to generate a specific action
  2. Inject the code to process the action in the Reducer

For example, you want to have the ability to upload pictures:

  1. Register listening events in EventEmitter to generate a specific action
  2. Inject the code to process the action in the Reducer

The important part of architecture design is that how to design a reasonable data structure is very important to the extension of functionality! Also, if the user wants to do secondary development, he needs to learn our data structure and understand it before he can design, which has a learning cost.

Draft is a rich text editor based on React, which means that you don’t need to learn new UI paradigms, as long as you know React, you can do secondary development.

details

The L1 kernel still relies on the native property of Contentededitable.

There is a problem with this: when you modify content directly in the edit area, there seems to be no way to stop it. Then the state in the Store and the content in the edit area do not correspond, and the page rendering depends on two factors: Users make changes directly to the edit area or trigger actions through events.

If I were to think of a solution, it would be better to prevent page rendering and change the view by generating actions. The next best thing is that the page does not block changes to the page, and then generates the same actions to keep the mapping between the state structure and the page DOM structure consistent.

Add: While specific workflows have been figured out, many of the details have yet to be considered. Such as what the data structure of Schema should be and how reducer maps the Schema into a real DOM. Take a look at other rich text editors in the community.

Write in the last

If you have any comments or questions about this article, I will reply when I am free.

Ah, finally finished, can rest. (Hit the king to relax). There is nothing to share, and we talk.

To be honest, tired is very tired, but also a lot of harvest. Research, when looking at some of the solutions in the community, there is a sense of light, but also play this way! If an interviewer asks me how TO learn front-end skills in future interviews, I will reply directly: Just having fun!

Later, I will find time to investigate how other editors in the community work, such as Quill, ProseMirror, Draft, and SLATE. Let me share with you what I have learned.

If there are friends to see here, that you really fierce, I feel this article weight is quite enough. If you look all the way through, you’re like me in the rich text editor world, sort of… Get started haha.

In addition, I hope you can stick to the summary! No matter at school or at work, we should adhere to the summary, precipitation down things, is their own. Every day to learn a pile of knowledge, if there is no summary, over a period of time will forget, although the summary will forget (yes, it is my own), but always record the current idea, later looking back, may give you new inspiration, this is also one of the reasons I insist on digging gold writing.

If you have any questions and want to contact me privately, you can add my wechat account: YM_coke (NO one seems to add qq, SO I will not post it).