import { useQueryClient } from '@tanstack/react-query' import { useEffect, useMemo } from 'react' import { chatQueryClient } from '../contexts/api_provider' import { ApiCollection, MessageResource } from '../types' import { anchoredPageParam, isBehindLiveEdge, livePageParam, markLiveIfCaughtUp, MessagesWindow, newerPageParam, newestFirst, } from '../utils/cache/messages_window' import { replaceEqualById } from '../utils/replace_equal_by_id' import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages' import { useApiClient } from './use_api_client' import { SuspensePaginatorOptions, useSuspensePaginator } from './use_suspense_api' type WindowOptions = { conversation_id: number; reply_root_id?: string | null } type Options = WindowOptions & { anchor?: string | null } export const useConversationMessages = ( { conversation_id, reply_root_id, anchor }: Options, opts?: SuspensePaginatorOptions ) => { const queryClient = useQueryClient() const apiClient = useApiClient() const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id }) const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id }) const { data, infiniteData, refetch, isRefetching, fetchNextPage, fetchPreviousPage, hasNextPage, hasPreviousPage, } = useSuspensePaginator(requestArgs, { structuralSharing: replaceEqualById, initialPageParam: anchor ? anchoredPageParam(anchor) : livePageParam, getPreviousPageParam: newerPageParam, transformPage: newestFirst, ...opts, }) const behind = isBehindLiveEdge(infiniteData) const fetchOlder = () => fetchNextPage({ cancelRefetch: false }) const fetchNewer = async () => { if (!hasPreviousPage) return await fetchPreviousPage({ cancelRefetch: false }) } const fetchLiveWindow = async () => { if (!behind) return const page = await apiClient.chat.get>(requestArgs) await queryClient.cancelQueries({ queryKey, exact: true }) queryClient.setQueryData(queryKey, { pages: [page], pageParams: [livePageParam], }) } const messages = useMemo( () => data .filter(message => message.deletedAt || message.attachments?.length || message.text?.length) .sort((a, b) => -a.id.localeCompare(b.id)), [data] ) useEnsureWindowOpenedAtAnchor({ conversation_id, reply_root_id, anchor }) // Once nothing newer remains, the top of the window is the live edge. Without this, // a refetch would replay from the anchor and drop rows Jolt has added since. useEffect(() => { const settled = markLiveIfCaughtUp(infiniteData) if (settled !== infiniteData) { queryClient.setQueryData(queryKey, settled) } }, [infiniteData, queryClient, queryKey]) return { messages, behind, refetch, isRefetching, fetchNextPage: fetchOlder, fetchNewer, fetchLiveWindow, hasNextPage, queryKey, } } const useEnsureWindowOpenedAtAnchor = ({ conversation_id, reply_root_id, anchor }: Options) => { const queryClient = useQueryClient() useEffect(() => { if (!anchor) return const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id }) const cached = queryClient.getQueryData(queryKey) if (!cached) return const openedAtAnchor = cached.pageParams[0]?.where?.id_around === anchor if (!openedAtAnchor) { queryClient.resetQueries({ queryKey, exact: true }) } }, [anchor, conversation_id, reply_root_id, queryClient]) } export const dropMessagesWindowIfBehind = (options: WindowOptions) => { const queryKey = getMessagesQueryKey(options) const cached = chatQueryClient.getQueryData(queryKey) if (isBehindLiveEdge(cached)) chatQueryClient.removeQueries({ queryKey, exact: true }) }