Slate文档编辑器-TS类型扩展与节点类型检查

在之前我们基于slate实现的文档编辑器探讨了WrapNode数据结构与操作变换,主要是对于嵌套类型的数据结构类型需要关注的Normalize与Transformers,那么接下来我们更专注于文档编辑器的数据结构设计,聊聊基于slate实现的文档编辑器类型系统。

关于slate文档编辑器项目的相关文章:

TS类型扩展

当我们使用TS引入slate时,可能会发现调用createEditor实例化编辑器时,并没有提供范型的定义类型传入,那么这是不是意味着slate无法在TS中定义类型,而是只能在处理属性时采用as的形式强制类型断言。那么作为成熟的编辑器引擎显然是不能这样的,对于富文本编辑器来说,能够在TS中定义类型是非常重要的,我们可以将富文本理解为带着属性的文本,如果属性的定义不明确,那么维护起来可能会变得越来越困难,而本身维护富文本就充斥着各种问题,所以维护类型的定义是很有必要的。

在研究slate的类型扩展之前,我们需要先看看TypeScript提供的declare module以及interface声明,我们可能经常会遇到对type和interface定义的接口类型的区别,实际上除了type可以定义联合类型以外,interface和type还有一个比较重要的区别就是interface可以被合并,也就是可以将定义的接口进行扩展。而type在同一个模块内是无法重新定义类型声明的。

interface A { a: string }
interface A { b: number }
const a: A = { a: "", b: 1 } type B = { a: string }
// type B = { b: number }
const b = { a: "" }

当我们初步了解时可能会觉得interface和type的区别不大几乎可以平替,但是在实际应用中interface的合并特性是非常重要的,特别是在declare module的场景下,我们可以用于为模块扩展声明类型,也就是为已经有类型定义的库增加额外类型定义。

declare module "some-library" {
export interface A {
a: number;
}
export function fn(): void;
}

那么在这里我们就可以通过declare module + interface的合并特性来扩展模块的类型定义了,这也就是slate实际上定义的类扩展方式,我们可以通过为slate模块扩展类型的方式,来注入我们需要的基本类型,而我们的基本类型都是使用interface关键字定义的,这样就表示我们的类型是可以不断通过declare module的方式进行扩展的。还有一点需要特别关注,当我们实现了declare module的类型扩展时,我们就可以按需加载类型的扩展,也就是说只有当实际引用到我们定义好的类型时,才会加载对应的类型声明,做到按需扩展类型。

// packages/delta/src/interface.ts
import type { BaseEditor } from "slate"; declare module "slate" {
interface CustomTypes {
Editor: BaseEditor;
Element: BlockElement;
Text: TextElement;
}
} export interface BlockElement {
text?: never;
children: BaseNode[];
[key: string]: unknown;
} export interface TextElement {
text: string;
children?: never;
[key: string]: unknown;
}

实际上我是非常推荐将类型定义独立抽离出单独的文件来定义的,否则当我们在IDE中使用跳转到类型定义的功能查看完整slate的类型文件时,发现其跳转的位置是我们上述定义的文件位置,同理当我们将slate及其相关模块独立抽离为单独的包时,会发现类型的跳转变得并不那么方便,即使我们的目标不是扩展类型的定义也会被跳转到我们declare module的文件位置。因此当我们将其抽离出来之后,声明文件单独作为独立的模块处理,这样就不会存在定位问题了,例如下面就是我们声明代码块格式的类型定义。

// packages/plugin/src/codeblock/types/index.ts
declare module "doc-editor-delta/dist/interface" {
interface BlockElement {
[CODE_BLOCK_KEY]?: boolean;
[CODE_BLOCK_CONFIG]?: { language: string };
}
interface TextElement {
[CODE_BLOCK_TYPE]?: string;
}
} export const CODE_BLOCK_KEY = "code-block";
export const CODE_BLOCK_TYPE = "code-block-type";
export const CODE_BLOCK_CONFIG = "code-block-config";

节点类型检查

那么在TS方面我们将类型扩展好之后,我们还需要关注实际的节点类型判断,毕竟TS只是帮我们实现了静态类型检查,在实际编译为Js运行在浏览器中时,通常是不会有实际额外代码注入的,而在我们平常使用的过程中是明显需要这些类型的判断的,例如通常我们需要检查当前的选区节点是否正确位于Text节点上,以便对用户的操作做出对应的响应。

那么我们就可以梳理一下在slate当中的节点类型,实际上根据slate导出的CustomTypes我们可以确定最基本的类型就只有Element和Text,而我更习惯将其重命名为BlockElement和TextElement。而实际上由于我们的业务复杂性,我们通常还需要扩展出InlineElement和TextBlockElement两个类型。

那么我们首先来看BlockElement,这是slate中定义的基本块元素,例如我们的代码块最外层的嵌套结构需要是BlockElement,而实际上由于可能存在的复杂嵌套结构,例如表格中嵌套分栏结构,分栏结构中继续嵌套代码块结构等,所以我们可以将其理解为BlockElement是嵌套所需的结构即可,而判断节点类型是否是BlockElement的方式则可以直接调度Editor.isBlock方法即可。

export interface BlockElement {
text?: never;
children: BaseNode[];
[key: string]: unknown;
} export const isBlock = (editor: Editor, node: Node | null): node is BlockElement => {
if (!node) return false;
return Editor.isBlock(editor, node);
}; // https://github.com/ianstormtaylor/slate/blob/25be3b/packages/slate/src/interfaces/editor.ts#L590
export const Editor = {
isBlock(editor: Editor, value: any): value is Element {
return Element.isElement(value) && !editor.isInline(value)
}
}

TextElement被定义为文本节点,需要注意的是这里同样也是可以加入属性的,在slate中被称为marker,无论什么节点类型的嵌套,数据结构即树形结构的叶子节点一定需要是文本节点,而针对于TextElement的判断则同样可以调度Text上的isText方法。

export interface TextElement {
text: string;
children?: never;
[key: string]: unknown;
} export const isText = (node: Node): node is TextElement => Text.isText(node); // https://github.com/ianstormtaylor/slate/blob/25be3b/packages/slate/src/interfaces/text.ts#L53
export const Text = {
isText(value: any): value is Text {
return isPlainObject(value) && typeof value.text === 'string'
},
}

如果仔细看slate判断BlockElement的条件,我们可以发现除了调用Element.isElement方法外,还判断了editor.isInline方法,由于slate中InlineElement是一种特殊的BlockElement,我们同样需要为其独立实现类型判断,而判断InlineElement的方式则需要由editor.isInline方法来实现,这部分实际上是由我们在with的时候需要定义好的。

export interface InlineElement {
text?: never;
children: TextElement[];
[key: string]: unknown;
} export const isInline = (editor: Editor, node:Node): node is InlineElement => {
return editor.isInline(node);
}

在业务中通常我们还需要判断文本段落实际渲染的节点TextBlockElement,这个判断同样非常重要,因为这里决定了当前的节点是我们实际要渲染的段落了,这在数据转换等场景中非常有用,当我们需要二次解析数据的时候,当遇到文本段落时,我们就可以确定当前的块级结构解析可以结束了,接下来可以构建段落的内容组合了。而由于段落中可能存在TextElement和InlineElement两种节点,我们的判断就需要将两种情况都考虑到,在规范的条件下我们通常只需要判断首个节点是否符合条件即可,而在开发模式下我们可以尝试对比所有节点的结构来判断并且校验脏数据。

export interface TextBlockElement {
text?: never;
children: (TextElement | InlineElement)[];
[key: string]: unknown;
} export const isTextBlock = (editor: Editor, node: Node): node is TextBlockElement => {
if (!isBlock(editor, node)) return false;
const firstNode = node.children[0];
const result = firstNode && (isText(firstNode) || isInline(editor, firstNode));
if (process.env.NODE_ENV === "development") {
const strictInspection = node.children.every(child => isText(firstNode) || isInline(editor, firstNode));
if (result !== strictInspection) {
console.error("Fatal Error: Text Block Check Fail", node);
}
}
return result;
};

最后

在这里我们更专注于文档编辑器的数据结构设计,聊聊基于slate实现的文档编辑器类型系统。在slate中还有很多额外的概念和操作需要关注,例如Range、Operation、Editor、Element、Path等,那么在后边的文章中我们就主要聊一聊在slate中Path的表达,以及在React中是如何控制其内容表达与正确维护Path路径与Element内容渲染的,并且我们还可以聊一聊表格模块的设计与实现。

Blog

https://github.com/WindRunnerMax/EveryDay

Slate文档编辑器-TS类型扩展与节点类型检查的更多相关文章

  1. 第10章 文档对象模型DOM 10.1 Node节点类型

    DOM是针对 HTML 和 XML 文档的一个 API(应用程序编程接口) .DOM描绘了一个层次化的节点树,允许开发人员添加.移除和修改页面的某一部分.DOM 脱胎于Netscape 及微软公司创始 ...

  2. 基于slate构建文档编辑器

    基于slate构建文档编辑器 slate.js是一个完全可定制的框架,用于构建富文本编辑器,在这里我们使用slate.js构建专注于文档编辑的富文本编辑器. 描述 Github | Editor DE ...

  3. 在线HTML文档编辑器使用入门之图片上传与图片管理的实现

    在线HTML文档编辑器使用入门之图片上传与图片管理的实现: 官方网址: http://kindeditor.net/demo.php 开发步骤: 1.开发中只需要导入选中的文件(通常在 webapp ...

  4. [Qt及Qt Quick开发实战精解] 第1章 多文档编辑器

      这一章的例子是对<Qt Creator快速人门>基础应用篇各章节知识的综合应用, 也是一个规范的实例程序.之所以说其规范,是因为在这个程序中,我们对菜单什么时候可用/什么时候不可用.关 ...

  5. PowerDesigner(九)-模型文档编辑器(生成项目文档)(转)

    模型文档编辑器 PowerDesigner的模型文档(Model  Report)是基于模型的,面向项目的概览文档,提供了灵活,丰富的模型文档编辑界面,实现了设计,修改和输出模型文档的全过程. 模型文 ...

  6. 使用Swing实现简易而不简单的文档编辑器

    本文通过Swing来实现文档简易而不简单的文档编辑器,该文档编辑器的功能包括: 设置字体样式:粗体,斜体,下划线,可扩展 设置字体:宋体,黑体,可扩展 设置字号:12,14,18,20,30,40, ...

  7. Linux_文档编辑器_简介

    1. vi 2. vim 3. ubuntu 有一个 自己的图形化的 文档编辑器,用起来比较方便: gedit 4. 5.

  8. Web页面引入文档编辑器报风险

    Web页面引入文档编辑器会报风险,则需要以下操作: <system.web> <httpRuntime requestValidationMode="2.0" / ...

  9. 读写XML文档时,去掉新增加节点的“空命名空间”(xmlns=””)

    在做对ReprotViewer编程时,想做一个用户可以更改显示/打印列的功能,大致看了下,只需要通过对rdlc文件中改变其<Hidden>节点值为false/true,即可实现对应某列的显 ...

  10. MKDOCS在线文档编辑器

    http://www.mkdocs.org/  api接口文档编写 ,效果非常不错

随机推荐

  1. placement new --特殊的内存分配

    placement new 是 C++ 中的一种特殊的内存分配技术,用来在指定的内存地址上直接构造对象.与普通的 new 运算符不同,placement new 并不分配新的内存,而是在已经分配好的内 ...

  2. uprobe的使用浅析

    uprobe是linux内核提供的一种trace用户态函数的机制 可以在不对二进制重新编译的情况下进行trace特定函数 本文描述了uprobe的基本使用方法 使用方法 官方的指引是这样的, 详细的可 ...

  3. 墨天轮沙龙 | SphereEx代野:Apache ShardingSphere-从中间件到分布式生态演进之路

    在9月22日举办的[墨天轮数据库沙龙第十期-国产中间件专场]中,SphereEx 解决方案专家 代野分享了Apache ShardingSphere:从中间件到分布式生态演进之路>主题演讲,本文 ...

  4. threejs - 渲染第一个3D场景 - 旋转的正方体

    1. 安装 threejs & 使用 2. 创建三要素  场景 scene 相机 camera 渲染器 render 3. 场景 new THREE.Scene() 相机分为 2 种  1. ...

  5. .NET高级调试 - 3.8线程操作

    简介 高级调试过程中,与线程与线程栈是打交道特别多的.因此如何查看线程与线程栈就显得至关重要了 查看线程 !Threads 使用 !t/!Threads 命令获取所有托管线程 含义 ThreadCou ...

  6. oracle中排序分析函数row_number()、rank()、dense_rank() 的区别,与rownum的注意事项

    row_number()产生的序号不会重复,即1.2.3... rank()产生的序号会重复,但是会跳号,出现1.2.2.4...的情况 dense_rank()产生的序号会重复,不会跳号,会出现1. ...

  7. mysql基础-事务

    本篇章为初步了解mysql数据事务控制问题,事务作为MySQL的基础篇章是至关重要的一部分内容! 事务 1.事务简介 事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一 ...

  8. 在 Ubuntu 22.04 上安装 KubeSphere 实战教程

    作者:老 Z,中电信数智科技有限公司山东分公司运维架构师,云原生爱好者,目前专注于云原生运维,云原生领域技术栈涉及 Kubernetes.KubeSphere.DevOps.OpenStack.Ans ...

  9. 云原生周刊 | 使用 K8s 可视化工具集来调试业务 | 2023-1-30

    开源项目推荐 k8z k8z 意在 K8s 业务层面,提供一个方便好用的 K8s 集群可视化工具集.目前包含以下功能: 终端:连接到集群任意 Pod 容器上,方便调试 Tcpdump:对集群内容器进行 ...

  10. NES 名词解释

    本文介绍了 NES(FC.红白机.小霸王)中一些名词或者术语,主要与 PPU 有关. Tile 8x8 像素图像.每像素 2 比特, 共 16 字节大小.每个像素可以使用 4 种颜色. Sprite ...