diff --git a/libs/components/common/src/lib/list/index.tsx b/libs/components/common/src/lib/list/index.tsx index 7de7e35b8e..c00a4e1415 100644 --- a/libs/components/common/src/lib/list/index.tsx +++ b/libs/components/common/src/lib/list/index.tsx @@ -57,7 +57,9 @@ export const CommonList = (props: MenuItemsProps) => { > {usedItems?.length ? ( usedItems.map((item, idx) => { - if (item.block) { + if (item.renderCustom) { + return item.renderCustom(item); + } else if (item.block) { return ( void; onClose?: () => void; @@ -25,7 +21,6 @@ export type DoubleLinkMenuContainerProps = { export const DoubleLinkMenuContainer = ({ hooks, - isShow = false, onSelected, onClose, types, @@ -66,12 +61,13 @@ export const DoubleLinkMenuContainer = ({ }, [needCheckIntoView, currentItem]); useEffect(() => { - if (isShow && types && !currentItem) setCurrentItem(types[0]); - if (!isShow) onClose?.(); - }, [currentItem, isShow, onClose, types]); + if (types && !currentItem) { + setCurrentItem(types[0]); + } + }, [currentItem, onClose, types]); useEffect(() => { - if (isShow && types) { + if (types) { if (!types.includes(currentItem)) { setNeedCheckIntoView(true); if (types.length) { @@ -81,11 +77,11 @@ export const DoubleLinkMenuContainer = ({ } } } - }, [isShow, types, currentItem]); + }, [types, currentItem]); const handleClickUp = useCallback( (event: React.KeyboardEvent) => { - if (isShow && types && event.code === 'ArrowUp') { + if (types && event.code === 'ArrowUp') { event.preventDefault(); if (!currentItem && types.length) { setCurrentItem(types[types.length - 1]); @@ -99,12 +95,12 @@ export const DoubleLinkMenuContainer = ({ } } }, - [isShow, types, currentItem] + [types, currentItem] ); const handleClickDown = useCallback( (event: React.KeyboardEvent) => { - if (isShow && types && event.code === 'ArrowDown') { + if (types && event.code === 'ArrowDown') { event.preventDefault(); if (!currentItem && types.length) { setCurrentItem(types[0]); @@ -118,17 +114,17 @@ export const DoubleLinkMenuContainer = ({ } } }, - [isShow, types, currentItem] + [types, currentItem] ); const handleClickEnter = useCallback( async (event: React.KeyboardEvent) => { - if (isShow && event.code === 'Enter' && currentItem) { + if (event.code === 'Enter' && currentItem) { event.preventDefault(); onSelected && onSelected(currentItem); } }, - [isShow, currentItem, onSelected] + [currentItem, onSelected] ); const handleKeyDown = useCallback( @@ -150,7 +146,7 @@ export const DoubleLinkMenuContainer = ({ }; }, [hooks, handleKeyDown]); - return isShow ? ( + return ( - ) : null; + ); }; const RootContainer = styled('div')(({ theme }) => ({ - // position: 'fixed', zIndex: 1, maxHeight: '525px', borderRadius: '10px', diff --git a/libs/components/editor-plugins/src/menu/double-link-menu/DoubleLinkMenu.tsx b/libs/components/editor-plugins/src/menu/double-link-menu/DoubleLinkMenu.tsx index 1f545809a3..e79ab7b90b 100644 --- a/libs/components/editor-plugins/src/menu/double-link-menu/DoubleLinkMenu.tsx +++ b/libs/components/editor-plugins/src/menu/double-link-menu/DoubleLinkMenu.tsx @@ -1,10 +1,10 @@ import { CommonListItem } from '@toeverything/components/common'; import { AddIcon } from '@toeverything/components/icons'; import { + Input, ListButton, MuiClickAwayListener, MuiGrow as Grow, - MuiOutlinedInput as OutlinedInput, MuiPaper as Paper, MuiPopper as Popper, styled, @@ -13,6 +13,7 @@ import { services } from '@toeverything/datasource/db-service'; import { HookType, PluginHooks, Virgo } from '@toeverything/framework/virgo'; import { getPageId } from '@toeverything/utils'; import React, { + ChangeEvent, useCallback, useEffect, useMemo, @@ -24,6 +25,7 @@ import { DoubleLinkMenuContainer } from './Container'; const ADD_NEW_SUB_PAGE = 'AddNewSubPage'; const ADD_NEW_PAGE = 'AddNewPage'; +const ARRAY_KEYS = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown']; export type DoubleLinkMenuProps = { editor: Virgo; @@ -31,7 +33,7 @@ export type DoubleLinkMenuProps = { style?: { left: number; top: number }; }; -type DoubleMenuStyle = { +type DoubleLinkMenuStyle = { left: number; top: number; height: number; @@ -42,58 +44,63 @@ export const DoubleLinkMenu = ({ hooks, style, }: DoubleLinkMenuProps) => { - const [isShow, setIsShow] = useState(false); - const [blockId, setBlockId] = useState(); - const [searchText, setSearchText] = useState(''); - const [searchBlocks, setSearchBlocks] = useState([]); - const [items, setItems] = useState([]); - const [isNewPage, setIsNewPage] = useState(false); + const [isOpen, setIsOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); - const ref = useRef(); - const ref1 = useRef(); - const [referenceMenuStyle, setReferenceMenuStyle] = - useState({ + const dialogRef = useRef(); + const newPageSearchRef = useRef(); + const [doubleLinkMenuStyle, setDoubleLinkMenuStyle] = + useState({ left: 0, top: 0, height: 0, }); - useEffect(() => { - QueryBlocks(editor, searchText, result => { - setSearchBlocks(result); - ref1?.current?.focus(); - const items: CommonListItem[] = []; - if (searchBlocks?.length > 0) { - if (isNewPage) { - items.push({ - renderCustom: () => { - return ; - }, - }); - } else { - items.push({ - renderCustom: () => { - return ; - }, - }); - } - items.push( - ...(searchBlocks?.map( - block => ({ block } as CommonListItem) - ) || []) - ); - } + const [curBlockId, setCurBlockId] = useState(); + const [searchText, setSearchText] = useState(''); + const [inAddNewPage, setInAddNewPage] = useState(false); + const [filterText, setFilterText] = useState(''); + const [searchResultBlocks, setSearchResultBlocks] = useState( + [] + ); - if (items.length > 0) { - items.push({ divider: 'newPage' }); - } + const menuTypes = useMemo(() => { + return Object.values(searchResultBlocks) + .map(({ id }) => id) + .concat([ADD_NEW_SUB_PAGE, ADD_NEW_PAGE]); + }, [searchResultBlocks]); + + const menuItems: CommonListItem[] = useMemo(() => { + const items: CommonListItem[] = []; + if (searchResultBlocks?.length > 0) { items.push({ - content: { - id: ADD_NEW_SUB_PAGE, - content: 'Add new sub-page', - icon: AddIcon, + renderCustom: () => { + return ( + + ); }, }); + items.push( + ...(searchResultBlocks?.map( + block => ({ block } as CommonListItem) + ) || []) + ); + } + + if (items.length > 0) { + items.push({ divider: 'newPage' }); + } + items.push({ + content: { + id: ADD_NEW_SUB_PAGE, + content: 'Add new sub-page', + icon: AddIcon, + }, + }); + !inAddNewPage && items.push({ content: { id: ADD_NEW_PAGE, @@ -101,26 +108,28 @@ export const DoubleLinkMenu = ({ icon: AddIcon, }, }); + return items; + }, [searchResultBlocks, inAddNewPage]); - setItems(items); + useEffect(() => { + const text = inAddNewPage ? filterText : searchText; + QueryBlocks(editor, text, result => { + setSearchResultBlocks(result); }); - }, [editor, searchText, isNewPage]); - - const types = useMemo(() => { - return Object.values(searchBlocks) - .map(({ id }) => id) - .concat([ADD_NEW_SUB_PAGE, ADD_NEW_PAGE]); - }, [searchBlocks]); + }, [editor, searchText, filterText, inAddNewPage]); const hideMenu = useCallback(() => { - setIsShow(false); - setIsNewPage(false); - editor.blockHelper.removeDoubleLinkSearchSlash(blockId); + setIsOpen(false); + setInAddNewPage(false); + editor.blockHelper.removeDoubleLinkSearchSlash(curBlockId); editor.scrollManager.unLock(); - }, [blockId, editor.blockHelper, editor.scrollManager]); + }, [curBlockId, editor]); - const handleSearch = useCallback( + const searchChange = useCallback( async (event: React.KeyboardEvent) => { + if (ARRAY_KEYS.includes(event.key)) { + return; + } const { type, anchorNode } = editor.selection.currentSelectInfo; if ( type === 'Range' && @@ -130,24 +139,31 @@ export const DoubleLinkMenu = ({ const text = editor.blockHelper.getBlockTextBeforeSelection( anchorNode.id ); - if (text.endsWith('[[')) { - if ( - [ - 'ArrowRight', - 'ArrowLeft', - 'ArrowUp', - 'ArrowDown', - ].includes(event.key) - ) { - return; - } if (event.key === 'Backspace') { hideMenu(); return; } - setBlockId(anchorNode.id); - editor.blockHelper.removeDoubleLinkSearchSlash(blockId); + editor.blockHelper.removeDoubleLinkSearchSlash(curBlockId); + setCurBlockId(anchorNode.id); + setSearchText(''); + setIsOpen(true); + editor.scrollManager.lock(); + const clientRect = + editor.selection.currentSelectInfo?.browserSelection + ?.getRangeAt(0) + ?.getBoundingClientRect(); + if (clientRect) { + const rectTop = clientRect.top; + const { top, left } = + editor.container.getBoundingClientRect(); + setDoubleLinkMenuStyle({ + top: rectTop - top, + left: clientRect.left - left, + height: clientRect.height, + }); + setAnchorEl(dialogRef.current); + } setTimeout(() => { const textSelection = editor.blockHelper.selectionToSlateRange( @@ -163,40 +179,22 @@ export const DoubleLinkMenu = ({ ); } }); - setSearchText(''); - setIsShow(true); - editor.scrollManager.lock(); - const rect = - editor.selection.currentSelectInfo?.browserSelection - ?.getRangeAt(0) - ?.getBoundingClientRect(); - if (rect) { - const rectTop = rect.top; - const { top, left } = - editor.container.getBoundingClientRect(); - setReferenceMenuStyle({ - top: rectTop - top, - left: rect.left - left, - height: rect.height, - }); - setAnchorEl(ref.current); - } } } - if (isShow) { + if (isOpen) { const searchText = - editor.blockHelper.getDoubleLinkSearchSlashText(blockId); + editor.blockHelper.getDoubleLinkSearchSlashText(curBlockId); if (searchText && searchText.startsWith('[[')) { setSearchText(searchText.slice(2).trim()); } } }, - [editor, isShow, blockId, hideMenu] + [editor, isOpen, curBlockId, hideMenu] ); const handleKeyup = useCallback( - (event: React.KeyboardEvent) => handleSearch(event), - [handleSearch] + (event: React.KeyboardEvent) => searchChange(event), + [searchChange] ); const handleKeyDown = useCallback( @@ -223,75 +221,86 @@ export const DoubleLinkMenu = ({ }; }, [handleKeyup, handleKeyDown, hooks]); - const handleSelected = async (linkBlockId: string) => { - if (blockId) { - if (linkBlockId === ADD_NEW_SUB_PAGE) { - handleAddSubPage(); - return; - } - if (linkBlockId === ADD_NEW_PAGE) { - setIsNewPage(true); - return; - } - if (isNewPage) { - const newPage = await services.api.editorBlock.create({ - workspace: editor.workspace, - type: 'page' as const, - }); - await services.api.pageTree.addChildPageToWorkspace( - editor.workspace, - linkBlockId, - newPage.id - ); - linkBlockId = newPage.id; - } - editor.blockHelper.setSelectDoubleLinkSearchSlash(blockId); + const insertDoubleLink = useCallback( + async (pageId: string) => { + editor.blockHelper.setSelectDoubleLinkSearchSlash(curBlockId); await editor.blockHelper.insertDoubleLink( editor.workspace, - linkBlockId, - blockId + pageId, + curBlockId ); hideMenu(); + }, + [editor, curBlockId, hideMenu] + ); + + const addSubPage = useCallback( + async (parentPageId: string) => { + const newPage = await services.api.editorBlock.create({ + workspace: editor.workspace, + type: 'page' as const, + }); + services.api.editorBlock.update({ + id: newPage.id, + workspace: editor.workspace, + properties: { + text: { value: [{ text: searchText }] }, + }, + }); + await services.api.pageTree.addChildPageToWorkspace( + editor.workspace, + parentPageId, + newPage.id + ); + return newPage.id; + }, + [searchText, editor] + ); + + const handleSelected = async (id: string) => { + if (curBlockId) { + if (id === ADD_NEW_PAGE) { + setInAddNewPage(true); + setTimeout(() => { + newPageSearchRef.current?.focus(); + }); + return; + } + if (id === ADD_NEW_SUB_PAGE) { + const pageId = await addSubPage(getPageId()); + insertDoubleLink(pageId); + return; + } + if (inAddNewPage) { + const pageId = await addSubPage(id); + insertDoubleLink(pageId); + } else { + insertDoubleLink(id); + } } }; - const handleClose = () => { - blockId && editor.blockHelper.removeDoubleLinkSearchSlash(blockId); - }; + const handleFilterChange = useCallback( + async (e: ChangeEvent) => { + const text = e.target.value; - const handleAddSubPage = async () => { - const newPage = await services.api.editorBlock.create({ - workspace: editor.workspace, - type: 'page' as const, - }); - setIsNewPage(false); - services.api.editorBlock.update({ - id: newPage.id, - workspace: editor.workspace, - properties: { - text: { value: [{ text: searchText }] }, - }, - }); - await services.api.pageTree.addChildPageToWorkspace( - editor.workspace, - getPageId(), - newPage.id - ); - handleSelected(newPage.id); - }; + await setFilterText(text); + }, + [] + ); return (
hideMenu()}> - - - {}} + {inAddNewPage && ( + + - - - + + )} + )} @@ -332,15 +342,6 @@ export const DoubleLinkMenu = ({ ); }; -const DoubleLinkMenuWrapper = styled('div')({ - zIndex: 1, -}); - -const SearchContainer = styled('div')({ - padding: '8px 8px', - input: { - height: '28px', - padding: '5px 10px', - with: '300px', - }, +const NewPageSearchContainer = styled('div')({ + padding: '8px 8px 0px 8px', });