@planningcenter/chat-react-native 4.2.1-qa-staging.2 → 4.2.1-qa-staging.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +1 -1
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/contexts/conversation_context.d.ts +3 -1
- package/build/contexts/conversation_context.d.ts.map +1 -1
- package/build/contexts/conversation_context.js +23 -18
- package/build/contexts/conversation_context.js.map +1 -1
- package/build/hooks/use_conversation_message_lookup.d.ts +7 -0
- package/build/hooks/use_conversation_message_lookup.d.ts.map +1 -0
- package/build/hooks/use_conversation_message_lookup.js +52 -0
- package/build/hooks/use_conversation_message_lookup.js.map +1 -0
- package/build/hooks/use_conversation_messages.d.ts +1 -0
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +8 -1
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts +12 -4
- package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +82 -88
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_fetch_newer_messages.d.ts.map +1 -1
- package/build/hooks/use_fetch_newer_messages.js +27 -21
- package/build/hooks/use_fetch_newer_messages.js.map +1 -1
- package/build/hooks/use_fetch_newer_on_foreground.d.ts +1 -1
- package/build/hooks/use_fetch_newer_on_foreground.d.ts.map +1 -1
- package/build/hooks/use_fetch_newer_on_foreground.js +2 -1
- package/build/hooks/use_fetch_newer_on_foreground.js.map +1 -1
- package/build/hooks/use_held_back_messages.d.ts +11 -0
- package/build/hooks/use_held_back_messages.d.ts.map +1 -0
- package/build/hooks/use_held_back_messages.js +24 -0
- package/build/hooks/use_held_back_messages.js.map +1 -0
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +91 -80
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/hooks/use_message_reaction_toggle.d.ts.map +1 -1
- package/build/hooks/use_message_reaction_toggle.js +45 -40
- package/build/hooks/use_message_reaction_toggle.js.map +1 -1
- package/build/screens/conversation_new/components/services_form.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +495 -157
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/message_actions_screen.d.ts.map +1 -1
- package/build/screens/message_actions_screen.js +21 -26
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/screens/message_report_screen.d.ts.map +1 -1
- package/build/screens/message_report_screen.js +17 -44
- package/build/screens/message_report_screen.js.map +1 -1
- package/build/screens/reactions_screen.d.ts.map +1 -1
- package/build/screens/reactions_screen.js +70 -88
- package/build/screens/reactions_screen.js.map +1 -1
- package/build/utils/cache/find_message_in_caches.d.ts +4 -0
- package/build/utils/cache/find_message_in_caches.d.ts.map +1 -0
- package/build/utils/cache/find_message_in_caches.js +16 -0
- package/build/utils/cache/find_message_in_caches.js.map +1 -0
- package/build/utils/cache/messages_cache.d.ts +1 -0
- package/build/utils/cache/messages_cache.d.ts.map +1 -1
- package/build/utils/cache/messages_cache.js +3 -0
- package/build/utils/cache/messages_cache.js.map +1 -1
- package/build/utils/cache/optimistically_update_message.d.ts +2 -1
- package/build/utils/cache/optimistically_update_message.d.ts.map +1 -1
- package/build/utils/cache/optimistically_update_message.js +4 -4
- package/build/utils/cache/optimistically_update_message.js.map +1 -1
- package/build/utils/cache/prepend_newer_page.d.ts +1 -1
- package/build/utils/cache/prepend_newer_page.d.ts.map +1 -1
- package/build/utils/cache/prepend_newer_page.js +10 -9
- package/build/utils/cache/prepend_newer_page.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/hooks/use_conversation_messages.test.ts +22 -0
- package/src/__tests__/hooks/use_conversation_messages_jolt_events.test.ts +22 -25
- package/src/components/conversation/message_form.tsx +1 -2
- package/src/contexts/conversation_context.tsx +6 -1
- package/src/hooks/__tests__/use_fetch_newer_messages.test.tsx +68 -0
- package/src/hooks/__tests__/use_held_back_messages.test.tsx +60 -0
- package/src/hooks/__tests__/use_message_reaction_toggle.test.tsx +73 -0
- package/src/hooks/use_conversation_message_lookup.ts +29 -0
- package/src/hooks/use_conversation_messages.ts +9 -0
- package/src/hooks/use_conversation_messages_jolt_events.ts +43 -41
- package/src/hooks/use_fetch_newer_messages.ts +19 -12
- package/src/hooks/use_fetch_newer_on_foreground.ts +2 -2
- package/src/hooks/use_held_back_messages.ts +33 -0
- package/src/hooks/use_message_create_or_update.ts +8 -8
- package/src/hooks/use_message_reaction_toggle.ts +3 -1
- package/src/screens/conversation_new/components/services_form.tsx +1 -1
- package/src/screens/conversation_screen.tsx +21 -20
- package/src/screens/message_actions_screen.tsx +3 -5
- package/src/screens/message_report_screen.tsx +2 -3
- package/src/screens/reactions_screen.tsx +2 -4
- package/src/utils/cache/__tests__/find_message_in_caches.test.ts +37 -0
- package/src/utils/cache/__tests__/messages_cache.test.ts +14 -1
- package/src/utils/cache/__tests__/prepend_newer_page.test.ts +13 -14
- package/src/utils/cache/find_message_in_caches.ts +20 -0
- package/src/utils/cache/messages_cache.ts +3 -0
- package/src/utils/cache/optimistically_update_message.ts +2 -5
- package/src/utils/cache/prepend_newer_page.ts +7 -10
|
@@ -2,10 +2,11 @@ import { useQueryClient } from '@tanstack/react-query'
|
|
|
2
2
|
import { useCallback } from 'react'
|
|
3
3
|
import { ApiCollection, MessageResource } from '../types'
|
|
4
4
|
import { prependNewerPage } from '../utils/cache/prepend_newer_page'
|
|
5
|
-
import { RequestData } from '../utils/client'
|
|
6
5
|
import { getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
7
6
|
import { useApiClient } from './use_api_client'
|
|
8
7
|
|
|
8
|
+
const MAX_PAGES = 5
|
|
9
|
+
|
|
9
10
|
type Options = {
|
|
10
11
|
conversation_id: number
|
|
11
12
|
reply_root_id?: string | null
|
|
@@ -17,20 +18,26 @@ export const useFetchNewerMessages = ({ conversation_id, reply_root_id, queryKey
|
|
|
17
18
|
const apiClient = useApiClient()
|
|
18
19
|
|
|
19
20
|
return useCallback(
|
|
20
|
-
(newestMessageId: string) => {
|
|
21
|
+
async (newestMessageId: string) => {
|
|
21
22
|
const { url, data } = getMessagesRequestArgs({ conversation_id, reply_root_id })
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
const pages: ApiCollection<MessageResource>[] = []
|
|
24
|
+
let cursor: string | undefined = newestMessageId
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
queryKey,
|
|
30
|
-
prev => prependNewerPage(prev, page, pageParam)
|
|
31
|
-
)
|
|
26
|
+
while (cursor && pages.length < MAX_PAGES) {
|
|
27
|
+
const page: ApiCollection<MessageResource> = await apiClient.chat.get({
|
|
28
|
+
url,
|
|
29
|
+
data: { ...data, where: { id_gt: cursor }, order: 'asc' },
|
|
32
30
|
})
|
|
33
|
-
.
|
|
31
|
+
pages.push(page)
|
|
32
|
+
cursor = page.meta?.next?.idGt
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
queryClient.setQueryData<Parameters<typeof prependNewerPage<MessageResource>>[0]>(
|
|
36
|
+
queryKey,
|
|
37
|
+
prev => prependNewerPage(prev, pages)
|
|
38
|
+
)
|
|
39
|
+
const moreThanWeFetched = !!cursor
|
|
40
|
+
if (moreThanWeFetched) await queryClient.invalidateQueries({ queryKey })
|
|
34
41
|
},
|
|
35
42
|
[apiClient, conversation_id, queryClient, queryKey, reply_root_id]
|
|
36
43
|
)
|
|
@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'
|
|
|
2
2
|
import { useAppState } from './use_app_state'
|
|
3
3
|
|
|
4
4
|
type Options = {
|
|
5
|
-
fetchNewer: (newestMessageId: string) => void
|
|
5
|
+
fetchNewer: (newestMessageId: string) => Promise<void>
|
|
6
6
|
newestMessageId?: string
|
|
7
7
|
enabled: boolean
|
|
8
8
|
}
|
|
@@ -20,6 +20,6 @@ export const useFetchNewerOnForeground = ({ fetchNewer, newestMessageId, enabled
|
|
|
20
20
|
wasInBackground.current = false
|
|
21
21
|
if (!returnedToForeground || !enabled || !newestMessageId) return
|
|
22
22
|
|
|
23
|
-
fetchNewer(newestMessageId)
|
|
23
|
+
fetchNewer(newestMessageId).catch(() => {})
|
|
24
24
|
}, [appState, enabled, fetchNewer, newestMessageId])
|
|
25
25
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
type Options = {
|
|
4
|
+
fetchNewer: (newestMessageId: string) => Promise<void>
|
|
5
|
+
newestMessageId?: string
|
|
6
|
+
readingHistory: boolean
|
|
7
|
+
caughtUp: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useHeldBackMessages = ({
|
|
11
|
+
fetchNewer,
|
|
12
|
+
newestMessageId,
|
|
13
|
+
readingHistory,
|
|
14
|
+
caughtUp,
|
|
15
|
+
}: Options) => {
|
|
16
|
+
const heldBackSince = useRef<string | null>(null)
|
|
17
|
+
|
|
18
|
+
const onSkippedNewMessage = useCallback(() => {
|
|
19
|
+
heldBackSince.current ??= newestMessageId ?? null
|
|
20
|
+
}, [newestMessageId])
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const since = heldBackSince.current
|
|
24
|
+
if (readingHistory || !caughtUp || !since) return
|
|
25
|
+
|
|
26
|
+
heldBackSince.current = null
|
|
27
|
+
fetchNewer(since).catch(() => {
|
|
28
|
+
heldBackSince.current ??= since
|
|
29
|
+
})
|
|
30
|
+
}, [caughtUp, fetchNewer, readingHistory])
|
|
31
|
+
|
|
32
|
+
return { onSkippedNewMessage }
|
|
33
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InfiniteData, useMutation } from '@tanstack/react-query'
|
|
2
2
|
import { chatQueryClient } from '../contexts/api_provider'
|
|
3
|
+
import { useConversationContext } from '../contexts/conversation_context'
|
|
3
4
|
import { ApiCollection, ApiResource, MessageResource } from '../types'
|
|
4
5
|
import { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'
|
|
5
6
|
import {
|
|
@@ -35,6 +36,12 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
35
36
|
const isEditing = !isNewMessage(message)
|
|
36
37
|
const apiClient = useApiClient()
|
|
37
38
|
const currentPerson = useCurrentPerson()
|
|
39
|
+
const { messagesQueryKey } = useConversationContext()
|
|
40
|
+
const liveQueryKey = getMessagesQueryKey({
|
|
41
|
+
conversation_id: conversationId,
|
|
42
|
+
reply_root_id: replyRootId,
|
|
43
|
+
})
|
|
44
|
+
const queryKey = isEditing ? (messagesQueryKey ?? liveQueryKey) : liveQueryKey
|
|
38
45
|
|
|
39
46
|
const mutation = useMutation({
|
|
40
47
|
mutationFn: ({ text, attachments, idempotentKey }: ResolvedMessageMutationVariables) => {
|
|
@@ -79,6 +86,7 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
79
86
|
message,
|
|
80
87
|
text,
|
|
81
88
|
replyRootId,
|
|
89
|
+
queryKey,
|
|
82
90
|
})
|
|
83
91
|
|
|
84
92
|
return { message: optimisticMessage }
|
|
@@ -102,10 +110,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
102
110
|
|
|
103
111
|
// Add error to the optimistic message from the cache on error
|
|
104
112
|
if (optimisticMessage) {
|
|
105
|
-
const queryKey = getMessagesQueryKey({
|
|
106
|
-
conversation_id: conversationId,
|
|
107
|
-
reply_root_id: replyRootId,
|
|
108
|
-
})
|
|
109
113
|
chatQueryClient.setQueryData(
|
|
110
114
|
queryKey,
|
|
111
115
|
(data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>
|
|
@@ -125,10 +129,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
125
129
|
const { message: optimisticMessage } = context || {}
|
|
126
130
|
const updatedMessage = result.data
|
|
127
131
|
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
128
|
-
const queryKey = getMessagesQueryKey({
|
|
129
|
-
conversation_id: conversationId,
|
|
130
|
-
reply_root_id: replyRootId,
|
|
131
|
-
})
|
|
132
132
|
|
|
133
133
|
// First remove the optimistic message if it exists
|
|
134
134
|
if (optimisticMessage) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
2
|
import { useCallback } from 'react'
|
|
3
3
|
import { Alert } from 'react-native'
|
|
4
|
+
import { useConversationContext } from '../contexts/conversation_context'
|
|
4
5
|
import { ApiCollection, ApiResource, MessageResource } from '../types'
|
|
5
6
|
import { ReactionCountResource } from '../types/resources/reaction'
|
|
6
7
|
import { updateRecordInPagesData, REACTION_EMOJIS } from '../utils'
|
|
@@ -30,7 +31,8 @@ export function useMessageReactionToggle({
|
|
|
30
31
|
}: UseMessageReactionToggleProps) {
|
|
31
32
|
const apiClient = useApiClient()
|
|
32
33
|
const queryClient = useQueryClient()
|
|
33
|
-
const
|
|
34
|
+
const { messagesQueryKey } = useConversationContext()
|
|
35
|
+
const queryKey = messagesQueryKey ?? getMessagesQueryKey({ conversation_id })
|
|
34
36
|
const message_id = message.id
|
|
35
37
|
|
|
36
38
|
const toggleReaction = useCallback(
|
|
@@ -45,7 +45,7 @@ export const ServicesForm = ({
|
|
|
45
45
|
const customAvatarsEnabled = featureEnabled(availableFeatures.custom_conversation_avatars)
|
|
46
46
|
const safetyLockEnabled = featureEnabled(availableFeatures.conversation_safety_lock)
|
|
47
47
|
const [selectedPlanId, setSelectedPlanId] = useState<number | undefined>(initialPlanId)
|
|
48
|
-
const [filterByPlan, setFilterByPlan] = useState(!!initialPlanId)
|
|
48
|
+
const [filterByPlan, setFilterByPlan] = useState<boolean>(!!initialPlanId)
|
|
49
49
|
const initialState = useMemo(() => uniq(initialTeamIds) || [], [initialTeamIds]) // Uniq here because services can send duplicates in the teams_i_lead response.
|
|
50
50
|
const [selectedTeamIds, setSelectedTeamIds] = useState<number[]>(initialState)
|
|
51
51
|
|
|
@@ -33,6 +33,7 @@ import { useConversationMessages } from '../hooks/use_conversation_messages'
|
|
|
33
33
|
import { useConversationMessagesJoltEvents } from '../hooks/use_conversation_messages_jolt_events'
|
|
34
34
|
import { useFetchNewerMessages } from '../hooks/use_fetch_newer_messages'
|
|
35
35
|
import { useFetchNewerOnForeground } from '../hooks/use_fetch_newer_on_foreground'
|
|
36
|
+
import { useHeldBackMessages } from '../hooks/use_held_back_messages'
|
|
36
37
|
import { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'
|
|
37
38
|
import {
|
|
38
39
|
analyticsEvents,
|
|
@@ -44,7 +45,9 @@ import { useVideoModerationJoltEvents } from '../hooks/use_video_moderation_jolt
|
|
|
44
45
|
import { ConversationResource } from '../types/resources/conversation'
|
|
45
46
|
import { ConversationBadgeResource } from '../types/resources/conversation_badge'
|
|
46
47
|
import { MessageResource } from '../types/resources/message'
|
|
48
|
+
import { newestPersistedMessageId } from '../utils/cache/messages_cache'
|
|
47
49
|
import { gatedMessageCache } from '../utils/gated_message_cache'
|
|
50
|
+
import { getMessagesQueryKey } from '../utils/request/get_messages'
|
|
48
51
|
import { decodeUlidTime } from '../utils/ulid'
|
|
49
52
|
|
|
50
53
|
export type ConversationRouteProps = {
|
|
@@ -85,6 +88,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
85
88
|
<ConversationContextProvider
|
|
86
89
|
conversationId={conversation_id}
|
|
87
90
|
currentPageReplyRootId={reply_root_id ?? null}
|
|
91
|
+
messagesQueryKey={getMessagesQueryKey({ conversation_id, reply_root_id, anchor })}
|
|
88
92
|
>
|
|
89
93
|
{anchor ? (
|
|
90
94
|
<AnchorNotFoundBoundary onAnchorNotFound={dropAnchor}>
|
|
@@ -134,23 +138,27 @@ function ConversationScreenContent({
|
|
|
134
138
|
const caughtUp = !hasPreviousPage
|
|
135
139
|
const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)
|
|
136
140
|
const readingHistory = showJumpToBottomButton
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
141
|
+
const newestMessageId = newestPersistedMessageId(messages)
|
|
142
|
+
const fetchNewer = useFetchNewerMessages({ conversation_id, reply_root_id, queryKey })
|
|
143
|
+
const { onSkippedNewMessage } = useHeldBackMessages({
|
|
144
|
+
fetchNewer,
|
|
145
|
+
newestMessageId,
|
|
146
|
+
readingHistory,
|
|
147
|
+
caughtUp,
|
|
148
|
+
})
|
|
149
|
+
useConversationJoltEvents({ conversationId: conversation_id })
|
|
150
|
+
useConversationMessagesJoltEvents({
|
|
151
|
+
conversationId: conversation_id,
|
|
152
|
+
replyRootId: reply_root_id ?? null,
|
|
153
|
+
messageCache: gatedMessageCache({
|
|
140
154
|
queryKey,
|
|
141
155
|
acceptNewMessages: caughtUp && !readingHistory,
|
|
142
|
-
onSkippedNewMessage
|
|
143
|
-
skippedNewMessages.current = true
|
|
144
|
-
},
|
|
156
|
+
onSkippedNewMessage,
|
|
145
157
|
}),
|
|
146
|
-
|
|
147
|
-
const newestMessageId = messages[0]?.id
|
|
148
|
-
useConversationJoltEvents({ conversationId: conversation_id })
|
|
149
|
-
useConversationMessagesJoltEvents({ conversationId: conversation_id, messageCaches })
|
|
158
|
+
})
|
|
150
159
|
const { markHandledByMessageForm } = useVideoModerationJoltEvents()
|
|
151
160
|
useEnsureConversationsRouteExists()
|
|
152
161
|
useMarkLatestMessageRead({ conversation, messages, enabled: !behindLatest })
|
|
153
|
-
const fetchNewer = useFetchNewerMessages({ conversation_id, reply_root_id, queryKey })
|
|
154
162
|
useFetchNewerOnForeground({ fetchNewer, newestMessageId, enabled: anchored && caughtUp })
|
|
155
163
|
const noMessages = messages.length === 0
|
|
156
164
|
|
|
@@ -187,9 +195,9 @@ function ConversationScreenContent({
|
|
|
187
195
|
}, [handleReturnToBottom, onExitAnchoredMode])
|
|
188
196
|
const jumpToLatest = useCallback(() => {
|
|
189
197
|
setShowJumpToBottomButton(false)
|
|
190
|
-
if (
|
|
198
|
+
if (anchored) exitAnchoredMode()
|
|
191
199
|
else handleReturnToBottom()
|
|
192
|
-
}, [
|
|
200
|
+
}, [anchored, exitAnchoredMode, handleReturnToBottom])
|
|
193
201
|
|
|
194
202
|
const trackScroll = (event: any) => {
|
|
195
203
|
const offsetY = event.nativeEvent.contentOffset.y
|
|
@@ -203,13 +211,6 @@ function ConversationScreenContent({
|
|
|
203
211
|
fetchPreviousPage()
|
|
204
212
|
}
|
|
205
213
|
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
if (readingHistory || !caughtUp || !newestMessageId || !skippedNewMessages.current) return
|
|
208
|
-
|
|
209
|
-
skippedNewMessages.current = false
|
|
210
|
-
fetchNewer(newestMessageId)
|
|
211
|
-
}, [caughtUp, fetchNewer, newestMessageId, readingHistory])
|
|
212
|
-
|
|
213
214
|
useEffect(() => {
|
|
214
215
|
if (reply_root_id) {
|
|
215
216
|
navigation.setParams({
|
|
@@ -9,6 +9,7 @@ import { useReactionStyles } from '../components/conversation/message_reaction'
|
|
|
9
9
|
import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
|
|
10
10
|
import { useCreateAndroidRippleColor, useFontScale, useTheme } from '../hooks'
|
|
11
11
|
import { useApiClient } from '../hooks/use_api_client'
|
|
12
|
+
import { useConversationMessageLookup } from '../hooks/use_conversation_message_lookup'
|
|
12
13
|
import { useConversationMessages } from '../hooks/use_conversation_messages'
|
|
13
14
|
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
14
15
|
import { useMessageReactionToggle } from '../hooks/use_message_reaction_toggle'
|
|
@@ -41,11 +42,8 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
41
42
|
isSystemMessage,
|
|
42
43
|
} = route.params
|
|
43
44
|
|
|
44
|
-
const {
|
|
45
|
-
|
|
46
|
-
{ refetchOnMount: false }
|
|
47
|
-
)
|
|
48
|
-
const message = messages.find(m => m.id === message_id)
|
|
45
|
+
const { refetch } = useConversationMessages({ conversation_id }, { refetchOnMount: false })
|
|
46
|
+
const message = useConversationMessageLookup({ conversation_id, message_id })
|
|
49
47
|
|
|
50
48
|
if (!message) return null
|
|
51
49
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from '../components/display/platform_modal_header_buttons'
|
|
15
15
|
import BlankState from '../components/primitive/blank_state_primitive'
|
|
16
16
|
import { useTheme } from '../hooks'
|
|
17
|
-
import {
|
|
17
|
+
import { useConversationMessageLookup } from '../hooks/use_conversation_message_lookup'
|
|
18
18
|
import { useReportMessage } from '../hooks/use_report_message'
|
|
19
19
|
import { MessageResource } from '../types'
|
|
20
20
|
import { MessageReportReason } from '../types/resources/message_report'
|
|
@@ -51,8 +51,7 @@ export type MessageReportScreenProps = StaticScreenProps<{
|
|
|
51
51
|
export function MessageReportScreen({ route }: MessageReportScreenProps) {
|
|
52
52
|
const { conversation_id, message_id } = route.params
|
|
53
53
|
|
|
54
|
-
const
|
|
55
|
-
const message = messages.find(m => m.id === message_id)
|
|
54
|
+
const message = useConversationMessageLookup({ conversation_id, message_id })
|
|
56
55
|
|
|
57
56
|
if (!message) return null
|
|
58
57
|
|
|
@@ -7,7 +7,7 @@ import { Avatar, Text } from '../components'
|
|
|
7
7
|
import { Tabs } from '../components/display/tabs'
|
|
8
8
|
import FormSheet, { getFormSheetScreenOptions } from '../components/primitive/form_sheet'
|
|
9
9
|
import { useSuspenseGet, useTheme } from '../hooks'
|
|
10
|
-
import {
|
|
10
|
+
import { useConversationMessageLookup } from '../hooks/use_conversation_message_lookup'
|
|
11
11
|
import { useFontScale } from '../hooks/use_font_scale'
|
|
12
12
|
import { MemberResource } from '../types'
|
|
13
13
|
import { ReactionCountResource } from '../types/resources/reaction'
|
|
@@ -31,9 +31,7 @@ export function ReactionsScreen({ route }: ReactionScreenProps) {
|
|
|
31
31
|
const { conversation_id, message_id, reaction_value } = route.params
|
|
32
32
|
const styles = useStyles()
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const message = messages.find(m => m.id === message_id)
|
|
34
|
+
const message = useConversationMessageLookup({ conversation_id, message_id })
|
|
37
35
|
const reactionCounts = message?.reactionCounts || []
|
|
38
36
|
const initialReactionCount =
|
|
39
37
|
reactionCounts.find(r => r.value === reaction_value) || reactionCounts[0]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { getMessagesQueryKey } from '../../request/get_messages'
|
|
3
|
+
import { findMessageInCaches } from '../find_message_in_caches'
|
|
4
|
+
|
|
5
|
+
const page = (ids: string[]) => ({
|
|
6
|
+
data: ids.map(id => ({ id, type: 'Message' })),
|
|
7
|
+
links: {},
|
|
8
|
+
meta: { count: ids.length, totalCount: ids.length },
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const seed = (queryClient: QueryClient, key: unknown[], ids: string[]) =>
|
|
12
|
+
queryClient.setQueryData(key, { pages: [page(ids)], pageParams: [{}] })
|
|
13
|
+
|
|
14
|
+
describe('findMessageInCaches', () => {
|
|
15
|
+
const queryClient = new QueryClient()
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
queryClient.clear()
|
|
19
|
+
seed(queryClient, getMessagesQueryKey({ conversation_id: 1 }), ['01C', '01B'])
|
|
20
|
+
seed(queryClient, getMessagesQueryKey({ conversation_id: 1, anchor: '01A' }), ['01A', '00Z'])
|
|
21
|
+
seed(queryClient, getMessagesQueryKey({ conversation_id: 1, reply_root_id: '01B' }), ['01R'])
|
|
22
|
+
seed(queryClient, getMessagesQueryKey({ conversation_id: 2 }), ['02A'])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('finds a message in the live cache', () => {
|
|
26
|
+
expect(findMessageInCaches(queryClient, 1, '01B')?.id).toBe('01B')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('finds a message that only the anchored cache holds', () => {
|
|
30
|
+
expect(findMessageInCaches(queryClient, 1, '00Z')?.id).toBe('00Z')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('ignores reply threads and other conversations', () => {
|
|
34
|
+
expect(findMessageInCaches(queryClient, 1, '01R')).toBeUndefined()
|
|
35
|
+
expect(findMessageInCaches(queryClient, 1, '02A')).toBeUndefined()
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InfiniteData, QueryClient } from '@tanstack/react-query'
|
|
2
2
|
import type { ApiCollection, MessageResource } from '../../../types'
|
|
3
3
|
import type { PersonResource } from '../../../types/resources/person'
|
|
4
|
-
import { updateCacheWithMessage } from '../messages_cache'
|
|
4
|
+
import { newestPersistedMessageId, updateCacheWithMessage } from '../messages_cache'
|
|
5
5
|
|
|
6
6
|
type MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
7
7
|
|
|
@@ -87,3 +87,16 @@ describe('updateCacheWithMessage on message.created', () => {
|
|
|
87
87
|
expect(idsInCache(queryClient)).toEqual(['01F', '01B-temp', '01A-temp'])
|
|
88
88
|
})
|
|
89
89
|
})
|
|
90
|
+
|
|
91
|
+
describe('newestPersistedMessageId', () => {
|
|
92
|
+
it('skips optimistic rows so a failed send cannot become the catch-up cursor', () => {
|
|
93
|
+
const messages = [message({ id: '01ZZZ-temp' }), message({ id: '01B' }), message({ id: '01A' })]
|
|
94
|
+
|
|
95
|
+
expect(newestPersistedMessageId(messages)).toBe('01B')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('is undefined when nothing has been persisted', () => {
|
|
99
|
+
expect(newestPersistedMessageId([message({ id: '01ZZZ-temp' })])).toBeUndefined()
|
|
100
|
+
expect(newestPersistedMessageId([])).toBeUndefined()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -5,31 +5,30 @@ const page = (ids: string[], next?: { idGt: string }) => ({
|
|
|
5
5
|
links: {},
|
|
6
6
|
meta: { count: ids.length, totalCount: ids.length, next },
|
|
7
7
|
})
|
|
8
|
-
const pageParam = { where: { id_gt: '01C' }, order: 'asc' }
|
|
9
8
|
|
|
10
9
|
describe('prependNewerPage', () => {
|
|
11
10
|
const cached = { pages: [page(['01C', '01B']), page(['01A'])], pageParams: [{}, { offset: 1 }] }
|
|
12
11
|
|
|
13
|
-
it('
|
|
14
|
-
const result = prependNewerPage(cached, page(['01C', '01D', '01E'])
|
|
12
|
+
it('merges the new rows into the first page and leaves the page params alone', () => {
|
|
13
|
+
const result = prependNewerPage(cached, [page(['01C', '01D', '01E'])])
|
|
15
14
|
|
|
16
|
-
expect(result?.pages
|
|
17
|
-
expect(result?.pages).
|
|
18
|
-
expect(result?.pageParams
|
|
15
|
+
expect(result?.pages).toHaveLength(2)
|
|
16
|
+
expect(result?.pages[0].data.map(m => m.id)).toEqual(['01D', '01E', '01C', '01B'])
|
|
17
|
+
expect(result?.pageParams).toEqual(cached.pageParams)
|
|
19
18
|
})
|
|
20
19
|
|
|
21
|
-
it('
|
|
22
|
-
|
|
23
|
-
})
|
|
20
|
+
it('merges every fetched batch, newest last', () => {
|
|
21
|
+
const result = prependNewerPage(cached, [page(['01D', '01E']), page(['01F'])])
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
expect(result?.pages[0].data.map(m => m.id)).toEqual(['01D', '01E', '01F', '01C', '01B'])
|
|
24
|
+
})
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
expect(
|
|
26
|
+
it('returns the same data when nothing new came back', () => {
|
|
27
|
+
expect(prependNewerPage(cached, [page(['01C'])])).toBe(cached)
|
|
28
|
+
expect(prependNewerPage(cached, [])).toBe(cached)
|
|
30
29
|
})
|
|
31
30
|
|
|
32
31
|
it('does nothing without cached data', () => {
|
|
33
|
-
expect(prependNewerPage(undefined, page(['01D'])
|
|
32
|
+
expect(prependNewerPage(undefined, [page(['01D'])])).toBeUndefined()
|
|
34
33
|
})
|
|
35
34
|
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InfiniteData, QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { ApiCollection, MessageResource } from '../../types'
|
|
3
|
+
import { getMessagesRequestArgs } from '../request/get_messages'
|
|
4
|
+
|
|
5
|
+
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
6
|
+
|
|
7
|
+
export const findMessageInCaches = (
|
|
8
|
+
queryClient: QueryClient,
|
|
9
|
+
conversation_id: number,
|
|
10
|
+
message_id: string
|
|
11
|
+
): MessageResource | undefined => {
|
|
12
|
+
const { url } = getMessagesRequestArgs({ conversation_id })
|
|
13
|
+
|
|
14
|
+
for (const [, data] of queryClient.getQueriesData<QueryData>({ queryKey: [url] })) {
|
|
15
|
+
const found = data?.pages.flatMap(page => page.data).find(message => message.id === message_id)
|
|
16
|
+
if (found) return found
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
@@ -135,6 +135,9 @@ type MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
|
135
135
|
export function isTemporaryMessageId(messageId?: string | null): boolean {
|
|
136
136
|
return !!messageId && messageId.endsWith('-temp')
|
|
137
137
|
}
|
|
138
|
+
export function newestPersistedMessageId(messages: MessageResource[]): string | undefined {
|
|
139
|
+
return messages.find(message => !isTemporaryMessageId(message.id))?.id
|
|
140
|
+
}
|
|
138
141
|
export function isNewMessage(message?: MessageResource): boolean {
|
|
139
142
|
return !message?.id || isTemporaryMessageId(message.id)
|
|
140
143
|
}
|
|
@@ -9,19 +9,16 @@ export function optimisticallyUpdateMessage({
|
|
|
9
9
|
message,
|
|
10
10
|
text,
|
|
11
11
|
replyRootId,
|
|
12
|
+
queryKey = getMessagesQueryKey({ conversation_id: conversationId, reply_root_id: replyRootId }),
|
|
12
13
|
}: {
|
|
13
14
|
conversationId: number
|
|
14
15
|
message: MessageResource
|
|
15
16
|
text: string
|
|
16
17
|
replyRootId?: string | null
|
|
18
|
+
queryKey?: unknown[]
|
|
17
19
|
}) {
|
|
18
20
|
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
19
21
|
|
|
20
|
-
const queryKey = getMessagesQueryKey({
|
|
21
|
-
conversation_id: conversationId,
|
|
22
|
-
reply_root_id: replyRootId,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
22
|
let optimisticMessage = message
|
|
26
23
|
|
|
27
24
|
chatQueryClient.setQueryData<QueryData>(queryKey, data =>
|
|
@@ -6,17 +6,14 @@ type PagesData<T extends ResourceObject> = InfiniteData<ApiCollection<T>, Partia
|
|
|
6
6
|
|
|
7
7
|
export function prependNewerPage<T extends ResourceObject>(
|
|
8
8
|
data: PagesData<T> | undefined,
|
|
9
|
-
|
|
10
|
-
pageParam: Partial<RequestData>
|
|
9
|
+
pages: ApiCollection<T>[]
|
|
11
10
|
): PagesData<T> | undefined {
|
|
12
|
-
if (!data) return data
|
|
11
|
+
if (!data || data.pages.length === 0) return data
|
|
13
12
|
|
|
14
|
-
const knownIds = new Set(data.pages.flatMap(
|
|
15
|
-
const fresh = page.data.filter(record => !knownIds.has(record.id))
|
|
16
|
-
if (fresh.length === 0
|
|
13
|
+
const knownIds = new Set(data.pages.flatMap(page => page.data.map(record => record.id)))
|
|
14
|
+
const fresh = pages.flatMap(page => page.data).filter(record => !knownIds.has(record.id))
|
|
15
|
+
if (fresh.length === 0) return data
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
pageParams: [pageParam, ...data.pageParams],
|
|
21
|
-
}
|
|
17
|
+
const [first, ...rest] = data.pages
|
|
18
|
+
return { ...data, pages: [{ ...first, data: [...fresh, ...first.data] }, ...rest] }
|
|
22
19
|
}
|