Files
AFFiNE-Mirror/libs/components/editor-plugins/src/search/Search.tsx
T
2022-09-06 14:20:07 +08:00

141 lines
3.9 KiB
TypeScript

import { BlockPreview } from '@toeverything/components/common';
import {
MuiBox as Box,
MuiBox,
styled,
TransitionsModal,
} from '@toeverything/components/ui';
import { BlockEditor, PluginHooks, Virgo } from '@toeverything/framework/virgo';
import { throttle } from '@toeverything/utils';
import { useCallback, useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router';
import style9 from 'style9';
const styles = style9.create({
wrapper: {
position: 'absolute',
top: '20%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '50vw',
display: 'flex',
flexDirection: 'column',
},
resultItem: {
width: '100%',
},
resultHide: {
opacity: 0,
},
});
export type QueryResult = Awaited<ReturnType<BlockEditor['search']>>;
const queryBlocksExec = (
editor: Virgo,
search: string,
callback: (result: QueryResult) => void
) => {
(editor as BlockEditor).search(search).then(pages => callback(pages));
};
export const QueryBlocks = throttle(queryBlocksExec, 500);
type SearchProps = {
editor: Virgo;
hooks: PluginHooks;
};
export const Search = (props: SearchProps) => {
const { workspace_id: workspaceId } = useParams();
const navigate = useNavigate();
const [open, setOpen] = useState(false);
const [search, setSearch] = useState<string>();
const [result, setResult] = useState<QueryResult>([]);
useEffect(() => {
search !== undefined &&
QueryBlocks(props.editor, search, result => {
setResult(result);
});
}, [props.editor, search]);
const handleNavigate = useCallback(
(id: string) => navigate(`/${workspaceId}/${id}`),
[navigate, workspaceId]
);
const handleSearch = useCallback(() => {
setOpen(true);
setSearch('');
}, []);
// useEffect(() => {
// const sub = props.hooks.get(HookType.ON_SEARCH).subscribe(handleSearch);
// return () => {
// sub.unsubscribe();
// };
// }, [props, handleSearch]);
return (
<TransitionsModal
open={open}
onClose={() => {
setOpen(false);
}}
>
<Box className={styles('wrapper')}>
<SearchInput
autoFocus
value={search}
onChange={e => setSearch(e.target.value)}
/>
<ResultContainer
sx={{ maxHeight: `${result.length * 28 + 32 + 20}px` }}
className={styles({
resultHide: !result.length,
})}
>
{result.map(block => (
<BlockPreview
className={styles('resultItem')}
key={block.id}
block={{
...block,
content: block.content || 'Untitled',
}}
onClick={() => {
handleNavigate(block.id);
}}
/>
))}
</ResultContainer>
</Box>
</TransitionsModal>
);
};
const SearchInput = styled('input')(({ theme }) => ({
margin: '0.5em',
backgroundColor: 'white',
boxShadow: theme.affine.shadows.shadow1,
padding: '16px 32px',
borderRadius: '10px',
}));
const ResultContainer = styled(MuiBox)(({ theme }) => ({
margin: '0.5em',
backgroundColor: 'white',
boxShadow: theme.affine.shadows.shadow1,
padding: '16px 32px',
borderRadius: '10px',
transitionProperty: 'max-height',
transitionDuration: '300ms',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
transitionDelay: '0ms',
overflowX: 'hidden',
overflowY: 'hidden',
}));