Skip to content

Commit 9d49811

Browse files
committed
feat: more deterministic normalization
1 parent 295a82c commit 9d49811

File tree

4 files changed

+122
-61
lines changed

4 files changed

+122
-61
lines changed

packages/core/src/schema/index.ts

Lines changed: 17 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import type { BaseNode, NodeEntry, Path } from "doc-editor-delta";
2-
import { Editor } from "doc-editor-delta";
3-
import { isBlock, setBlockNode, setUnBlockNode } from "doc-editor-utils";
1+
import type { BaseNode } from "doc-editor-delta";
2+
import type { Editor } from "doc-editor-delta";
3+
import { isBlock } from "doc-editor-utils";
44

55
import type { EditorSuite } from "../editor/types";
6+
import { Normalize } from "./normalize";
67
import type { EditorSchema } from "./types";
78

8-
export class Schema {
9-
private wrap: Map<string, string> = new Map();
10-
private pair: Map<string, string> = new Map();
11-
private void: Set<string> = new Set<string>();
12-
private block: Set<string> = new Set<string>();
9+
export class Schema extends Normalize {
1310
public readonly raw: EditorSchema;
1411

1512
constructor(schema: EditorSchema) {
13+
super();
1614
this.raw = schema;
1715
for (const [key, value] of Object.entries(schema)) {
1816
if (value.void) {
@@ -27,53 +25,21 @@ export class Schema {
2725
this.wrap.set(value.wrap, key);
2826
this.pair.set(key, value.wrap);
2927
}
30-
}
31-
}
32-
33-
private normalizeNode(editor: Editor, entry: NodeEntry) {
34-
const [node, path] = entry;
35-
// 如果不是块级元素则返回 会继续处理默认的`Normalize`
36-
if (!isBlock(editor, node)) return void 0;
37-
// 对块级节点的属性值进行处理
38-
for (const key of Object.keys(node)) {
39-
// --- 当前节点是`Wrap Node`的`Normalize` ---
40-
if (this.wrap.has(key)) {
41-
const pairKey = this.wrap.get(key) as string;
42-
// 子节点上一定需要存在`Pair Key`
43-
// 否则需要在子节点加入`Pair Key`
44-
const children = node.children || [];
45-
children.forEach((child, index) => {
46-
if (isBlock(editor, child) && !child[pairKey]) {
47-
const location: Path = [...path, index];
48-
if (process.env.NODE_ENV === "development") {
49-
console.log("NormalizeWrapNode: ", location, `${key}->${pairKey}`);
50-
}
51-
setBlockNode(editor, { [pairKey]: true }, { node: child });
52-
}
53-
});
54-
}
55-
// --- --- ---
56-
57-
// --- 当前节点如果是`Pair Node`的`Normalize` ---
58-
if (this.pair.has(key)) {
59-
const wrapKey = this.pair.get(key) as string;
60-
const ancestor = Editor.parent(editor, path);
61-
const parent = ancestor && ancestor[0];
62-
// 父节点上一定需要存在`Wrap Node`
63-
// 否则在当前节点上删除`Pair Key`
64-
if (!parent || !isBlock(editor, parent) || !parent[wrapKey]) {
65-
if (process.env.NODE_ENV === "development") {
66-
console.log("NormalizePairNode: ", path, `${wrapKey}<-${key}`);
67-
}
68-
setUnBlockNode(editor, [key], { node });
69-
}
28+
if (value.inline) {
29+
this.inline.add(key);
7030
}
71-
// --- --- ---
7231
}
7332
}
7433

7534
public with(editor: Editor): EditorSuite {
76-
const { isVoid, normalizeNode } = editor;
35+
const { isVoid, normalizeNode, isInline } = editor;
36+
37+
editor.isInline = element => {
38+
for (const key of Object.keys(element)) {
39+
if (this.inline.has(key)) return true;
40+
}
41+
return isInline(element);
42+
};
7743

7844
editor.isVoid = element => {
7945
for (const key of Object.keys(element)) {
@@ -88,13 +54,7 @@ export class Schema {
8854
normalizeNode(entry);
8955
return void 0;
9056
}
91-
try {
92-
Editor.withoutNormalizing(editor, () => {
93-
this.normalizeNode(editor, entry);
94-
});
95-
} catch (error) {
96-
console.error("Normalize Error: ", error);
97-
}
57+
this.normalize(editor, entry);
9858
normalizeNode(entry);
9959
};
10060

packages/core/src/schema/normalize.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import type { NodeEntry, Path } from "doc-editor-delta";
2+
import { Editor } from "doc-editor-delta";
3+
import { setUnWrapNodesExactly } from "doc-editor-utils";
4+
import { isBlock, setUnBlockNode } from "doc-editor-utils";
5+
6+
export class Normalize {
7+
/** Wrap - Pair */
8+
protected wrap: Map<string, string> = new Map();
9+
/** Pair - Wrap */
10+
protected pair: Map<string, string> = new Map();
11+
/** Void */
12+
protected void: Set<string> = new Set<string>();
13+
/** Block */
14+
protected block: Set<string> = new Set<string>();
15+
/** Inline */
16+
protected inline: Set<string> = new Set<string>();
17+
18+
protected normalize(editor: Editor, entry: NodeEntry) {
19+
try {
20+
Editor.withoutNormalizing(editor, () => {
21+
this.normalizeWrapNode(editor, entry);
22+
this.normalizePairNode(editor, entry);
23+
});
24+
} catch (error) {
25+
console.error("Normalize Error: ", error);
26+
}
27+
}
28+
29+
private normalizeWrapNode(editor: Editor, entry: NodeEntry) {
30+
const [node, path] = entry;
31+
// 如果不是块级元素则返回 会继续处理默认的`Normalize`
32+
if (!isBlock(editor, node)) return void 0;
33+
for (const key of Object.keys(node)) {
34+
// --- 当前节点是`Wrap Node`的`Normalize` ---
35+
if (this.wrap.has(key)) {
36+
const pairKey = this.wrap.get(key) as string;
37+
const children = node.children || [];
38+
// 子节点上一定需要存在`Pair Key`
39+
// 否则需要在子节点的父节点移除`Wrap Key`
40+
children.forEach((child, index) => {
41+
if (isBlock(editor, child) && !child[pairKey]) {
42+
const location: Path = [...path, index];
43+
if (process.env.NODE_ENV === "development") {
44+
console.log("NormalizeWrapNode: ", location, `${key}->${pairKey}`);
45+
}
46+
// COMPAT: 为什么不在子节点加入`Pair Key`而是移除`Wrap Key`?
47+
// 因为在`setBlockNode`时无法确定该节点的`value` 只能给予默认值`true`
48+
// 当然这里也可以交予插件化本身做`Normalize`来解决这个问题
49+
setUnWrapNodesExactly(editor, {
50+
wrapKey: key,
51+
pairKey: pairKey,
52+
wrapNode: node,
53+
pairPath: location,
54+
});
55+
}
56+
});
57+
}
58+
}
59+
}
60+
61+
private normalizePairNode(editor: Editor, entry: NodeEntry) {
62+
const [node, path] = entry;
63+
// 如果不是块级元素则返回 会继续处理默认的`Normalize`
64+
if (!isBlock(editor, node)) return void 0;
65+
for (const key of Object.keys(node)) {
66+
// --- 当前节点如果是`Pair Node`的`Normalize` ---
67+
if (this.pair.has(key)) {
68+
const wrapKey = this.pair.get(key) as string;
69+
const ancestor = Editor.parent(editor, path);
70+
const parent = ancestor && ancestor[0];
71+
// 父节点上一定需要存在`Wrap Node`
72+
// 否则在当前节点上删除`Pair Key`
73+
if (!parent || !isBlock(editor, parent) || !parent[wrapKey]) {
74+
if (process.env.NODE_ENV === "development") {
75+
console.log("NormalizePairNode: ", path, `${wrapKey}<-${key}`);
76+
}
77+
setUnBlockNode(editor, [key], { node });
78+
}
79+
}
80+
}
81+
}
82+
}

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export {
3737
setUnBlockNode,
3838
setUnTextNode,
3939
setUnWrapNodes,
40+
setUnWrapNodesExactly,
4041
setWrapNodes,
4142
} from "./set";
4243
export type { Object, Reflex, String } from "laser-utils";

packages/utils/src/set.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,17 @@ export const setUnWrapNodes = (
105105
options: {
106106
at?: Location;
107107
wrapKey: string;
108-
itemKey: string;
108+
pairKey: string;
109109
}
110110
) => {
111-
const { at, wrapKey, itemKey } = options;
111+
const { at, wrapKey, pairKey } = options;
112112
const wrap = getAboveNode(editor, { match: n => existKey(n, wrapKey), at });
113-
const pair = getAboveNode(editor, { match: n => existKey(n, itemKey), at });
113+
const pair = getAboveNode(editor, { match: n => existKey(n, pairKey), at });
114114
if (!wrap || !pair) return void 0;
115115
const wrapAttrs = getBlockAttributes(wrap.node, [wrapKey]);
116116
Editor.withoutNormalizing(editor, () => {
117117
Transforms.setNodes(editor, wrapAttrs, { at: pair.path });
118-
Transforms.unsetNodes(editor, [itemKey], { at: pair.path });
118+
Transforms.unsetNodes(editor, [pairKey], { at: pair.path });
119119
Transforms.unwrapNodes(editor, {
120120
match: (_, p) => Path.equals(p, wrap.path),
121121
split: true,
@@ -127,3 +127,21 @@ export const setUnWrapNodes = (
127127
});
128128
});
129129
};
130+
131+
export const setUnWrapNodesExactly = (
132+
editor: Editor,
133+
options: {
134+
wrapKey: string;
135+
pairKey: string;
136+
pairPath: Path;
137+
wrapNode: BlockElement;
138+
}
139+
) => {
140+
const { wrapNode, pairPath, wrapKey, pairKey } = options;
141+
const wrapAttrs = getBlockAttributes(wrapNode, [wrapKey]);
142+
Editor.withoutNormalizing(editor, () => {
143+
Transforms.setNodes(editor, wrapAttrs, { at: pairPath });
144+
Transforms.unsetNodes(editor, [pairKey], { at: pairPath });
145+
Transforms.unwrapNodes(editor, { split: true, at: pairPath });
146+
});
147+
};

0 commit comments

Comments
 (0)