diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts b/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts index c110d57793..4d28ce3cf8 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts @@ -9,30 +9,35 @@ import { import { Clip } from './clip'; import assert from 'assert'; import ClipboardParse from './clipboard-parse'; +import { Subscription } from 'rxjs'; class ClipboardPopulator { - private editor: Editor; - private hooks: PluginHooks; - private selection_manager: SelectionManager; - private clipboard: any; - private clipboard_parse: ClipboardParse; + private _editor: Editor; + private _hooks: PluginHooks; + private _selectionManager: SelectionManager; + private _clipboardParse: ClipboardParse; + private _sub = new Subscription(); constructor( editor: Editor, hooks: PluginHooks, - selectionManager: SelectionManager, - clipboard: any + selectionManager: SelectionManager ) { - this.editor = editor; - this.hooks = hooks; - this.selection_manager = selectionManager; - this.clipboard = clipboard; - this.clipboard_parse = new ClipboardParse(editor); - hooks.addHook(HookType.BEFORE_COPY, this.populate_app_clipboard, this); - hooks.addHook(HookType.BEFORE_CUT, this.populate_app_clipboard, this); + this._editor = editor; + this._hooks = hooks; + this._selectionManager = selectionManager; + this._clipboardParse = new ClipboardParse(editor); + this._sub.add( + hooks + .get(HookType.BEFORE_COPY) + .subscribe(this._populateAppClipboard) + ); + this._sub.add( + hooks.get(HookType.BEFORE_CUT).subscribe(this._populateAppClipboard) + ); } - private async populate_app_clipboard(e: ClipboardEvent) { + private _populateAppClipboard = async (e: ClipboardEvent) => { e.preventDefault(); e.stopPropagation(); const clips = await this.getClips(); @@ -58,7 +63,8 @@ class ClipboardPopulator { } } } - } + }; + private copy_to_cliboard_from_pc(clips: any[]) { let success = false; const tempElem = document.createElement('textarea'); @@ -91,8 +97,8 @@ class ClipboardPopulator { } private async get_clip_block_info(selBlock: SelectBlock) { - const block = await this.editor.getBlockById(selBlock.blockId); - const block_view = this.editor.getView(block.type); + const block = await this._editor.getBlockById(selBlock.blockId); + const block_view = this._editor.getView(block.type); assert(block_view); const block_info: ClipBlockInfo = { type: block.type, @@ -112,7 +118,8 @@ class ClipboardPopulator { private async get_inner_clip(): Promise { const clips: ClipBlockInfo[] = []; - const select_info: SelectInfo = await this.selection_manager.getSelectInfo(); + const select_info: SelectInfo = + await this._selectionManager.getSelectInfo(); for (let i = 0; i < select_info.blocks.length; i++) { const sel_block = select_info.blocks[i]; const clip_block_info = await this.get_clip_block_info(sel_block); @@ -136,7 +143,7 @@ class ClipboardPopulator { ) ); - const html_clip = await this.clipboard_parse.generateHtml(); + const html_clip = await this._clipboardParse.generateHtml(); html_clip && clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, html_clip)); @@ -144,12 +151,8 @@ class ClipboardPopulator { } disposeInternal() { - this.hooks.removeHook( - HookType.BEFORE_COPY, - this.populate_app_clipboard - ); - this.hooks.removeHook(HookType.BEFORE_CUT, this.populate_app_clipboard); - this.hooks = null; + this._sub.unsubscribe(); + this._hooks = null; } } diff --git a/libs/components/editor-core/src/editor/editor.ts b/libs/components/editor-core/src/editor/editor.ts index 29950c0395..a0e28f3574 100644 --- a/libs/components/editor-core/src/editor/editor.ts +++ b/libs/components/editor-core/src/editor/editor.ts @@ -186,8 +186,7 @@ export class Editor implements Virgo { this.clipboard_populator = new ClipboardPopulator( this, this.hooks, - this.selectionManager, - this.clipboard + this.selectionManager ); } } diff --git a/libs/components/editor-core/src/editor/plugin/hooks.ts b/libs/components/editor-core/src/editor/plugin/hooks.ts index 1e6e94f842..ffc56dffea 100644 --- a/libs/components/editor-core/src/editor/plugin/hooks.ts +++ b/libs/components/editor-core/src/editor/plugin/hooks.ts @@ -1,91 +1,37 @@ -import { - HooksRunner, - PluginHooks, - HookType, - HookBaseArgs, - BlockDomInfo, - AnyFunction, - AnyThisType, -} from '../types'; - -interface PluginHookInfo { - thisObj?: AnyThisType; - callback: AnyFunction; - once: boolean; -} +import { Observable, Subject } from 'rxjs'; +import { HooksRunner, HookType, BlockDomInfo, PluginHooks } from '../types'; export class Hooks implements HooksRunner, PluginHooks { - private _hooksMap: Map = new Map(); + private _subject: Record> = {}; + private _observable: Record> = {}; dispose() { - this._hooksMap.clear(); + this._subject = {}; } private _runHook(key: HookType, ...params: unknown[]): void { - const hookInfos: PluginHookInfo[] = this._hooksMap.get(key) || []; - hookInfos.forEach(hookInfo => { - if (hookInfo.once) { - this.removeHook(key, hookInfo.callback); - } - let isStoppedPropagation = false; - const hookOption: HookBaseArgs = { - stopImmediatePropagation: () => { - isStoppedPropagation = true; - }, - }; - hookInfo.callback.call( - hookInfo.thisObj || this, - ...params, - hookOption - ); - return isStoppedPropagation; - }); + if (this._subject[key] == null) { + this._subject[key] = new Subject(); + this._observable[key] = this._subject[key].asObservable(); + } + let payload: unknown = params; + + if (params.length === 0) { + payload = undefined; + } + if (params.length === 1) { + payload = params[0]; + } + this._subject[key].next(payload); } - private _hasHook(key: HookType, callback: AnyFunction): boolean { - const hookInfos: PluginHookInfo[] = this._hooksMap.get(key) || []; - for (let i = hookInfos.length - 1; i >= 0; i--) { - if (hookInfos[i].callback === callback) { - return true; - } + public get(key: K) { + if (this._subject[key] == null) { + this._subject[key] = new Subject(); + this._observable[key] = this._subject[key].asObservable(); } - return false; - } - // 执行多次 - public addHook( - key: HookType, - callback: AnyFunction, - thisObj?: AnyThisType, - once?: boolean - ): void { - if (this._hasHook(key, callback)) { - throw new Error('Duplicate registration of the same class'); - } - if (!this._hooksMap.has(key)) { - this._hooksMap.set(key, []); - } - const hookInfos: PluginHookInfo[] = this._hooksMap.get(key); - hookInfos.push({ callback, thisObj, once }); - } - - // 执行一次 - public addOnceHook( - key: HookType, - callback: AnyFunction, - thisObj?: AnyThisType - ): void { - this.addHook(key, callback, thisObj, true); - } - - // 移除 - public removeHook(key: HookType, callback: AnyFunction): void { - const hookInfos: PluginHookInfo[] = this._hooksMap.get(key) || []; - for (let i = hookInfos.length - 1; i >= 0; i--) { - if (hookInfos[i].callback === callback) { - hookInfos.splice(i, 1); - } - } + return this._observable[key] as any; } public init(): void { diff --git a/libs/components/editor-core/src/editor/scroll/scroll.ts b/libs/components/editor-core/src/editor/scroll/scroll.ts index 1c8ccbd112..08651fe63b 100644 --- a/libs/components/editor-core/src/editor/scroll/scroll.ts +++ b/libs/components/editor-core/src/editor/scroll/scroll.ts @@ -1,7 +1,7 @@ import EventEmitter from 'eventemitter3'; import { domToRect, Rect } from '@toeverything/utils'; -import type { Editor as Block_editor } from '../editor'; +import type { Editor as BlockEditor } from '../editor'; import { AsyncBlock } from '../block'; @@ -9,7 +9,7 @@ type VerticalTypes = 'up' | 'down' | null; type HorizontalTypes = 'left' | 'right' | null; export class ScrollManager { - private _editor: Block_editor; + private _editor: BlockEditor; private _animationFrame: null | number = null; private _eventName = 'scrolling'; private _currentMoveDirection: [HorizontalTypes, VerticalTypes] = [ @@ -20,9 +20,8 @@ export class ScrollManager { private _scrollMoveOffset = 8; private _scrollingEvent = new EventEmitter(); - constructor(editor: Block_editor) { + constructor(editor: BlockEditor) { this._editor = editor; - console.log('scrollmanager constructor', this._editor.ui_container); } private _updateScrollInfo(left: number, top: number) { diff --git a/libs/components/editor-core/src/editor/types.ts b/libs/components/editor-core/src/editor/types.ts index 6d4112ba66..b9928ae571 100644 --- a/libs/components/editor-core/src/editor/types.ts +++ b/libs/components/editor-core/src/editor/types.ts @@ -21,6 +21,7 @@ import type { BlockHelper } from './block/block-helper'; import type { BlockCommands } from './commands/block-commands'; import type { DragDropManager } from './drag-drop'; import { MouseManager } from './mouse'; +import { Observable } from 'rxjs'; // import { BrowserClipboard } from './clipboard/browser-clipboard'; @@ -176,10 +177,6 @@ export enum HookType { BEFORE_CUT = 'beforeCut', } -export interface HookBaseArgs { - stopImmediatePropagation: () => void; -} - export interface BlockDomInfo { blockId: string; dom: HTMLElement; @@ -227,25 +224,16 @@ export interface HooksRunner { beforeCut: (e: ClipboardEvent) => void; } -export type AnyFunction = (...args: any[]) => any; -export type AnyThisType = ThisParameterType; +export type PayloadType> = T extends [] + ? void + : T extends [infer U] + ? U + : T; -// hook管理,在editor、plugin中使用 export interface PluginHooks { - // 执行多次 - addHook: ( - key: HookType, - callback: AnyFunction, - thisObj?: AnyThisType, - once?: boolean - ) => void; - // 执行一次 - addOnceHook: ( - key: HookType, - callback: AnyFunction, - thisObj?: AnyThisType - ) => void; - // 移除 - removeHook: (key: HookType, callback: AnyFunction) => void; + get( + key: K + ): Observable>>; } + export * from './drag-drop/types'; diff --git a/libs/components/editor-plugins/src/base-plugin.ts b/libs/components/editor-plugins/src/base-plugin.ts index f6086fe56d..bc3738cc76 100644 --- a/libs/components/editor-plugins/src/base-plugin.ts +++ b/libs/components/editor-plugins/src/base-plugin.ts @@ -1,17 +1,17 @@ import { - HookType, Virgo, Plugin, PluginHooks, + HookType, } from '@toeverything/framework/virgo'; import { genErrorObj } from '@toeverything/utils'; +import { Subscription } from 'rxjs'; export abstract class BasePlugin implements Plugin { protected editor: Virgo; protected hooks: PluginHooks; - private hook_queue: [type: HookType, fn: (...args: unknown[]) => void][] = - []; - private is_disposed = false; + protected sub: Subscription; + private _disposed = false; // Unique identifier to distinguish between different Plugins public static get pluginName(): string { @@ -27,22 +27,8 @@ export abstract class BasePlugin implements Plugin { constructor(editor: Virgo, hooks: PluginHooks) { this.editor = editor; - // TODO perfect it - this.hooks = { - addHook: (...args) => { - this.hook_queue.push([args[0], args[1]]); - return hooks.addHook(...args); - }, - addOnceHook(...args) { - return hooks.addHook(...args); - }, - // TODO fix remove - removeHook(...args) { - return hooks.removeHook(...args); - }, - }; - this._onRender = this._onRender.bind(this); - hooks.addHook(HookType.RENDER, this._onRender, this); + this.hooks = hooks; + this.sub = hooks.get(HookType.RENDER).subscribe(() => this._onRender()); } /** @@ -62,18 +48,12 @@ export abstract class BasePlugin implements Plugin { public dispose(): void { // See https://stackoverflow.com/questions/33387318/access-to-static-properties-via-this-constructor-in-typescript const pluginName = (this.constructor as typeof BasePlugin).pluginName; - if (this.is_disposed) { + if (this._disposed) { console.warn(`Plugin '${pluginName}' already disposed`); return; } - this.is_disposed = true; - // FIX will remove hook multiple times - // if the hook has been removed manually - // or set once flag when add hook - this.hook_queue.forEach(([type, fn]) => { - this.hooks.removeHook(type, fn); - }); - this.hook_queue = []; + this.sub.unsubscribe(); + this._disposed = true; const errorMsg = `You are trying to access an invalid editor or hooks. The plugin '${pluginName}' has been disposed. diff --git a/libs/components/editor-plugins/src/block-property/Plugin.tsx b/libs/components/editor-plugins/src/block-property/Plugin.tsx new file mode 100644 index 0000000000..6178064f52 --- /dev/null +++ b/libs/components/editor-plugins/src/block-property/Plugin.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { BasePlugin } from '../base-plugin'; +import { BlockDomInfo, HookType } from '@toeverything/framework/virgo'; + +import View from './View'; +import { Subscription } from 'rxjs'; +const PLUGIN_NAME = 'block-property'; + +export class BlockPropertyPlugin extends BasePlugin { + public static override get pluginName(): string { + return PLUGIN_NAME; + } + + private _root: Root | undefined; + private _rootDom: HTMLElement; + // record mouse moving block id + private _currentSlidingBlockInfo: BlockDomInfo; + private _hover = false; + + private _setHover = (isHover: boolean) => { + this._hover = isHover; + }; + private _insertRootToBlock = async () => { + this._rootDom = document.createElement('div'); + this._rootDom.style.position = 'relative'; + this._rootDom.style.zIndex = '1000'; + this._rootDom.classList.add(`id-${PLUGIN_NAME}`); + this._currentSlidingBlockInfo.dom.appendChild(this._rootDom); + this._root = createRoot(this._rootDom); + }; + + private _onSlidingBlockChange = async (blockDomInfo: BlockDomInfo) => { + this._currentSlidingBlockInfo = blockDomInfo; + await this._insertRootToBlock(); + this._renderView(); + }; + + private _onMouseMove = async ([event, blockDomInfo]: [ + React.MouseEvent, + BlockDomInfo + ]) => { + if ( + blockDomInfo.blockId !== this._currentSlidingBlockInfo?.blockId && + !this._hover + ) { + await this.dispose(); + + await this._onSlidingBlockChange(blockDomInfo); + } + }; + + private _renderView = () => { + this._root.render( + + ); + }; + + protected override _onRender(): void { + const sub = this.hooks + .get(HookType.AFTER_ON_NODE_MOUSE_MOVE) + .subscribe(this._onMouseMove); + + this.sub.add(sub); + } + + override async dispose() { + if (this._currentSlidingBlockInfo) { + this._currentSlidingBlockInfo.dom.removeChild(this._rootDom); + this._currentSlidingBlockInfo = undefined; + } + + this._rootDom = undefined; + if (this._root) { + this._root.unmount(); + } + super.dispose(); + } +} diff --git a/libs/components/editor-plugins/src/block-property/view.tsx b/libs/components/editor-plugins/src/block-property/View.tsx similarity index 100% rename from libs/components/editor-plugins/src/block-property/view.tsx rename to libs/components/editor-plugins/src/block-property/View.tsx diff --git a/libs/components/editor-plugins/src/block-property/index.tsx b/libs/components/editor-plugins/src/block-property/index.tsx deleted file mode 100644 index ced1105839..0000000000 --- a/libs/components/editor-plugins/src/block-property/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import { createRoot, type Root } from 'react-dom/client'; -import { BasePlugin } from '../base-plugin'; -import { BlockDomInfo, HookType } from '@toeverything/framework/virgo'; - -import View from './view'; -const PLUGIN_NAME = 'block-property'; - -export class BlockPropertyPlugin extends BasePlugin { - public static override get pluginName(): string { - return PLUGIN_NAME; - } - - private root: Root | undefined; - private root_dom: HTMLElement; - // record mouse moving block id - private current_sliding_block_info: BlockDomInfo; - private is_render = false; - private is_hover = false; - - private set_is_hover = (isHover: boolean) => { - this.is_hover = isHover; - }; - private insert_root_to_block = async () => { - this.root_dom = document.createElement('div'); - this.root_dom.style.position = 'relative'; - this.root_dom.style.zIndex = '1000'; - this.root_dom.classList.add(`id-${PLUGIN_NAME}`); - this.current_sliding_block_info.dom.appendChild(this.root_dom); - this.root = createRoot(this.root_dom); - }; - - private on_sliding_block_change = async (blockDomInfo: BlockDomInfo) => { - this.current_sliding_block_info = blockDomInfo; - await this.insert_root_to_block(); - this.render_view(); - this.is_render = true; - }; - - private on_mouse_move = async ( - event: React.MouseEvent, - blockDomInfo: BlockDomInfo - ) => { - if ( - blockDomInfo.blockId !== this.current_sliding_block_info?.blockId && - !this.is_hover - ) { - await this.dispose(); - - await this.on_sliding_block_change(blockDomInfo); - } - }; - - private render_view = () => { - this.root.render( - - ); - }; - - protected override _onRender(): void { - this.hooks.addHook( - HookType.AFTER_ON_NODE_MOUSE_MOVE, - this.on_mouse_move, - this - ); - } - - override async dispose() { - if (this.current_sliding_block_info) { - this.current_sliding_block_info.dom.removeChild(this.root_dom); - this.current_sliding_block_info = undefined; - } - - this.root_dom = undefined; - this.is_render = false; - if (this.root) { - this.root.unmount(); - } - } -} diff --git a/libs/components/editor-plugins/src/menu/command-menu/Container.tsx b/libs/components/editor-plugins/src/menu/command-menu/Container.tsx index 2fc965e041..216a601524 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Container.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Container.tsx @@ -190,13 +190,12 @@ export const CommandMenuContainer = ({ ); useEffect(() => { - hooks.addHook(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, handleKeyDown); + const sub = hooks + .get(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE) + .subscribe(handle_key_down); return () => { - hooks.removeHook( - HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, - handleKeyDown - ); + sub.unsubscribe(); }; }, [hooks, handleKeyDown]); diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index b56354521e..2194059724 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -18,7 +18,7 @@ import { commonCommandMenuHandler, menuItemsMap, } from './config'; -import { QueryBlocks, QueryResult } from '../../search'; +import { QueryResult } from '../../search'; export type CommandMenuProps = { editor: Virgo; @@ -31,9 +31,11 @@ type CommandMenuPosition = { bottom: number | 'initial'; }; +const COMMAND_MENU_HEIGHT = 509; + export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { - const [is_show, set_is_show] = useState(false); - const [block_id, set_block_id] = useState(); + const [show, setShow] = useState(false); + const [blockId, setBlockId] = useState(); const [commandMenuPosition, setCommandMenuPosition] = useState({ left: 0, @@ -85,8 +87,8 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { anchorNode.id ); if (text.endsWith('/')) { - set_block_id(anchorNode.id); - editor.blockHelper.removeSearchSlash(block_id); + setBlockId(anchorNode.id); + editor.blockHelper.removeSearchSlash(blockId); setTimeout(() => { const textSelection = editor.blockHelper.selectionToSlateRange( @@ -103,18 +105,16 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { } }); set_search_text(''); - set_is_show(true); + setShow(true); const rect = editor.selection.currentSelectInfo?.browserSelection ?.getRangeAt(0) ?.getBoundingClientRect(); if (rect) { let top = rect.top; - let clientHeight = + const clientHeight = document.documentElement.clientHeight; - const COMMAND_MENU_HEIGHT = - window.innerHeight * 0.4; if (clientHeight - top <= COMMAND_MENU_HEIGHT) { top = clientHeight - top + 10; setCommandMenuPosition({ @@ -135,33 +135,33 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { } } }, - [editor, block_id] + [editor, blockId] ); const handle_click_others = useCallback( (event: React.KeyboardEvent) => { - if (is_show) { + if (show) { const { anchorNode } = editor.selection.currentSelectInfo; - if (anchorNode.id !== block_id) { - set_is_show(false); + if (anchorNode.id !== blockId) { + setShow(false); return; } setTimeout(() => { const searchText = - editor.blockHelper.getSearchSlashText(block_id); + editor.blockHelper.getSearchSlashText(blockId); // check if has search text if (searchText && searchText.startsWith('/')) { set_search_text(searchText.slice(1)); } else { - set_is_show(false); + setShow(false); } if (searchText.length > 6 && !types.length) { - set_is_show(false); + setShow(false); } }); } }, - [editor, is_show, block_id, types] + [editor, show, blockId, types] ); const handle_keyup = useCallback( @@ -175,59 +175,61 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { const handle_key_down = useCallback( (event: React.KeyboardEvent) => { if (event.code === 'Escape') { - set_is_show(false); + setShow(false); } }, [] ); useEffect(() => { - hooks.addHook(HookType.ON_ROOT_NODE_KEYUP, handle_keyup); - hooks.addHook(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, handle_key_down); + const sub = hooks + .get(HookType.ON_ROOT_NODE_KEYUP) + .subscribe(handle_keyup); + sub.add( + hooks + .get(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE) + .subscribe(handle_key_down) + ); return () => { - hooks.removeHook(HookType.ON_ROOT_NODE_KEYUP, handle_keyup); - hooks.removeHook( - HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, - handle_key_down - ); + sub.unsubscribe(); }; }, [handle_keyup, handle_key_down, hooks]); const handle_click_away = () => { - set_is_show(false); + setShow(false); }; const handle_selected = async (type: BlockFlavorKeys | string) => { - const text = await editor.commands.textCommands.getBlockText(block_id); - editor.blockHelper.removeSearchSlash(block_id, true); + const text = await editor.commands.textCommands.getBlockText(blockId); + editor.blockHelper.removeSearchSlash(blockId, true); if (type.startsWith('Virgo')) { const handler = commandMenuHandlerMap[Protocol.Block.Type.reference]; - handler(block_id, type, editor); + handler(blockId, type, editor); } else if (text.length > 1) { const handler = commandMenuHandlerMap[type]; if (handler) { - await handler(block_id, type, editor); + await handler(blockId, type, editor); } else { - await commonCommandMenuHandler(block_id, type, editor); + await commonCommandMenuHandler(blockId, type, editor); } - const block = await editor.getBlockById(block_id); + const block = await editor.getBlockById(blockId); block.remove(); } else { if (Protocol.Block.Type[type as BlockFlavorKeys]) { const block = await editor.commands.blockCommands.convertBlock( - block_id, + blockId, type as BlockFlavorKeys ); block.firstCreateFlag = true; } } - set_is_show(false); + setShow(false); }; const handle_close = () => { - editor.blockHelper.removeSearchSlash(block_id); + editor.blockHelper.removeSearchSlash(blockId); }; return ( @@ -237,24 +239,21 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { ref={commandMenuContentRef} > - {/* MuiClickAwayListener 渲染子节点问题*/} -
- -
+
); diff --git a/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx b/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx index 2040cfc33f..d4220e731d 100644 --- a/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx +++ b/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx @@ -91,29 +91,31 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) { [editor, groupBlock] ); - const handleRootDragEnd = (e: DragEvent) => { + const handleRootDragEnd = () => { setDragOverGroup(null); }; useEffect(() => { - hooks.addHook(HookType.ON_ROOTNODE_MOUSE_MOVE, handleRootMouseMove); - hooks.addHook(HookType.ON_ROOTNODE_MOUSE_DOWN, handleRootMouseDown); - hooks.addHook(HookType.ON_ROOTNODE_DRAG_OVER, handleRootDragOver); - hooks.addHook(HookType.ON_ROOTNODE_DRAG_END, handleRootDragEnd); + const sub = hooks + .get(HookType.ON_ROOTNODE_MOUSE_MOVE) + .subscribe(handleRootMouseMove); + sub.add( + hooks + .get(HookType.ON_ROOTNODE_MOUSE_DOWN) + .subscribe(handleRootMouseDown) + ); + sub.add( + hooks + .get(HookType.ON_ROOTNODE_DRAG_OVER) + .subscribe(handleRootDragOver) + ); + sub.add( + hooks + .get(HookType.ON_ROOTNODE_DRAG_END) + .subscribe(handleRootDragEnd) + ); return () => { - hooks.removeHook( - HookType.ON_ROOTNODE_MOUSE_MOVE, - handleRootMouseMove - ); - hooks.removeHook( - HookType.ON_ROOTNODE_MOUSE_DOWN, - handleRootMouseDown - ); - hooks.removeHook( - HookType.ON_ROOTNODE_DRAG_OVER, - handleRootDragOver - ); - hooks.removeHook(HookType.ON_ROOTNODE_DRAG_END, handleRootDragEnd); + sub.unsubscribe(); }; }, [ hooks, diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx index 44932ac99c..e8e8b0fc6d 100644 --- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx +++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx @@ -3,7 +3,6 @@ import { useState, useEffect, FC } from 'react'; import { Virgo, BlockDomInfo, - HookType, PluginHooks, BlockDropPlacement, } from '@toeverything/framework/virgo'; @@ -116,7 +115,7 @@ function DragComponent(props: { } export const LeftMenuDraggable: FC = props => { - const { editor, blockInfo, defaultVisible, hooks, lineInfo } = props; + const { editor, blockInfo, defaultVisible, lineInfo } = props; const [visible, setVisible] = useState(defaultVisible); const [anchorEl, setAnchorEl] = useState(); @@ -128,10 +127,6 @@ export const LeftMenuDraggable: FC = props => { window.addEventListener('dragover', handleDragOverCapture, { capture: true, }); - hooks.addHook( - HookType.ON_ROOTNODE_DRAG_OVER_CAPTURE, - handleDragOverCapture - ); const onDragStart = async (event: React.DragEvent) => { editor.dragDropManager.isOnDrag = true; diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx index 98eda5a1e7..08f80c13bb 100644 --- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx +++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx @@ -8,10 +8,10 @@ import { Subject } from 'rxjs'; import { domToRect, last, Point } from '@toeverything/utils'; export class LeftMenuPlugin extends BasePlugin { - private mousedown?: boolean; - private root?: PluginRenderRoot; - private preBlockId: string; - private hideTimer: number; + private _mousedown?: boolean; + private _root?: PluginRenderRoot; + private _preBlockId: string; + private _hideTimer: number; private _blockInfo: Subject = new Subject(); private _lineInfo: LineInfoSubject = new Subject(); @@ -21,39 +21,49 @@ export class LeftMenuPlugin extends BasePlugin { } public override init(): void { - this.hooks.addHook( - HookType.AFTER_ON_NODE_MOUSE_MOVE, - this._handleMouseMove + this.sub.add( + this.hooks + .get(HookType.AFTER_ON_NODE_MOUSE_MOVE) + .subscribe(this._handleMouseMove) ); - this.hooks.addHook( - HookType.ON_ROOTNODE_MOUSE_DOWN, - this._handleMouseDown + this.sub.add( + this.hooks + .get(HookType.ON_ROOTNODE_MOUSE_DOWN) + .subscribe(this._handleMouseDown) ); - this.hooks.addHook( - HookType.ON_ROOTNODE_MOUSE_LEAVE, - this._handleRootMouseLeave, - this + this.sub.add( + this.hooks + .get(HookType.ON_ROOTNODE_MOUSE_LEAVE) + .subscribe(this._hideLeftMenu) ); - this.hooks.addHook(HookType.ON_ROOTNODE_MOUSE_UP, this._handleMouseUp); - this.hooks.addHook( - HookType.AFTER_ON_NODE_DRAG_OVER, - this._handleDragOverBlockNode + this.sub.add( + this.hooks + .get(HookType.ON_ROOTNODE_MOUSE_UP) + .subscribe(this._handleMouseUp) + ); + this.sub.add( + this.hooks + .get(HookType.AFTER_ON_NODE_DRAG_OVER) + .subscribe(this._handleDragOverBlockNode) + ); + this.sub.add( + this.hooks + .get(HookType.ON_ROOT_NODE_KEYDOWN) + .subscribe(this._handleKeyDown) + ); + this.sub.add( + this.hooks.get(HookType.ON_ROOTNODE_DROP).subscribe(this._onDrop) ); - this.hooks.addHook(HookType.ON_ROOT_NODE_KEYDOWN, this._handleKeyDown); - this.hooks.addHook(HookType.ON_ROOTNODE_DROP, this._onDrop); } - private _handleRootMouseLeave() { - this._hideLeftMenu(); - } private _onDrop = () => { - this.preBlockId = ''; + this._preBlockId = ''; this._lineInfo.next(undefined); }; - private _handleDragOverBlockNode = async ( - event: React.DragEvent, - blockInfo: BlockDomInfo - ) => { + private _handleDragOverBlockNode = async ([event, blockInfo]: [ + React.DragEvent, + BlockDomInfo + ]) => { const { type, dom, blockId } = blockInfo; event.preventDefault(); if (this.editor.dragDropManager.isDragBlock(event)) { @@ -70,24 +80,24 @@ export class LeftMenuPlugin extends BasePlugin { } }; - private _handleMouseMove = async ( - e: React.MouseEvent, - node: BlockDomInfo - ) => { - if (!this.hideTimer) { - this.hideTimer = window.setTimeout(() => { - if (this.mousedown) { + private _handleMouseMove = async ([e, node]: [ + React.MouseEvent, + BlockDomInfo + ]) => { + if (!this._hideTimer) { + this._hideTimer = window.setTimeout(() => { + if (this._mousedown) { this._hideLeftMenu(); return; } - this.hideTimer = 0; + this._hideTimer = 0; }, 300); } if (this.editor.readonly) { this._hideLeftMenu(); return; } - if (node.blockId !== this.preBlockId) { + if (node.blockId !== this._preBlockId) { if (node.dom) { const mousePoint = new Point(e.clientX, e.clientY); const children = await ( @@ -109,27 +119,21 @@ export class LeftMenuPlugin extends BasePlugin { } } } - this.preBlockId = node.blockId; + this._preBlockId = node.blockId; this._showLeftMenu(node); } }; - private _handleMouseUp( - e: React.MouseEvent, - node: BlockDomInfo - ) { - if (this.hideTimer) { - window.clearTimeout(this.hideTimer); - this.hideTimer = 0; + private _handleMouseUp() { + if (this._hideTimer) { + window.clearTimeout(this._hideTimer); + this._hideTimer = 0; } - this.mousedown = false; + this._mousedown = false; } - private _handleMouseDown = ( - e: React.MouseEvent, - node: BlockDomInfo - ) => { - this.mousedown = true; + private _handleMouseDown = () => { + this._mousedown = true; }; private _hideLeftMenu = (): void => { @@ -148,14 +152,14 @@ export class LeftMenuPlugin extends BasePlugin { }; protected override _onRender(): void { - this.root = new PluginRenderRoot({ + this._root = new PluginRenderRoot({ name: LeftMenuPlugin.pluginName, render: (...args) => { return this.editor.reactRenderRoot?.render(...args); }, }); - this.root.mount(); - this.root.render( + this._root.mount(); + this._root.render( { - hooks.addHook(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, handle_key_down); + const sub = hooks + .get(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE) + .subscribe(handle_key_down); return () => { - hooks.removeHook( - HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, - handle_key_down - ); + sub.unsubscribe(); }; }, [hooks, handle_key_down]); diff --git a/libs/components/editor-plugins/src/menu/reference-menu/ReferenceMenu.tsx b/libs/components/editor-plugins/src/menu/reference-menu/ReferenceMenu.tsx index ab16648833..cbd443927a 100644 --- a/libs/components/editor-plugins/src/menu/reference-menu/ReferenceMenu.tsx +++ b/libs/components/editor-plugins/src/menu/reference-menu/ReferenceMenu.tsx @@ -5,7 +5,7 @@ import { MuiClickAwayListener } from '@toeverything/components/ui'; import { Virgo, HookType, PluginHooks } from '@toeverything/framework/virgo'; import { Point } from '@toeverything/utils'; -import { ReferenceMenuContainer } from './container'; +import { ReferenceMenuContainer } from './Container'; import { QueryBlocks, QueryResult } from '../../search'; export type ReferenceMenuProps = { @@ -87,15 +87,17 @@ export const ReferenceMenu = ({ editor, hooks, style }: ReferenceMenuProps) => { ); useEffect(() => { - hooks.addHook(HookType.ON_ROOT_NODE_KEYUP, handle_keyup); - hooks.addHook(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, handle_key_down); + const sub = hooks + .get(HookType.ON_ROOT_NODE_KEYUP) + .subscribe(handle_keyup); + sub.add( + hooks + .get(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE) + .subscribe(handle_key_down) + ); return () => { - hooks.removeHook(HookType.ON_ROOT_NODE_KEYUP, handle_keyup); - hooks.removeHook( - HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE, - handle_key_down - ); + sub.unsubscribe(); }; }, [handle_keyup, handle_key_down, hooks]); diff --git a/libs/components/editor-plugins/src/menu/selection-group-menu/MenuApp.tsx b/libs/components/editor-plugins/src/menu/selection-group-menu/MenuApp.tsx index 3c26ad4dec..c55e474915 100644 --- a/libs/components/editor-plugins/src/menu/selection-group-menu/MenuApp.tsx +++ b/libs/components/editor-plugins/src/menu/selection-group-menu/MenuApp.tsx @@ -3,30 +3,10 @@ import { styled, } from '@toeverything/components/ui'; import { Protocol } from '@toeverything/datasource/db-service'; -import type { - AsyncBlock, - PluginHooks, - Virgo, -} from '@toeverything/framework/virgo'; -import { - createContext, - useContext, - useEffect, - useState, - type CSSProperties, -} from 'react'; +import type { AsyncBlock, Virgo } from '@toeverything/framework/virgo'; +import { useEffect, useState, type CSSProperties } from 'react'; -export type Store = - | { - editor: Virgo; - hooks: PluginHooks; - } - | Record; - -export const StoreContext = createContext({}); - -export const MenuApp = () => { - const { editor } = useContext(StoreContext); +export const MenuApp = ({ editor }: { editor: Virgo }) => { const [show, setShow] = useState(false); const [style, setStyle] = useState(); const [selectedNodes, setSelectedNodes] = useState([]); diff --git a/libs/components/editor-plugins/src/menu/selection-group-menu/SelectionGroupPlugin.tsx b/libs/components/editor-plugins/src/menu/selection-group-menu/SelectionGroupPlugin.tsx index 38726df026..970740b9a2 100644 --- a/libs/components/editor-plugins/src/menu/selection-group-menu/SelectionGroupPlugin.tsx +++ b/libs/components/editor-plugins/src/menu/selection-group-menu/SelectionGroupPlugin.tsx @@ -1,7 +1,7 @@ import { StrictMode } from 'react'; import { BasePlugin } from '../../base-plugin'; import { PluginRenderRoot } from '../../utils'; -import { MenuApp, StoreContext } from './MenuApp'; +import { MenuApp } from './MenuApp'; const PLUGIN_NAME = 'selection-group'; @@ -10,27 +10,23 @@ export class SelectionGroupPlugin extends BasePlugin { return PLUGIN_NAME; } - private root: PluginRenderRoot | undefined; + private _root: PluginRenderRoot | undefined; protected override _onRender() { - this.root = new PluginRenderRoot({ + this._root = new PluginRenderRoot({ name: SelectionGroupPlugin.pluginName, render: this.editor.reactRenderRoot?.render, }); - this.root.mount(); - this.root.render( + this._root.mount(); + this._root.render( - - - + ); } public override dispose() { - this.root?.unmount(); + this._root?.unmount(); super.dispose(); } } diff --git a/libs/components/editor-plugins/src/search/index.tsx b/libs/components/editor-plugins/src/search/index.tsx index 41a6c44db6..f49de00a70 100644 --- a/libs/components/editor-plugins/src/search/index.tsx +++ b/libs/components/editor-plugins/src/search/index.tsx @@ -15,7 +15,9 @@ export class FullTextSearchPlugin extends BasePlugin { } public override init(): void { - this.hooks.addHook(HookType.ON_SEARCH, this.handle_search, this); + this.sub.add( + this.hooks.get(HookType.ON_SEARCH).subscribe(this._handleSearch) + ); } protected override _onRender(): void { @@ -31,12 +33,13 @@ export class FullTextSearchPlugin extends BasePlugin { this.#root.unmount(); // this.#root = undefined; } + this.sub.unsubscribe(); } - private handle_search() { + private _handleSearch = () => { this.editor.setHotKeysScope('search'); this.render_search(); - } + }; private render_search() { if (this.#root) { this.#root.mount();