import { t } from '../../../i18n'; import { getEditorName, useBuilder, useEditor } from '@tempi/core-editor'; import { ButtonSubmitV3Editor, ColEditor, RowEditor, transformNestedContent, } from '@tempi/core-elements'; import { ARTICLE_HEADER, ROOT } from '@tempi/core-renderer'; import { message } from 'antd'; import { useEffect } from 'react'; import { ExtraBuilderState } from 'sites/wb-editor/contexts/BuilderProvider'; const MODALS_SELECTOR = 'div[role="dialog"][aria-modal="true"]:not(.dnd-modal)'; // Exclude low-code dnd-modal export const useShortcuts = () => { const { enabled, canUndo, canRedo, actions, selectedNodeId, duplicateNode, query, getSiblings, onPaste, onCopy, } = useEditor((state, query) => ({ enabled: state.options.enabled, canUndo: query.history.canUndo(), canRedo: query.history.canRedo(), selectedNodeId: state.events.selected, })); const { frame, isEditingText, setShowSettingDialog, showSettingDialog } = useBuilder(); const canCopyPaste = !showSettingDialog; // Kiểm tra selectedNode có thể sử dụng shortcut không const canChangeNodeByShortcut = () => { const isInvalid = Array.from(selectedNodeId).some((id) => { const selectedNode = query.node(id).get(); const name = selectedNode?.data?.name; return [getEditorName({ ButtonSubmitV3Editor })].includes(name); }); return !isInvalid; }; const handleKeyDown = (event: KeyboardEvent) => { const isAnyDialogOpen = !!frame.querySelector(MODALS_SELECTOR); const allowNodeShortcut = !!selectedNodeId && !selectedNodeId.has(ROOT); // Không cho phím tắt hoạt động khi có modal đang mở hoặc đang editText if (isAnyDialogOpen || isEditingText) return; const { ctrlKey, metaKey, shiftKey, code } = event; const isCtrlOrMeta = ctrlKey || metaKey; if (isCtrlOrMeta && code === 'KeyZ' && enabled) { if (!shiftKey && canUndo) actions.history.undo(); if (shiftKey && canRedo) actions.history.redo(); } // Kiểm tra có thể sử dụng shortcut cho node k if (!allowNodeShortcut) return; // Mở setting của Node if (isCtrlOrMeta && code === 'KeyE') { event.preventDefault(); setShowSettingDialog(!showSettingDialog); } // Xoá Node if ((isCtrlOrMeta && code === 'Backspace') || code === 'Delete') { if (!canChangeNodeByShortcut() || selectedNodeId.has(ARTICLE_HEADER)) return; selectedNodeId.forEach((value) => actions.delete(value)); } // Kiểm tra có cho phép nhân bản/copy/pase không if (selectedNodeId.size > 1 || !canChangeNodeByShortcut()) return; // Nhân bản Node if (isCtrlOrMeta && code === 'KeyD') { event.preventDefault(); const node = query.node(selectedNodeId.values().next().value).get(); const index = getSiblings(node.id).findIndex( (el: string) => el === node.id, ); duplicateNode(node, node.data.parent, index + 1); } // Copy Node if (isCtrlOrMeta && code === 'KeyC' && canCopyPaste) { const node = query.node(selectedNodeId.values().next().value).get(); const nodeName = node?.data?.name; if ( [getEditorName({ RowEditor }), getEditorName({ ColEditor })].includes( nodeName, ) ) { message.warning({ content: t('Không thể sao chép hàng, cột') }); return; } onCopy(selectedNodeId.values().next().value); } // Paste Node if (isCtrlOrMeta && code === 'KeyV' && canCopyPaste) { const node = query.node(selectedNodeId.values().next().value).get(); let index: number, parentId: string; const nodeName = node?.data?.name; if (!nodeName || nodeName === getEditorName({ RowEditor })) return; if (nodeName === getEditorName({ ColEditor })) { parentId = node.id; } else { index = getSiblings(node.id).findIndex((el: string) => el === node.id); parentId = node.data.parent; } onPaste(index + 1, parentId, transformNestedContent); } }; useEffect(() => { frame?.addEventListener('keydown', handleKeyDown); return () => { frame?.removeEventListener('keydown', handleKeyDown); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ canRedo, canUndo, isEditingText, history, enabled, selectedNodeId, showSettingDialog, ]); };