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

如何在React Lexical编辑器中实现类Gmail折叠内容功能?

类Gmail折叠引用块实现方案(React + Lexical)

这个功能通用名称是可折叠引用块(Collapsible Quote Block),以下是具体实现思路和代码示例:

1. 自定义可折叠引用节点

Lexical默认的QuoteNode不支持折叠状态,需要扩展它来添加状态管理:

import { QuoteNode } from '@lexical/rich-text';
import { createCommand, LexicalNode } from 'lexical';

// 定义切换折叠状态的自定义命令
export const TOGGLE_QUOTE_COLLAPSE = createCommand('TOGGLE_QUOTE_COLLAPSE');

export class CollapsibleQuoteNode extends QuoteNode {
  constructor(isCollapsed = false) {
    super();
    this.__isCollapsed = isCollapsed;
  }

  // 克隆节点时保留折叠状态
  clone() {
    return new CollapsibleQuoteNode(this.__isCollapsed);
  }

  // 序列化节点,保存折叠状态到JSON
  exportJSON() {
    return {
      ...super.exportJSON(),
      type: 'collapsible-quote',
      isCollapsed: this.__isCollapsed,
    };
  }

  // 从JSON反序列化节点
  static importJSON(serializedNode) {
    const node = new CollapsibleQuoteNode(serializedNode.isCollapsed);
    node.setFormat(serializedNode.format);
    return node;
  }

  // 获取折叠状态
  isCollapsed() {
    return this.__isCollapsed;
  }

  // 切换折叠状态
  toggleCollapse() {
    this.__isCollapsed = !this.__isCollapsed;
    this.getWritable().setDirty();
  }
}

// 注册节点类型
export function registerCollapsibleQuoteNode(editor) {
  editor.registerNode(CollapsibleQuoteNode, {
    replace: QuoteNode,
    importJSON: CollapsibleQuoteNode.importJSON,
  });
}

2. 实现React渲染组件

为自定义节点编写渲染逻辑,添加折叠触发按钮:

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
import { $getNearestNodeOfType } from 'lexical';
import { CollapsibleQuoteNode, TOGGLE_QUOTE_COLLAPSE } from './CollapsibleQuoteNode';

export default function CollapsibleQuoteNodeComponent({ node }) {
  const [editor] = useLexicalComposerContext();
  const isCollapsed = node.isCollapsed();

  // 处理折叠按钮点击
  const handleToggle = () => {
    editor.dispatchCommand(TOGGLE_QUOTE_COLLAPSE, node.getKey());
  };

  // 监听自定义命令,更新节点状态
  useEffect(() => {
    return editor.registerCommand(
      TOGGLE_QUOTE_COLLAPSE,
      (nodeKey) => {
        editor.update(() => {
          const quoteNode = $getNearestNodeOfType(nodeKey, CollapsibleQuoteNode);
          if (quoteNode) {
            quoteNode.toggleCollapse();
          }
        });
        return true;
      },
      1
    );
  }, [editor]);

  return (
    <div className="collapsible-quote">
      <button 
        onClick={handleToggle} 
        className="quote-toggle-btn"
        aria-label={isCollapsed ? "展开引用" : "折叠引用"}
      >
        {isCollapsed ? "..." : "▼"}
      </button>
      <blockquote 
        className={`quote-content ${isCollapsed ? 'collapsed' : ''}`}
        style={{ display: isCollapsed ? 'none' : 'block' }}
      >
        <slot />
      </blockquote>
    </div>
  );
}

// 注册渲染组件
export function registerCollapsibleQuoteNodeComponent(editor) {
  editor.registerComponent(CollapsibleQuoteNode, CollapsibleQuoteNodeComponent);
}

3. 集成到编辑器初始化流程

在编辑器初始化时注册自定义节点和组件:

import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
import { useLexicalComposerContext, useEffect } from 'react';
import { registerCollapsibleQuoteNode, registerCollapsibleQuoteNodeComponent } from './CollapsibleQuote';

const initialConfig = {
  namespace: 'email-editor',
  nodes: [CollapsibleQuoteNode],
  onError: (error) => console.error(error),
};

function EmailEditor() {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    registerCollapsibleQuoteNode(editor);
    registerCollapsibleQuoteNodeComponent(editor);
  }, [editor]);

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <RichTextPlugin
        contentEditable={<ContentEditable />}
        placeholder={<div>写邮件...</div>}
      />
      <HistoryPlugin />
      {/* 其他插件 */}
    </LexicalComposer>
  );
}

4. 回复时插入可折叠引用块

点击回复按钮时,创建CollapsibleQuoteNode并插入到编辑器(默认设为折叠状态):

function handleReply(originalEmail) {
  const editor = getEditor(); // 获取编辑器实例
  editor.update(() => {
    // 创建引用内容节点
    const quoteContent = $createParagraphNode();
    quoteContent.append($createTextNode(`From: ${originalEmail.sender}\nDate: ${originalEmail.date}\n\n${originalEmail.content}`));
    
    // 创建可折叠引用节点,默认折叠
    const collapsibleQuote = new CollapsibleQuoteNode(true);
    collapsibleQuote.append(quoteContent);
    
    // 插入到编辑器开头
    const root = $getRoot();
    root.prepend(collapsibleQuote);
  });
}

样式参考

添加CSS美化折叠按钮和引用块:

.collapsible-quote {
  position: relative;
  margin: 12px 0;
}

.quote-toggle-btn {
  position: absolute;
  left: -20px;
  top: 4px;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 16px;
  color: #666;
}

.quote-content {
  margin-left: 20px;
  padding-left: 12px;
  border-left: 3px solid #ccc;
  color: #555;
  font-style: normal;
}

内容的提问来源于stack exchange,提问作者hashi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 19:52:49