import { BlockType } from '@/appflowy_app/interfaces/document'; import { useAppSelector } from '@/appflowy_app/stores/store'; import { debounce } from '@/appflowy_app/utils/tool'; import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { YDocControllerContext } from '../../../stores/effects/document/document_controller'; import { Node } from '@/appflowy_app/stores/reducers/document/slice'; import { v4 } from 'uuid'; export function useBlockSideTools({ container }: { container: HTMLDivElement }) { const [nodeId, setHoverNodeId] = useState(''); const ref = useRef(null); const nodes = useAppSelector((state) => state.document.nodes); const { insertAfter } = useController(); const handleMouseMove = useCallback((e: MouseEvent) => { const { clientX, clientY } = e; const x = clientX; const y = clientY; const id = getNodeIdByPoint(x, y); if (!id) { setHoverNodeId(''); } else { if ([BlockType.ColumnBlock].includes(nodes[id].type)) { setHoverNodeId(''); return; } setHoverNodeId(id); } }, []); const debounceMove = useMemo(() => debounce(handleMouseMove, 30), [handleMouseMove]); useEffect(() => { const el = ref.current; if (!el || !nodeId) return; const node = nodes[nodeId]; if (!node) { el.style.opacity = '0'; el.style.zIndex = '-1'; } else { el.style.opacity = '1'; el.style.zIndex = '1'; el.style.top = '1px'; if (node?.type === BlockType.HeadingBlock) { if (node.data.style?.level === 1) { el.style.top = '8px'; } else if (node.data.style?.level === 2) { el.style.top = '6px'; } else { el.style.top = '5px'; } } } }, [nodeId, nodes]); const handleAddClick = useCallback(() => { if (!nodeId) return; insertAfter(nodes[nodeId]); }, [nodeId, nodes]); useEffect(() => { container.addEventListener('mousemove', debounceMove); return () => { container.removeEventListener('mousemove', debounceMove); }; }, [debounceMove]); return { nodeId, ref, handleAddClick, }; } function useController() { const controller = useContext(YDocControllerContext); const insertAfter = useCallback((node: Node) => { const parentId = node.parent; if (!parentId || !controller) return; controller.transact([ () => { const newNode = { id: v4(), delta: [], type: BlockType.TextBlock, }; controller.insert(newNode, parentId, node.id); }, ]); }, []); return { insertAfter, }; } function getNodeIdByPoint(x: number, y: number) { const viewportNodes = document.querySelectorAll('[data-block-id]'); let node: { el: Element; rect: DOMRect; } | null = null; viewportNodes.forEach((el) => { const rect = el.getBoundingClientRect(); if (rect.x + rect.width - 1 >= x && rect.y + rect.height - 1 >= y && rect.y <= y) { if (!node || rect.y > node.rect.y) { node = { el, rect, }; } } }); return node ? ( node as { el: Element; rect: DOMRect; } ).el.getAttribute('data-block-id') : null; }