-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvements in insertNodes #5201
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
bd81d9a
to
705d53c
Compare
output = output.replace(/\s__playwright_target__="[^"]+"/, ''); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
locally sometimes some tests fail me due to this attribute that playwright uses. I don't know if it happens to any of you too.
It seems to be a bug:
microsoft/playwright#22004
I'm good here - gonna let @zurfyx take a pass since he's more familiar with the new $insertNodes logic at the moment. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you GermanJablo! Sorry for the late review
return; | ||
} | ||
|
||
selection.insertNodes([heading, $createTextNode('bar')]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have tests for the correct behavior of this? I think it's important to ensure that block + block will remain on future iterations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this case (block + text) and also the one you mention (block + block) are covered in the copy paste suite
* | ||
* | ||
* */ | ||
insertRangeAfter(firstToInsert: LexicalNode, lastToInsert?: LexicalNode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you share some more thoughts on why we need this additional method on the API? I feel like insertAfter
already covers most use cases and otherwise a spread (array) params like we have for append
will do. The signature is also a bit confusing in my opinion, I understand the performance aspect of it but it's a hard trade-off.
For the sake of insertNodes I would personally have this function as a module level.
insertRangeAfter(node: LexicalNode, ...other params)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, two reasons.
-
First, it provides a way to modify the behavior of multiple nodes inserted after another. For example, in my case I want that when several blocks are pasted, the indent level of the initial block is added to all of them. This is not currently possible. Using PASTE_COMMAND would mean having to repeat hundreds of lines of code. See Modularize the paste process with a new INSERT_NODES_COMMAND #4900
-
Secondly, these methods offer a much more performant API than
splice
andappend
, optimized for the double linked list structure that uses lexical. See Deprecatesplice
,append
,getChildIndex
andgetIndexWithinParent
#5194
I don't recommend using a spread array param, since you almost always insert nodes that are already siblings anyway, and that would require an additional iteration loop.
I also wouldn't "hide" it inside the insertNodes
module for internal use. This use case is not only useful for copy-paste. For example, in my application you can drag and drop a series of blocks to another position.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GermanJablo - The two points you made are totally valid, but I also think that having an ergonomic API and backward compatibility to certain extent is very important.
In my opinion, linked lists are a fairly advanced CS concept and even with a good understanding of them, having firstToInsert
and lastToInsert
would still lead me to some trial and error to fully understand how the function works.
As for performance, I would take into account real-life use cases. insertNodes
may be worth optimizing, a plugin which handles clipboard paste once every 5 min can probably do with a function that takes slightly longer to initialize. At the end of the day the cost is a linear O(n)
.
I propose the following to unblock this PR. We hide this function from the public API and I bring this and #5194 up for team discussion to gather more opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is backward compatible.
...other params
would be O(n), but lastToInsert
is O(1). Yes, in insertNodes
you have to iterate all the nodes anyway. But this may not always be the case. A real life example of this is dragging consecutive blocks. firstToInsert
and lastToInsert
are obtained immediately from the anchor and focus of the selection.
I understand that ...other params
may be a little easier to understand, but do you think that even with the JSDOC comments lastToInsert
is difficult to understand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the function implementation O(2n)
? The while loop + for loop. I believe the additional for loop isn't necessary but in any case we're now talking about O(3n)
which is the same order of magnitude.
IMO the function is not immediately obvious, it's a sort of slice based function and the preconditions are that the first and the last have to be children of the same parent. To me having insertAfter(node1, node2, node3)
is more intuitive and iff optimal performance a must have I'd probably consider a utility that does this for you
$insertChildrenAfter(refNode: LexicalNode, parent: ElementNode);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I haven't finished the function. I'm simply iterating and applying insertAfter on each node to save time. But the idea is to correct the implementation later.
My idea of the algorithm is:
- loop to check from
firstToInsert
tolastToInsert
with 3 goals: (1) check thatlastToInsert
is a later sibling offirstToInsert
, (2) calculate the distance between those two nodes (count++
) , needed to update thesize
property of the newParent and oldParent and (3) update theparent
property of the nodes in the range.
If we use ...params
, you will be doing the same job + additionally:
- You need an additional iteration to store the nodes to be inserted in an array (plus memory space)
- modifying the
size
of newParent and oldParent N times. - Modifying nextSibling and prevSibling 3N times (for each node moved, unnecessarily).
Yes, we are talking about the same order of magnitude. The 2 cases are O(N)... the difference is that in the first case fewer iterations are done, and also fewer things in the iterations. I wouldn't be surprised if the second implementation is 3-5X slower when there are many nodes.
Please correct me if I'm wrong
} else if (notInline(node)) { | ||
currentBlock = currentBlock.insertAfter(node) as ElementNode; | ||
function restoreSelection() { | ||
if (nodeToSelect.select) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be more inclined to keep against $isTextNode
and $isElementNode
as it would give us type-safetiness, it's currently typed as any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or better yet: #5205
? 😅
const isLI = (node: LexicalNode) => | ||
'__value' in node && '__checked' in node; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this part of the command abstraction part? Like code blocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current version of insertNodes
it was pretending to be lexical/list
agnostic, but it wasn't.
That's why one of the current conditions of isMergeable
is (!firstBlock.isEmpty() || !$isRootOrShadowRoot(firstBlock.getParentOrThrow()));
.
Surely anyone who reviews that will not understand that condition (in fact it's still hard for me lol), so I came clean with a more declarative condition.
insertNodes
I divided the process into two cases: All elements of the array are inline, or at least one element is not inline. That makesinsertNodes
about 10 lines longer, but much easier to reason with and refactor.$basicInsertStrategy
to$wrapInlineNodes
and moved it fromclipboard
toinsertNodes
. NowinsertNodes
andpaste
should not have different behaviors, which also made refactoring more difficult. Thanks to this, an incorrect test was detected and corrected: "'white-space: pre (no touchy) (2)".insertNodes
, these tests verified that an error was thrown in that case. This does not happen now. However, the case with "disjoint sibling text node" has already been tested in the G Docs pasting suite.insertRangeAfter
method in LexicalNode that can be used to let users define how the nodes should be pasted. This PR makes Modularize the paste process with a new INSERT_NODES_COMMAND #4900 obsolete as it offers a simpler solution.