lexical - Lexical是一个基于 JavaScript 的 Web 文本编辑器框架,具有可扩展、可访问和高性能的特性。

Created at: 2020-12-04 06:53:26
Language: TypeScript
License: MIT

词汇的

GitHub 工作流状态 访问 NPM 页面 将自己添加到我们的不和谐中 在推特上关注我们

⚠️Lexical目前处于早期开发阶段,API和包可能会经常更改。

Lexical是一个可扩展的JavaScript Web文本编辑器框架,强调可靠性,可访问性和性能。Lexical 旨在提供一流的开发人员体验,以便你可以放心地轻松构建原型和构建功能。结合高度可扩展的体系结构,Lexical 允许开发人员创建可扩展大小和功能的独特文本编辑体验。

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

以下是你可以使用 Lexical 执行哪些操作的一些示例:



开始使用 React

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

安装并:

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';

const theme = {
  // Theme styling goes here
  // ...
}

// When the editor changes, you can get notified via the
// LexicalOnChangePlugin!
function onChange(editorState) {
  editorState.read(() => {
    // Read the contents of the EditorState here.
    const root = $getRoot();
    const selection = $getSelection();

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

// Lexical React plugins are React components, which makes them
// highly composable. Furthermore, you can lazy load plugins if
// desired, so you don't pay the cost for plugins until you
// actually use them.
function MyCustomAutoFocusPlugin() {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    // Focus the editor when the effect fires!
    editor.focus();
  }, [editor]);

  return null;
}

// Catch any errors that occur during Lexical updates and log them
// or throw them as needed. If you don't throw them, Lexical will
// try to recover gracefully without losing user data.
function onError(error) {
  console.error(error);
}

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

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <PlainTextPlugin
        contentEditable={<ContentEditable />}
        placeholder={<div>Enter some text...</div>}
      />
      <OnChangePlugin onChange={onChange} />
      <HistoryPlugin />
      <MyCustomAutoFocusPlugin />
    </LexicalComposer>
  );
}

词汇是一个框架

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

编辑器实例

编辑器实例是将所有内容连接在一起的核心内容。你可以将内容可编辑的 DOM 元素附加到编辑器实例,还可以注册侦听器和命令。最重要的是,编辑器允许对其.你可以使用API创建编辑器实例,但是在使用框架绑定时通常不必担心,例如为你处理的。

EditorState
createEditor()
@lexical/react

编辑状态

编辑器状态是基础数据模型,表示要在 DOM 上显示的内容。编辑器状态包含两个部分:

  • 词法节点树
  • 词法选择对象

编辑器状态在创建后是不可变的,为了创建一个编辑器状态,你必须通过 。但是,你也可以使用节点转换或命令处理程序“挂接”到现有更新中 - 这些处理程序作为现有更新工作流的一部分调用,以防止更新的级联/瀑布。你可以使用 检索当前编辑器状态。

editor.update(() => {...})
editor.getEditorState()

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

editor.parseEditorState()

编辑器更新

如果要更改编辑器状态中的某些内容,则必须通过更新 .传递给更新调用的关闭很重要。在这里,你可以拥有活动编辑器状态的完整“词法”上下文,并公开对基础编辑器状态的节点树的访问权限。我们提倡在此上下文中使用前缀函数,因为它表示可以专门使用它们的地方。尝试在更新之外使用它们将触发运行时错误,并显示相应的错误。对于那些熟悉 React Hooks 的人来说,你可以认为这些钩子具有类似的功能(除了函数可以按任何顺序使用)。

editor.update(() => {...})
$
$

DOM Reconciler

Lexical有自己的DOM协调器,它采用一组编辑器状态(始终是“当前”和“待定”),并对它们应用“diff”。然后,它使用此差异仅更新 DOM 中需要更改的部分。你可以把它看作是一种虚拟的DOM,除了Lexical能够跳过做大部分的差异工作,因为它知道在给定的更新中发生了什么变化。DOM 协调器采用性能优化,有利于内容可编辑的典型启发式方法,并能够自动确保 LTR 和 RTL 语言的一致性。

侦听器、节点转换和命令

除了调用更新之外,使用 Lexical 完成的大部分工作是通过侦听器、节点转换和命令完成的。这些都源于编辑器,并以 为前缀。另一个重要功能是,所有 register 方法都返回一个函数来轻松取消订阅它们。例如,以下是你收听词法编辑器更新的方式:

register

const unregisterListener = editor.registerUpdateListener(({editorState}) => {
  // An update has occurred!
  console.log(editorState);
});

// Ensure we remove the listener later!
unregisterListener();

命令是用于在 Lexical 中将所有内容连接在一起的通信系统。可以使用 创建自定义命令,并使用 将 命令分派给编辑器。当触发按键和其他重要信号发生时,Lexical 在内部调度命令。也可以使用 来处理命令,并且传入的命令按优先级通过所有处理程序传播,直到处理程序停止传播(类似于浏览器中的事件传播)。

createCommand()
editor.dispatchCommand(command, payload)
editor.registerCommand(handler, priority)

使用词法

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

创建编辑器并使用它

使用 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)

更新编辑器的最常见方法是使用 。调用此函数需要传入一个函数,该函数将提供更改基础编辑器状态的访问权限。启动全新更新时,将克隆当前编辑器状态并将其用作起点。从技术角度来看,这意味着 Lexical 在更新期间利用了一种称为双缓冲的技术。有一个编辑器状态表示屏幕上的当前内容,另一个正在进行的编辑器状态表示将来的更改。

editor.update()

创建更新通常是一个异步过程,它允许 Lexical 在单个更新中将多个更新一起批处理,从而提高性能。当 Lexical 准备好将更新提交到 DOM 时,更新中的基础突变和更改将形成新的不可变编辑器状态。然后,调用将根据更新中的更改返回最新的编辑器状态。

editor.getEditorState()

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

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

// Inside the `editor.update` you can use special $ prefixed helper functions.
// These functions cannot be used outside the closure, and will error if you try.
// (If you're familiar with React, you can imagine these to be a bit like using a hook
// outside of a React function component).
editor.update(() => {
  // Get the RootNode from the EditorState
  const root = $getRoot();

  // Get the selection from the EditorState
  const selection = $getSelection();

  // Create a new ParagraphNode
  const paragraphNode = $createParagraphNode();

  // Create a new TextNode
  const textNode = $createTextNode('Hello world');

  // Append the text node to the paragraph
  paragraphNode.append(textNode);

  // Finally, append the paragraph to the root
  root.append(paragraphNode);
});

如果你想知道编辑器何时更新,以便对更改做出 React ,则可以向编辑器添加更新侦听器,如下所示:

editor.registerUpdateListener(({editorState}) => {
  // The latest EditorState can be found as `editorState`.
  // To read the contents of the EditorState, use the following API:

  editorState.read(() => {
    // Just like editor.update(), .read() expects a closure where you can use
    // the $ prefixed helper functions.
  });
});

创建自定义词法节点

为词汇做贡献

  1. 克隆此存储库

  2. 安装依赖项

    • npm install
  3. 启动本地服务器并运行测试

    • npm run start
    • npm run test-e2e:chromium
      仅运行铬 e2e 测试
      • 服务器需要运行以进行 e2e 测试

npm run start
将同时启动开发服务器和协作服务器。如果不需要协作,请使用仅启动开发服务器。
npm run dev

可选但建议使用 VSCode 进行开发

  1. 下载并安装 VSCode

  2. 安装扩展

    • 流语言支持
      • 确保按照自述文件中的设置步骤进行操作
    • 漂亮
      • 将 prettier 设置为 默认格式化程序
        editor.defaultFormatter
      • 可选:保存时设置格式
        editor.formatOnSave
    • ESlint

文档

浏览器支持

  • 火狐 52+
  • 铬 49+
  • Edge 79+ (当 Edge 切换到 Chromium 时)
  • 野生动物园 11+
  • iOS 11+ (Safari)
  • iPad OS 13+ (Safari)
  • 安卓浏览器 72+

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

贡献

  1. 创建新分支
    • git checkout -b my-new-branch
  2. 提交更改
    • git commit -a -m 'Description of the changes'
      • 有很多方法可以做到这一点,这只是一个建议
  3. 将你的分支推送到 GitHub
    • git push origin my-new-branch
  4. 转到GitHub中的存储库页面,然后单击“比较和拉取请求”
    • GitHub CLI 允许你跳过此步骤的 Web 界面(以及更多内容)

支持

如果你对Lexical有任何疑问,想讨论错误报告,或对新集成有疑问,请随时将自己添加到我们的Discord服务器

词法工程师会定期检查这一点。

运行测试

  • npm run test-unit
    仅运行单元测试。
  • npm run test-e2e:chromium
    仅运行铬 e2e 测试。
  • npm run debug-test-e2e:chromium
    在磁头模式下仅运行铬 e2e 测试以进行调试。
  • npm run test-e2e: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 许可的