如何在Lexical编辑器中保留文本节点的class属性?
解决Lexical编辑器导入/导出HTML时保留节点class属性的问题
核心需求是在通过$generateHtmlFromNodes导入HTML时,保留h4等文本节点的class属性,并确保这些属性在编辑器中持久化,导出HTML时能完整保留。以下是修正后的ClassNode实现,解决原代码中的关键问题:
import { TextNode, EditorConfig, LexicalEditor, DOMExportOutput, DOMConversionMap, DOMConversionOutput, NodeKey, SerializedTextNode, } from "lexical" // 定义序列化后的节点结构,用于持久化存储 type SerializedClassNode = SerializedTextNode & { tagName: string; className: string; type: "class-node"; } export class ClassNode extends TextNode { private tagName: string; private __class: string; constructor(tagName: string, classAttribute: string, text?: string, key?: NodeKey) { super(text || "", key); this.tagName = tagName; this.__class = classAttribute; } static getType(): string { return "class-node"; } static clone(node: ClassNode): ClassNode { return new ClassNode(node.tagName, node.__class, node.__text, node.__key); } // 序列化节点,把tagName和class存入JSON,确保持久化不丢失 toJSON(): SerializedClassNode { return { ...super.toJSON(), tagName: this.tagName, className: this.__class, type: "class-node", version: 1, }; } // 从JSON反序列化节点,恢复tagName和class属性 static importJSON(serializedNode: SerializedClassNode): ClassNode { const node = new ClassNode( serializedNode.tagName, serializedNode.className, serializedNode.text, ); node.setFormat(serializedNode.format); node.setDetail(serializedNode.detail); node.setMode(serializedNode.mode); node.setStyle(serializedNode.style); return node; } static convertClassNodeElement(domNode: HTMLElement): DOMConversionOutput | null { const className = domNode.className.trim(); if (!className) return null; // 提取DOM节点的文本内容,避免导入后文本丢失 const textContent = domNode.textContent || ""; // 使用DOM节点的实际标签名,不再硬编码h4 return { node: new ClassNode(domNode.tagName.toLowerCase(), className, textContent) }; } createDOM(config: EditorConfig): HTMLElement { const element = document.createElement(this.tagName); element.className = this.__class; // 继承TextNode的样式和格式设置 this.applyDOMProperties(element); return element; } updateDOM(prevNode: ClassNode, domNode: HTMLElement): boolean { // 当标签名或class属性变化时,需要重建DOM if (prevNode.tagName !== this.tagName || prevNode.__class !== this.__class) { return true; } // 调用父类方法处理文本和格式的变化 return super.updateDOM(prevNode, domNode); } static importDOM(): DOMConversionMap | null { // 支持多个需要保留class的标签,可根据业务需求扩展 const supportedTags = ["h1", "h2", "h3", "h4", "h5", "h6", "p", "span"]; const conversionMap: DOMConversionMap = {}; supportedTags.forEach(tag => { conversionMap[tag] = (domNode: HTMLElement) => { return { conversion: ClassNode.convertClassNodeElement, priority: 4, // 优先级高于默认节点(默认通常为1-3),确保优先使用自定义转换 }; }; }); return conversionMap; } exportDOM(editor: LexicalEditor): DOMExportOutput { const element = document.createElement(this.tagName); element.className = this.__class; // 调用父类的exportDOM获取文本元素,确保文本内容正确导出 const { element: textElement } = super.exportDOM(editor); if (textElement) { element.appendChild(textElement); } return { element }; } // 实现节点合并逻辑:相邻同类型、同标签、同class的节点自动合并,符合Lexical编辑行为 mergeWith(node: TextNode): boolean { if (node instanceof ClassNode && node.tagName === this.tagName && node.__class === this.__class) { this.append(node.__text); return true; } return false; } }
关键修改说明:
- 持久化支持:新增
toJSON和importJSON方法,把tagName和class属性存入序列化数据,确保编辑器重启或刷新后属性不丢失。 - 动态标签处理:转换函数不再硬编码h4,而是读取DOM节点的实际标签名,支持多种文本类标签。
- 文本内容同步:转换DOM节点时同步提取文本内容,避免导入后节点为空。
- DOM更新优化:
updateDOM方法对比标签名和class属性,仅在必要时重建DOM,同时保留父类的文本和格式更新逻辑。 - 多标签支持:
importDOM中扩展支持h1-h6、p、span等常用标签,可按需增减。 - 节点合并逻辑:实现
mergeWith方法,让相邻同属性的节点自动合并,符合编辑器的自然编辑体验。 - 正确导出HTML:调用父类的导出逻辑处理文本,确保导出的HTML包含完整的文本内容和class属性。
使用注意:
需要将ClassNode注册到编辑器的nodes配置中,才能生效:
const editorConfig = { nodes: [ClassNode], // 其他编辑器配置... };
内容的提问来源于stack exchange,提问作者user26587660
相关产品推荐
相关产品推荐

