import classNames from 'clsx'; import React, { useEffect, useRef } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { expandSearch, setFilter, setSearchAccount } from 'soapbox/actions/search'; import { fetchTrendingStatuses } from 'soapbox/actions/trending-statuses'; import Hashtag from 'soapbox/components/hashtag'; import IconButton from 'soapbox/components/icon-button'; import ScrollableList from 'soapbox/components/scrollable-list'; import { HStack, Tabs, Text } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import StatusContainer from 'soapbox/containers/status-container'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account'; import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder-hashtag'; import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; import type { VirtuosoHandle } from 'react-virtuoso'; import type { SearchFilter } from 'soapbox/reducers/search'; const messages = defineMessages({ accounts: { id: 'search_results.accounts', defaultMessage: 'People' }, statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' }, hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' }, }); const SearchResults = () => { const node = useRef(null); const intl = useIntl(); const dispatch = useAppDispatch(); const value = useAppSelector((state) => state.search.submittedValue); const results = useAppSelector((state) => state.search.results); const suggestions = useAppSelector((state) => state.suggestions.items); const trendingStatuses = useAppSelector((state) => state.trending_statuses.items); const trends = useAppSelector((state) => state.trends.items); const submitted = useAppSelector((state) => state.search.submitted); const selectedFilter = useAppSelector((state) => state.search.filter); const filterByAccount = useAppSelector((state) => state.search.accountId); const account = useAppSelector((state) => state.accounts.get(filterByAccount)?.acct); const handleLoadMore = () => dispatch(expandSearch(selectedFilter)); const handleUnsetAccount = () => dispatch(setSearchAccount(null)); const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter)); const renderFilterBar = () => { const items = [ { text: intl.formatMessage(messages.accounts), action: () => selectFilter('accounts'), name: 'accounts', }, { text: intl.formatMessage(messages.statuses), action: () => selectFilter('statuses'), name: 'statuses', }, { text: intl.formatMessage(messages.hashtags), action: () => selectFilter('hashtags'), name: 'hashtags', }, ]; return ; }; const getCurrentIndex = (id: string): number => { return resultsIds?.keySeq().findIndex(key => key === id); }; const handleMoveUp = (id: string) => { if (!resultsIds) return; const elementIndex = getCurrentIndex(id) - 1; selectChild(elementIndex); }; const handleMoveDown = (id: string) => { if (!resultsIds) return; const elementIndex = getCurrentIndex(id) + 1; selectChild(elementIndex); }; const selectChild = (index: number) => { node.current?.scrollIntoView({ index, behavior: 'smooth', done: () => { const element = document.querySelector(`#search-results [data-index="${index}"] .focusable`); element?.focus(); }, }); }; useEffect(() => { dispatch(fetchTrendingStatuses()); }, []); let searchResults; let hasMore = false; let loaded; let noResultsMessage; let placeholderComponent = PlaceholderStatus as React.ComponentType; let resultsIds: ImmutableOrderedSet; if (selectedFilter === 'accounts') { hasMore = results.accountsHasMore; loaded = results.accountsLoaded; placeholderComponent = PlaceholderAccount; if (results.accounts && results.accounts.size > 0) { searchResults = results.accounts.map(accountId => ); } else if (!submitted && suggestions && !suggestions.isEmpty()) { searchResults = suggestions.map(suggestion => ); } else if (loaded) { noResultsMessage = (
); } } if (selectedFilter === 'statuses') { hasMore = results.statusesHasMore; loaded = results.statusesLoaded; if (results.statuses && results.statuses.size > 0) { searchResults = results.statuses.map((statusId: string) => ( // @ts-ignore )); resultsIds = results.statuses; } else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) { searchResults = trendingStatuses.map((statusId: string) => ( // @ts-ignore )); resultsIds = trendingStatuses; } else if (loaded) { noResultsMessage = (
); } } if (selectedFilter === 'hashtags') { hasMore = results.hashtagsHasMore; loaded = results.hashtagsLoaded; placeholderComponent = PlaceholderHashtag; if (results.hashtags && results.hashtags.size > 0) { searchResults = results.hashtags.map(hashtag => ); } else if (!submitted && suggestions && !suggestions.isEmpty()) { searchResults = trends.map(hashtag => ); } else if (loaded) { noResultsMessage = (
); } } return ( <> {filterByAccount ? ( ) : renderFilterBar()} {noResultsMessage || ( {searchResults || []} )} ); }; export default SearchResults;