You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:52:04