Project Icon

lexical

高度可扩展的JavaScript网页文本编辑框架

Lexical是一个专注于可靠性、可访问性和性能的JavaScript网页文本编辑框架。它为开发者提供优秀的开发体验,便于创建独特的文本编辑功能。Lexical采用高度可扩展的架构,支持简单到复杂的多种编辑需求,可根据项目进行定制。这个框架适用于不同规模的文本编辑项目开发。

Lexical

GitHub工作流状态 访问NPM页面 加入我们的Discord 在Twitter上关注我们

Lexical是一个可扩展的JavaScript网页文本编辑器框架,注重可靠性、可访问性和性能。Lexical旨在提供一流的开发者体验,让您能够轻松地进行原型设计和功能构建。结合高度可扩展的架构,Lexical允许开发者创建独特的、可扩展的文本编辑体验。

有关Lexical的文档和更多信息,请务必访问Lexical网站

以下是一些使用Lexical可以实现的例子:


概述:


React入门

注意:Lexical不仅限于React。只要为特定库创建了绑定,Lexical就可以支持任何基于DOM的底层库。

安装lexical@lexical/react

npm install --save lexical @lexical/react

以下是使用lexical@lexical/react的基本纯文本编辑器示例(自己试试)。

import {$getRoot, $getSelection} from 'lexical';
import {useEffect} from 'react';

import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';

const theme = {
  // 主题样式在这里
  // ...
}

// 当编辑器发生变化时,您可以通过LexicalOnChangePlugin获得通知!
function onChange(editorState) {
  editorState.read(() => {
    // 在这里读取EditorState的内容。
    const root = $getRoot();
    const selection = $getSelection();

    console.log(root, selection);
  });
}

// Lexical React插件是React组件,这使它们具有高度的可组合性。
// 此外,您可以按需懒加载插件,这样在实际使用之前不会产生插件的开销。
function MyCustomAutoFocusPlugin() {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    // 当effect触发时,聚焦编辑器!
    editor.focus();
  }, [editor]);

  return null;
}

// 捕获Lexical更新期间发生的任何错误并记录它们
// 或根据需要抛出它们。如果不抛出,Lexical将
// 尝试优雅地恢复而不丢失用户数据。
function onError(error) {
  console.error(error);
}

function Editor() {
  const initialConfig = {
    namespace: 'MyEditor',
    theme,
    onError,
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <PlainTextPlugin
        contentEditable={<ContentEditable />}
        placeholder={<div>输入一些文本...</div>}
        ErrorBoundary={LexicalErrorBoundary}
      />
      <OnChangePlugin onChange={onChange} />
      <HistoryPlugin />
      <MyCustomAutoFocusPlugin />
    </LexicalComposer>
  );
}

Lexical是一个框架

Lexical的核心是一个无依赖的文本编辑器框架,允许开发者构建强大的、简单和复杂的编辑器界面。Lexical有一些值得探索的概念:

编辑器实例

编辑器实例是将所有内容连接在一起的核心。您可以将可编辑的DOM元素附加到编辑器实例,并注册监听器和命令。最重要的是,编辑器允许更新其EditorState。您可以使用createEditor() API创建一个编辑器实例,但在使用如@lexical/react这样的框架绑定时通常不需要担心这一点,因为这已经为您处理好了。

编辑器状态

编辑器状态是表示您想在DOM上显示的内容的底层数据模型。编辑器状态包含两个部分:

  • Lexical节点树
  • Lexical选择对象

一旦创建,编辑器状态是不可变的,要创建一个编辑器状态,您必须通过editor.update(() => {...})来完成。但是,您也可以使用节点转换或命令处理程序"挂钩"到现有的更新中 - 这些在现有的更新工作流程中调用,以防止更新的级联/瀑布效应。您可以使用editor.getEditorState()检索当前的编辑器状态。

编辑器状态也可以完全序列化为JSON,并且可以使用editor.parseEditorState()轻松地反序列化回编辑器。

读取和更新编辑器状态

当你想要读取和/或更新词法节点树时,必须通过 editor.update(() => {...}) 来执行。你也可以通过 editor.read(() => {...})editor.getEditorState().read(() => {...}) 对编辑器状态进行只读操作。

传递给 update 或 read 调用的闭包很重要,且必须是同步的。这是你拥有完整"词法"上下文的活动编辑器状态的唯一地方,并为你提供访问编辑器状态节点树的权限。我们提倡使用带有 $ 前缀的函数(如 $getRoot())的约定,以表明这些函数必须在此上下文中调用。在 read 或 update 之外尝试使用它们将触发运行时错误。

对于熟悉 React Hooks 的人来说,你可以将这些 $函数视为具有类似功能:

特性React HooksLexical $函数
命名约定useFunction$function
所需上下文只能在渲染时调用只能在 update 或 read 中调用
可组合Hooks 可以调用其他 hooks$函数可以调用其他 $函数
必须是同步的
其他规则❌ 必须无条件地以相同顺序调用✅ 无

节点转换和命令监听器是在隐式的 editor.update(() => {...}) 上下文中调用的。

允许进行嵌套更新或嵌套读取,但更新不应嵌套在读取中,反之亦然。例如,editor.update(() => editor.update(() => {...})) 是允许的。允许在 editor.update 的末尾嵌套 editor.read,但这会立即刷新更新,并且在该回调中的任何额外更新都会抛出错误。

所有 Lexical 节点都依赖于相关的编辑器状态。除了少数例外,你应该只在 read 或 update 调用中调用 Lexical 节点的方法和访问属性(就像 $ 函数一样)。Lexical 节点上的方法首先会尝试使用节点的唯一键从活动编辑器状态中定位最新(可能是可写的)版本的节点。一个逻辑节点的所有版本都有相同的键。这些键由编辑器管理,仅在运行时存在(不序列化),应被视为随机和不透明的(不要编写假设键有硬编码值的测试)。

这样做是因为编辑器状态的节点树在协调后会被递归冻结,以支持高效的时间旅行(撤销/重做和类似用例)。更新节点的方法首先调用 node.getWritable(),这将创建一个冻结节点的可写克隆。这通常意味着任何现有引用(如局部变量)都会引用节点的过时版本,但让 Lexical 节点始终引用编辑器状态可以实现更简单、更不容易出错的数据模型。

:::提示

如果你使用 editor.read(() => { /* 回调 */ }),它会首先刷新任何待处理的更新,因此你总会看到一个一致的状态。当你在 editor.update 中时,你总是在处理待处理的状态,其中节点转换和 DOM 协调可能还没有运行。editor.getEditorState().read() 将使用最新协调的 EditorState(在任何节点转换、DOM 协调等已经运行之后),任何待处理的 editor.update 变更尚未可见。

:::

DOM 协调器

Lexical 有自己的 DOM 协调器,它接收一组编辑器状态(总是"当前"和"待处理"的),并对它们应用"差异"。然后它使用这个差异来只更新 DOM 中需要改变的部分。你可以将此视为一种虚拟 DOM,但 Lexical 能够跳过大部分差异比较工作,因为它知道在给定的更新中发生了什么变化。DOM 协调器采用了有利于内容可编辑典型启发式的性能优化,并能够自动确保 LTR 和 RTL 语言的一致性。

监听器、节点转换和命令

除了调用更新外,使用 Lexical 的大部分工作是通过监听器、节点转换和命令完成的。这些都源于编辑器,并以 register 为前缀。另一个重要特性是所有注册方法都返回一个函数,可以轻松取消订阅。例如,这里展示了如何监听 Lexical 编辑器的更新:

const unregisterListener = editor.registerUpdateListener(({editorState}) => {
  // 发生了更新!
  console.log(editorState);
});

// 确保稍后移除监听器!
unregisterListener();

命令是用于在 Lexical 中连接所有内容的通信系统。可以使用 createCommand() 创建自定义命令,并使用 editor.dispatchCommand(command, payload) 将其分派给编辑器。当触发按键和其他重要信号时,Lexical 内部会分派命令。命令也可以使用 editor.registerCommand(handler, priority) 进行处理,传入的命令会按优先级通过所有处理程序传播,直到某个处理程序停止传播(类似于浏览器中的事件传播)。

使用 Lexical

本节介绍如何独立于任何框架或库使用 Lexical。对于打算在 React 应用程序中使用 Lexical 的人来说,建议查看 @lexical/react 中提供的 hooks 的源代码

创建编辑器并使用它

使用 Lexical 时,通常使用单个编辑器实例。编辑器实例可以被视为负责将 EditorState 与 DOM 连接起来的对象。编辑器也是你可以注册自定义节点、添加监听器和转换的地方。

可以从 lexical 包创建编辑器实例,它接受一个可选的配置对象,允许主题设置和其他选项:

import {createEditor} from 'lexical';

const config = {
  namespace: 'MyEditor',
  theme: {
    ...
  },
};

const editor = createEditor(config);

一旦你有了编辑器实例,准备就绪后,你可以将编辑器实例与文档中的内容可编辑 <div> 元素关联:

const contentEditableElement = document.getElementById('editor');

editor.setRootElement(contentEditableElement);

如果你想清除编辑器实例与元素的关联,可以传入 null。或者,如果需要,你可以切换到另一个元素,只需将另一个元素引用传递给 setRootElement()

使用编辑器状态

在 Lexical 中,真实来源不是 DOM,而是 Lexical 维护并与编辑器实例相关联的底层状态模型。你可以通过调用 editor.getEditorState() 从编辑器获取最新的编辑器状态。 编辑器状态可序列化为 JSON,编辑器实例提供了一个有用的方法来反序列化字符串化的编辑器状态。

const stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());

const newEditorState = editor.parseEditorState(stringifiedEditorState);

更新编辑器

有几种方法可以更新编辑器实例:

  • 使用 editor.update() 触发更新
  • 通过 editor.setEditorState() 设置编辑器状态
  • 通过 editor.registerNodeTransform() 在现有更新中应用更改
  • 使用 editor.registerCommand(EXAMPLE_COMMAND, () => {...}, priority) 的命令监听器

更新编辑器最常见的方式是使用 editor.update()。调用此函数需要传入一个函数,该函数将提供访问权限以修改底层编辑器状态。当开始一个新的更新时,当前编辑器状态会被克隆并用作起点。从技术角度来看,这意味着 Lexical 在更新期间利用了一种称为双缓冲的技术。有一个编辑器状态代表屏幕上当前显示的内容,另一个正在进行中的编辑器状态代表未来的更改。

协调更新通常是一个异步过程,允许 Lexical 将编辑器状态的多个同步更新批量处理为对 DOM 的单次更新 - 从而提高性能。当 Lexical 准备将更新提交到 DOM 时,更新批次中的底层变更和更改将形成一个新的不可变编辑器状态。然后调用 editor.getEditorState() 将返回基于更新中的更改的最新编辑器状态。

以下是如何更新编辑器实例的示例:

import {$getRoot, $getSelection, $createParagraphNode} from 'lexical';

// 在 `editor.update` 内部,你可以使用特殊的 $ 前缀辅助函数。
// 这些函数不能在闭包之外使用,如果尝试使用会报错。
// (如果你熟悉 React,你可以想象这些有点像在 React 函数组件之外使用 hook)。
editor.update(() => {
  // 从 EditorState 获取 RootNode
  const root = $getRoot();

  // 从 EditorState 获取选择
  const selection = $getSelection();

  // 创建一个新的 ParagraphNode
  const paragraphNode = $createParagraphNode();

  // 创建一个新的 TextNode
  const textNode = $createTextNode('Hello world');

  // 将文本节点附加到段落
  paragraphNode.append(textNode);

  // 最后,将段落附加到根节点
  root.append(paragraphNode);
});

如果你想知道编辑器何时更新以便对更改做出反应,你可以向编辑器添加一个更新监听器,如下所示:

editor.registerUpdateListener(({editorState}) => {
  // 最新的 EditorState 可以在 `editorState` 中找到。
  // 要读取 EditorState 的内容,请使用以下 API:

  editorState.read(() => {
    // 就像 editor.update(),.read() 期望一个闭包,
    // 你可以在其中使用 $ 前缀的辅助函数。
  });
});

为 Lexical 做贡献

请阅读 CONTRIBUTING.md

可选但推荐,使用 VSCode 进行开发

  1. 下载并安装 VSCode

    • 这里下载(建议使用未修改版本)
  2. 安装扩展

    • Flow Language Support
      • 请确保按照 README 中的步骤进行设置
    • Prettier
      • editor.defaultFormatter 中将 prettier 设置为默认格式化程序
      • 可选:设置保存时格式化 editor.formatOnSave
    • ESlint

文档

浏览器支持

  • Firefox 52+
  • Chrome 49+
  • Edge 79+(当 Edge 切换到 Chromium 时)
  • Safari 11+
  • iOS 11+(Safari)
  • iPad OS 13+(Safari)
  • Android Chrome 72+

注意:Lexical 不支持 Internet Explorer 或旧版 Edge。

贡献

  1. 创建一个新分支
    • git checkout -b my-new-branch
  2. 提交你的更改
    • git commit -a -m '更改描述'
      • 有很多方法可以做到这一点,这只是一个建议
  3. 将你的分支推送到 GitHub
    • git push origin my-new-branch
  4. 转到 GitHub 中的仓库页面,点击"Compare & pull request"
    • GitHub CLI 允许你跳过这一步的网页界面(以及更多操作)

支持

如果你有任何关于 Lexical 的问题,想讨论 bug 报告,或对新集成有疑问,欢迎加入我们的 Discord 服务器

Lexical 工程师会定期查看这里。

运行测试

  • npm run test-unit 仅运行单元测试。
  • npm run test-e2e-chromium 仅运行 chromium e2e 测试。
  • npm run debug-test-e2e-chromium 以调试模式运行 chromium e2e 测试。
  • npm run test-e2e-firefox 仅运行 firefox e2e 测试。
  • npm run debug-test-e2e-firefox 以调试模式运行 firefox e2e 测试。
  • npm run test-e2e-webkit 仅运行 webkit e2e 测试。
  • npm run debug-test-e2e-webkit 以调试模式运行 webkit e2e 测试。

许可证

Lexical 采用 MIT 许可证

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号