Skip to content
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

[lexical][lexical-overflow] Refactor: simplified removeText and insertText rewrite (part 1) #6456

Merged
merged 31 commits into from
Sep 4, 2024

Conversation

GermanJablo
Copy link
Contributor

This is something I've been wanting to do since I did the insertNodes rewrite. These are also methods with long and very complex implementations.

Currently, removeText depends on insertText, since removeText is defined as inserting an empty string (insertText("")). This PR seeks to reverse the dependency of these methods, making insertText dependent on removeText.

This can be achieved in 2 steps:

  1. Rewrite removeText without making it dependent on insertText.
  2. Rewrite insertText with the new removeText.

So far in this PR, I have managed to complete step 1 and 95% of step 2. I decided to leave step 2 for a later PR because there are about 4 tests that fail and, after having dedicated so much time to it, I want to do a pause on this.

Copy link

vercel bot commented Jul 24, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lexical ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 4, 2024 11:11pm
lexical-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 4, 2024 11:11pm

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 24, 2024
Copy link

github-actions bot commented Jul 24, 2024

size-limit report 📦

Path Size
lexical - cjs 29.38 KB (0%)
lexical - esm 29.22 KB (0%)
@lexical/rich-text - cjs 37.87 KB (0%)
@lexical/rich-text - esm 31.08 KB (0%)
@lexical/plain-text - cjs 36.45 KB (0%)
@lexical/plain-text - esm 28.44 KB (0%)
@lexical/react - cjs 39.64 KB (0%)
@lexical/react - esm 32.52 KB (0%)

Comment on lines 76 to 88

isInline(): boolean {
return true;
}

static transform(): (node: LexicalNode) => void {
return (node: LexicalNode) => {
invariant($isOverflowNode(node), 'node is not a OverflowNode');
if (node.isEmpty()) {
node.remove();
}
};
}
Copy link
Contributor Author

@GermanJablo GermanJablo Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's likely that insert/removeText currently has some logic that prevents this. If it works in all cases, I don't know.

Ensuring that an overFlowNode cannot be empty in lexical-overflow is a more robust and less error-prone solution, and it frees up the load on other packages.

Due to this change, it can be understood why the eliminated unit test is no longer necessary.

Comment on lines +717 to +761
// Now that "removeText" has been improved and does not depend on
// insertText, insertText can be greatly simplified. The next
// commented version is a WIP (about 5 tests fail).
//
// this.removeText();
// if (text === '') {
// return;
// }
// const anchorNode = this.anchor.getNode();
// const textNode = $createTextNode(text);
// textNode.setFormat(this.format);
// textNode.setStyle(this.style);
// if ($isTextNode(anchorNode)) {
// const parent = anchorNode.getParentOrThrow();
// if (this.anchor.offset === 0) {
// if (parent.isInline() && !anchorNode.__prev) {
// parent.insertBefore(textNode);
// } else {
// anchorNode.insertBefore(textNode);
// }
// } else if (this.anchor.offset === anchorNode.getTextContentSize()) {
// if (parent.isInline() && !anchorNode.__next) {
// parent.insertAfter(textNode);
// } else {
// anchorNode.insertAfter(textNode);
// }
// } else {
// const [before] = anchorNode.splitText(this.anchor.offset);
// before.insertAfter(textNode);
// }
// } else {
// anchorNode.splice(this.anchor.offset, 0, [textNode]);
// }
// const nodeToSelect = textNode.isAttached() ? textNode : anchorNode;
// nodeToSelect.selectEnd();
// // When composing, we need to adjust the anchor offset so that
// // we correctly replace that right range.
// if (
// textNode.isComposing() &&
// this.anchor.type === 'text' &&
// anchorNode.getTextContent() !== ''
// ) {
// this.anchor.offset -= text.length;
// }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the failing tests are fixed and this implementation is activated, 364 lines of code can be removed ($transferStartingElementPointToTextPoint is only used in the current implementation of insertText).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you expect to add that to this PR, or after this one lands?

@GermanJablo
Copy link
Contributor Author

done @etrepum

Copy link
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use another set of eyes on this one since it's such a fundamental change to removeText

@ivailop7
Copy link
Collaborator

Let's do 0.17.1 first and then do this one as a 0.18 with the defaults change.

@@ -96,6 +96,18 @@ export class CollapsibleTitleNode extends ElementNode {
return true;
}

static transform(): (node: LexicalNode) => void {
Copy link
Collaborator

@ivailop7 ivailop7 Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding this method to this specific node, when deletion of this one here happens on container node-level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not always. If you remove this you will see that some tests fail because there may be empty CollapsibleTitleNodes.
Apparently, it wasn't necessary because it was one of the many things that insertText did.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, let's update those tests and remove the transform part from the node, since when people copy-paste the Playground it would create more confusion, if it's purely there for tests to pass.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both may be necessary for correct behavior, not just tests. The container won't be empty if it has an empty title since it's measured by children and not text, so the empty title is removed first and then the next pass removes the container

Copy link
Contributor Author

@GermanJablo GermanJablo Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's not just about the tests. I don't see anything wrong or confusing here. It's a pattern that other nodes use.

When I said "it wasn't necessary because it was one of the many things that insertText did", I meant that insertText solved this for certain cases, but we don't know what other situations weren't covered. This solution is more robust and secure.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the part that confuses me, the Layout plugin should be in the same boat by that rule, since it's near identical implementation to the Collapsible Plugin, unless it's purely because we are missing tests for it and the transform method should be added to the Layout plugin as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general rule I think it's a good idea to use transforms to make sure that nodes that should be used together can't be orphaned.
I don't know what the layout plugin is but I think improving that would be out of scope of this PR.

PS: I updated the branch and there is a flaky test that failed. Can you please restart it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in general we are missing transforms for a handful of nodes to guarantee correct behavior, e.g. the merge-unmerge issue with TableCellNode not having paragraph children. The static transform method is experimental and new-ish, previously you had to have the correct editor and/or plugin configuration to have all of the required transforms. I would agree that adding other transforms is out of scope for this PR, unless the previous removeText implementation also handled them.

Copy link
Collaborator

@ivailop7 ivailop7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, trusting you both you have this planned out. Haven't spent much time getting the whole context here.

@etrepum etrepum added this pull request to the merge queue Sep 4, 2024
Merged via the queue into facebook:main with commit bd507b9 Sep 4, 2024
38 checks passed
@adrianmxb
Copy link
Contributor

adrianmxb commented Oct 1, 2024

I am a bit late to the party but the changes here break removal of TextNodes in token mode. @ivailop7 @GermanJablo

One example
CleanShot 2024-10-01 at 15 58 03

@etrepum
Copy link
Collaborator

etrepum commented Oct 1, 2024

Do you mind creating a new issue so that this isn't lost?

@GermanJablo
Copy link
Contributor Author

Isn't that supposedly the correct behavior, or am I missing something? Are you sure it worked differently before? There were a lot of those tests I had to pass.

@adrianmxb
Copy link
Contributor

adrianmxb commented Oct 1, 2024

Chose an unfortunate example here, the mention node created in the GIF is in token mode to demo the issue.

Anyway, our users can insert placeholder nodes, which are basically a custom text node in token mode.

According to the docs:

mode
token - acts as immutable node, can't change its content and is deleted all at once

Before these changes, token nodes behaved as described in the documentation.

For segmented mode the behaviour is correct, I updated my initial comment, sorry for that one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants