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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:25:39