import { CommonListItem, isUrl } from '@toeverything/components/common'; import { LinkIcon } from '@toeverything/components/icons'; import { ListButton, MuiClickAwayListener, MuiGrow as Grow, MuiPaper as Paper, MuiPopper as Popper, styled, } from '@toeverything/components/ui'; import { PluginHooks, Virgo } from '@toeverything/framework/virgo'; import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { useParams } from 'react-router-dom'; import { QueryBlocks, QueryResult } from '../../search'; import { DoubleLinkMenuContainer } from '../double-link-menu/Container'; const ADD_NEW_SUB_PAGE = 'AddNewSubPage'; const ADD_NEW_PAGE = 'AddNewPage'; export type LinkMenuProps = { editor: Virgo; hooks: PluginHooks; }; type LinkMenuStyle = { left: number; top: number; height: number; }; const normalizeUrl = (url: string) => { // eslint-disable-next-line no-restricted-globals return /^https?/.test(url) ? url : `${location.protocol}//${url}`; }; export const LinkMenu = ({ editor, hooks }: LinkMenuProps) => { const { page_id: curPageId } = useParams(); const [isOpen, setIsOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); const dialogRef = useRef(); const inputEl = useRef(); const [linkMenuStyle, setLinkMenuStyle] = useState({ left: 0, top: 0, height: 0, }); const url = ''; const [curBlockId, setCurBlockId] = useState(); const [searchText, setSearchText] = useState(); const [searchResultBlocks, setSearchResultBlocks] = useState( [] ); 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({ renderCustom: () => { return ; }, }); items.push( ...(searchResultBlocks?.map( block => ({ block: { ...block, content: block.content || 'Untitled', }, } as CommonListItem) ) || []) ); } return items; }, [searchResultBlocks]); useEffect(() => { const text = searchText; QueryBlocks(editor, text, result => { result = result.filter(item => item.id !== curPageId); setSearchResultBlocks(result); }); }, [editor, searchText, curPageId]); const hideMenu = useCallback(() => { setIsOpen(false); editor.blockHelper.removeDoubleLinkSearchSlash(curBlockId); editor.scrollManager.unLock(); }, [curBlockId, editor]); const resetState = useCallback( (preNodeId: string, nextNodeId: string) => { setCurBlockId(nextNodeId); 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(); setLinkMenuStyle({ top: rectTop - top, left: clientRect.left - left, height: clientRect.height, }); setAnchorEl(dialogRef.current); } }, [editor] ); useEffect(() => { const showLinkMenu = () => { const { anchorNode } = editor.selection.currentSelectInfo; resetState('', anchorNode.id); }; editor.plugins.observe('showAddLink', showLinkMenu); return () => editor.plugins.unobserve('showAddLink', showLinkMenu); }, [editor, resetState]); const handleSelected = async (id: string) => {}; const handleFilterChange = useCallback( async (e: ChangeEvent) => { const text = e.target.value; await setSearchText(text); }, [] ); const addLinkUrlToText = () => { const newUrl = inputEl.current.value; if (newUrl && newUrl !== url && isUrl(normalizeUrl(newUrl))) { editor.blockHelper.wrapLink(curBlockId, newUrl); hideMenu(); return; } }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter') { addLinkUrlToText(); } if (e.key === 'Escape') { hideMenu(); } }; return (
{isOpen && ( hideMenu()}> {({ TransitionProps }) => ( {menuItems.length > 0 && ( )} )} )}
); }; const LinkModalContainer = styled('div')(({ theme }) => ({ display: 'flex', borderRadius: '4px', boxShadow: theme.affine.shadows.shadow1, backgroundColor: '#fff', alignItems: 'center', zIndex: '1', })); const LinkModalContainerIcon = styled('div')(({ theme }) => ({ display: 'flex', width: '16px', margin: '0 16px 0 4px', color: '#4C6275', })); const LinkModalContainerInput = styled('input')(({ theme }) => ({ flex: '1', outline: 'none', border: 'none', padding: '8px', fontFamily: 'Helvetica,Arial,"Microsoft Yahei",SimHei,sans-serif', '::-webkit-input-placeholder': { color: '#98acbd', }, color: '#4C6275', }));