diff --git a/lib/hooks/search-sidebars.js b/lib/hooks/search-sidebars.js index bcd69fef8..3a91edebe 100644 --- a/lib/hooks/search-sidebars.js +++ b/lib/hooks/search-sidebars.js @@ -1,73 +1,88 @@ // @flow import * as React from 'react'; import { sidebarInfoSelector } from '../selectors/thread-selectors'; import SearchIndex from '../shared/search-index'; import { threadSearchText } from '../shared/thread-utils'; import type { SetState } from '../types/hook-types'; import type { SidebarInfo, ThreadInfo } from '../types/thread-types'; import { useSelector } from '../utils/redux-utils'; type SidebarSearchState = { +text: string, +results: $ReadOnlySet, }; function useSearchSidebars( threadInfo: ThreadInfo, ): { +listData: $ReadOnlyArray, +searchState: SidebarSearchState, +setSearchState: SetState, - +searchIndex: SearchIndex, + +onChangeSearchInputText: (text: string) => mixed, } { const [searchState, setSearchState] = React.useState({ text: '', results: new Set(), }); const userInfos = useSelector(state => state.userStore.userInfos); const sidebarInfos = useSelector( state => sidebarInfoSelector(state)[threadInfo.id] ?? [], ); const listData = React.useMemo(() => { if (!searchState.text) { return sidebarInfos; } return sidebarInfos.filter(sidebarInfo => searchState.results.has(sidebarInfo.threadInfo.id), ); }, [sidebarInfos, searchState]); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const searchIndex = React.useMemo(() => { const index = new SearchIndex(); for (const sidebarInfo of sidebarInfos) { const threadInfoFromSidebarInfo = sidebarInfo.threadInfo; index.addEntry( threadInfoFromSidebarInfo.id, threadSearchText(threadInfoFromSidebarInfo, userInfos, viewerID), ); } return index; }, [sidebarInfos, userInfos, viewerID]); + const onChangeSearchInputText = React.useCallback( + (text: string) => { + setSearchState({ + text, + results: new Set(searchIndex.getSearchResults(text)), + }); + }, + [searchIndex, setSearchState], + ); + React.useEffect(() => { setSearchState(curState => ({ ...curState, results: new Set(searchIndex.getSearchResults(curState.text)), })); }, [searchIndex, setSearchState]); return React.useMemo( - () => ({ listData, searchState, setSearchState, searchIndex }), - [listData, setSearchState, searchState, searchIndex], + () => ({ + listData, + searchState, + setSearchState, + onChangeSearchInputText, + }), + [listData, setSearchState, searchState, onChangeSearchInputText], ); } export { useSearchSidebars }; diff --git a/native/chat/sidebar-list-modal.react.js b/native/chat/sidebar-list-modal.react.js index c0e833291..e684def07 100644 --- a/native/chat/sidebar-list-modal.react.js +++ b/native/chat/sidebar-list-modal.react.js @@ -1,127 +1,118 @@ // @flow import * as React from 'react'; import { TextInput, FlatList, StyleSheet } from 'react-native'; import { useSearchSidebars } from 'lib/hooks/search-sidebars'; import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; import Modal from '../components/modal.react'; import Search from '../components/search.react'; import type { RootNavigationProp } from '../navigation/root-navigator.react'; import type { NavigationRoute } from '../navigation/route-names'; import { useIndicatorStyle } from '../themes/colors'; import { waitForModalInputFocus } from '../utils/timers'; import { useNavigateToThread } from './message-list-types'; import { SidebarItem } from './sidebar-item.react'; export type SidebarListModalParams = { +threadInfo: ThreadInfo, }; function keyExtractor(sidebarInfo: SidebarInfo) { return sidebarInfo.threadInfo.id; } function getItemLayout(data: ?$ReadOnlyArray, index: number) { return { length: 24, offset: 24 * index, index }; } type Props = { +navigation: RootNavigationProp<'SidebarListModal'>, +route: NavigationRoute<'SidebarListModal'>, }; function SidebarListModal(props: Props): React.Node { const { listData, searchState, setSearchState, - searchIndex, + onChangeSearchInputText, } = useSearchSidebars(props.route.params.threadInfo); - const onChangeSearchText = React.useCallback( - (searchText: string) => - setSearchState({ - text: searchText, - results: new Set(searchIndex.getSearchResults(searchText)), - }), - [searchIndex, setSearchState], - ); - const searchTextInputRef = React.useRef(); const setSearchTextInputRef = React.useCallback( async (textInput: ?React.ElementRef) => { searchTextInputRef.current = textInput; if (!textInput) { return; } await waitForModalInputFocus(); if (searchTextInputRef.current) { searchTextInputRef.current.focus(); } }, [], ); const navigateToThread = useNavigateToThread(); const onPressItem = React.useCallback( (threadInfo: ThreadInfo) => { setSearchState({ text: '', results: new Set(), }); if (searchTextInputRef.current) { searchTextInputRef.current.blur(); } navigateToThread({ threadInfo }); }, [navigateToThread, setSearchState], ); const renderItem = React.useCallback( (row: { item: SidebarInfo, ... }) => { return ( ); }, [onPressItem], ); const indicatorStyle = useIndicatorStyle(); return ( ); } const styles = StyleSheet.create({ search: { marginBottom: 8, }, sidebar: { backgroundColor: 'transparent', paddingLeft: 0, paddingRight: 5, }, }); export default SidebarListModal; diff --git a/web/modals/chat/sidebar-list-modal.react.js b/web/modals/chat/sidebar-list-modal.react.js index 68723aa6d..a888ca846 100644 --- a/web/modals/chat/sidebar-list-modal.react.js +++ b/web/modals/chat/sidebar-list-modal.react.js @@ -1,107 +1,104 @@ // @flow import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import * as React from 'react'; import { useSearchSidebars } from 'lib/hooks/search-sidebars'; import type { ThreadInfo } from 'lib/types/thread-types'; import chatThreadListCSS from '../../chat/chat-thread-list.css'; import SidebarItem from '../../chat/sidebar-item.react'; import globalCSS from '../../style.css'; import { MagnifyingGlass } from '../../vectors.react'; import Input from '../input.react'; import { useModalContext } from '../modal-provider.react'; import Modal from '../modal.react'; type Props = { +threadInfo: ThreadInfo, }; function SidebarListModal(props: Props): React.Node { const { threadInfo } = props; const { listData, searchState, setSearchState, - searchIndex, + onChangeSearchInputText, } = useSearchSidebars(threadInfo); const { popModal } = useModalContext(); const sidebars = React.useMemo( () => listData.map(item => (
)), [popModal, listData], ); - const onChangeSearchText = React.useCallback( - (event: SyntheticEvent) => { - const searchText = event.currentTarget.value; - setSearchState({ - text: searchText, - results: new Set(searchIndex.getSearchResults(searchText)), - }); - }, - [searchIndex, setSearchState], - ); - const clearQuery = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); setSearchState({ text: '', results: new Set() }); }, [setSearchState], ); let clearQueryButton = null; if (searchState.text) { clearQueryButton = ( ); } + const handleOnChangeSearchText = React.useCallback( + (event: SyntheticEvent) => { + const { value } = event.currentTarget; + onChangeSearchInputText(value); + }, + [onChangeSearchInputText], + ); + return (
{clearQueryButton}
    {sidebars}
); } export default SidebarListModal;