@planningcenter/chat-react-native 3.18.0-rc.5 → 3.18.0-rc.7
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.d.ts.map +1 -1
- package/build/components/conversation/message.js +2 -1
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversations/conversations.d.ts.map +1 -1
- package/build/components/conversations/conversations.js +2 -3
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +23 -70
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_message_create_or_update.d.ts +0 -2
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +10 -8
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +3 -4
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_new/components/form_list.d.ts +2 -2
- package/build/screens/conversation_new/components/form_list.d.ts.map +1 -1
- package/build/screens/conversation_new/components/form_list.js +2 -3
- package/build/screens/conversation_new/components/form_list.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js +2 -3
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js +2 -3
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js.map +1 -1
- package/build/types/jolt_events/reaction_events.d.ts +1 -0
- package/build/types/jolt_events/reaction_events.d.ts.map +1 -1
- package/build/types/jolt_events/reaction_events.js.map +1 -1
- package/build/utils/cache/messages_cache.d.ts +9 -0
- package/build/utils/cache/messages_cache.d.ts.map +1 -0
- package/build/utils/cache/messages_cache.js +89 -0
- package/build/utils/cache/messages_cache.js.map +1 -0
- package/build/utils/cache/optimistically_create_message.d.ts +2 -1
- package/build/utils/cache/optimistically_create_message.d.ts.map +1 -1
- package/build/utils/cache/optimistically_create_message.js +6 -3
- package/build/utils/cache/optimistically_create_message.js.map +1 -1
- package/package.json +2 -3
- package/src/components/conversation/message.tsx +2 -1
- package/src/components/conversations/conversations.tsx +7 -9
- package/src/hooks/use_conversation_messages_jolt_events.ts +39 -81
- package/src/hooks/use_message_create_or_update.ts +10 -9
- package/src/screens/conversation_details_screen.tsx +3 -4
- package/src/screens/conversation_new/components/form_list.tsx +3 -5
- package/src/screens/conversation_select_recipients/conversation_select_group_recipients_screen.tsx +2 -4
- package/src/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.tsx +2 -4
- package/src/types/jolt_events/reaction_events.ts +1 -0
- package/src/utils/cache/messages_cache.ts +113 -0
- package/src/utils/cache/optimistically_create_message.ts +7 -2
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native'
|
|
2
|
-
import { FlashList } from '@shopify/flash-list'
|
|
3
2
|
import React, { useMemo } from 'react'
|
|
4
|
-
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import { FlatList, StyleSheet, View } from 'react-native'
|
|
5
4
|
import { useConversationsContext } from '../../contexts/conversations_context'
|
|
6
5
|
import { useTheme } from '../../hooks'
|
|
7
6
|
import { ConversationResource } from '../../types'
|
|
@@ -35,7 +34,7 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
|
|
|
35
34
|
|
|
36
35
|
const showBadges = !chat_group_graph_id
|
|
37
36
|
|
|
38
|
-
const data:
|
|
37
|
+
const data: FlatListItem[] = useMemo(() => {
|
|
39
38
|
if (isLoading) {
|
|
40
39
|
return loadingPlaceholder
|
|
41
40
|
}
|
|
@@ -54,9 +53,8 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
|
|
|
54
53
|
|
|
55
54
|
return (
|
|
56
55
|
<View style={styles.container}>
|
|
57
|
-
<
|
|
56
|
+
<FlatList
|
|
58
57
|
data={data}
|
|
59
|
-
estimatedItemSize={97}
|
|
60
58
|
keyExtractor={item => item.id.toString()}
|
|
61
59
|
contentContainerStyle={styles.contentContainer}
|
|
62
60
|
onRefresh={refetch}
|
|
@@ -114,18 +112,18 @@ const useStyles = () => {
|
|
|
114
112
|
})
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
interface
|
|
115
|
+
interface FlatListLoadingItem {
|
|
118
116
|
type: 'loading'
|
|
119
117
|
id: string
|
|
120
118
|
}
|
|
121
|
-
interface
|
|
119
|
+
interface FlatListConversationItem {
|
|
122
120
|
type: 'conversation'
|
|
123
121
|
resource: ConversationResource
|
|
124
122
|
id: number
|
|
125
123
|
}
|
|
126
|
-
type
|
|
124
|
+
type FlatListItem = FlatListLoadingItem | FlatListConversationItem
|
|
127
125
|
|
|
128
|
-
const loadingPlaceholder:
|
|
126
|
+
const loadingPlaceholder: FlatListItem[] = Array.from({ length: 5 }, (_, i) => ({
|
|
129
127
|
type: 'loading',
|
|
130
128
|
id: `loading${i}`,
|
|
131
129
|
}))
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { ApiCollection, MessageResource } from '../types'
|
|
2
2
|
import { useJoltChannel, useJoltEvent } from './use_jolt'
|
|
3
|
-
import {
|
|
4
|
-
deleteRecordInPagesData,
|
|
5
|
-
updateOrCreateRecordInPagesData,
|
|
6
|
-
updateRecordInPagesData,
|
|
7
|
-
} from '../utils'
|
|
3
|
+
import { deleteRecordInPagesData } from '../utils'
|
|
8
4
|
import { MessageCreatedEvent, MessageDeletedEvent } from '../types/jolt_events/message_events'
|
|
9
5
|
import { InfiniteData, useQueryClient } from '@tanstack/react-query'
|
|
10
6
|
import { useCurrentPerson } from './use_current_person'
|
|
11
7
|
import { transformMessageEventDataToMessageResource } from '../utils/jolt/transform_message_event_data_to_message_resource'
|
|
12
8
|
import { getRequestQueryKey } from './use_suspense_api'
|
|
13
9
|
import { JoltReactionEvent, JoltTypingEvent } from '../types/jolt_events'
|
|
14
|
-
import { transformReactionEventDataToReactionCountResource } from '../utils/jolt/transform_reaction_event_data_to_reaction_count_resource'
|
|
15
10
|
import { getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
16
11
|
import { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'
|
|
17
|
-
import { isTemporaryMessageId } from './use_message_create_or_update'
|
|
18
12
|
import { completeMessageCreationTracking } from '../utils/performance_tracking'
|
|
19
13
|
import { useApiClient } from './use_api_client'
|
|
14
|
+
import {
|
|
15
|
+
updateCacheWithMessage,
|
|
16
|
+
updateCacheWithReaction,
|
|
17
|
+
getThreadedMessagesQueryKey,
|
|
18
|
+
} from '../utils/cache/messages_cache'
|
|
20
19
|
|
|
21
20
|
interface Props {
|
|
22
21
|
conversationId: number
|
|
@@ -47,42 +46,17 @@ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
isTemporaryMessageId(existingMessage.id) &&
|
|
62
|
-
existingMessage.text === message.text &&
|
|
63
|
-
existingMessage.mine
|
|
64
|
-
)
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return updateOrCreateRecordInPagesData({
|
|
70
|
-
data: dataAfterTempRemoval,
|
|
71
|
-
record: message,
|
|
72
|
-
processRecord: (record, current) => {
|
|
73
|
-
return { ...current, ...record }
|
|
74
|
-
},
|
|
75
|
-
})
|
|
76
|
-
} else {
|
|
77
|
-
return updateRecordInPagesData({
|
|
78
|
-
data: prev,
|
|
79
|
-
record: message,
|
|
80
|
-
processRecord: (record, current) => {
|
|
81
|
-
return { ...current, ...record }
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
})
|
|
49
|
+
// Update the main conversation cache
|
|
50
|
+
updateCacheWithMessage(queryClient, messagesQueryKey, message, e.event)
|
|
51
|
+
|
|
52
|
+
// If message has a reply_root_id, also update the threaded cache
|
|
53
|
+
if (data.reply_root_id) {
|
|
54
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(
|
|
55
|
+
conversationId,
|
|
56
|
+
data.reply_root_id
|
|
57
|
+
)
|
|
58
|
+
updateCacheWithMessage(queryClient, threadedMessagesQueryKey, message, e.event)
|
|
59
|
+
}
|
|
86
60
|
}
|
|
87
61
|
|
|
88
62
|
const handleMessageDeleted = async (e: MessageDeletedEvent) => {
|
|
@@ -92,50 +66,34 @@ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
|
|
|
92
66
|
currentPersonId: currentPerson.id,
|
|
93
67
|
})
|
|
94
68
|
|
|
69
|
+
// Update the main conversation cache
|
|
95
70
|
queryClient.setQueryData<QueryData>(messagesQueryKey, prev =>
|
|
96
71
|
deleteRecordInPagesData({ data: prev, record: message })
|
|
97
72
|
)
|
|
73
|
+
|
|
74
|
+
// If message has a reply_root_id, also update the threaded cache
|
|
75
|
+
if (data.reply_root_id) {
|
|
76
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(
|
|
77
|
+
conversationId,
|
|
78
|
+
data.reply_root_id
|
|
79
|
+
)
|
|
80
|
+
queryClient.setQueryData<QueryData>(threadedMessagesQueryKey, prev =>
|
|
81
|
+
deleteRecordInPagesData({ data: prev, record: message })
|
|
82
|
+
)
|
|
83
|
+
}
|
|
98
84
|
}
|
|
99
85
|
|
|
100
86
|
const handleReactionJoltEvent = async (e: JoltReactionEvent) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (reactionCount.value === data.value) {
|
|
112
|
-
foundMatch = true
|
|
113
|
-
return transformReactionEventDataToReactionCountResource({
|
|
114
|
-
data,
|
|
115
|
-
oldData: reactionCount,
|
|
116
|
-
event: e.event,
|
|
117
|
-
currentPersonId: currentPerson.id,
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
return reactionCount
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
if (!foundMatch) {
|
|
124
|
-
const newReactionCount = transformReactionEventDataToReactionCountResource({
|
|
125
|
-
data,
|
|
126
|
-
event: e.event,
|
|
127
|
-
currentPersonId: currentPerson.id,
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
if (newReactionCount?.count) {
|
|
131
|
-
newReactionCounts = [...newReactionCounts, newReactionCount]
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return { ...oldMessage, reactionCounts: newReactionCounts }
|
|
136
|
-
},
|
|
137
|
-
})
|
|
138
|
-
)
|
|
87
|
+
// Update the main conversation cache and capture the reply_root_id if present
|
|
88
|
+
updateCacheWithReaction(queryClient, messagesQueryKey, e, currentPerson.id)
|
|
89
|
+
|
|
90
|
+
const replyRootId = e.data.data.reply_root_id
|
|
91
|
+
|
|
92
|
+
// If the message has a reply_root_id, also update the threaded cache
|
|
93
|
+
if (replyRootId) {
|
|
94
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(conversationId, replyRootId)
|
|
95
|
+
updateCacheWithReaction(queryClient, threadedMessagesQueryKey, e, currentPerson.id)
|
|
96
|
+
}
|
|
139
97
|
}
|
|
140
98
|
|
|
141
99
|
const handleTypingEvent = async (e: JoltTypingEvent) => {
|
|
@@ -14,6 +14,7 @@ import { useCurrentPerson } from './use_current_person'
|
|
|
14
14
|
import { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message'
|
|
15
15
|
import { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message'
|
|
16
16
|
import { startMessageCreationTracking } from '../utils/performance_tracking'
|
|
17
|
+
import { isNewMessage } from '../utils/cache/messages_cache'
|
|
17
18
|
|
|
18
19
|
interface Props {
|
|
19
20
|
conversationId: number
|
|
@@ -93,6 +94,7 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
93
94
|
text,
|
|
94
95
|
attachments,
|
|
95
96
|
currentPerson,
|
|
97
|
+
replyRootId,
|
|
96
98
|
})
|
|
97
99
|
|
|
98
100
|
return { message: optimisticMessage }
|
|
@@ -103,7 +105,10 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
103
105
|
|
|
104
106
|
// Add error to the optimistic message from the cache on error
|
|
105
107
|
if (optimisticMessage) {
|
|
106
|
-
const queryKey = getMessagesQueryKey({
|
|
108
|
+
const queryKey = getMessagesQueryKey({
|
|
109
|
+
conversation_id: conversationId,
|
|
110
|
+
reply_root_id: replyRootId,
|
|
111
|
+
})
|
|
107
112
|
chatQueryClient.setQueryData(
|
|
108
113
|
queryKey,
|
|
109
114
|
(data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>
|
|
@@ -123,7 +128,10 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
123
128
|
const { message: optimisticMessage } = context || {}
|
|
124
129
|
const updatedMessage = result.data
|
|
125
130
|
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
126
|
-
const queryKey = getMessagesQueryKey({
|
|
131
|
+
const queryKey = getMessagesQueryKey({
|
|
132
|
+
conversation_id: conversationId,
|
|
133
|
+
reply_root_id: replyRootId,
|
|
134
|
+
})
|
|
127
135
|
|
|
128
136
|
// First remove the optimistic message if it exists
|
|
129
137
|
if (optimisticMessage) {
|
|
@@ -148,13 +156,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
148
156
|
return mutation
|
|
149
157
|
}
|
|
150
158
|
|
|
151
|
-
export function isTemporaryMessageId(messageId?: string | null): boolean {
|
|
152
|
-
return !!messageId && messageId.endsWith('-temp')
|
|
153
|
-
}
|
|
154
|
-
export function isNewMessage(message?: MessageResource): boolean {
|
|
155
|
-
return !message?.id || isTemporaryMessageId(message.id)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
159
|
/**
|
|
159
160
|
* Generate a random UUID (v4) for idempotent keys.
|
|
160
161
|
* Uses Math.random, which is not cryptographically secure.
|
|
@@ -9,6 +9,7 @@ import React, {
|
|
|
9
9
|
useRef,
|
|
10
10
|
} from 'react'
|
|
11
11
|
import {
|
|
12
|
+
FlatList,
|
|
12
13
|
StyleSheet,
|
|
13
14
|
TextInput,
|
|
14
15
|
View,
|
|
@@ -40,7 +41,6 @@ import {
|
|
|
40
41
|
} from '../hooks/use_conversation'
|
|
41
42
|
import { MemberResource, isDefined } from '../types'
|
|
42
43
|
import { HeaderTextButton } from '../components/display/platform_modal_header_buttons'
|
|
43
|
-
import { FlashList } from '@shopify/flash-list'
|
|
44
44
|
import { tokens } from '../vendor/tapestry/tokens'
|
|
45
45
|
import { ButtonAppearanceUnion } from '../components/display/utils/button_colors'
|
|
46
46
|
import { GroupResource } from '../types/resources/group_resource'
|
|
@@ -300,9 +300,8 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
300
300
|
|
|
301
301
|
return (
|
|
302
302
|
<View style={styles.listContainer}>
|
|
303
|
-
<
|
|
303
|
+
<FlatList
|
|
304
304
|
data={listData as SectionListData}
|
|
305
|
-
estimatedItemSize={52}
|
|
306
305
|
contentContainerStyle={styles.contentContainer}
|
|
307
306
|
renderItem={({ item, index }) => {
|
|
308
307
|
const [isStart, isEnd] = [
|
|
@@ -375,7 +374,7 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
375
374
|
return null
|
|
376
375
|
}
|
|
377
376
|
}}
|
|
378
|
-
onEndReached={fetchNextPageOfMembers}
|
|
377
|
+
onEndReached={() => fetchNextPageOfMembers()}
|
|
379
378
|
/>
|
|
380
379
|
</View>
|
|
381
380
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { StyleSheet, View } from 'react-native'
|
|
3
|
-
import { FlashList, type FlashListProps } from '@shopify/flash-list'
|
|
2
|
+
import { FlatList, type FlatListProps, StyleSheet, View } from 'react-native'
|
|
4
3
|
import { MemberResource } from '../../../types'
|
|
5
4
|
import { Person, Text } from '../../../components/display'
|
|
6
5
|
import { useTheme } from '../../../hooks'
|
|
@@ -8,7 +7,7 @@ import { useTheme } from '../../../hooks'
|
|
|
8
7
|
interface FormListProps {
|
|
9
8
|
memberData: MemberResource[]
|
|
10
9
|
loadingMore?: boolean
|
|
11
|
-
FormContent?:
|
|
10
|
+
FormContent?: FlatListProps<MemberResource>['ListHeaderComponent']
|
|
12
11
|
listEmptyText?: string
|
|
13
12
|
onEndReached?: () => void
|
|
14
13
|
}
|
|
@@ -23,7 +22,7 @@ export const FormList = ({
|
|
|
23
22
|
const styles = useStyles()
|
|
24
23
|
|
|
25
24
|
return (
|
|
26
|
-
<
|
|
25
|
+
<FlatList
|
|
27
26
|
data={memberData}
|
|
28
27
|
ListHeaderComponent={FormContent}
|
|
29
28
|
renderItem={({ item }) => <Person person={item} style={styles.person} />}
|
|
@@ -31,7 +30,6 @@ export const FormList = ({
|
|
|
31
30
|
loadingMore ? <Text style={styles.loadingMore}>Loading more...</Text> : null
|
|
32
31
|
}
|
|
33
32
|
keyExtractor={item => item.id.toString()}
|
|
34
|
-
estimatedItemSize={45}
|
|
35
33
|
ListEmptyComponent={<ListEmptyText text={listEmptyText || 'No members found'} />}
|
|
36
34
|
onEndReached={onEndReached}
|
|
37
35
|
/>
|
package/src/screens/conversation_select_recipients/conversation_select_group_recipients_screen.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import { StyleSheet, View } from 'react-native'
|
|
4
|
-
import { FlashList } from '@shopify/flash-list'
|
|
3
|
+
import { FlatList, StyleSheet, View } from 'react-native'
|
|
5
4
|
import { Heading } from '../../components'
|
|
6
5
|
import { GroupsGroupResource } from '../../types'
|
|
7
6
|
import { useGroupsGroups } from '../../hooks/use_groups_groups'
|
|
@@ -31,10 +30,9 @@ export const ConversationSelectGroupRecipientsScreen = ({
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
return (
|
|
34
|
-
<
|
|
33
|
+
<FlatList
|
|
35
34
|
data={groupsWithCreatePermission}
|
|
36
35
|
keyExtractor={item => item.id.toString()}
|
|
37
|
-
estimatedItemSize={65}
|
|
38
36
|
contentContainerStyle={styles.contentContainer}
|
|
39
37
|
ListHeaderComponent={
|
|
40
38
|
<View style={styles.sectionHeader}>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import { StyleSheet, View } from 'react-native'
|
|
4
|
-
import { FlashList } from '@shopify/flash-list'
|
|
3
|
+
import { FlatList, StyleSheet, View } from 'react-native'
|
|
5
4
|
import { Heading } from '../../components'
|
|
6
5
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
7
6
|
import { ConversationSelectRecipientsScreenProps } from './types/screen_props'
|
|
@@ -30,10 +29,9 @@ export const ConversationSelectTeamsILeadRecipientsScreen = ({
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
return (
|
|
33
|
-
<
|
|
32
|
+
<FlatList
|
|
34
33
|
data={serviceTypes}
|
|
35
34
|
keyExtractor={item => item.id.toString()}
|
|
36
|
-
estimatedItemSize={65}
|
|
37
35
|
contentContainerStyle={styles.contentContainer}
|
|
38
36
|
ListHeaderComponent={
|
|
39
37
|
<View style={styles.sectionHeader}>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { InfiniteData, QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { ApiCollection, MessageResource } from '../../types'
|
|
3
|
+
import { deleteRecordInPagesData } from './page_mutations'
|
|
4
|
+
import { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations'
|
|
5
|
+
import { JoltReactionEvent } from '../../types/jolt_events'
|
|
6
|
+
import { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource'
|
|
7
|
+
import { getMessagesRequestArgs } from '../request/get_messages'
|
|
8
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api'
|
|
9
|
+
|
|
10
|
+
export function updateCacheWithMessage(
|
|
11
|
+
queryClient: QueryClient,
|
|
12
|
+
queryKey: unknown[],
|
|
13
|
+
message: MessageResource,
|
|
14
|
+
event: 'message.created' | 'message.updated'
|
|
15
|
+
) {
|
|
16
|
+
queryClient.setQueryData<MessagesQueryData>(queryKey, prev => {
|
|
17
|
+
if (event === 'message.created') {
|
|
18
|
+
// Before adding the new message, remove any pending temporary messages
|
|
19
|
+
// with matching text to prevent duplicates from race conditions
|
|
20
|
+
let dataAfterTempRemoval = prev
|
|
21
|
+
if (prev && message.text && message.mine) {
|
|
22
|
+
dataAfterTempRemoval = deleteRecordInPagesData({
|
|
23
|
+
data: prev,
|
|
24
|
+
record: message,
|
|
25
|
+
matchFn: (existingMessage, _record) => {
|
|
26
|
+
return (
|
|
27
|
+
isTemporaryMessageId(existingMessage.id) &&
|
|
28
|
+
existingMessage.text === message.text &&
|
|
29
|
+
existingMessage.mine
|
|
30
|
+
)
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return updateOrCreateRecordInPagesData({
|
|
36
|
+
data: dataAfterTempRemoval,
|
|
37
|
+
record: message,
|
|
38
|
+
processRecord: (record, current) => {
|
|
39
|
+
return { ...current, ...record }
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
} else {
|
|
43
|
+
return updateRecordInPagesData({
|
|
44
|
+
data: prev,
|
|
45
|
+
record: message,
|
|
46
|
+
processRecord: (record, current) => {
|
|
47
|
+
return { ...current, ...record }
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function updateCacheWithReaction(
|
|
55
|
+
queryClient: QueryClient,
|
|
56
|
+
queryKey: unknown[],
|
|
57
|
+
event: JoltReactionEvent,
|
|
58
|
+
currentPersonId: number
|
|
59
|
+
) {
|
|
60
|
+
const message = { id: event.data.data.message_sort_key } as MessageResource
|
|
61
|
+
queryClient.setQueryData<MessagesQueryData>(queryKey, prev =>
|
|
62
|
+
updateRecordInPagesData({
|
|
63
|
+
data: prev,
|
|
64
|
+
record: message,
|
|
65
|
+
processRecord: (record, oldMessage) => {
|
|
66
|
+
const reactionCounts = oldMessage.reactionCounts || []
|
|
67
|
+
let foundMatch = false
|
|
68
|
+
let newReactionCounts = reactionCounts.map(reactionCount => {
|
|
69
|
+
if (reactionCount.value === event.data.data.value) {
|
|
70
|
+
foundMatch = true
|
|
71
|
+
return transformReactionEventDataToReactionCountResource({
|
|
72
|
+
data: event.data.data,
|
|
73
|
+
oldData: reactionCount,
|
|
74
|
+
event: event.event,
|
|
75
|
+
currentPersonId,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
return reactionCount
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (!foundMatch) {
|
|
82
|
+
const newReactionCount = transformReactionEventDataToReactionCountResource({
|
|
83
|
+
data: event.data.data,
|
|
84
|
+
event: event.event,
|
|
85
|
+
currentPersonId,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (newReactionCount?.count) {
|
|
89
|
+
newReactionCounts = [...newReactionCounts, newReactionCount]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { ...oldMessage, reactionCounts: newReactionCounts }
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
100
|
+
export function isTemporaryMessageId(messageId?: string | null): boolean {
|
|
101
|
+
return !!messageId && messageId.endsWith('-temp')
|
|
102
|
+
}
|
|
103
|
+
export function isNewMessage(message?: MessageResource): boolean {
|
|
104
|
+
return !message?.id || isTemporaryMessageId(message.id)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string) {
|
|
108
|
+
const requestArgs = getMessagesRequestArgs({
|
|
109
|
+
conversation_id: conversationId,
|
|
110
|
+
reply_root_id: replyRootId,
|
|
111
|
+
})
|
|
112
|
+
return getRequestQueryKey(requestArgs)
|
|
113
|
+
}
|
|
@@ -14,12 +14,14 @@ export function optimisticallyCreateMessage({
|
|
|
14
14
|
attachments,
|
|
15
15
|
currentPerson,
|
|
16
16
|
message,
|
|
17
|
+
replyRootId,
|
|
17
18
|
}: {
|
|
18
19
|
conversationId: number
|
|
19
20
|
text: string
|
|
20
21
|
attachments?: DenormalizedAttachmentResourceForCreate[]
|
|
21
22
|
currentPerson: CurrentPersonResource
|
|
22
23
|
message?: MessageResource
|
|
24
|
+
replyRootId?: string | null
|
|
23
25
|
}) {
|
|
24
26
|
const id = message?.id || generateTempMessageId()
|
|
25
27
|
|
|
@@ -49,12 +51,15 @@ export function optimisticallyCreateMessage({
|
|
|
49
51
|
lastInGroup: true,
|
|
50
52
|
pending: true,
|
|
51
53
|
replyCount: 0,
|
|
52
|
-
replyRootId: null,
|
|
54
|
+
replyRootId: replyRootId || null,
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
// Add the optimistic message to the cache
|
|
56
58
|
type QueryData = InfiniteData<ApiCollection<MessageResource>>
|
|
57
|
-
const queryKey = getMessagesQueryKey({
|
|
59
|
+
const queryKey = getMessagesQueryKey({
|
|
60
|
+
conversation_id: conversationId,
|
|
61
|
+
reply_root_id: replyRootId,
|
|
62
|
+
})
|
|
58
63
|
|
|
59
64
|
chatQueryClient.setQueryData<QueryData>(queryKey, data =>
|
|
60
65
|
updateOrCreateRecordInPagesData({
|