-- | | -- block|id -- ref|r -- |
| --
-- text ··· text|r -- em|r ---- |
```
接下来我们将会以`delta`数据结构为例,处理扁平结构的剪贴板模块设计。同样分别以行内结构、段落结构、组合结构、嵌入结构、块级结构为基础,在上述基本模式的调度下,分类型进行序列化与反序列化的插件实现。
### 行内结构
行内结构指的是加粗、斜体、下划线、删除线、行内代码块等行内的结构样式,这里以加粗为例来处理序列化与反序列化。序列化行内结构部分基本与`slate`一致,从这里开始我们采用单元测试的方式执行。
```js
// packages/core/test/clipboard/bold.test.ts
it("serialize", () => {
const plugin = getMockedPlugin({
serialize(context) {
if (context.op.attributes?.bold) {
const strong = document.createElement("strong");
strong.appendChild(context.html);
context.html = strong;
}
},
});
editor.plugin.register(plugin);
const delta = new Delta().insert("Hello", { bold: "true" }).insert("World");
const root = editor.clipboard.copyModule.serialize(delta);
const plainText = getFragmentText(root);
const htmlText = serializeHTML(root);
expect(plainText).toBe("HelloWorld");
expect(htmlText).toBe(`HelloWorld
`);
});
```
反序列化部分则是判断当前正在处理的`HTML`节点是否为加粗节点,如果是的话就将其转换为`Delta`节点。
```js
// packages/core/test/clipboard/bold.test.ts
it("deserialize", () => {
const plugin = getMockedPlugin({
deserialize(context) {
const { delta, html } = context;
if (!isHTMLElement(html)) return void 0;
if (isMatchHTMLTag(html, "strong") || isMatchHTMLTag(html, "b") || html.style.fontWeight === "bold") {
// applyMarker packages/core/src/clipboard/utils/deserialize.ts
applyMarker(delta, { bold: "true" });
}
},
});
editor.plugin.register(plugin);
const parser = new DOMParser();
const transferHTMLText = `HelloWorld
`;
const html = parser.parseFromString(transferHTMLText, "text/html");
const rootDelta = editor.clipboard.pasteModule.deserialize(html.body);
const delta = new Delta().insert("Hello", { bold: "true" }).insert("World");
expect(rootDelta).toEqual(delta);
});
```
### 段落结构
段落结构指的是标题、行高、文本对齐等结构样式,这里则以标题为例来处理序列化与反序列化。序列化段落结构,我们只需要`Node`是标题节点时,构造相关的`HTML`节点,将本来的节点原地包装并赋值到`context`即可,同样采用嵌套节点的方式。
```js
// packages/core/test/clipboard/heading.test.ts
it("serialize", () => {
const plugin = getMockedPlugin({
serialize(context) {
const { op, html } = context;
if (isEOLOp(op) && op.attributes?.heading) {
const element = document.createElement(op.attributes.heading);
element.appendChild(html);
context.html = element;
}
},
});
editor.plugin.register(plugin);
const delta = new MutateDelta().insert("Hello").insert("\n", { heading: "h1" });
const root = editor.clipboard.copyModule.serialize(delta);
const plainText = getFragmentText(root);
const htmlText = serializeHTML(root);
expect(plainText).toBe("Hello");
expect(htmlText).toBe(`Hello
`);
});
```
反序列化则是相反的操作,判断当前正在处理的`HTML`节点是否为标题节点,如果是的话就将其转换为`Node`节点。这里同样需要原地处理数据,与行内节点不同的是,需要使用`applyLineMarker`将所有的行节点加入标题格式。
```js
// packages/core/test/clipboard/heading.test.ts
it("deserialize", () => {
const plugin = getMockedPlugin({
deserialize(context) {
const { delta, html } = context;
if (!isHTMLElement(html)) return void 0;
if (["h1", "h2"].indexOf(html.tagName.toLowerCase()) > -1) {
applyLineMarker(delta, { heading: html.tagName.toLowerCase() });
}
},
});
editor.plugin.register(plugin);
const parser = new DOMParser();
const transferHTMLText = `Hello
World
`;
const html = parser.parseFromString(transferHTMLText, TEXT_HTML);
const rootDelta = editor.clipboard.pasteModule.deserialize(html.body);
const delta = new Delta()
.insert("Hello")
.insert("\n", { heading: "h1" })
.insert("World")
.insert("\n", { heading: "h2" });
expect(rootDelta).toEqual(MutateDelta.from(delta));
});
```
### 组合结构
组合结构在这里指的是引用块、有序列表、无序列表等结构样式,这里则以引用块为例来处理序列化与反序列化。序列化组合结构,我同样需要`Node`是引用块节点时,构造相关的`HTML`节点进行包装。在扁平结构下类似组合结构的处理方式会是渲染时进行的,因此序列化的过程与先前标题一致。
```js
// packages/core/test/clipboard/quote.test.ts
it("serialize", () => {
const plugin = getMockedPlugin({
serialize(context) {
const { op, html } = context;
if (isEOLOp(op) && op.attributes?.quote) {
const element = document.createElement("blockquote");
element.appendChild(html);
context.html = element;
}
},
});
editor.plugin.register(plugin);
const delta = new MutateDelta().insert("Hello").insert("\n", { quote: "true" });
const root = editor.clipboard.copyModule.serialize(delta);
const plainText = getFragmentText(root);
const htmlText = serializeHTML(root);
expect(plainText).toBe("Hello");
expect(htmlText).toBe(`Hello
`);
});
```
反序列化同样是判断是否为引用块节点,并且构造对应的`Node`节点。这里与标题模块不同的是,标题是将格式应用到相关的行节点上,而引用块则是在原本的节点上嵌套一层结构。反序列化的结构处理方式也类似于标题处理方式,由于在`HTML`的结构上是嵌套结构,在应用时在所有行节点上加入引用格式。
```js
// packages/core/test/clipboard/quote.test.ts
it("deserialize", () => {
const plugin = getMockedPlugin({
deserialize(context) {
const { delta, html } = context;
if (!isHTMLElement(html)) return void 0;
if (isMatchHTMLTag(html, "p")) {
applyLineMarker(delta, {});
}
if (isMatchHTMLTag(html, "blockquote")) {
applyLineMarker(delta, { quote: "true" });
}
},
});
editor.plugin.register(plugin);
const parser = new DOMParser();
const transferHTMLText = ``;
const html = parser.parseFromString(transferHTMLText, TEXT_HTML);
const rootDelta = editor.clipboard.pasteModule.deserialize(html.body);
const delta = new Delta()
.insert("Hello")
.insert("\n", { quote: "true" })
.insert("World")
.insert("\n", { quote: "true" });
expect(rootDelta).toEqual(MutateDelta.from(delta));
});
```
### 嵌入结构
嵌入结构在这里指的是图片、视频、流程图等结构样式,这里则以图片为例来处理序列化与反序列化。序列化嵌入结构,我们只需要`Node`是图片节点时,构造相关的`HTML`节点进行包装。与之前的节点不同的是,此时我们不需要嵌套`DOM`节点了,将独立节点原地替换即可。
```js
// packages/core/test/clipboard/image.test.ts
it("serialize", () => {
const plugin = getMockedPlugin({
serialize(context) {
const { op } = context;
if (op.attributes?.image && op.attributes.src) {
const element = document.createElement("img");
element.src = op.attributes.src;
context.html = element;
}
},
});
editor.plugin.register(plugin);
const delta = new Delta().insert(" ", {
image: "true",
src: "https://example.com/image.png",
});
const root = editor.clipboard.copyModule.serialize(delta);
const plainText = getFragmentText(root);
const htmlText = serializeHTML(root);
expect(plainText).toBe("");
expect(htmlText).toBe(``);
});
```
对于反序列化的结构,判断当前正在处理的`HTML`节点是否为图片节点,如果是的话就将其转换为`Node`节点。同样的,这里还有个常用的操作是,粘贴图片内容通常需要将原本的`src`转储到我们的服务上,例如飞书的图片就是临时链接,在生产环境中需要转储资源。
```js
// packages/core/test/clipboard/image.test.ts
it("deserialize", () => {
const plugin = getMockedPlugin({
deserialize(context) {
const { html } = context;
if (!isHTMLElement(html)) return void 0;
if (isMatchHTMLTag(html, "img")) {
const src = html.getAttribute("src") || "";
const delta = new Delta();
delta.insert(" ", { image: "true", src: src });
context.delta = delta;
}
},
});
editor.plugin.register(plugin);
const parser = new DOMParser();
const transferHTMLText = `
`;
const html = parser.parseFromString(transferHTMLText, TEXT_HTML);
const rootDelta = editor.clipboard.pasteModule.deserialize(html.body);
const delta = new Delta().insert(" ", { image: "true", src: "https://example.com/image.png" });
expect(rootDelta).toEqual(delta);
});
```
### 块级结构
### 块级结构
块级结构指的是高亮块、代码块、表格等结构样式,这里则以块结构为例来处理序列化与反序列化。这里的嵌套结构还没有实现,因此这里仅仅是实现了上述`deltas`图示的测试用例,主要的处理方式是当存在引用关系时,主动调用序列化的方式将其写入到`HTML`中。
```js
it("serialize", () => {
const block = new Delta().insert("inside");
const inside = editor.clipboard.copyModule.serialize(block);
const plugin = getMockedPlugin({
serialize(context) {
const { op } = context;
if (op.attributes?._ref) {
const element = document.createElement("div");
element.setAttribute("data-block", op.attributes._ref);
element.appendChild(inside);
context.html = element;
}
},
});
editor.plugin.register(plugin);
const delta = new Delta().insert(" ", { _ref: "id" });
const root = editor.clipboard.copyModule.serialize(delta);
const plainText = getFragmentText(root);
const htmlText = serializeHTML(root);
expect(plainText).toBe("inside\n");
expect(htmlText).toBe(
``
);
});
```
反序列化则是判断当前正在处理的`HTML`节点是否为块级节点,如果是的话就将其转换为`Node`节点。这里的处理方式则是,深度优先遍历处理节点内容时,若是出现`block`节点,则生成`id`并放置于`deltas`中,然后在`ROOT`结构中引用该节点。
```js
it("deserialize", () => {
const deltas: Record = {};
const plugin = getMockedPlugin({
deserialize(context) {
const { html } = context;
if (!isHTMLElement(html)) return void 0;
if (isMatchHTMLTag(html, "div") && html.hasAttribute("data-block")) {
const id = html.getAttribute("data-block")!;
deltas[id] = context.delta;
context.delta = new Delta().insert(" ", { _ref: id });
}
},
});
editor.plugin.register(plugin);
const parser = new DOMParser();
const transferHTMLText = ``;
const html = parser.parseFromString(transferHTMLText, TEXT_HTML);
const rootDelta = editor.clipboard.pasteModule.deserialize(html.body);
deltas[ROOT_BLOCK] = rootDelta;
expect(deltas).toEqual({
[ROOT_BLOCK]: new Delta().insert(" ", { _ref: "id" }),
id: new Delta().insert("inside"),
});
});
```
## 每日一题
-
## 参考
-
-
-
-
-
-