-
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
LexicalBlockNormalizer #6025
base: main
Are you sure you want to change the base?
LexicalBlockNormalizer #6025
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
size-limit report 📦
|
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.
The failures might be related to the Rollup/Vite merge. LGTM otherwise. Thanks for this!
Hey @etrepum, do you have any idea what's causing the integrity issues above? I added a new file inside @lexical/react and run
|
@zurfyx in my experience those sort of errors are caused by a bad cache, maybe we have something that is populating the cache incorrectly somewhere? I think maybe the solution to this is to change our workflows to use explicit save and restore points so we don't end up populating the cache with weird stuff. I'll do a PR for that today. |
return; | ||
} | ||
|
||
invariant( |
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.
Sort of minor, but do we need to run this check on every transform? can we just check in the render function? Maybe in an effect?
I thought I replied, but either I didn't submit or I commented on the wrong PR. Even after the merged CI fix, I reran the tests here and they still fail. 😕 |
const emptyFunction = () => {}; | ||
|
||
/** | ||
* Ensures that block nodes live at the very top of the tree. |
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 think we need much better documentation here, especially now that we're getting more caught up on docs and organization.
I understand what this does, but I don't think we've clearly communicated who should use it and when, what practical problems it solves, etc.
I think one thing we need at least, in addition to this, is a list of constraints or rules that we plan to enforce, like Slate:
https://docs.slatejs.org/concepts/11-normalizing#built-in-constraints
We actually share a lot of these, but I don't think a magic plugin is a great solution long-term without a clear description of what it's doing and why.
I don't think you've ever adequately explained your objection here - we should discuss this more, as there's a fundamental directional choice here between node-centric APIs, editor-centric APIs, and a combination of both. |
@ivailop7 The CI fix was merged to main, but main hasn't been merged into this PR |
You are right, my fault. |
@acywatson I personally think the static node-centric transform (or some new API that like a transform) makes sense in cases where you need to enforce some ElementNode invariants or constraints related to children or parents when a node is dirty. The other benefit is that you get this normalization in subclasses whereas otherwise you have some work to do (and maybe you can't actually do it without a mess of copy paste because the plugin didn't export the transform function for you) if you extend a node that has these kinds of constraints. |
Yea, I'm inclined to agree - recalling past conversations, I think the principled basis for Gerard's disagreement with this approach is that we should somehow limit the number of ways that one can do something in Lexical, in order to just simplify the developer experience. I personally think that there seem to be use cases where each of these different approaches is more ergonomic, and providing the flexibility in those cases outweighs any confusion that might arise from having two ways to declare a transform. There's a similar thing with importDOM/exportDOM - the node-centric approach is pretty great, but makes certain common cases really awkward (like overriding text node import/export to add more styles). |
It's definitely worth considering if there's a more general, but still accessible, way to achieve these sorts of features. Possibly something worth considering would be to have a more general static method when a node is attached to an editor and get rid of the static transform, e.g.: class WhoNeedsAPluginNode extends LexicalNode {
static registerWithEditor(editor: LexicalEditor): void {
// I don't think it makes any sense to return the dispose functions b/c you can't unregister a node
editor.registerNodeTransform(this, doStaticTransform);
editor.registerCommand(…);
}
} function createEditor(…) {
const editor = new LexicalEditor(…);
for (const klass of registeredNodes.values()) {
if (klass.registerWithEditor !== LexicalNode.registerWithEditor) {
klass.registerWithEditor(editor);
}
}
} I think it would be fine as long as this was sequenced after the editor was created and before the non-empty initialState is attached? |
This is become a larger discussion that is larger than this PR, however, let's go ahead with this for now. Since it's proven and it works. @zurfyx can we go forward with this one. |
I copy what I said in #6066:
The only difference I see between this PR and the SchemaPlugin that were shown in the last 3 comments of #3833 is that a generic normalization can be offered for all blocks, regardless of which and how many they are, and whether the user knows about them or no. I'm not sure if I like that something so important and opinionated is done implicitly by the library. I think a |
return; | ||
} | ||
|
||
const event = lastPasteCommand + 250 > Date.now() ? 'paste' : undefined; |
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.
what is the 250 for? isit 250 ms?
FYI ported this over internally to test and it broke the collapsible sections. Haven't investigated why yet. |
LexicalBlockNormalizer (aka NestingEnforcementPlugin)
This was probably our first ever formal normalizer after some patches in the HTML
importDOM
functions (it has been internal only for a while). The idea behind this normalizer is to make sure that non-inline elements are always at the very top of the tree.Invalid states can happen anytime and there is no mechanism to enforce them. The idea behind Schemas #3833 is still not very appealing for the fact that we trade-off state consistency for reliability which, from a user perspective, is still bad reliablity. The idea of normalization seems to have gotten more traction over the past years, when we opened an API to do static transforms and started standalone normalizers (i.e. table #5824). FWIW, I'm not a fan of static transforms but the point still stands.
While this plugin won't be the final shape of normalizers, this brings us a step closer to having a formal API to define plugins, which can standardize normalizers.
Credit for the implementation goes to @fantactuka, I just ported the code and adjusted the API.
Note for reviewers: the best test plan I can offer for this is the unit test itself. The 11 test cases will walk you through what this plugin can normalize.