如何为Lexical扩展ElementNode添加可编辑自定义卡片节点?
实现可编辑的Bootstrap风格自定义卡片节点
问题背景
需要实现一个类似Bootstrap卡片的自定义节点,支持在卡片内部编辑段落、文本等元素。当前实现的CardNode仅能显示空卡片或静态文本,无法进行编辑操作。
解决方案
要让自定义卡片节点支持编辑,核心是让它成为可容纳子节点的容器型节点,并正确处理子节点的序列化、DOM渲染和编辑交互。以下是修正后的完整实现:
1. 完整的CardNode实现
import { ElementNode, $createParagraphNode } from 'lexical'; // 根据你的Lexical版本调整导入 export function $createCardNode() { const cardNode = new CardNode(); // 默认添加空段落,确保卡片初始即可编辑 const paragraph = $createParagraphNode(); cardNode.append(paragraph); return cardNode; } export function $isCardNode(node) { return node instanceof CardNode; } export class CardNode extends ElementNode { static getType() { return 'card'; } static clone(node) { const clonedNode = new CardNode(); // 克隆原节点的所有子节点 node.getChildren().forEach(child => clonedNode.append(child.clone())); return clonedNode; } static importJSON(serializedNode) { const node = $createCardNode(); // 导入子节点 serializedNode.children?.forEach(child => { const importedChild = ElementNode.importJSON(child); if (importedChild) node.append(importedChild); }); return node; } constructor() { super('card'); // 用节点类型作为key更合理 } exportJSON() { return { type: 'card', version: 1, // 序列化子节点 children: this.getChildren().map(child => child.exportJSON()), }; } static importDOM() { return { div: (domNode) => { // 识别带有card类的div元素 if (domNode.classList.contains('card')) { return { node: $createCardNode(), }; } return null; }, }; } exportDOM() { const element = document.createElement('div'); element.classList.add('card', 'p-3', 'border', 'rounded'); // 添加Bootstrap卡片样式类 return { element }; } createDOM() { const element = document.createElement('div'); element.classList.add('card', 'p-3', 'border', 'rounded'); element.contentEditable = 'true'; // 确保容器可编辑 return element; } updateDOM(prevNode, domNode) { // 子节点变化时更新DOM if (prevNode.getChildrenSize() !== this.getChildrenSize()) { return true; } return false; } // 修正装饰器模式下的渲染 decorate() { return ( <div className="card p-3 border rounded"> {this.getChildren().map(child => this.decorateChild(child))} </div> ); } // 允许卡片内插入节点 canInsertBefore() { return true; } canInsertAfter() { return true; } // 标记为容器节点 isParent() { return true; } // 开启编辑权限 isEditable() { return true; } }
2. 更新编辑器配置
确保编辑器配置中包含卡片内需要的可编辑节点(比如段落):
import { ParagraphNode, HeadingNode } from 'lexical'; import { CardNode } from './CardNode'; // 带卡片的初始内容示例 const CONTENT = '{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"children\":[{\"children\":[],\"type\":\"text\",\"text\":\"卡片内可编辑文本\"}],\"type\":\"paragraph\"}],\"type\":\"card\"}],\"type\":\"page\",\"number\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}'; const editorConfig = { editorState: CONTENT, theme: ExampleTheme, onError(error) { throw error; }, nodes: [ HeadingNode, ParagraphNode, // 必须添加,卡片内依赖段落节点实现编辑 CardNode ] };
关键修正点说明
- 支持子节点管理:通过
append、clone、importJSON、exportJSON方法处理子节点的增删改和序列化逻辑 - 开启可编辑权限:设置DOM元素
contentEditable = 'true',并通过isEditable()返回true标记节点可编辑 - Bootstrap样式还原:添加
card、p-3、border、rounded等Bootstrap类,实现卡片视觉效果 - 初始可编辑状态:创建卡片时默认插入空段落,确保用户可以直接输入内容
内容的提问来源于stack exchange,提问作者Coder1234
相关产品推荐
相关产品推荐

