Skip to content

Commit

Permalink
feat: doc utils implement
Browse files Browse the repository at this point in the history
  • Loading branch information
WindRunnerMax committed Aug 25, 2024
1 parent 2c377c1 commit c950d30
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 161 deletions.
1 change: 0 additions & 1 deletion packages/plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"@arco-design/web-react": ">=2.60.3"
},
"dependencies": {
"ahooks": "3.3.13",
"react-live-runtime": "0.0.3",
"embed-drawio": "0.0.18",
"lodash-es": "4.17.21",
Expand Down
52 changes: 26 additions & 26 deletions packages/plugin/src/float-toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import "./styles/index.scss";

import { Menu } from "@arco-design/web-react";
import { useMemoizedFn } from "ahooks";
import type { EditorKit } from "doc-editor-core";
import type { TextElement } from "doc-editor-delta";
import { Editor } from "doc-editor-delta";
import { ReactEditor } from "doc-editor-delta";
import { EVENT_ENUM } from "doc-editor-utils";
import { omit } from "doc-editor-utils";
import { Collection } from "doc-editor-utils";
import type { FC } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";

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

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

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

const exec = useMemoizedFn(
(param: string, event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const [key, extraKey] = param.split(".");
const marks = Editor.marks(editor.raw);
const position = { left: 0, top: 0 };
const toolbar = toolbarRef.current;
setSelectedMarks(execSelectMarks(key, selectedMarks, MUTEX_SELECT));
if (toolbar) {
position.top = toolbar.offsetTop + toolbar.offsetHeight / 2;
position.left = toolbar.offsetLeft + toolbar.offsetWidth / 2;
}
const result = props.editor.command.exec(key, {
extraKey,
event,
position,
marks: marks as TextElement,
});
if (result) {
keepStatus.current = true;
result.then(() => (keepStatus.current = false));
}
const exec = useMemoFn((param: string, event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const [key, extraKey] = param.split(".");
const marks = Editor.marks(editor.raw);
const position = { left: 0, top: 0 };
const toolbar = toolbarRef.current;
setSelectedMarks(execSelectMarks(key, selectedMarks, MUTEX_SELECT));
if (toolbar) {
position.top = toolbar.offsetTop + toolbar.offsetHeight / 2;
position.left = toolbar.offsetLeft + toolbar.offsetWidth / 2;
}
);
const result = props.editor.command.exec(key, {
extraKey,
event,
position,
marks: marks as TextElement,
});
if (result) {
keepStatus.current = true;
result.then(() => (keepStatus.current = false));
}
});

const HoverMenu = useMemo(
() => (
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin/src/float-toolbar/utils/selection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { omit } from "doc-editor-utils";
import { Collection } from "doc-editor-utils";
import ReactDOM from "react-dom";

export const maskMenuToolBar = (element: HTMLDivElement) => {
Expand All @@ -24,8 +24,8 @@ export const execSelectMarks = (key: string, marks: string[], mutexKeys: string[
const isKeyInMarks = marks.indexOf(key) > -1;
const isKeyInMutexKeys = mutexKeys.indexOf(key) > -1;
return isKeyInMarks
? omit(marks, [key])
? Collection.omit(marks, [key])
: isKeyInMutexKeys
? [...omit(marks, mutexKeys), key]
? [...Collection.omit(marks, mutexKeys), key]
: [...marks, key];
};
14 changes: 6 additions & 8 deletions packages/plugin/src/font-base/components/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Button, InputNumber } from "@arco-design/web-react";
import { useMemoizedFn } from "ahooks";
import type { FC } from "react";
import { useMemo } from "react";

import { useMemoFn } from "../../shared/hooks/preset";
import type { FontBaseConfig } from "../types";

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

const onChange = useMemoizedFn(
(key: keyof FontBaseConfig, value: string | number | undefined) => {
changedConfig = { ...changedConfig, [key]: value };
props.onChange(changedConfig);
}
);
const onChange = useMemoFn((key: keyof FontBaseConfig, value: string | number | undefined) => {
changedConfig = { ...changedConfig, [key]: value };
props.onChange(changedConfig);
});

const resetDefault = useMemoizedFn(() => {
const resetDefault = useMemoFn(() => {
props.onChange({});
changedConfig = {};
});
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/highlight-block/components/wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Button, Trigger } from "@arco-design/web-react";
import { IconPalette } from "@arco-design/web-react/icon";
import { useMemoizedFn } from "ahooks";
import type { EditorKit } from "doc-editor-core";
import type { BlockElement } from "doc-editor-delta";
import { ReactEditor } from "doc-editor-delta";
Expand All @@ -9,6 +8,7 @@ import { setBlockNode } from "doc-editor-utils";
import type { FC } from "react";
import { useMemo } from "react";

import { useMemoFn } from "../../shared/hooks/preset";
import { HIGHLIGHT_BLOCK_KEY } from "../types";
import { COLOR_MAP } from "../types";

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

const switchAction = useMemoizedFn((index: number) => {
const switchAction = useMemoFn((index: number) => {
const path = ReactEditor.findPath(editor.raw, element);
setBlockNode(
editor.raw,
Expand Down
50 changes: 26 additions & 24 deletions packages/plugin/src/react-live/components/viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Button, Space, Spin } from "@arco-design/web-react";
import { useDebounceEffect } from "ahooks";
import type { EditorKit } from "doc-editor-core";
import { Void } from "doc-editor-core";
import type { BlockElement } from "doc-editor-delta";
import { debounce } from "doc-editor-utils";
import { isText } from "doc-editor-utils";
import type { FC } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
Expand All @@ -26,31 +26,33 @@ export const ReactLiveView: FC<{
.join("\n");
}, [props.element]);

useDebounceEffect(
() => {
const el = ref.current;
if (!el) return;
try {
const sandbox = withSandbox({ React, Button, console, Space });
// JS Plain Object -> ({...})
// React.FC -> React.Fragment / div
const compiledCode = compileWithSucrase("<div>" + code + "</div>");
const Component = renderWithDependency(compiledCode, sandbox) as JSX.Element;
const App = () => {
useEffect(() => {
setLoading(false);
}, []);
return Component;
};
ReactDOM.render(<App></App>, el);
} catch (error) {
console.log("Render Component Error", error);
}
},
[code],
{ wait: 300 }
const onParseCode = useMemo(
() =>
debounce((code: string) => {
const el = ref.current;
if (!el) return;
try {
const sandbox = withSandbox({ React, Button, console, Space });
// JS Plain Object -> ({...})
// React.FC -> React.Fragment / div
const compiledCode = compileWithSucrase("<div>" + code + "</div>");
const Component = renderWithDependency(compiledCode, sandbox) as JSX.Element;
const App = () => {
useEffect(() => {
setLoading(false);
}, []);
return Component;
};
ReactDOM.render(<App></App>, el);
} catch (error) {
console.log("Render Component Error", error);
}
}, 300),
[]
);

useEffect(() => onParseCode(code), [code, onParseCode]);

return (
<div className="react-live-container">
<Void selectable>
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/src/table/components/cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EDITOR_STATE } from "doc-editor-core";
import type { SetNodeOperation } from "doc-editor-delta";
import { HistoryEditor, Transforms } from "doc-editor-delta";
import { cs, EVENT_ENUM, findNodePath, getNodeTupleByDepth, isNil } from "doc-editor-utils";
import throttle from "lodash-es/throttle";
import { throttle } from "doc-editor-utils";
import type { FC } from "react";
import { useMemo } from "react";

Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/table/components/table.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useMemoizedFn } from "ahooks";
import type { BlockContext, EditorKit } from "doc-editor-core";
import { Transforms, useSelected } from "doc-editor-delta";
import { EVENT_ENUM } from "doc-editor-utils";
import type { FC } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";

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

const onEditorSelectionChange = useMemoizedFn((e: SelectChangeEvent) => {
const onEditorSelectionChange = useMemoFn((e: SelectChangeEvent) => {
const { previous, current } = e;
if (!previous && current && sel) {
setSel(null);
Expand Down
1 change: 0 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"dependencies": {
"@arco-design/web-react": "2.60.3",
"react": "17.0.2",
"ahooks": "^3.3.13",
"lodash-es": "4.17.21",
"react-dom": "17.0.2",
"doc-editor-core": "workspace: *",
Expand Down
84 changes: 84 additions & 0 deletions packages/utils/src/collection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { Array } from "laser-utils";
import type { Object } from "laser-utils";
import { isArray, isObject } from "laser-utils";

export class Collection {
/**
* Pick
* @param target Object.Any
* @param keys keyof Object.Any
*/
public static pick<T extends Object.Any, K extends keyof T>(
target: T,
keys: K | K[]
): Pick<T, K> {
const set: Set<unknown> = new Set(isArray(keys) ? keys : [keys]);
const next = {} as T;
for (const key in target) {
if (!set.has(key)) continue;
next[key] = target[key];
}
return next;
}

/**
* Omit
* @param target Array.Any | Object.Any
* @param keys keys: Array.Any
*/
public static omit<T extends Array.Any>(target: T, keys: T): T;
public static omit<T extends Object.Any, K extends keyof T>(target: T, keys: K | K[]): Omit<T, K>;
public static omit<T extends Array.Any | Object.Any>(target: T, keys: Array.Any): T | Object.Any {
const set = new Set(isArray(keys) ? keys : [keys]);
if (isObject(target)) {
const next = {} as Object.Unknown;
for (const key in target) {
if (set.has(key)) continue;
next[key] = target[key];
}
return next;
}
return target.filter(item => !set.has(item));
}

/**
* Patch
* @param a Set<T> | T[]
* @param b Set<T> | T[]
*/
public static patch<T>(a: Set<T> | T[], b: Set<T> | T[]) {
const prev = a instanceof Set ? a : new Set(a);
const next = b instanceof Set ? b : new Set(b);
const effects: T[] = [];
const added: T[] = [];
const removed: T[] = [];
for (const id of next) {
if (!prev.has(id)) added.push(id);
}
for (const id of prev) {
if (!next.has(id)) removed.push(id);
}
effects.push(...added, ...removed);
return { effects, added, removed };
}

/**
* Union
* @param a Set<T> | T[]
* @param b Set<T> | T[]
*/
public static union<T>(a: Set<T> | T[], b: Set<T> | T[]) {
return [...a, ...b];
}

/**
* Intersection
* @param a Set<T> | T[]
* @param b Set<T> | T[]
*/
public static intersection<T>(a: Set<T> | T[], b: Set<T> | T[]) {
const prev = [...a];
const next = b instanceof Set ? b : new Set(b);
return new Set([...prev].filter(id => next.has(id)));
}
}
31 changes: 31 additions & 0 deletions packages/utils/src/decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { isFunction } from "laser-utils";

// ExperimentalDecorators
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html

/**
* Bind 装饰器
* @param _
* @param key
* @param descriptor
*/
export function Bind<T>(_: T, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {
const originalMethod = descriptor.value;
if (!isFunction(originalMethod)) {
throw new TypeError(`${originalMethod} is not a function`);
}

return {
configurable: true,
get() {
const boundFunction = originalMethod.bind(this);
Object.defineProperty(this, key, {
value: boundFunction,
configurable: true,
writable: true,
enumerable: false,
});
return boundFunction;
},
};
}
Loading

0 comments on commit c950d30

Please sign in to comment.