Skip to content

Commit c950d30

Browse files
committed
feat: doc utils implement
1 parent 2c377c1 commit c950d30

File tree

15 files changed

+266
-161
lines changed

15 files changed

+266
-161
lines changed

packages/plugin/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"@arco-design/web-react": ">=2.60.3"
2727
},
2828
"dependencies": {
29-
"ahooks": "3.3.13",
3029
"react-live-runtime": "0.0.3",
3130
"embed-drawio": "0.0.18",
3231
"lodash-es": "4.17.21",

packages/plugin/src/float-toolbar/index.tsx

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import "./styles/index.scss";
22

33
import { Menu } from "@arco-design/web-react";
4-
import { useMemoizedFn } from "ahooks";
54
import type { EditorKit } from "doc-editor-core";
65
import type { TextElement } from "doc-editor-delta";
76
import { Editor } from "doc-editor-delta";
87
import { ReactEditor } from "doc-editor-delta";
98
import { EVENT_ENUM } from "doc-editor-utils";
10-
import { omit } from "doc-editor-utils";
9+
import { Collection } from "doc-editor-utils";
1110
import type { FC } from "react";
1211
import React, { useEffect, useMemo, useRef, useState } from "react";
1312

1413
import { FONT_BASE_KEY } from "../font-base/types";
1514
import { HYPER_LINK_KEY } from "../hyper-link/types";
1615
import { LINE_HEIGHT_KEY } from "../line-height/types";
16+
import { useMemoFn } from "../shared/hooks/preset";
1717
import { MenuItems } from "./components/menu";
1818
import { execSelectMarks, getSelectionRect, maskMenuToolBar, Portal } from "./utils/selection";
1919

@@ -32,11 +32,13 @@ export const MenuToolBar: FC<{
3232
const toolbarRef = useRef<HTMLDivElement>(null);
3333
const [selectedMarks, setSelectedMarks] = useState<string[]>([]);
3434

35-
const wakeUpToolbar = useMemoizedFn((wakeUp: boolean) => {
35+
const wakeUpToolbar = useMemoFn((wakeUp: boolean) => {
3636
const toolbar = toolbarRef.current;
3737
if (!toolbar) return void 0;
3838
if (ReactEditor.isFocused(editor.raw) && wakeUp) {
39-
setSelectedMarks(omit(Object.keys(Editor.marks(editor.raw) || []), NOT_INIT_SELECT));
39+
setSelectedMarks(
40+
Collection.omit(Object.keys(Editor.marks(editor.raw) || []), NOT_INIT_SELECT)
41+
);
4042
const rect = getSelectionRect();
4143
if (rect) {
4244
toolbar.style.top = `${rect.top + window.pageYOffset - TOOLBAR_OFFSET_HEIGHT - 10}px`;
@@ -74,29 +76,27 @@ export const MenuToolBar: FC<{
7476
};
7577
}, [editor, wakeUpToolbar, props.readonly]);
7678

77-
const exec = useMemoizedFn(
78-
(param: string, event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
79-
const [key, extraKey] = param.split(".");
80-
const marks = Editor.marks(editor.raw);
81-
const position = { left: 0, top: 0 };
82-
const toolbar = toolbarRef.current;
83-
setSelectedMarks(execSelectMarks(key, selectedMarks, MUTEX_SELECT));
84-
if (toolbar) {
85-
position.top = toolbar.offsetTop + toolbar.offsetHeight / 2;
86-
position.left = toolbar.offsetLeft + toolbar.offsetWidth / 2;
87-
}
88-
const result = props.editor.command.exec(key, {
89-
extraKey,
90-
event,
91-
position,
92-
marks: marks as TextElement,
93-
});
94-
if (result) {
95-
keepStatus.current = true;
96-
result.then(() => (keepStatus.current = false));
97-
}
79+
const exec = useMemoFn((param: string, event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
80+
const [key, extraKey] = param.split(".");
81+
const marks = Editor.marks(editor.raw);
82+
const position = { left: 0, top: 0 };
83+
const toolbar = toolbarRef.current;
84+
setSelectedMarks(execSelectMarks(key, selectedMarks, MUTEX_SELECT));
85+
if (toolbar) {
86+
position.top = toolbar.offsetTop + toolbar.offsetHeight / 2;
87+
position.left = toolbar.offsetLeft + toolbar.offsetWidth / 2;
9888
}
99-
);
89+
const result = props.editor.command.exec(key, {
90+
extraKey,
91+
event,
92+
position,
93+
marks: marks as TextElement,
94+
});
95+
if (result) {
96+
keepStatus.current = true;
97+
result.then(() => (keepStatus.current = false));
98+
}
99+
});
100100

101101
const HoverMenu = useMemo(
102102
() => (

packages/plugin/src/float-toolbar/utils/selection.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { omit } from "doc-editor-utils";
1+
import { Collection } from "doc-editor-utils";
22
import ReactDOM from "react-dom";
33

44
export const maskMenuToolBar = (element: HTMLDivElement) => {
@@ -24,8 +24,8 @@ export const execSelectMarks = (key: string, marks: string[], mutexKeys: string[
2424
const isKeyInMarks = marks.indexOf(key) > -1;
2525
const isKeyInMutexKeys = mutexKeys.indexOf(key) > -1;
2626
return isKeyInMarks
27-
? omit(marks, [key])
27+
? Collection.omit(marks, [key])
2828
: isKeyInMutexKeys
29-
? [...omit(marks, mutexKeys), key]
29+
? [...Collection.omit(marks, mutexKeys), key]
3030
: [...marks, key];
3131
};

packages/plugin/src/font-base/components/menu.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Button, InputNumber } from "@arco-design/web-react";
2-
import { useMemoizedFn } from "ahooks";
32
import type { FC } from "react";
43
import { useMemo } from "react";
54

5+
import { useMemoFn } from "../../shared/hooks/preset";
66
import type { FontBaseConfig } from "../types";
77

88
interface Props {
@@ -29,14 +29,12 @@ export const FontBaseMenu: FC<Props> = props => {
2929
const left = props.left - 180;
3030
let changedConfig: FontBaseConfig = props.config;
3131

32-
const onChange = useMemoizedFn(
33-
(key: keyof FontBaseConfig, value: string | number | undefined) => {
34-
changedConfig = { ...changedConfig, [key]: value };
35-
props.onChange(changedConfig);
36-
}
37-
);
32+
const onChange = useMemoFn((key: keyof FontBaseConfig, value: string | number | undefined) => {
33+
changedConfig = { ...changedConfig, [key]: value };
34+
props.onChange(changedConfig);
35+
});
3836

39-
const resetDefault = useMemoizedFn(() => {
37+
const resetDefault = useMemoFn(() => {
4038
props.onChange({});
4139
changedConfig = {};
4240
});

packages/plugin/src/highlight-block/components/wrapper.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Button, Trigger } from "@arco-design/web-react";
22
import { IconPalette } from "@arco-design/web-react/icon";
3-
import { useMemoizedFn } from "ahooks";
43
import type { EditorKit } from "doc-editor-core";
54
import type { BlockElement } from "doc-editor-delta";
65
import { ReactEditor } from "doc-editor-delta";
@@ -9,6 +8,7 @@ import { setBlockNode } from "doc-editor-utils";
98
import type { FC } from "react";
109
import { useMemo } from "react";
1110

11+
import { useMemoFn } from "../../shared/hooks/preset";
1212
import { HIGHLIGHT_BLOCK_KEY } from "../types";
1313
import { COLOR_MAP } from "../types";
1414

@@ -20,7 +20,7 @@ export const HighlightBlockWrapper: FC<{
2020
}> = props => {
2121
const { editor, element, config, readonly } = props;
2222

23-
const switchAction = useMemoizedFn((index: number) => {
23+
const switchAction = useMemoFn((index: number) => {
2424
const path = ReactEditor.findPath(editor.raw, element);
2525
setBlockNode(
2626
editor.raw,

packages/plugin/src/react-live/components/viewer.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Button, Space, Spin } from "@arco-design/web-react";
2-
import { useDebounceEffect } from "ahooks";
32
import type { EditorKit } from "doc-editor-core";
43
import { Void } from "doc-editor-core";
54
import type { BlockElement } from "doc-editor-delta";
5+
import { debounce } from "doc-editor-utils";
66
import { isText } from "doc-editor-utils";
77
import type { FC } from "react";
88
import React, { useEffect, useMemo, useRef, useState } from "react";
@@ -26,31 +26,33 @@ export const ReactLiveView: FC<{
2626
.join("\n");
2727
}, [props.element]);
2828

29-
useDebounceEffect(
30-
() => {
31-
const el = ref.current;
32-
if (!el) return;
33-
try {
34-
const sandbox = withSandbox({ React, Button, console, Space });
35-
// JS Plain Object -> ({...})
36-
// React.FC -> React.Fragment / div
37-
const compiledCode = compileWithSucrase("<div>" + code + "</div>");
38-
const Component = renderWithDependency(compiledCode, sandbox) as JSX.Element;
39-
const App = () => {
40-
useEffect(() => {
41-
setLoading(false);
42-
}, []);
43-
return Component;
44-
};
45-
ReactDOM.render(<App></App>, el);
46-
} catch (error) {
47-
console.log("Render Component Error", error);
48-
}
49-
},
50-
[code],
51-
{ wait: 300 }
29+
const onParseCode = useMemo(
30+
() =>
31+
debounce((code: string) => {
32+
const el = ref.current;
33+
if (!el) return;
34+
try {
35+
const sandbox = withSandbox({ React, Button, console, Space });
36+
// JS Plain Object -> ({...})
37+
// React.FC -> React.Fragment / div
38+
const compiledCode = compileWithSucrase("<div>" + code + "</div>");
39+
const Component = renderWithDependency(compiledCode, sandbox) as JSX.Element;
40+
const App = () => {
41+
useEffect(() => {
42+
setLoading(false);
43+
}, []);
44+
return Component;
45+
};
46+
ReactDOM.render(<App></App>, el);
47+
} catch (error) {
48+
console.log("Render Component Error", error);
49+
}
50+
}, 300),
51+
[]
5252
);
5353

54+
useEffect(() => onParseCode(code), [code, onParseCode]);
55+
5456
return (
5557
<div className="react-live-container">
5658
<Void selectable>

packages/plugin/src/table/components/cell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { EDITOR_STATE } from "doc-editor-core";
33
import type { SetNodeOperation } from "doc-editor-delta";
44
import { HistoryEditor, Transforms } from "doc-editor-delta";
55
import { cs, EVENT_ENUM, findNodePath, getNodeTupleByDepth, isNil } from "doc-editor-utils";
6-
import throttle from "lodash-es/throttle";
6+
import { throttle } from "doc-editor-utils";
77
import type { FC } from "react";
88
import { useMemo } from "react";
99

packages/plugin/src/table/components/table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { useMemoizedFn } from "ahooks";
21
import type { BlockContext, EditorKit } from "doc-editor-core";
32
import { Transforms, useSelected } from "doc-editor-delta";
43
import { EVENT_ENUM } from "doc-editor-utils";
54
import type { FC } from "react";
65
import React, { useEffect, useMemo, useRef, useState } from "react";
76

7+
import { useMemoFn } from "../../shared/hooks/preset";
88
import type { SelectChangeEvent } from "../../shared/types/event";
99
import { createResizeObserver } from "../../shared/utils/resize";
1010
import { useCompose } from "../hooks/use-compose";
@@ -85,7 +85,7 @@ export const Table: FC<{
8585
};
8686
}, [provider.ref, props.readonly]);
8787

88-
const onEditorSelectionChange = useMemoizedFn((e: SelectChangeEvent) => {
88+
const onEditorSelectionChange = useMemoFn((e: SelectChangeEvent) => {
8989
const { previous, current } = e;
9090
if (!previous && current && sel) {
9191
setSel(null);

packages/react/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
"dependencies": {
2626
"@arco-design/web-react": "2.60.3",
2727
"react": "17.0.2",
28-
"ahooks": "^3.3.13",
2928
"lodash-es": "4.17.21",
3029
"react-dom": "17.0.2",
3130
"doc-editor-core": "workspace: *",

packages/utils/src/collection.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type { Array } from "laser-utils";
2+
import type { Object } from "laser-utils";
3+
import { isArray, isObject } from "laser-utils";
4+
5+
export class Collection {
6+
/**
7+
* Pick
8+
* @param target Object.Any
9+
* @param keys keyof Object.Any
10+
*/
11+
public static pick<T extends Object.Any, K extends keyof T>(
12+
target: T,
13+
keys: K | K[]
14+
): Pick<T, K> {
15+
const set: Set<unknown> = new Set(isArray(keys) ? keys : [keys]);
16+
const next = {} as T;
17+
for (const key in target) {
18+
if (!set.has(key)) continue;
19+
next[key] = target[key];
20+
}
21+
return next;
22+
}
23+
24+
/**
25+
* Omit
26+
* @param target Array.Any | Object.Any
27+
* @param keys keys: Array.Any
28+
*/
29+
public static omit<T extends Array.Any>(target: T, keys: T): T;
30+
public static omit<T extends Object.Any, K extends keyof T>(target: T, keys: K | K[]): Omit<T, K>;
31+
public static omit<T extends Array.Any | Object.Any>(target: T, keys: Array.Any): T | Object.Any {
32+
const set = new Set(isArray(keys) ? keys : [keys]);
33+
if (isObject(target)) {
34+
const next = {} as Object.Unknown;
35+
for (const key in target) {
36+
if (set.has(key)) continue;
37+
next[key] = target[key];
38+
}
39+
return next;
40+
}
41+
return target.filter(item => !set.has(item));
42+
}
43+
44+
/**
45+
* Patch
46+
* @param a Set<T> | T[]
47+
* @param b Set<T> | T[]
48+
*/
49+
public static patch<T>(a: Set<T> | T[], b: Set<T> | T[]) {
50+
const prev = a instanceof Set ? a : new Set(a);
51+
const next = b instanceof Set ? b : new Set(b);
52+
const effects: T[] = [];
53+
const added: T[] = [];
54+
const removed: T[] = [];
55+
for (const id of next) {
56+
if (!prev.has(id)) added.push(id);
57+
}
58+
for (const id of prev) {
59+
if (!next.has(id)) removed.push(id);
60+
}
61+
effects.push(...added, ...removed);
62+
return { effects, added, removed };
63+
}
64+
65+
/**
66+
* Union
67+
* @param a Set<T> | T[]
68+
* @param b Set<T> | T[]
69+
*/
70+
public static union<T>(a: Set<T> | T[], b: Set<T> | T[]) {
71+
return [...a, ...b];
72+
}
73+
74+
/**
75+
* Intersection
76+
* @param a Set<T> | T[]
77+
* @param b Set<T> | T[]
78+
*/
79+
public static intersection<T>(a: Set<T> | T[], b: Set<T> | T[]) {
80+
const prev = [...a];
81+
const next = b instanceof Set ? b : new Set(b);
82+
return new Set([...prev].filter(id => next.has(id)));
83+
}
84+
}

packages/utils/src/decorator.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { isFunction } from "laser-utils";
2+
3+
// ExperimentalDecorators
4+
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html
5+
6+
/**
7+
* Bind 装饰器
8+
* @param _
9+
* @param key
10+
* @param descriptor
11+
*/
12+
export function Bind<T>(_: T, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {
13+
const originalMethod = descriptor.value;
14+
if (!isFunction(originalMethod)) {
15+
throw new TypeError(`${originalMethod} is not a function`);
16+
}
17+
18+
return {
19+
configurable: true,
20+
get() {
21+
const boundFunction = originalMethod.bind(this);
22+
Object.defineProperty(this, key, {
23+
value: boundFunction,
24+
configurable: true,
25+
writable: true,
26+
enumerable: false,
27+
});
28+
return boundFunction;
29+
},
30+
};
31+
}

0 commit comments

Comments
 (0)