-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Decoupling plugin and nodes #1262
Description
Problem
Plugins and nodes are tightly coupled and it makes it harder to extend default nodes and maintaining existing plugins functionality.
Example
Let's take markdown plugin (copy/paste logic is also a good example), it exports bunch of node creation helpers ($createHeadingNode, $createCodeBlockNode, $createListNode, etc).
Then if I want to extend HeadingNode behaviour, for example to add id attribute to each heading so it can be used as an anchor (smth like <h1 id="getting-started-with-react">Getting started with React</h1>, see our README doing it). To do so I'd extend default node and add new behaviour on top:
class AnchorHeadingNode extends HeadingNode {
static getType() {
return 'heading';
}
createDOM<EditorContext>(config: EditorConfig<EditorContext>): HTMLElement {
const element = super.createDOM(config);
element.setAttribute('id', getAnchorID(this));
return element;
}
updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean {
dom.setAttribute('id', getAnchorID(this));
return false;
}
}But now the problem is that all plugins that might insert HeadingNode (markdown, toolbar, copy/paste), they all keep using default HeadingNode, because they all use $createHeadingNode imported from default heading node file.
Potential solution:
Replace $createHeadingNode() implementation from
function $createHeadingNode(tag): HeadingNode {
return new HeadingNode(tag);
}to
function $createHeadingNode(tag): HeadingNode {
const NodeConstructor = $getNodeFromRegistry(HeadingNode);
return new NodeConstructor(tag);
}
...
// and smth like:
function $getNodeFromRegistry<T>(nodeKlass: T): T {
return $getActiveEditor()._registeredNodes.get(nodeKlass.getType());
}This will allow passing AnchorHeadingNode into LexicalComposer, and since it has the same type ('heading'), it'll be used whenever $createHeadingNode is used, so all callsites that create heading will use our extended heading node.