From b6cb52f702dbba3c37b07ff80f2438ff806336f7 Mon Sep 17 00:00:00 2001 From: Whitewater Date: Mon, 27 Feb 2023 02:08:06 -0800 Subject: [PATCH] refactor: search input (#1205) --- .../src/components/quick-search/Footer.tsx | 18 ++- .../web/src/components/quick-search/Input.tsx | 100 ------------- .../quick-search/PublishedResults.tsx | 95 ++++++------ .../src/components/quick-search/Results.tsx | 140 ++++++++---------- .../components/quick-search/SearchInput.tsx | 33 +++++ .../web/src/components/quick-search/index.tsx | 52 +++++-- 6 files changed, 192 insertions(+), 246 deletions(-) delete mode 100644 apps/web/src/components/quick-search/Input.tsx create mode 100644 apps/web/src/components/quick-search/SearchInput.tsx diff --git a/apps/web/src/components/quick-search/Footer.tsx b/apps/web/src/components/quick-search/Footer.tsx index 832e77d224..7904f19674 100644 --- a/apps/web/src/components/quick-search/Footer.tsx +++ b/apps/web/src/components/quick-search/Footer.tsx @@ -6,10 +6,22 @@ import React from 'react'; import { usePageHelper } from '@/hooks/use-page-helper'; import { StyledModalFooterContent } from './style'; -export const Footer = (props: { query: string; onClose: () => void }) => { + +const MAX_QUERY_SHOW_LENGTH = 20; + +export const Footer = ({ + query, + onClose, +}: { + query: string; + onClose: () => void; +}) => { const { openPage, createPage } = usePageHelper(); const { t } = useTranslation(); - const { query, onClose } = props; + const normalizedQuery = + query.length > MAX_QUERY_SHOW_LENGTH + ? query.slice(0, MAX_QUERY_SHOW_LENGTH) + '...' + : query; return ( void }) => { {query ? ( - {t('New Keyword Page', { query: query })} + {t('New Keyword Page', { query: normalizedQuery })} ) : ( {t('New Page')} )} diff --git a/apps/web/src/components/quick-search/Input.tsx b/apps/web/src/components/quick-search/Input.tsx deleted file mode 100644 index 45e2627d40..0000000000 --- a/apps/web/src/components/quick-search/Input.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useTranslation } from '@affine/i18n'; -import { SearchIcon } from '@blocksuite/icons'; -import { Command } from 'cmdk'; -import React, { - Dispatch, - SetStateAction, - useEffect, - useRef, - useState, -} from 'react'; - -import { StyledInputContent, StyledLabel } from './style'; -export const Input = (props: { - open: boolean; - query: string; - setQuery: Dispatch>; - setLoading: Dispatch>; - isPublic: boolean; - publishWorkspaceName: string | undefined; -}) => { - const { open, query, setQuery, setLoading, isPublic, publishWorkspaceName } = - props; - const [isComposition, setIsComposition] = useState(false); - const [inputValue, setInputValue] = useState(''); - const inputRef = useRef(null); - const { t } = useTranslation(); - useEffect(() => { - if (open) { - const inputElement = inputRef.current; - return inputElement?.focus(); - } - }, [open]); - useEffect(() => { - const inputElement = inputRef.current; - if (!open) { - return; - } - const handleFocus = () => { - inputElement?.focus(); - }; - inputElement?.addEventListener('blur', handleFocus, true); - return () => inputElement?.removeEventListener('blur', handleFocus, true); - }, [inputRef, open]); - useEffect(() => { - setInputValue(query); - }, [query]); - return ( - - - - - { - setIsComposition(true); - }} - onCompositionEnd={e => { - setQuery(e.data); - setIsComposition(false); - if (!query) { - setLoading(true); - } - }} - onValueChange={str => { - setInputValue(str); - if (!isComposition) { - setQuery(str); - if (!query) { - setLoading(true); - } - } - }} - onKeyDown={(e: React.KeyboardEvent) => { - if (e.key === 'a' && e.metaKey) { - e.stopPropagation(); - inputRef.current?.select(); - return; - } - if (isComposition) { - if ( - e.key === 'ArrowDown' || - e.key === 'ArrowUp' || - e.key === 'Enter' - ) { - e.stopPropagation(); - } - } - }} - placeholder={ - isPublic - ? t('Quick search placeholder2', { - workspace: publishWorkspaceName, - }) - : t('Quick search placeholder') - } - /> - - ); -}; diff --git a/apps/web/src/components/quick-search/PublishedResults.tsx b/apps/web/src/components/quick-search/PublishedResults.tsx index 7f2bd6ff01..63ae937128 100644 --- a/apps/web/src/components/quick-search/PublishedResults.tsx +++ b/apps/web/src/components/quick-search/PublishedResults.tsx @@ -12,18 +12,17 @@ import { useGlobalState } from '@/store/app'; import { NoResultSVG } from './NoResultSVG'; import { StyledListItem, StyledNotFound } from './style'; -export const PublishedResults = (props: { +export const PublishedResults = ({ + query, + onClose, + setPublishWorkspaceName, +}: { query: string; - loading: boolean; - setLoading: Dispatch>; setPublishWorkspaceName: Dispatch>; onClose: () => void; }) => { const [workspace, setWorkspace] = useState(); - const { query, loading, setLoading, onClose, setPublishWorkspaceName } = - props; const { search } = usePageHelper(); - const [results, setResults] = useState(new Map()); const dataCenter = useGlobalState(store => store.dataCenter); const router = useRouter(); const [pageList, setPageList] = useState([]); @@ -42,57 +41,49 @@ export const PublishedResults = (props: { }); }, [router, dataCenter, setPublishWorkspaceName]); const { t } = useTranslation(); - useEffect(() => { - setResults(search(query, workspace)); - setLoading(false); - //Save the Map obtained from the search as state - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [query, setResults, setLoading]); + + if (!query) { + return <>; + } + + const results = search(query, workspace); const pageIds = [...results.values()]; const resultsPageMeta = pageList.filter( page => pageIds.indexOf(page.id) > -1 && !page.trash ); - return loading ? null : ( - <> - {query ? ( - resultsPageMeta.length ? ( - - {resultsPageMeta.map(result => { - return ( - { - router.push( - `/public-workspace/${router.query.workspaceId}/${result.id}` - ); - onClose(); - }} - value={result.id} - > - - {result.mode === 'edgeless' ? ( - - ) : ( - - )} - {result.title} - - + if (!resultsPageMeta.length) { + return ( + + {t('Find 0 result')} + + + ); + } + + return ( + + {resultsPageMeta.map(result => { + return ( + { + router.push( + `/public-workspace/${router.query.workspaceId}/${result.id}` ); - })} - - ) : ( - - {t('Find 0 result')} - - - ) - ) : ( - <> - )} - + onClose(); + }} + value={result.id} + > + + {result.mode === 'edgeless' ? : } + {result.title} + + + ); + })} + ); }; diff --git a/apps/web/src/components/quick-search/Results.tsx b/apps/web/src/components/quick-search/Results.tsx index b02580e550..cb86e81fe9 100644 --- a/apps/web/src/components/quick-search/Results.tsx +++ b/apps/web/src/components/quick-search/Results.tsx @@ -2,13 +2,7 @@ import { useTranslation } from '@affine/i18n'; import { EdgelessIcon, PaperIcon } from '@blocksuite/icons'; import { Command } from 'cmdk'; import { useRouter } from 'next/router'; -import { - Dispatch, - SetStateAction, - useCallback, - useEffect, - useState, -} from 'react'; +import { Dispatch, SetStateAction, useCallback, useEffect } from 'react'; import usePageHelper from '@/hooks/use-page-helper'; import { useGlobalState } from '@/store/app'; @@ -16,14 +10,16 @@ import { useGlobalState } from '@/store/app'; import { useSwitchToConfig } from './config'; import { NoResultSVG } from './NoResultSVG'; import { StyledListItem, StyledNotFound } from './style'; -export const Results = (props: { + +export const Results = ({ + query, + setShowCreatePage, + onClose, +}: { query: string; - loading: boolean; onClose: () => void; - setLoading: Dispatch>; setShowCreatePage: Dispatch>; }) => { - const { query, loading, setLoading, setShowCreatePage, onClose } = props; const { openPage } = usePageHelper(); const router = useRouter(); const currentWorkspace = useGlobalState( @@ -34,80 +30,74 @@ export const Results = (props: { ); const { search } = usePageHelper(); const List = useSwitchToConfig(currentWorkspace?.id); - const [results, setResults] = useState(new Map()); const { t } = useTranslation(); - useEffect(() => { - setResults(search(query)); - setLoading(false); - //Save the Map obtained from the search as state - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [query, setResults, setLoading]); - const pageIds = [...results.values()]; + const results = search(query); + const pageIds = [...results.values()]; const resultsPageMeta = pageList.filter( page => pageIds.indexOf(page.id) > -1 && !page.trash ); + // TODO lift this state up and remove this effect! useEffect(() => { setShowCreatePage(!resultsPageMeta.length); //Determine whether to display the ‘+ New page’ }, [resultsPageMeta, setShowCreatePage]); - return loading ? null : ( - <> - {query ? ( - resultsPageMeta.length ? ( - + {List.map(link => { + return ( + { + onClose(); + router.push(link.href); + }} + > + + + {link.title} + + + ); + })} + + ); + } + + if (!resultsPageMeta.length) { + return ( + + {t('Find 0 result')} + + + ); + } + + return ( + + {resultsPageMeta.map(result => { + return ( + { + onClose(); + openPage(result.id); + }} + value={result.id} > - {resultsPageMeta.map(result => { - return ( - { - onClose(); - openPage(result.id); - }} - value={result.id} - > - - {result.mode === 'edgeless' ? ( - - ) : ( - - )} - {result.title} - - - ); - })} - - ) : ( - - {t('Find 0 result')} - - - ) - ) : ( - - {List.map(link => { - return ( - { - onClose(); - router.push(link.href); - }} - > - - - {link.title} - - - ); - })} - - )} - + + {result.mode === 'edgeless' ? : } + {result.title} + + + ); + })} + ); }; diff --git a/apps/web/src/components/quick-search/SearchInput.tsx b/apps/web/src/components/quick-search/SearchInput.tsx new file mode 100644 index 0000000000..d36ee7305a --- /dev/null +++ b/apps/web/src/components/quick-search/SearchInput.tsx @@ -0,0 +1,33 @@ +import { SearchIcon } from '@blocksuite/icons'; +import { Command } from 'cmdk'; +import { forwardRef } from 'react'; + +import { StyledInputContent, StyledLabel } from './style'; + +export const SearchInput = forwardRef< + HTMLInputElement, + Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' | 'type' + > & { + /** + * Optional controlled state for the value of the search input. + */ + value?: string; + /** + * Event handler called when the search value changes. + */ + onValueChange?: (search: string) => void; + } & React.RefAttributes +>((props, ref) => { + return ( + + + + + + + ); +}); + +SearchInput.displayName = 'SearchInput'; diff --git a/apps/web/src/components/quick-search/index.tsx b/apps/web/src/components/quick-search/index.tsx index d8f9400369..541e98d2cf 100644 --- a/apps/web/src/components/quick-search/index.tsx +++ b/apps/web/src/components/quick-search/index.tsx @@ -1,15 +1,16 @@ import { Modal, ModalWrapper } from '@affine/component'; +import { useTranslation } from '@affine/i18n'; import { Command } from 'cmdk'; import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useModal } from '@/store/globalModal'; import { getUaHelper } from '@/utils'; import { Footer } from './Footer'; -import { Input } from './Input'; import { PublishedResults } from './PublishedResults'; import { Results } from './Results'; +import { SearchInput } from './SearchInput'; import { StyledContent, StyledModalDivider, @@ -17,10 +18,12 @@ import { StyledModalHeader, StyledShortcut, } from './style'; + type TransitionsModalProps = { open: boolean; onClose: () => void; }; + const isMac = () => { return getUaHelper().isMacOs; }; @@ -28,8 +31,9 @@ const isMac = () => { // fixme(himself65): support ssr export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { const router = useRouter(); + const { t } = useTranslation(); + const inputRef = useRef(null); const [query, setQuery] = useState(''); - const [loading, setLoading] = useState(true); const [isPublic, setIsPublic] = useState(false); const [publishWorkspaceName, setPublishWorkspaceName] = useState(''); const [showCreatePage, setShowCreatePage] = useState(true); @@ -38,7 +42,6 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { return isPublic && query.length === 0; }; const handleClose = () => { - setQuery(''); onClose(); }; // Add ‘⌘+K’ shortcut keys as switches @@ -73,10 +76,19 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { }, [router]); useEffect(() => { if (router.pathname.startsWith('/404')) { - return onClose(); + onClose(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + if (open) { + // Waiting for DOM rendering + requestAnimationFrame(() => { + const inputElement = inputRef.current; + inputElement?.focus(); + }); + } + }, [open]); return ( { }} > - { + setQuery(value); + }} + onKeyDown={e => { + // Avoid triggering the cmdk onSelect event when the input method is in use + if (e.nativeEvent.isComposing) { + e.stopPropagation(); + return; + } + }} + placeholder={ + isPublic + ? t('Quick search placeholder2', { + workspace: publishWorkspaceName, + }) + : t('Quick search placeholder') + } /> {isMac() ? '⌘ + K' : 'Ctrl + K'} @@ -128,16 +152,12 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { {!isPublic ? ( ) : (