Slate Document Editor - WrapNode Data Structures and Operations Transformations
Earlier we chatted a bit about theslate
The basic concepts of rich-text engines and a good understanding of the concepts of rich-text engines based on theslate
The realization of some of the plug-in capabilities of the document editor design, type of expansion, specific programs, etc. were explored, then the next we focus more on the details of the document editor, from shallow to deep talk about the design of the relevant capabilities of the document editor.
- Online Editor./DocEditor
- Open source address./WindrunnerMax/DocEditor
with respect toslate
Related posts to document editor project.
- Building a document editor based on Slate
- Slate Document Editor - WrapNode Data Structures and Operations Transformations
- Slate Document Editor - TS Type Expansion and Node Type Checking
- Slate Document Editor - Decorator Decorator Render Scheduler
Normalize
existslate
The regularization of the data structures in this project is a troublesome matter, especially for structures that need to be nested, such as those that exist in this projectQuote
cap (a poem)List
Then there are various options when regularizing the data structure, again taking these two data structures as an example, each of theWrap
There must be a correspondingPair
The structure of the nested, then for the data structure has the following program. In fact, I think it is very difficult to solve this type of problem, nested data structures are not so efficient for additions, deletions, modifications and checks, so in the absence of best practices related to the input case, can only continue to figure out.
The first is to reuse the current block structure, which means that theQuote Key
cap (a poem)List Key
It's all leveled and the same itsPair Key
Also reuse up, the advantage is that there will not be too many levels of nested relationships, for the content of the search and related processing will be much simpler. But the same problem will also occur, if in theQuote
cap (a poem)List
The case of non-matching, that is to say, the case where it is not an exactly equivalent relationship, would require the presence of aPair
mismatchWrap
situation, at which point it becomes difficult to ensureNormalize
, because we are in need of predictable structures.
{
"quote-wrap": true,
"list-wrap": true,
children: [
{ "quote-pair": true, "list-pair": 1, children: [/* ... */] },
{ "quote-pair": true, "list-pair": 2, children: [/* ... */] },
{ "quote-pair": true, children: [/* ... */] },
{ "quote-pair": true, "list-pair": 1, children: [/* ... */] },
{ "quote-pair": true, "list-pair": 2, children: [/* ... */] },
]
}
Then if we don't do very sophisticated control of the content in theslate
is processed using the default behavior in , then its data structure expression will appear as follows, in which case the data structure is predictable, then theNormalize
It would not be a problem, and since this is its default behavior, there would not be much operational data processing to be concerned about. But the problem is also more obvious, in this case the data is predictable, but the processing is particularly cumbersome, when we maintain the correspondence, we must recursively process all the child nodes, in the case of particularly many levels of nesting, this amount of computation is quite complex, if in the case of support for the structure of tables and so on, it becomes even more difficult to control.
{
"quote-wrap": true,
children: [
{
"list-wrap": true,
children: [
{ "quote-pair": true, "list-pair": 1, children: [/* ... */] },
{ "quote-pair": true, "list-pair": 2, children: [/* ... */] },
]
},
{ "quote-pair": true, children: [/* ... */] },
{ "quote-pair": true, children: [/* ... */] },
]
}
Then this data structure isn't actually perfect, and its biggest problem is that thewrap - pair
interval is too large, such a treatment will appear more boundary problems, to take a more extreme example, suppose we have the outermost layer of the existence of the reference block, in the reference block and nested in the table, the table is nested in the highlighting block, the highlighting block is nested in the reference block, in this case we have thewrap
Need to passN
Multiple layers to matchpair
The biggest impact of this situation is theNormalize
We need to have very deepDFS
Processing to be able to deal with it, processing not only requires performance intensive deep traversal, but also prone to cause a lot of problems due to poor processing.
In this case, then, we can simplify the nesting of the hierarchy as much as possible, which means that we need to avoid thewrap - pair
of the spacing problem, then it is obvious that we directly and strictly specify that thewrap
allchildren
must bepair
In this case we doNormalize
It's a lot easier to just add thewrap
and traversing its children in the case of thepair
In this case, we can just check the parent node if it's the same as the parent node. Of course, this solution is not without its drawbacks, which makes us have more stringent requirements for the accuracy of data manipulation, because here we will not go to the default behavior, but all need to control themselves, especially all the nesting relationships and boundaries need to be strictly defined, which also has higher requirements for the design of the editor behavior.
{
"quote-wrap": true,
children: [
{
"list-wrap": true,
"quote-pair": true,
children: [
{ "list-pair": 1, children: [/* ... */] },
{ "list-pair": 2, children: [/* ... */] },
{ "list-pair": 3, children: [/* ... */] },
]
},
{ "quote-pair": true, children: [/* ... */] },
{ "quote-pair": true, children: [/* ... */] },
{ "quote-pair": true, children: [/* ... */] },
]
}
So why does the data structure get complicated? Take the above structure as an example.list-pair: 2
This node is disarmed.list-wrap
node's nested structure, then we need to change the node to the following type, and we can see that the difference in structure will be more significant here, except for the fact that in addition to changing thelist-wrap
Beyond splitting into two, we need to deal with otherlist-pair
There are more operations to do here, so if we want to implement the more genericSchema
It would require more design and specification.
And one of the most overlooked points here is that we need to provide the originallist-pair: 2
This node joins the"quote-pair": true
because at this point the line becomesquote-wrap
child elements, which in summary means that we need to change the elements that were originally in thelist-wrap
attribute and then make a copy of it to thelist-pair: 2
to maintain the correct nesting structure. So why isn't this done with the help of thenormalize
to passively add it but rather to actively copy it, for the simple reason that if thequote-pair
is fine if it's a passive process, but if it's a passive process then it's set directly to thetrue
That's fine, but if it'slist-pair
We have no way of knowing what the data structure of this value should look like if we implement it, and this implementation can only be attributed to the plugin'snormalize
to realize it.
{
"quote-wrap": true,
children: [
{
"list-wrap": true,
"quote-pair": true,
children: [
{ "list-pair": 1, children: [/* ... */] },
]
},
{ "quote-pair": true, children: [/* ... */] },
{
"list-wrap": true,
"quote-pair": true,
children: [
{ "list-pair": 1, children: [/* ... */] },
]
},
{ "quote-pair": true, children: [/* ... */] },
{ "quote-pair": true, children: [/* ... */] },
{ "quote-pair": true, children: [/* ... */] },
]
}
Transformers
As mentioned earlier, there are default behaviors in nested data structures, and there were not many data handling problems before because the default behaviors were always followed, however, when the data structure was changed, it was found that the data structure was not so easy to control in many cases. Previously, when dealing withSetBlock
When I do, I usually go through thematch
parameter matchingBlock
type of node, since nothing usually goes wrong with this processing in the case of the default behavior.
However, in the process of changing the data structure, the handling of theNormalize
It had a problem with matching block elements that did not behave as expected, which resulted in the data it processed not being processed correctly all the time.Normalize
It also fails to complete until it throws an exception. The main problem here is caused by the fact that its iteration order doesn't match what I expected, for example in theDEMO
Execute on page[...(editor, {at: [9, 1, 0] })]
, which returns the result of the topEditor
bottomNode
And, of course, this will include all of the scope of theLeaf
The nodes are equivalent to theRange
。
[] Editor
[9] Wrap
[9, 1] List
[9, 1, 9] Line
[9, 1, 0] Text
In fact in this case if the original(path, at)
is not a problem, here is too much reliance on its default behavior before, which leads to too poor control of the accuracy of the data, we should need to have predictability in the processing of data, rather than relying on the default behavior. In addition, theslate
The documentation is still too concise, many details are not mentioned, in this case you still need to read the source code to have a better understanding of the data processing, for example, looking at the source code here let me understand that every time I do an operation I take theRange
All eligible elements performmatch
, which may occur multiple times in a single call to theOp
Dispatch.
In addition, since this processing is mainly for nested element support, theunwrapNodes
or a related data processing feature, when I call theunwrapNodes
whenat
The values passed in are not the same, respectivelyA-[3, 1, 0]
cap (a poem)B-[3, 1, 0, 0]
, a key point here is that at the time of matching we are all strictly equal to the[3, 1, 0]
, but the result of the call is different, in that theA
center[3, 1, 0]
All elements have beenunwrap
It's up, and theB
just[3, 1, 0, 0]
(indicates passive-voice clauses)unwrap
Now, here's what we can guaranteematch
The results are identical, so the problem is that theat
on. At this point, if you don't understandslate
If the model of data manipulation, we must go to the source code, in the reading of the source code we can find that there will beHelped us narrow it down, so here it is
at
value will then affect the final result.
unwrapNodes(editor, { match: (_, p) => (p, [3, 1, 0]), at: [3, 1, 0] }); // A
unwrapNodes(editor, { match: (_, p) => (p, [3, 1, 0]), at: [3, 1, 0, 0] }); // B
The above problem also means that all our data should not be passed around, and we should be very clear about the data we want to manipulate and its structure. In fact, the previous also mentioned a problem that is difficult to deal with the situation of multi-level nesting, which actually involves an editorial boundary, making the maintenance of the data has become complex. As an example, join this time we have a table nested in moreCell
, if we are multi-instanceCell
structure, at which point we filter theEditor
Any data processed after the instance does not affect otherEditor
instance, whereas if we were at this pointJSON
Nested expression of the structure, we may have more than the operation of the boundary and affect other data, especially the parent data structure of the situation. So we must also pay attention to the handling of boundary conditions, that is, as mentioned earlier, we need to be very clear about the data structure to be processed, and clearly delineate the operation nodes and ranges.
{
children: [
{
BLOCK_EDGE: true, // block structure boundaries
children: [
{ children: [/* ... */] }, { children: [/* ...
{ children: [/* ... */] }, [ { children: [/* ... */] }, { children: [/* ...
]
}, { children: [/* ... */] }, ]
{ children: [/* ... */] }, { children: [/* ...
{ children: [/* ... */] }, { children: [/* ... */] }, { children: [/* ...
]
}
Additionally, debugging code in pages already on the thread can be a challenge, especially when theeditor
It wasn't exposed towindow
In the case that you want to get an instance of the editor directly, you need to reproduce the online environment locally, in which case you can do so with the help of theReact
wouldFiber
The actual writing is inDOM
The node is characterized by theDOM
The node directly obtainsEditor
instance, though the nativeslate
The use of a large number ofWeakMap
to store the data, in which case there is no good solution for the time being unless theeditor
actually references such an object or has an instance of it, otherwise it can only be accessed via thedebug
breakpoints, and then the object is temporarily stored as a global variable for use during debugging.
const el = (`[data-slate-editor="true"]`);
const key = (el).find(it => ("__react"));
const editor = el[key].;
ultimate
Here we chat aboutWrapNode
Data Structures and Operational Transformations, mainly for nested types of data structures need to be concerned about, and in fact the types of nodes can be divided into a variety of other things, we can have in the broader contextBlockNode
、TextBlockNode
、TextNode
inBlockNode
In this we can again divideBaseNode
、WrapNode
、PairNode
、InlineBlockNode
、VoidNode
、InstanceNode
etc., so that what is recounted in the text is still of a relatively basic nature in terms ofslate
There are many additional concepts and operations to focus on in theRange
、Operation
、Editor
、Element
、Path
etc. So in the latter article we'll talk mostly about the issues in theslate
centerPath
expression, as well as in theReact
How do you control the expression and proper maintenance of your content?Path
pathwayElement
Content rendered.