Skip to content

Commit

Permalink
feat: more deterministic normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
WindRunnerMax committed Jun 2, 2024
1 parent 295a82c commit 9d49811
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 61 deletions.
74 changes: 17 additions & 57 deletions packages/core/src/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import type { BaseNode, NodeEntry, Path } from "doc-editor-delta";
import { Editor } from "doc-editor-delta";
import { isBlock, setBlockNode, setUnBlockNode } from "doc-editor-utils";
import type { BaseNode } from "doc-editor-delta";
import type { Editor } from "doc-editor-delta";
import { isBlock } from "doc-editor-utils";

import type { EditorSuite } from "../editor/types";
import { Normalize } from "./normalize";
import type { EditorSchema } from "./types";

export class Schema {
private wrap: Map<string, string> = new Map();
private pair: Map<string, string> = new Map();
private void: Set<string> = new Set<string>();
private block: Set<string> = new Set<string>();
export class Schema extends Normalize {
public readonly raw: EditorSchema;

constructor(schema: EditorSchema) {
super();
this.raw = schema;
for (const [key, value] of Object.entries(schema)) {
if (value.void) {
Expand All @@ -27,53 +25,21 @@ export class Schema {
this.wrap.set(value.wrap, key);
this.pair.set(key, value.wrap);
}
}
}

private normalizeNode(editor: Editor, entry: NodeEntry) {
const [node, path] = entry;
// 如果不是块级元素则返回 会继续处理默认的`Normalize`
if (!isBlock(editor, node)) return void 0;
// 对块级节点的属性值进行处理
for (const key of Object.keys(node)) {
// --- 当前节点是`Wrap Node`的`Normalize` ---
if (this.wrap.has(key)) {
const pairKey = this.wrap.get(key) as string;
// 子节点上一定需要存在`Pair Key`
// 否则需要在子节点加入`Pair Key`
const children = node.children || [];
children.forEach((child, index) => {
if (isBlock(editor, child) && !child[pairKey]) {
const location: Path = [...path, index];
if (process.env.NODE_ENV === "development") {
console.log("NormalizeWrapNode: ", location, `${key}->${pairKey}`);
}
setBlockNode(editor, { [pairKey]: true }, { node: child });
}
});
}
// --- --- ---

// --- 当前节点如果是`Pair Node`的`Normalize` ---
if (this.pair.has(key)) {
const wrapKey = this.pair.get(key) as string;
const ancestor = Editor.parent(editor, path);
const parent = ancestor && ancestor[0];
// 父节点上一定需要存在`Wrap Node`
// 否则在当前节点上删除`Pair Key`
if (!parent || !isBlock(editor, parent) || !parent[wrapKey]) {
if (process.env.NODE_ENV === "development") {
console.log("NormalizePairNode: ", path, `${wrapKey}<-${key}`);
}
setUnBlockNode(editor, [key], { node });
}
if (value.inline) {
this.inline.add(key);
}
// --- --- ---
}
}

public with(editor: Editor): EditorSuite {
const { isVoid, normalizeNode } = editor;
const { isVoid, normalizeNode, isInline } = editor;

editor.isInline = element => {
for (const key of Object.keys(element)) {
if (this.inline.has(key)) return true;
}
return isInline(element);
};

editor.isVoid = element => {
for (const key of Object.keys(element)) {
Expand All @@ -88,13 +54,7 @@ export class Schema {
normalizeNode(entry);
return void 0;
}
try {
Editor.withoutNormalizing(editor, () => {
this.normalizeNode(editor, entry);
});
} catch (error) {
console.error("Normalize Error: ", error);
}
this.normalize(editor, entry);
normalizeNode(entry);
};

Expand Down
82 changes: 82 additions & 0 deletions packages/core/src/schema/normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { NodeEntry, Path } from "doc-editor-delta";
import { Editor } from "doc-editor-delta";
import { setUnWrapNodesExactly } from "doc-editor-utils";
import { isBlock, setUnBlockNode } from "doc-editor-utils";

export class Normalize {
/** Wrap - Pair */
protected wrap: Map<string, string> = new Map();
/** Pair - Wrap */
protected pair: Map<string, string> = new Map();
/** Void */
protected void: Set<string> = new Set<string>();
/** Block */
protected block: Set<string> = new Set<string>();
/** Inline */
protected inline: Set<string> = new Set<string>();

protected normalize(editor: Editor, entry: NodeEntry) {
try {
Editor.withoutNormalizing(editor, () => {
this.normalizeWrapNode(editor, entry);
this.normalizePairNode(editor, entry);
});
} catch (error) {
console.error("Normalize Error: ", error);
}
}

private normalizeWrapNode(editor: Editor, entry: NodeEntry) {
const [node, path] = entry;
// 如果不是块级元素则返回 会继续处理默认的`Normalize`
if (!isBlock(editor, node)) return void 0;
for (const key of Object.keys(node)) {
// --- 当前节点是`Wrap Node`的`Normalize` ---
if (this.wrap.has(key)) {
const pairKey = this.wrap.get(key) as string;
const children = node.children || [];
// 子节点上一定需要存在`Pair Key`
// 否则需要在子节点的父节点移除`Wrap Key`
children.forEach((child, index) => {
if (isBlock(editor, child) && !child[pairKey]) {
const location: Path = [...path, index];
if (process.env.NODE_ENV === "development") {
console.log("NormalizeWrapNode: ", location, `${key}->${pairKey}`);
}
// COMPAT: 为什么不在子节点加入`Pair Key`而是移除`Wrap Key`?
// 因为在`setBlockNode`时无法确定该节点的`value` 只能给予默认值`true`
// 当然这里也可以交予插件化本身做`Normalize`来解决这个问题
setUnWrapNodesExactly(editor, {
wrapKey: key,
pairKey: pairKey,
wrapNode: node,
pairPath: location,
});
}
});
}
}
}

private normalizePairNode(editor: Editor, entry: NodeEntry) {
const [node, path] = entry;
// 如果不是块级元素则返回 会继续处理默认的`Normalize`
if (!isBlock(editor, node)) return void 0;
for (const key of Object.keys(node)) {
// --- 当前节点如果是`Pair Node`的`Normalize` ---
if (this.pair.has(key)) {
const wrapKey = this.pair.get(key) as string;
const ancestor = Editor.parent(editor, path);
const parent = ancestor && ancestor[0];
// 父节点上一定需要存在`Wrap Node`
// 否则在当前节点上删除`Pair Key`
if (!parent || !isBlock(editor, parent) || !parent[wrapKey]) {
if (process.env.NODE_ENV === "development") {
console.log("NormalizePairNode: ", path, `${wrapKey}<-${key}`);
}
setUnBlockNode(editor, [key], { node });
}
}
}
}
}
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export {
setUnBlockNode,
setUnTextNode,
setUnWrapNodes,
setUnWrapNodesExactly,
setWrapNodes,
} from "./set";
export type { Object, Reflex, String } from "laser-utils";
Expand Down
26 changes: 22 additions & 4 deletions packages/utils/src/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,17 @@ export const setUnWrapNodes = (
options: {
at?: Location;
wrapKey: string;
itemKey: string;
pairKey: string;
}
) => {
const { at, wrapKey, itemKey } = options;
const { at, wrapKey, pairKey } = options;
const wrap = getAboveNode(editor, { match: n => existKey(n, wrapKey), at });
const pair = getAboveNode(editor, { match: n => existKey(n, itemKey), at });
const pair = getAboveNode(editor, { match: n => existKey(n, pairKey), at });
if (!wrap || !pair) return void 0;
const wrapAttrs = getBlockAttributes(wrap.node, [wrapKey]);
Editor.withoutNormalizing(editor, () => {
Transforms.setNodes(editor, wrapAttrs, { at: pair.path });
Transforms.unsetNodes(editor, [itemKey], { at: pair.path });
Transforms.unsetNodes(editor, [pairKey], { at: pair.path });
Transforms.unwrapNodes(editor, {
match: (_, p) => Path.equals(p, wrap.path),
split: true,
Expand All @@ -127,3 +127,21 @@ export const setUnWrapNodes = (
});
});
};

export const setUnWrapNodesExactly = (
editor: Editor,
options: {
wrapKey: string;
pairKey: string;
pairPath: Path;
wrapNode: BlockElement;
}
) => {
const { wrapNode, pairPath, wrapKey, pairKey } = options;
const wrapAttrs = getBlockAttributes(wrapNode, [wrapKey]);
Editor.withoutNormalizing(editor, () => {
Transforms.setNodes(editor, wrapAttrs, { at: pairPath });
Transforms.unsetNodes(editor, [pairKey], { at: pairPath });
Transforms.unwrapNodes(editor, { split: true, at: pairPath });
});
};

0 comments on commit 9d49811

Please sign in to comment.