diff --git a/packages/core/src/schema/index.ts b/packages/core/src/schema/index.ts index 12bee5d..283df63 100644 --- a/packages/core/src/schema/index.ts +++ b/packages/core/src/schema/index.ts @@ -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 = new Map(); - private pair: Map = new Map(); - private void: Set = new Set(); - private block: Set = new Set(); +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) { @@ -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)) { @@ -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); }; diff --git a/packages/core/src/schema/normalize.ts b/packages/core/src/schema/normalize.ts new file mode 100644 index 0000000..2ec0252 --- /dev/null +++ b/packages/core/src/schema/normalize.ts @@ -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 = new Map(); + /** Pair - Wrap */ + protected pair: Map = new Map(); + /** Void */ + protected void: Set = new Set(); + /** Block */ + protected block: Set = new Set(); + /** Inline */ + protected inline: Set = new Set(); + + 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 }); + } + } + } + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index cb701de..88349a1 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -37,6 +37,7 @@ export { setUnBlockNode, setUnTextNode, setUnWrapNodes, + setUnWrapNodesExactly, setWrapNodes, } from "./set"; export type { Object, Reflex, String } from "laser-utils"; diff --git a/packages/utils/src/set.ts b/packages/utils/src/set.ts index 733263e..d1366d6 100644 --- a/packages/utils/src/set.ts +++ b/packages/utils/src/set.ts @@ -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, @@ -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 }); + }); +};