diff --git a/web/modals/search/message-search-modal.react.js b/web/modals/search/message-search-modal.react.js index b9622b405..ba8dedc95 100644 --- a/web/modals/search/message-search-modal.react.js +++ b/web/modals/search/message-search-modal.react.js @@ -1,255 +1,265 @@ // @flow import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { type ChatMessageItem, messageListData, } from 'lib/selectors/chat-selectors.js'; import { createMessageInfo, modifyItemForResultScreen, } from 'lib/shared/message-utils.js'; import { useSearchMessages } from 'lib/shared/search-utils.js'; import type { RawMessageInfo } from 'lib/types/message-types.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; import css from './message-search-modal.css'; import { useTooltipContext } from '../../chat/tooltip-provider.js'; import Button from '../../components/button.react.js'; import MessageResult from '../../components/message-result.react.js'; import Search from '../../components/search.react.js'; import LoadingIndicator from '../../loading-indicator.react.js'; import { useSelector } from '../../redux/redux-utils.js'; import { useMessageSearchContext } from '../../search/message-search-state-provider.react.js'; import Modal from '../modal.react.js'; type ContentProps = { +query: string, +threadInfo: ThreadInfo, }; function MessageSearchModalContent(props: ContentProps): React.Node { const { query, threadInfo } = props; const [lastID, setLastID] = React.useState(); const [searchResults, setSearchResults] = React.useState([]); const [endReached, setEndReached] = React.useState(false); const appendSearchResults = React.useCallback( (newMessages: $ReadOnlyArray, end: boolean) => { setSearchResults(oldMessages => [...oldMessages, ...newMessages]); setEndReached(end); }, [], ); React.useEffect(() => { setSearchResults([]); setLastID(undefined); setEndReached(false); }, [query]); const searchMessages = useSearchMessages(); React.useEffect( () => searchMessages(query, threadInfo.id, appendSearchResults, lastID), [appendSearchResults, lastID, query, searchMessages, threadInfo.id], ); const userInfos = useSelector(state => state.userStore.userInfos); const translatedSearchResults = React.useMemo(() => { const threadInfos = { [threadInfo.id]: threadInfo }; return searchResults .map(rawMessageInfo => createMessageInfo(rawMessageInfo, null, userInfos, threadInfos), ) .filter(Boolean); }, [searchResults, threadInfo, userInfos]); const chatMessageInfos = useSelector( messageListData(threadInfo.id, translatedSearchResults), ); const filteredChatMessageInfos = React.useMemo(() => { if (!chatMessageInfos) { return []; } const idSet = new Set(translatedSearchResults.map(item => item.id)); const chatMessageInfoItems = chatMessageInfos.filter( item => item.messageInfo && idSet.has(item.messageInfo.id), ); const uniqueChatMessageInfoItemsMap = new Map(); chatMessageInfoItems.forEach( item => item.messageInfo && item.messageInfo.id && uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item), ); const sortedChatMessageInfoItems = []; for (let i = 0; i < translatedSearchResults.length; i++) { sortedChatMessageInfoItems.push( uniqueChatMessageInfoItemsMap.get(translatedSearchResults[i].id), ); } return sortedChatMessageInfoItems.filter(Boolean); }, [chatMessageInfos, translatedSearchResults]); const modifiedItems = React.useMemo( () => filteredChatMessageInfos.map(item => modifyItemForResultScreen(item)), [filteredChatMessageInfos], ); const renderItem = React.useCallback( item => ( ), [threadInfo], ); const messages = React.useMemo( () => modifiedItems.map(item => renderItem(item)), [modifiedItems, renderItem], ); const messageContainer = React.useRef(null); const messageContainerRef = (msgContainer: ?HTMLDivElement) => { messageContainer.current = msgContainer; messageContainer.current?.addEventListener('scroll', onScroll); }; const { clearTooltip } = useTooltipContext(); const possiblyLoadMoreMessages = React.useCallback(() => { if (!messageContainer.current) { return; } const loaderTopOffset = 32; const { scrollTop, scrollHeight, clientHeight } = messageContainer.current; if ( endReached || Math.abs(scrollTop) + clientHeight + loaderTopOffset < scrollHeight ) { return; } setLastID(modifiedItems ? oldestMessageID(modifiedItems) : undefined); }, [endReached, modifiedItems]); const onScroll = React.useCallback(() => { if (!messageContainer.current) { return; } clearTooltip(); possiblyLoadMoreMessages(); }, [clearTooltip, possiblyLoadMoreMessages]); const footer = React.useMemo(() => { if (query === '') { return (
Your search results will appear here
); } if (!endReached) { return (
); } if (modifiedItems.length > 0) { return
End of results
; } return (
No results. Please try using different keywords to refine your search
); }, [query, endReached, modifiedItems.length]); return (
{messages} {footer}
); } function oldestMessageID(data: $ReadOnlyArray) { for (let i = data.length - 1; i >= 0; i--) { if (data[i].itemType === 'message' && data[i].messageInfo.id) { return data[i].messageInfo.id; } } return undefined; } type Props = { +threadInfo: ThreadInfo, }; function MessageSearchModal(props: Props): React.Node { const { threadInfo } = props; const { popModal } = useModalContext(); const { getQuery, setQuery, clearQuery } = useMessageSearchContext(); const query = React.useMemo( () => getQuery(threadInfo.id), [getQuery, threadInfo.id], ); const [input, setInput] = React.useState(query); const onPressSearch = React.useCallback( () => setQuery(input, threadInfo.id), [setQuery, input, threadInfo.id], ); const { uiName } = useResolvedThreadInfo(threadInfo); const searchPlaceholder = `Searching in ${uiName}`; const clearQueryWrapper = React.useCallback( () => clearQuery(threadInfo.id), [clearQuery, threadInfo.id], ); + const onKeyDown = React.useCallback( + event => { + if (event.key === 'Enter') { + onPressSearch(); + } + }, + [onPressSearch], + ); + return (
); } export default MessageSearchModal;