import React, { RefObject, useEffect, useMemo, useRef } from 'react'; import { Text, Box, Icon, Icons, config, Spinner, IconButton, Line, toRem } from 'folds'; import { useAtomValue } from 'jotai'; import { useVirtualizer } from '@tanstack/react-virtual'; import { useInfiniteQuery } from '@tanstack/react-query'; import { useSearchParams } from 'react-router-dom'; import { SearchOrderBy } from 'matrix-js-sdk'; import { PageHero, PageHeroSection } from '../../components/page'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { _SearchPathSearchParams } from '../../pages/paths'; import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; import { SequenceCard } from '../../components/sequence-card'; import { useRoomNavigate } from '../../hooks/useRoomNavigate'; import { ScrollTopContainer } from '../../components/scroll-top-container'; import { ContainerColor } from '../../styles/ContainerColor.css'; import { decodeSearchParamValueArray, encodeSearchParamValueArray } from '../../pages/pathUtils'; import { useRooms } from '../../state/hooks/roomList'; import { allRoomsAtom } from '../../state/room-list/roomList'; import { mDirectAtom } from '../../state/mDirectList'; import { MessageSearchParams, useMessageSearch } from './useMessageSearch'; import { SearchResultGroup } from './SearchResultGroup'; import { SearchInput } from './SearchInput'; import { SearchFilters } from './SearchFilters'; import { VirtualTile } from '../../components/virtualizer'; const useSearchPathSearchParams = (searchParams: URLSearchParams): _SearchPathSearchParams => useMemo( () => ({ global: searchParams.get('global') ?? undefined, term: searchParams.get('term') ?? undefined, order: searchParams.get('order') ?? undefined, rooms: searchParams.get('rooms') ?? undefined, senders: searchParams.get('senders') ?? undefined, }), [searchParams] ); type MessageSearchProps = { defaultRoomsFilterName: string; allowGlobal?: boolean; rooms: string[]; senders?: string[]; scrollRef: RefObject; }; export function MessageSearch({ defaultRoomsFilterName, allowGlobal, rooms, senders, scrollRef, }: MessageSearchProps) { const mx = useMatrixClient(); const mDirects = useAtomValue(mDirectAtom); const allRooms = useRooms(mx, allRoomsAtom, mDirects); const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [urlPreview] = useSetting(settingsAtom, 'urlPreview'); const [mxidColor] = useSetting(settingsAtom, 'mxidColor'); const searchInputRef = useRef(null); const scrollTopAnchorRef = useRef(null); const [searchParams, setSearchParams] = useSearchParams(); const searchPathSearchParams = useSearchPathSearchParams(searchParams); const { navigateRoom } = useRoomNavigate(); const searchParamRooms = useMemo(() => { if (searchPathSearchParams.rooms) { const joinedRoomIds = decodeSearchParamValueArray(searchPathSearchParams.rooms).filter( (rId) => allRooms.includes(rId) ); return joinedRoomIds; } return undefined; }, [allRooms, searchPathSearchParams.rooms]); const searchParamsSenders = useMemo(() => { if (searchPathSearchParams.senders) { return decodeSearchParamValueArray(searchPathSearchParams.senders); } return undefined; }, [searchPathSearchParams.senders]); const msgSearchParams: MessageSearchParams = useMemo(() => { const isGlobal = searchPathSearchParams.global === 'true'; const defaultRooms = isGlobal ? undefined : rooms; return { term: searchPathSearchParams.term, order: searchPathSearchParams.order ?? SearchOrderBy.Recent, rooms: searchParamRooms ?? defaultRooms, senders: searchParamsSenders ?? senders, }; }, [searchPathSearchParams, searchParamRooms, searchParamsSenders, rooms, senders]); const searchMessages = useMessageSearch(msgSearchParams); const { status, data, error, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ enabled: !!msgSearchParams.term, queryKey: [ 'search', msgSearchParams.term, msgSearchParams.order, msgSearchParams.rooms, msgSearchParams.senders, ], queryFn: ({ pageParam }) => searchMessages(pageParam), initialPageParam: '', getNextPageParam: (lastPage) => lastPage.nextToken, }); const groups = useMemo(() => data?.pages.flatMap((result) => result.groups) ?? [], [data]); const highlights = useMemo(() => { const mixed = data?.pages.flatMap((result) => result.highlights); return Array.from(new Set(mixed)); }, [data]); const virtualizer = useVirtualizer({ count: groups.length, getScrollElement: () => scrollRef.current, estimateSize: () => 40, overscan: 1, }); const vItems = virtualizer.getVirtualItems(); const handleSearch = (term: string) => { setSearchParams((prevParams) => { const newParams = new URLSearchParams(prevParams); newParams.delete('term'); newParams.append('term', term); return newParams; }); }; const handleSearchClear = () => { if (searchInputRef.current) { searchInputRef.current.value = ''; } setSearchParams((prevParams) => { const newParams = new URLSearchParams(prevParams); newParams.delete('term'); return newParams; }); }; const handleSelectedRoomsChange = (selectedRooms?: string[]) => { setSearchParams((prevParams) => { const newParams = new URLSearchParams(prevParams); newParams.delete('rooms'); if (selectedRooms && selectedRooms.length > 0) { newParams.append('rooms', encodeSearchParamValueArray(selectedRooms)); } return newParams; }); }; const handleGlobalChange = (global?: boolean) => { setSearchParams((prevParams) => { const newParams = new URLSearchParams(prevParams); newParams.delete('global'); if (global) { newParams.append('global', 'true'); } return newParams; }); }; const handleOrderChange = (order?: string) => { setSearchParams((prevParams) => { const newParams = new URLSearchParams(prevParams); newParams.delete('order'); if (order) { newParams.append('order', order); } return newParams; }); }; const lastVItem = vItems[vItems.length - 1]; const lastVItemIndex: number | undefined = lastVItem?.index; const lastGroupIndex = groups.length - 1; useEffect(() => { if ( lastGroupIndex > -1 && lastGroupIndex === lastVItemIndex && !isFetchingNextPage && hasNextPage ) { fetchNextPage(); } }, [lastVItemIndex, lastGroupIndex, fetchNextPage, isFetchingNextPage, hasNextPage]); return ( virtualizer.scrollToOffset(0)} variant="SurfaceVariant" radii="Pill" outlined size="300" aria-label="Scroll to Top" > {!msgSearchParams.term && status === 'pending' && ( } title="Search Messages" subTitle="Find helpful messages in your community by searching with related keywords." /> )} {msgSearchParams.term && groups.length === 0 && status === 'success' && ( No results found for {`"${msgSearchParams.term}"`} )} {((msgSearchParams.term && status === 'pending') || (groups.length > 0 && vItems.length === 0)) && ( {[...Array(8).keys()].map((key) => ( ))} )} {vItems.length > 0 && ( {`Results for "${msgSearchParams.term}"`}
{vItems.map((vItem) => { const group = groups[vItem.index]; if (!group) return null; const groupRoom = mx.getRoom(group.roomId); if (!groupRoom) return null; return ( ); })}
{isFetchingNextPage && ( )}
)} {error && ( {error.name} {error.message} )}
); }