facebook / lexical

Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.
https://lexical.dev
MIT License
20.02k stars 1.71k forks source link

Feature: Add a member __editor to LexicalNode #5313

Open yf-yang opened 1 year ago

yf-yang commented 1 year ago

Scenario

I am writing a multi-editor application, where I want to move nodes between editors. In order to move the node, I have to serialize (toJSON) then deserialize (importJSON) from the source editor and then to the destination editor. Within importJSON, I want to inject some contextual data, where the data is editor specific and non-serializable. Then, I find there is no way to inject any contextual data.

Similar request: https://github.com/facebook/lexical/discussions/3338

Some Alternative Solutions

First solution: React.Context alike mechanism

Unfortunately, Lexical is not React. The LexicalNode class is outside of React tree, so no React.Context is available.

Second solution: add an additional parameter to LexicalEditor that stores "contextual data", then make the data available to all the nodes.

It can work, but it involves lots of API changes. LexicalEditor has to add another field, then this field should be passed to all the nodes.

Proposed Solution

I personally come up with a solution that requires minimum framework change. I can create a map, where the editor is the key. Then, I can manage those contextual data myself when initiating the editor. The key problem here is, there is no way to access the editor or something equivalent from the node. A node does belong to an editor, and you can access all nodes of an editor, but there is no way you can access the editor from a node.

Reason 1: A node belongs to an editor, but there is no backlink from the node to the editor. Other use cases may also benefit from this backlink.

That means, you cannot control a node from an editor it does not belong to.

https://github.com/facebook/lexical/blob/27308950cffc9cb2fcf53f670e40e6f9e30d2d02/packages/lexical/src/LexicalUtils.ts#L381-L391

Here, in function $getNodeByKey (which is called by getLatest and getWritable), it checks if the node really belongs to the active editor. Therefore, a node cannot be controlled by the editor it does not belong to.

Reason 2: An editor's lifecycle is always longer than all of its nodes.

When a node exists, the editor of the node will never change. When a node needs to be moved to another editor, there is no way to do so with the original node instance. Serialization/Deserialization (and another new node instance creation) is mandatory.

Proposed Implementation

  1. Add another member __editor to LexicalNode.
  2. Add a line to assign the member with getActiveEditor in constructor.
  3. Add another method getEditor() to LexicalNode. In that way, there is no breaking change.

I am also willing to make a PR.

yf-yang commented 12 months ago

An alternative way would be simply expose getActiveEditor. I am not so sure why Lexical does not expose all the functions in the main package. For example, many functions in LexicalUtils. Is it possible to just export them all?