Location>code7788 >text

Implementing Rich Text Editor from Zero #2 - Editor Architecture Design Based on MVC Mode

Popularity:140 ℃/2025-04-15 10:29:00

In the previous plan, we need to implement itMVCThe architecture editor divides the application into three core components: controller, model, and view. When the controller executes commands, the current data model will be modified, and then displayed in the rendering of the view. Simply put, it is to build a data model that describes the structure and content of the document and use a customexecCommandModify the data description model. This is achievedL1The rich text editor solves the problem that dirty data and complex functions in rich text are difficult to achieve by extracting the data model.

  • Open source address:/WindRunnerMax/BlockKit
  • Online editing:/BlockKit/
  • Project Notes:/WindRunnerMax/BlockKit/blob/master/

Articles on implementing rich text editor projects from scratch:

  • Feeling like nothing, I'm ready to try to write a rich text editor from scratch
  • Implementing Rich Text Editor from Zero #2 - Editor Architecture Design Based on MVC Mode

A streamlined editor

In the design of the entire system architecture, the most important core concept isStatus Synchronization, If the state model is used as the benchmark, then the state synchronization we need to maintain can be summarized into the following two aspects:

  • Synchronize the user's operation status into the state model. When the user operates the text status, such as the user's selection operation, input operation, delete operation, etc., the change operation needs to be put into the state model.
  • Synchronize the state of the state model to the view layer. When executing commands in the control layer, the new state model generated after the change needs to be synchronized to the view layer to ensure that the data state is consistent with the view.

In fact, the synchronization of these two states is a positive dependency process. The state formed by user operations is synchronized to the state model, and the changes of the state model are synchronized to the view layer, and the view layer is the basis for user operations. For example, when a user selects a part of text by dragging and dropping, it needs to synchronize its selected range to the state model. When performing the deletion operation at this time, you need to delete this part of the text in the data, and then refresh the view to the new one.DOMstructure. The next time the loop needs to continue to ensure the synchronization of the state, and then perform operations such as input and refreshing the view.

Therefore, our goal is mainly state synchronization. Although there are only two simple principles, this thing is not that simple. When we perform state synchronization, we rely heavily on browser relatedAPIsuch as selection, input, keyboard and other events. However, at this time we have to deal with browser-related issues, such as the currentContentEditableCan't really stop itIMEinput,EditContextCompatibility needs to be improved, and these are all issues we need to deal with.

In fact, the more browsers we useAPITo implement, the more browser compatibility issues we need to consider. Therefore, the implementation of rich text editors will have many non-ContentEditableImplementation of such as DingTalk Document Self-drawn Selection,Google DocofCanvasDocument drawing, etc. However, this can reduce some browsersAPIReliance, but it cannot be truly completely out of the browser implementation, so evenCanvasThe drawn document must also depend on the browserAPIto implement input, position calculation, etc.

Going back to our streamlined editor model, the previous article has mentionedContentEditableProperties andexecCommandCommand, passTo execute command modificationHTMLAlthough the solution is simple, it is obvious that its controllability is poor.execCommandThe behavior of commands is inconsistent in each browser, which is also a type of browser-compatible behavior we mentioned earlier. However, we have no way to control these behaviors, and these are their default behaviors.

<div>
   <button >Bolding</button>
   <div style="border: 1px solid #eee; outline: none" contenteditable>123123</div>
 </div>
 <script>
   $ = () => {
     ("bold");
   };
 </script>

Therefore, in order to strengthen expansion and controllability, it also solves the problem that data and views cannot correspond.L1The rich text editor uses the concept of custom data models. That's rightDOMThe data structure extracted from the tree, the same data structure can ensure renderingHTMLThe same is true. The data model is directly controlled with custom commands to ultimately ensure rendering.HTMLDocument consistency. For the expression of the selection, it is necessary toDOMContinuous constituenciesnormalizeSelectionModel

This is what we are going to talk about todayMVCModel architecture, we organize editor projects throughmonorepoto manage related packages in the form of the form, so that a hierarchical architecture can be formed naturally. But before that, we canHTMLThe most basic editor in the fileOf course, we still realize the most basic bolding ability, and the main focus is on the control of the entire process. The ability to input is a more complex issue, and we will not deal with it for the time being. This part needs to be described in separate chapters.

Data Model

First of all, we need to define the data model. The data model here needs to have two parts, one is the node that describes the content of the document, and the other is the operation for the data structure. First, let’s look at the content describing the document. We still describe the content in a flat data structure, and in addition, for the sake of simple descriptionDOMStructure, there will be no multi-levelDOMNested.

let model = [
  { type: "strong", text: "123", start: 0, len: 3 },
  { type: "span", text: "123123", start: 3, len: 6 },
];

In the above data,typeThat is the node type,textIt is the text content. It is not enough to describe the data structure only. We also need to add additional state to describe the location information, that is, thestartandlen, This part of the data is very useful for us to calculate the selection transformation.

Therefore, this part of the data model is not just data, but should be called state. Next is operations for data structures, that is, operations such as inserting, deleting, and modifying the data model. Here we simply define the operation of data interception, and the completecomposeFor operation, please refer to

The operation to intercept data is to executecomposeThe basis of operation. When we have the original text and the change description, we need to convert it into iterator objects to intercept data respectively to construct a new data model. The iterator part here is defined firstpeekLengthandhasNextTwo methods are used to determine whether there are remaining available parts of the current data and whether it can continue to iterate.

peekLength() {
  if ([]) {
    return []. - ;
  } else {
    return Infinity;
  }
}

hasNext() {
  return () < Infinity;
}

nextThe method is more complicated, our goal here is totextPart of the content. Note that every time we callnextIt won't cross nodes, which means every timenextGet the current at mostindexThe node stored ininsertlength. Because if the content is taken more than a singleopThe corresponding properties of the length of , theoretically, are inconsistent, so they cannot be merged directly.

CallnextWhen the method does not existlengthThe default parameter isInfinity. Then we take the current oneindexThe node of the current node calculates the remaining length, iflengthgreater than the remaining length, take the remaining length, otherwise take the desired resultlengthlength. Then according tooffsetandlengthCome and intercepttextcontent.

next(length) {
  if (!length) length = Infinity;
  const nextOp = [];
  if (nextOp) {
    const offset = ;
    const opLength = ;
    const restLength = opLength - offset;
    if (length >= restLength) {
      length = restLength;
       =  + 1;
       = 0;
    } else {
       =  + length;
    }
    const newOp = { ...nextOp };
     = (offset, offset + length);
    return newOp;
  }
  return null;
}

In this way, we simply define the state of the data model and the iterator that can be used to intercept the data structure. This part is the basis for describing the content of the data structure and changes. Of course, we have simplified a lot of content here, so it seems to be relatively simple. In fact, there are very complex implementations here, such as how to implement themimmutableTo reduce duplicate rendering to ensure performance.

View layer

The view layer is mainly responsible for rendering the data model, which we can use.ReactTo render, but in this simple example, we can create it directlyDOMJust do it. Therefore, here we directly traverse the data model and create the corresponding one according to the node type.DOMnode, then insert it intocontenteditableofdivmiddle.

const render = () => {
   = "";
  for (const data of model) {
    const node = ();
    ("data-leaf", "true");
     = ;
    (node);
    MODEL_TO_DOM.set(data, node);
    DOM_TO_MODEL.set(node, data);
  }
  ();
};

We have added additionaldata-leafProperties to facilitate marking of leaf nodes. Our selection update requires marking leaf nodes so that the selection needs to fall on a certainDOMon the node. andMODEL_TO_DOMandDOM_TO_MODELIt is used for maintenanceModelandDOMMapping relationship, because we need toDOMandMODELto get the corresponding values ​​from each other.

In this way, we define a very simple view layer, and we don't need to consider too many performance issues in the example. But inReactWhen the view layer is truly completed, it is uncontrolledContentEditableWe need to consider a lot of issues, such askeyValue maintenance, dirtyDOMcheck, reduce duplicate rendering, batch schedule refresh, selection correction, etc.

Controller

The controller is the most complex part of our architecture, and there is a lot of logical processing here. Our editor controller model needs to be implemented based on data structures and view layers, so we will describe it at the end, just hereMVCThe last part of the model sequence isController. In the controller layer, the most important function is synchronization, that is, synchronize the state of the data model and view layer.

For example, our view layer is rendered based on the data model. If we enter content on a node, then we need to synchronize the input content into the data model. If we do not synchronize the data model correctly at this time, there will be problems in the length calculation of the selection. In this case, it will naturally lead to problems in the index synchronization of the selection. Here we also need to distinguish between controlled and uncontrolled problems.

First of all, we need to pay attention to the synchronization of the selection. The selection is the basis of the editor's operation, and the selected state is the reference position of the operation. The essential implementation of synchronization requires the browserAPITo synchronize to the data model, the browser's selection existsselectionchangeEvents, through this event, we can pay attention to the changes in the selection, and at this time we can obtain the latest selection information.

passMethod we can obtain the information of the current selection and then passgetRangeAtYou can get the constituencyRangeWe can pass the object naturallyRangeObject to get the start and end positions of the selection. With the start and end positions of the selection, we can take the corresponding positions through the previously set mapping relationship.

("selectionchange", () => {
  const selection = ();
  const range = (0);
  const { startContainer, endContainer, startOffset, endOffset } = range;
  const startLeaf = ("[data-leaf]");
  const endLeaf = ("[data-leaf]");
  const startModel = DOM_TO_MODEL.get(startLeaf);
  const endModel = DOM_TO_MODEL.get(endLeaf);
  const start =  + startOffset;
  const end =  + endOffset;
  ({ start, len: end - start });
  ();
});

Here, the corresponding one is obtained through the selection nodeDOMNodes are not necessarily the nodes we need. The browser's selection location rules are uncertain for our model, so we need to find the target leaf nodes based on the selection node. For example, if the normal text is selected, the selection is on the text node, and if the three-click to select it, the entire line isDOMon the node.

So hereclosestIt only deals with the most ordinary text node selection, and complex situations still need to be carried out.normalizeoperate. andDOM_TO_MODELIt is a state map to obtain the most recent one[data-leaf]The node is to get the corresponding status. After obtaining the latest selection location, it needs to be updated.DOMThe actual selection position is equivalent to correcting the selection status of the browser itself.

updateDOMselectionThe method is the exact opposite operation, the above event processing is done throughDOMSelection UpdateModelconstituency, andupdateDOMselectionIt is throughModelSelection UpdateDOMConstituency. Then at this time we have onlystart/len, based on these two numbers to the correspondingDOMIt's not a simple thing, we need to searchDOMnode.

const leaves = (("[data-leaf]"));

There will be many here tooDOMSearch, so in actual operations, it is necessary to minimize the range of selection. In our actual design, search based on behavioral benchmarks.spanNode of type. Then you need to traverse the entireleavesArray, then continue to passDOM_TO_MODELCome and get itDOMThe corresponding state is then obtainedrangeNeed nodes and offsets.

const { start, len } = ;
 const end = start + len;
 for (const leaf of leaves) {
   const data = DOM_TO_MODEL.get(leaf);
   const leafStart = ;
   const leafLen = ;
   if (start >= leafStart && start <= leafStart + leafLen) {
     startLeaf = leaf;
     startLeafOffset = start - leafStart;
     // In the collapsed selection state, start and end can be consistent with
     if () {
       endLeaf = startLeaf;
       endLeafOffset = startLeafOffset;
       break;
     }
   }
   if (end >= leafStart && end <= leafStart + leafLen) {
     endLeaf = leaf;
     endLeafOffset = end - leafStart;
     break;
   }
 }

When the target is foundDOMAfter the node, we can constructmodelRange, and set it as a browser selection. But it should be noted that we need to check here whether the current selection is the same as the original selection. Imagine if the selection is set again, it will triggerSelectionChangeEvents, which will lead to an infinite loop, naturally, this problem needs to be avoided.

if ( > 0) {
   range = (0);
   // The current selection is the same as the Model selection, so no update is required
   if (
      === &&
      === &&
      === &&
      ===
   ) {
     return void 0;
   }
 }
 (
   ,
   startLeafOffset,
   ,
   endLeafOffset
 );

In fact, the problem with the selection is no less than the problem with the input method. Here we simply realize the synchronization between the browser selection and our model selection, and the core is still the synchronization of state. Next, the synchronization of the data model can be achieved, which is where we actually execute commands, rather than using them directly.

At this time, the data iterator we defined earlier came in handy, and the goal of our operation also needs to be usedrangeto implement, for example123123This text isstart: 3, len: 2the constituency, andstrongThe type of data in this range will become123[12 strong]3, this is the operation of cropping long data.

We first construct the selection according to the required operationretainarray, although this part of the description itself should be constructedopsCome and operate, but more additions are needed herecompose, so here we only use an array and index to identify it.

let retain = [start, len, Infinity];
let retainIndex = 0;

Then you need to define the iterator andretainLet's merge data, our operation here is0Index to move pointers and intercept data within the index.1Index to the actual change typetype2We pin it toInfinity, in this case we are taking all the remaining data. What's important herelengthThis is to take the shorter values ​​of the two to achieve data interception.

const iterator = new Iterator(model);
while (()) {
  const length = ((), retain[retainIndex]);
  const isApplyAttrs = retainIndex === 1;
  const thisOp = (length);
  const nextRetain = retain[retainIndex] - length;
  retain[retainIndex] = nextRetain;
  if (retain[retainIndex] === 0) {
    retainIndex = retainIndex + 1;
  }
  if (!thisOp) break;
  isApplyAttrs && ( = type);
  (thisOp);
}

In the end, I still remember that the data we maintain is not only a data expression, but also a description of the state of the entire data. Therefore, we need to refresh all the data in the end to ensure that the final data model is correct. At this time, we still need to call itrenderto rerender the view layer and then refresh the browser selection.

let index = 0;
for (const data of newModel) {
   = index;
   = ;
  index = index + ;
}
render();
();

In this way, we define a relatively complex controller layer. The controller layer here mainly synchronizes the state of the data model and view layer, and implements the most basic command operations, and of course does not handle many complex boundary situations. In actual editor implementation, this part of the logic will be very complicated because we need to deal with a lot of problems, such as input method, selection model, clipboard, etc.

Project architecture design

Then our basic editorMVCThe model has been implemented, so it can be naturally abstracted into independentpackage, we also happened to passmonorepoto manage projects in the form of. So here you can abstract it ascoredeltareactutilsFour core packages correspond to the editor's core logic, data model, view layer, and tool functions. The specific editor module implementations are all defined in the form of plug-inspluginIn the package.

Core

CoreThe module encapsulates the core logic of the editor, including clipboard module, history operation module, input module, selection module, status module, etc. All modules are instantiated.editorObject reference. In addition to the hierarchical logical implementation, it is also expected that the module's expansion capabilities can be realized. After referring to the editor module and expanding the capabilities, it can be reloaded on the editor.

Core
 ├── clipboard
 ├── collect
 ├── editor
 ├── event
 ├── history
 ├── input
 ├── model
 ├── perform
 ├── plugin
 ├── rect
 ├── ref
 ├── schema
 ├── selection
 ├── state
 └── ...

In factCoreThere is a dependency in the module. For example, the selection module depends on event distribution of event modules. This is mainly because the module needs to rely on instances of other modules during construction to initialize its own data and events. Therefore, the order of event instantiation is more important, but when we actually talk about it, we directly follow the above definition order, and do not follow the directed graph order that is directly dependent.

clipboardThe module is mainly responsible for data serialization and deserialization, as well as clipboard operations. Generally speaking, rich text editorsDOMThe structure is not that standardized, for example,slateWe can see suchdata-slate-nodedata-slate-leafWe can understand the node attributes as template structures.

<p data-slate-node="element">
  <span data-slate-node="text">
    <span data-slate-leaf="true">
      <span data-slate-string="true">text</span>
    </span>
  </span>
</p>

Then we passreactThis template structure will naturally exist when building view layers, so in the process of serialization, this complex structure needs to be serialized into relatively standardizedHTML. Especially for many styles, we do not use standard semantic tags, butstyleattributes are implemented, so it is very important to regularize them.

Deserialization willHTMLConvert to the editor's data model, and this part of the implementation is to paste content across the editor. The built-in data structures of the editor are usually inconsistent, so a more standardized intermediate structure is required across editors. This is actually an unwritten rule in the editor.AWhen serializing the editor, as standard as possible.BEditor deserialization can be better handled.

collectThe module can obtain relevant data based on the selection data. For example, when the user selects a piece of text and performs copying, he needs to take out the selected part of the data content before serialization can be performed. also,collectThe module can also obtain a certain locationopnode,marksInheritance processing, etc.

editorModules are the editor's module aggregation class, which mainly manages the entire editor's life cycle, such as instantiation and mountDOM, destruction and other states. This module needs to combine all modules, and also pay attention to the directed graph organization dependencies of the module, the main editorAPIAll should be exposed from this module.

eventThe module is an event distribution module, and the binding of native events is implemented in this module, and all events in the editor should be distributed from this module. This method can have a higher level of customization space, such as extending plug-in-level event execution, and can reduce the probability of memory leaks. After all, as long as we can ensure that the editor's destroy method calls, all events can be correctly uninstalled.

historyModules are modules that maintain historical operations and are implemented in the editorundoandredoIt is relatively complex. We need to perform based on atomization operations, rather than storing a full data snapshot of the editor, and we need to maintain two stacks to handle data transfer. In addition, we also need to implement extensions on this basis, such as automatic combination, operation merging, collaborative processing, etc.

The automatic combination here refers to when the user performs high-frequency continuous operations, we need to merge them into one operation. Operation merge means that we can passAPITo achieve merging, for example, after the user uploads the picture, performs other input operations, and then the operation generated after the upload is successful. Finally, this operation should be merged into the operation of uploading the picture. Coordinated processing requires a principle that we can only revoke our own operations, but not others who collaborate with each other.

inputModules are modules that process inputs. Input is one of the core operations of the editor. We need to handle input operations such as input methods, keyboards, and mouse. The interactive processing of the input method requires a lot of compatible processing, for example, the input method also has candidate words, associative words, shortcut input, accents, etc. Even the input method of the mobile terminal is more troublesome, indraftThe compatibility issues of mobile input methods are also listed separately.

Let's give a relatively common example at present.ContentEditableCan't really stop itIMEThis causes us to be unable to truly block Chinese input. In the following example, inputting English and numbers will not respond, but Chinese can be input normally, which is also one of the reasons why many editors choose to draw selections and inputs, for exampleVSCode, DingTalk Documents, etc.

<div contenteditable ></div>
<script>
  const stop = (e) => {
    ();
    ();
  };
  $('beforeinput', stop);
  $('input', stop);
  $('keydown', stop);
  $('keypress', stop);
  $('keyup', stop);
  $('compositionstart', stop);
  $('compositionupdate', stop);
  $('compositionend', stop);
</script>

modelModules are used to mapDOMThe relationship between view and state model, which is the bridge between view layer and data model, and in many cases we need to passDOMTo obtain the state model, you also need to obtain the correspondingDOMview. This part is to useWeakMapto maintain the mapping to achieve state synchronization.

performThe module is a basic module that encapsulates changes to the data model, because the basic constructiondeltaOperations can be more complicated, such as executing propertiesmarksChanges need to be filtered out\nThis oneop, in turn, the operation of line attributes requires filtering out ordinary textop. Therefore, it is necessary to encapsulate this part of the operation to simplify the cost of performing changes.

pluginThe module implements the editor's plug-in mechanism, and plug-in is very necessary. In theory, all formats outside ordinary text should be implemented by plug-ins. The plug-in here mainly provides basic plug-in definitions and types, manages the life cycle of the plug-in, and capabilities such as grouping by method call, method scheduling priority.

rectModules are used to process the editor's location information, and in many cases we need to use it according toDOMNodes calculate positions and need to provide the relative position of the node in the editor, especially many additional capabilities, such as viewport locking for virtual scrolling, virtual layers of contrasting views, high positioning of comment capabilities, etc. In addition, the location information of the selection is also very important, such as the pop-up position of the floating toolbar.

refThe module implements the editor's position transfer reference, and this department actually uses collaboration.transformThe index information to process is similar toslateofPathRef. For example, when the user uploads the picture, other content insertion operations may be performed. At this time, the index value of the picture will change, and userefThe module can get the latest index value.

schemaThe module is used to define the editor's data application rules. We need to define the methods that data attributes need to be processed here, such as bold attributes.marksYou need to continue to inherit bold attributes after input, and the in-line code isinlineTypes do not need to be inherited, similar to pictures and dividing lines, they need to be defined as exclusive to the entire line.Voidtype,MentionEmojietc. need to be defined asEmbedtype.

selectionModules are modules used to process selections. Selections are the core operating benchmark of the editor. We need to handle selection synchronization, selection correction, etc. In fact, the synchronization of the selection is very complicated, from the browserDOMMapping to a selection model itself is something that needs to be carefully designed, and the correction of the selection requires handling a lot of boundary situations.

We have also mentioned related issues before,DOMStructure as an example, if we want to express the selection folded in4When this character is left, multiple expressions will also appear to achieve this position, which will actually rely heavily on the browser's default behavior. Therefore, we need to ensure the mapping of this selection and the correction logic in unconventional states.

// <span>123</span><b><em>456</em></b><span>789</span>
{ node: 123, offset: 3 }
{ node: <em></em>, offset: 0 }
{ node: <b></b>, offset: 0 }

stateThe module maintains the core state of the editor. After passing the basic data when instantiating the editor, the content we maintain subsequently becomes state, rather than the data content we first pass. Our state change method will also be implemented here, especiallyImmutable/KeyWe need to ensure the immutability of the state to reduce repeated rendering.

                             |-- LeafState
             |-- LineState --|-- LeafState
             |               |-- LeafState            
BlockState --|
             |               |-- LeafState
             |-- LineState --|-- LeafState
                             |-- LeafState

Delta

DeltaThe module encapsulates the editor's data model. We need to describe the editor's content and changes in the editor's content based on the data model. In addition, many operations of the data model are encapsulated, such ascomposetransforminvertdiffIteratoretc.

Delta
 ├── attributes
 ├── delta
 ├── mutate
 ├── utils
 └── ...

HereDeltaThe implementation is based onQuilldata model transformation,QuillThe data model design is excellent, especially the encapsulation based onOToperation transformation and other methods. However, there are still inconveniences in the design, so I'll refer to itEtherPadOn this basis, some implementations have been transformed. We will talk about the data structure design in detail later.

Also, it should be noted that ourDeltaThe most important implementation is to describe documents and changes, which is equivalent to an implementation of serialization and deserialization. The above also mentioned that after initializing the editor, the data we maintain becomes a built-in state, rather than the initial initial data content. Therefore, many methods have separate designs at the controller level, for exampleimmutablestatus maintenance.

attributesThe module maintains operations for text description properties, and we simplify the implementation of properties here, i.e.AttributeMapThe type is defined as<string, string>type. The specific modules are definedcomposeinverttransformdiffetc. to realize the merge, inversion, transformation, difference and other operations of attributes.

deltaThe module implements the data model of the entire editor.deltapassopsA data model of linear structure is realized.opsThe structure mainly includes three operations.insertUsed to describe the insert text,deleteUsed to describe the deletion text,retainUsed to describe retained text/move pointers and on this basiscomposetransformWait for methods.

mutateThe module is implementedimmutableofdeltaModule implementation and independent\nAs independentop. The initial controller design implementation was based on data changes, and it was subsequently transformed into original state maintenance, so this part of the implementation moved todeltaIn the module, this part can be directly corresponding to the editor's state maintenance, and can be used for unit testing, etc.

utilsThe module is encapsulated foropas well asdeltaauxiliary methods,cloneRelated methods implement suchopdeltaIso-deep copy and peer method, of course, since our new design does not need to be introducedlodashrelated methods. In addition, some data judgment and formatting methods are implemented, such as the start/end string judgment and segmentation of data\nMethods and so on.

React

ReactThe module implements the view layer adapter, providing basicTextVoidEmbedand other types of nodes, as well as related rendering modes, are equivalent to encapsulating theCoreThe scheduling paradigm of the pattern. Also provided with relevant packagingHOCnodes, andHooksetc. to achieve the view layer extension of the plug-in.

React
 ├── hooks
 ├── model
 ├── plugin
 ├── preset
 └── ...

hooksThe module implements the method of obtaining editor instances, which is convenient forReactGet the editor instance in the component, of course this depends on ourProviderComponents. In addition, it has also been implementedreadonlyThe state method, here the read-only state maintenance itself is maintained in the plug-in, but it was extracted later toReactIn the component, this makes it easier to switch edit/read-only states.

modelThe module implements the built-in data model of the editor, which actually corresponds to it.CoreLayerState,Right nowBlock/Line/Leafdata model, in addition toDOMIn addition to the patterns that nodes need to follow, they also implement such as dirtyDOMTesting method, etc. In addition, there are special ones hereEOLNode is a specialLeafModel, the rendering of the end of the line node will be scheduled according to the policy.

pluginThe module implements the editor's plug-in mechanism. The plug-in here mainly extends the basic plug-in definition and type, for example,CoreThe return value of the plugin method type defined inany, here we need to define it as a specificReactNodetype. In addition, a plug-in during rendering is also implemented here, that is, the type that does not maintain state at the core level, mainlyWrapType of node plug-in.

presetThe module presets the editor exposed to the outsideAPIComponents, such as editorsContextVoidEmbedEditableComponents, etc., mainly provide basic components for building editor views, as well as component extensions of plug-in layer, etc. Of course, it also encapsulates many interactive implementations, such as autofocus, selection synchronization, view refresh adapter, etc.

Utils

UtilsThe module implements many general tool functions, mainly dealing with general logic in the editor, such as anti-shake, throttling, etc., and also processingDOMThere are auxiliary methods of structure, including event distribution processing methods, event binding decorators, etc., as well as list operations, internationalization, clipboard operations, etc.

Utils
 ├── 
 ├── 
 ├── 
 └── ...

Summarize

Here we implement a simple editorMVCThe architecture example is then naturally abstracted the editor's core modules, data models, view layers, tool functions, etc., and made a simple description. In the future, we will describe the editor's data model design and introduce ourDeltaData structure method, and related application scenarios in the editor. Data structures are very important designs, because the core operations of the editor are based on data models. If you cannot understand the design of the data structure, it will be difficult to understand many operating models of the editor.

One question every day

  • /WindRunnerMax/EveryDay

refer to

  • /en-US/docs/Web/API/EditContext
  • /translate/why-contenteditable-is-terrible
  • /questions/78268797/how-to-prevent-ime-input-method-editor-to-mutate-the-contenteditable-element