Skip to content

Commit 4c16458

Browse files
committed
feat: block serialize
1 parent 6b8dcf8 commit 4c16458

File tree

8 files changed

+146
-14
lines changed

8 files changed

+146
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import type { BaseNode } from "doc-editor-delta";
22

3+
/** Fragment => HTML */
34
export type CopyContext = {
5+
/** Node 基准 */
46
node: BaseNode;
7+
/** HTML 目标 */
58
html: Node;
69
};
710

11+
/** HTML => Fragment */
812
export type PasteContext = {
13+
/** Node 目标 */
914
nodes: BaseNode[];
15+
/** HTML 基准 */
1016
html: Node;
17+
/** FILE 基准 */
1118
files?: File[];
1219
};
1320

21+
/** Clipboard => Context */
1422
export type PasteNodesContext = {
23+
/** Node 基准 */
1524
nodes: BaseNode[];
1625
};

packages/plugin/src/bold/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class BoldPlugin extends LeafPlugin {
4646
const { nodes, html } = context;
4747
if (!isHTMLElement(html)) return void 0;
4848
if (isMatchTag(html, "strong") || isMatchTag(html, "b") || html.style.fontWeight === "bold") {
49-
applyMarker(nodes, BOLD_KEY);
49+
context.nodes = applyMarker(nodes, { [BOLD_KEY]: true });
5050
}
5151
}
5252

+54-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,64 @@
1+
import type { EditorKit } from "doc-editor-core";
12
import type { BaseNode } from "doc-editor-delta";
2-
import { isText } from "doc-editor-utils";
3+
import type { BlockElement } from "doc-editor-delta";
4+
import { isText, isTextBlock } from "doc-editor-utils";
35

4-
export const applyMarker = (nodes: BaseNode[], key: string, value = true) => {
6+
/**
7+
* 将 Mark 应用到文本节点上
8+
* 原地修改 返回传入的节点引用
9+
* @param nodes
10+
* @param map
11+
*/
12+
export const applyMarker = (nodes: BaseNode[], map: Record<string, unknown>) => {
513
const queue: BaseNode[] = [...nodes];
614
while (queue.length) {
715
const node = queue.shift();
816
if (!node) continue;
17+
// 文本节点直接应用 marks
918
if (isText(node)) {
10-
node[key] = value;
11-
} else {
12-
const children = node.children || [];
13-
queue.unshift(...children);
19+
for (const [key, value] of Object.entries(map)) {
20+
node[key] = value;
21+
}
22+
continue;
1423
}
24+
const children = node.children || [];
25+
queue.unshift(...children);
1526
}
27+
return nodes;
28+
};
29+
30+
/**
31+
* 将 Mark 应用到块节点上
32+
* 非原地修改 返回新的节点引用
33+
* @param nodes
34+
* @param map
35+
*/
36+
export const applyLineMarker = (
37+
editor: EditorKit,
38+
nodes: BaseNode[],
39+
map: Record<string, unknown>
40+
) => {
41+
const target: BaseNode[] = [];
42+
for (const node of nodes) {
43+
// 如果是文本节点则需要包装成块级节点
44+
if (isText(node)) {
45+
const block: BlockElement = { children: [node] };
46+
for (const [key, value] of Object.entries(map)) {
47+
block[key] = value;
48+
}
49+
target.push(block);
50+
continue;
51+
}
52+
// 如果是块级节点则直接应用样式
53+
// 考虑多级节点嵌套的情况是否需要复用块节点 ?
54+
if (isTextBlock(editor.raw, node)) {
55+
for (const [key, value] of Object.entries(map)) {
56+
node[key] = value;
57+
}
58+
target.push(node);
59+
continue;
60+
}
61+
target.push(node);
62+
}
63+
return target;
1664
};

packages/plugin/src/clipboard/utils/is.ts

+11
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@ import { isDOMElement } from "doc-editor-core";
22

33
export const BLOCK_TAG_NAME = ["p", "div"];
44

5+
/**
6+
* 匹配节点标签
7+
* @param node
8+
* @param name
9+
* @returns
10+
*/
511
export const isMatchTag = (node: Node, name: string) => {
612
return isDOMElement(node) && node.tagName.toLocaleLowerCase() === name.toLocaleLowerCase();
713
};
814

15+
/**
16+
* 判断节点为块级节点
17+
* @param node
18+
* @returns
19+
*/
920
export const isMatchBlockTag = (node: Node) => {
1021
return BLOCK_TAG_NAME.some(name => isMatchTag(node, name));
1122
};

packages/plugin/src/heading/index.tsx

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import "./styles/index.scss";
22

3-
import type { BlockContext, CommandFn, EventContext } from "doc-editor-core";
3+
import type {
4+
BlockContext,
5+
CommandFn,
6+
CopyContext,
7+
EventContext,
8+
PasteContext,
9+
} from "doc-editor-core";
410
import type { EditorKit } from "doc-editor-core";
5-
import { BlockPlugin, EDITOR_EVENT } from "doc-editor-core";
11+
import { BlockPlugin, EDITOR_EVENT, isHTMLElement } from "doc-editor-core";
612
import type { RenderElementProps } from "doc-editor-delta";
13+
import type { BlockElement } from "doc-editor-delta";
714
import { Transforms } from "doc-editor-delta";
8-
import { getUniqueId, KEYBOARD } from "doc-editor-utils";
15+
import { getId, getUniqueId, KEYBOARD } from "doc-editor-utils";
916
import { isObject } from "doc-editor-utils";
1017
import { getBlockAttributes, getBlockNode } from "doc-editor-utils";
1118
import {
@@ -20,6 +27,7 @@ import {
2027
import { setBlockNode, setUnBlockNode } from "doc-editor-utils";
2128
import type { KeyboardEvent } from "react";
2229

30+
import { applyLineMarker } from "../clipboard/utils/apply";
2331
import { H1, H2, H3, HEADING_KEY } from "./types";
2432

2533
export class HeadingPlugin extends BlockPlugin {
@@ -125,4 +133,30 @@ export class HeadingPlugin extends BlockPlugin {
125133
return context.stop();
126134
}
127135
};
136+
137+
public serialize(context: CopyContext): void {
138+
const element = context.node as BlockElement;
139+
const heading = element[HEADING_KEY];
140+
if (!heading) return void 0;
141+
const id = heading.id;
142+
const type = heading.type;
143+
const node = document.createElement(type);
144+
node.id = id;
145+
node.setAttribute("data-type", HEADING_KEY);
146+
node.appendChild(context.html);
147+
context.html = node;
148+
}
149+
150+
public deserialize(context: PasteContext): void {
151+
const { nodes, html } = context;
152+
if (!isHTMLElement(html)) return void 0;
153+
const tagName = html.tagName.toLocaleLowerCase();
154+
if (tagName.startsWith("h") && tagName.length === 2) {
155+
let level = Number(tagName.replace("h", ""));
156+
if (level <= 0 || level > 3) level = 3;
157+
context.nodes = applyLineMarker(this.editor, nodes, {
158+
[HEADING_KEY]: { type: `h${level}`, id: getId() },
159+
});
160+
}
161+
}
128162
}

packages/plugin/src/quote-block/index.tsx

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import "./styles/index.scss";
22

3-
import type { BlockContext, CommandFn, EventContext } from "doc-editor-core";
3+
import type {
4+
BlockContext,
5+
CommandFn,
6+
CopyContext,
7+
EventContext,
8+
PasteContext,
9+
} from "doc-editor-core";
410
import type { EditorKit } from "doc-editor-core";
5-
import { BlockPlugin, EDITOR_EVENT } from "doc-editor-core";
6-
import type { RenderElementProps } from "doc-editor-delta";
11+
import { BlockPlugin, EDITOR_EVENT, isHTMLElement } from "doc-editor-core";
12+
import type { BlockElement, RenderElementProps } from "doc-editor-delta";
713
import { isMatchWrapNode, isObject } from "doc-editor-utils";
814
import { getBlockNode } from "doc-editor-utils";
915
import { isCollapsed, isFocusLineStart, isMatchedEvent, isWrappedEdgeNode } from "doc-editor-utils";
1016
import { setUnWrapNodes, setWrapNodes } from "doc-editor-utils";
1117
import { KEYBOARD } from "doc-editor-utils";
1218
import type { KeyboardEvent } from "react";
1319

20+
import { applyLineMarker } from "../clipboard/utils/apply";
21+
import { isMatchTag } from "../clipboard/utils/is";
1422
import { QUOTE_BLOCK_ITEM_KEY, QUOTE_BLOCK_KEY } from "./types";
1523

1624
export class QuoteBlockPlugin extends BlockPlugin {
@@ -75,4 +83,25 @@ export class QuoteBlockPlugin extends BlockPlugin {
7583
return context.stop();
7684
}
7785
};
86+
87+
public serialize(context: CopyContext): void {
88+
const element = context.node as BlockElement;
89+
const quote = element[QUOTE_BLOCK_KEY];
90+
if (!quote) return void 0;
91+
const node = document.createElement("blockquote");
92+
node.setAttribute("data-type", QUOTE_BLOCK_KEY);
93+
node.appendChild(context.html);
94+
context.html = node;
95+
}
96+
97+
public deserialize(context: PasteContext): void {
98+
const { nodes, html } = context;
99+
if (!isHTMLElement(html)) return void 0;
100+
if (isMatchTag(html, "blockquote")) {
101+
const current = applyLineMarker(this.editor, nodes, {
102+
[QUOTE_BLOCK_ITEM_KEY]: true,
103+
});
104+
context.nodes = [{ children: current, [QUOTE_BLOCK_KEY]: true }];
105+
}
106+
}
78107
}

packages/react/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"scripts": {
1515
"dev": "react-app-rewired --openssl-legacy-provider start",
16-
"build": "react-app-rewired build",
16+
"build": "react-app-rewired --openssl-legacy-provider build",
1717
"lint-es": "eslint --fix --ext .js,.jsx,.ts,.tsx --ignore-path .gitignore .",
1818
"lint-style": "stylelint \"**/*.{css,scss,sass}\" --fix --ignore-path .gitignore",
1919
"lint": "npm run lint-es && npm run lint-style",

packages/utils/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type { Func, Reflex } from "laser-utils";
5050
export {
5151
Clipboard,
5252
cs,
53+
getId,
5354
getUniqueId,
5455
IS_DEV,
5556
IS_MAC,

0 commit comments

Comments
 (0)