@planningcenter/chat-react-native 3.18.0-rc.1 → 3.18.0-rc.10
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 +2 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +28 -17
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +7 -6
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/messages_disabled_banners.d.ts +3 -0
- package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -0
- package/build/components/conversation/messages_disabled_banners.js +53 -0
- package/build/components/conversation/messages_disabled_banners.js.map +1 -0
- package/build/components/conversation/reply_connectors.d.ts.map +1 -1
- package/build/components/conversation/reply_connectors.js +0 -5
- package/build/components/conversation/reply_connectors.js.map +1 -1
- package/build/components/conversation/typing_indicator.d.ts +1 -5
- package/build/components/conversation/typing_indicator.d.ts.map +1 -1
- package/build/components/conversation/typing_indicator.js +2 -2
- package/build/components/conversation/typing_indicator.js.map +1 -1
- package/build/components/conversations/conversation_actions.d.ts.map +1 -1
- package/build/components/conversations/conversation_actions.js +1 -2
- package/build/components/conversations/conversation_actions.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/contexts/conversation_context.d.ts +13 -0
- package/build/contexts/conversation_context.d.ts.map +1 -0
- package/build/contexts/conversation_context.js +14 -0
- package/build/contexts/conversation_context.js.map +1 -0
- package/build/hooks/use_broadcast_typing_status.d.ts +1 -1
- package/build/hooks/use_broadcast_typing_status.d.ts.map +1 -1
- package/build/hooks/use_broadcast_typing_status.js +7 -3
- package/build/hooks/use_broadcast_typing_status.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +2 -1
- package/build/hooks/use_conversation_messages.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_features.d.ts +9 -0
- package/build/hooks/use_features.d.ts.map +1 -0
- package/build/hooks/use_features.js +35 -0
- package/build/hooks/use_features.js.map +1 -0
- 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/hooks/use_typing_indicators.d.ts +1 -1
- package/build/hooks/use_typing_indicators.d.ts.map +1 -1
- package/build/hooks/use_typing_indicators.js +16 -3
- package/build/hooks/use_typing_indicators.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +9 -6
- 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_screen.d.ts +2 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +41 -18
- package/build/screens/conversation_screen.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/screens/message_actions_screen.js +4 -2
- package/build/screens/message_actions_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/types/jolt_events/typing_events.d.ts +1 -0
- package/build/types/jolt_events/typing_events.d.ts.map +1 -1
- package/build/types/jolt_events/typing_events.js.map +1 -1
- package/build/types/resources/feature_resource.d.ts +7 -0
- package/build/types/resources/feature_resource.d.ts.map +1 -0
- package/build/types/resources/feature_resource.js +2 -0
- package/build/types/resources/feature_resource.js.map +1 -0
- 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/build/utils/index.d.ts +0 -1
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +0 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/request/get_features.d.ts +11 -0
- package/build/utils/request/get_features.d.ts.map +1 -0
- package/build/utils/request/get_features.js +18 -0
- package/build/utils/request/get_features.js.map +1 -0
- package/package.json +2 -3
- package/src/components/conversation/message.tsx +42 -20
- package/src/components/conversation/message_form.tsx +6 -11
- package/src/components/conversation/messages_disabled_banners.tsx +69 -0
- package/src/components/conversation/reply_connectors.tsx +0 -3
- package/src/components/conversation/typing_indicator.tsx +2 -6
- package/src/components/conversations/conversation_actions.tsx +1 -1
- package/src/components/conversations/conversations.tsx +7 -9
- package/src/contexts/conversation_context.tsx +34 -0
- package/src/hooks/use_broadcast_typing_status.ts +7 -3
- package/src/hooks/use_conversation_messages.ts +3 -1
- package/src/hooks/use_conversation_messages_jolt_events.ts +39 -81
- package/src/hooks/use_features.ts +47 -0
- package/src/hooks/use_message_create_or_update.ts +10 -9
- package/src/hooks/use_typing_indicators.ts +15 -3
- package/src/screens/conversation_details_screen.tsx +9 -6
- package/src/screens/conversation_new/components/form_list.tsx +3 -5
- package/src/screens/conversation_screen.tsx +58 -20
- 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/screens/message_actions_screen.tsx +4 -2
- package/src/types/jolt_events/reaction_events.ts +1 -0
- package/src/types/jolt_events/typing_events.ts +1 -0
- package/src/types/resources/feature_resource.ts +6 -0
- package/src/utils/cache/messages_cache.ts +113 -0
- package/src/utils/cache/optimistically_create_message.ts +7 -2
- package/src/utils/index.ts +0 -1
- package/src/utils/request/get_features.ts +20 -0
- package/build/components/conversation/disabled_replies_banners.d.ts +0 -3
- package/build/components/conversation/disabled_replies_banners.d.ts.map +0 -1
- package/build/components/conversation/disabled_replies_banners.js +0 -41
- package/build/components/conversation/disabled_replies_banners.js.map +0 -1
- package/build/utils/replies_local_feature_flag.d.ts +0 -2
- package/build/utils/replies_local_feature_flag.d.ts.map +0 -1
- package/build/utils/replies_local_feature_flag.js +0 -3
- package/build/utils/replies_local_feature_flag.js.map +0 -1
- package/src/components/conversation/disabled_replies_banners.tsx +0 -58
- package/src/utils/replies_local_feature_flag.ts +0 -2
|
@@ -14,9 +14,9 @@ import { FlatList, Platform, StyleSheet, View } from 'react-native'
|
|
|
14
14
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
15
15
|
import { Badge, Icon, Text } from '../components'
|
|
16
16
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '../components/conversation/
|
|
17
|
+
LeaderMessagesDisabledBanner,
|
|
18
|
+
MemberMessagesDisabledBanner,
|
|
19
|
+
} from '../components/conversation/messages_disabled_banners'
|
|
20
20
|
import { EmptyConversationBlankState } from '../components/conversation/empty_conversation_blank_state'
|
|
21
21
|
import BlankState from '../components/primitive/blank_state_primitive'
|
|
22
22
|
import { Message } from '../components/conversation/message'
|
|
@@ -35,7 +35,8 @@ import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles'
|
|
|
35
35
|
import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
36
36
|
import { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'
|
|
37
37
|
import { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'
|
|
38
|
-
import {
|
|
38
|
+
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
39
|
+
import { ConversationContextProvider } from '../contexts/conversation_context'
|
|
39
40
|
|
|
40
41
|
export type ConversationRouteProps = {
|
|
41
42
|
conversation_id: number
|
|
@@ -53,6 +54,19 @@ export type ConversationRouteProps = {
|
|
|
53
54
|
export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
|
|
54
55
|
|
|
55
56
|
export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
57
|
+
const { conversation_id, reply_root_id } = route.params
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<ConversationContextProvider
|
|
61
|
+
conversationId={conversation_id}
|
|
62
|
+
currentPageReplyRootId={reply_root_id ?? null}
|
|
63
|
+
>
|
|
64
|
+
<ConversationScreenContent route={route} />
|
|
65
|
+
</ConversationContextProvider>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
56
70
|
const styles = useStyles()
|
|
57
71
|
const navigation = useNavigation()
|
|
58
72
|
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
|
|
@@ -66,7 +80,13 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
66
80
|
useConversationMessagesJoltEvents({ conversationId: conversation_id })
|
|
67
81
|
useEnsureConversationsRouteExists()
|
|
68
82
|
useMarkLatestMessageRead({ conversation, messages })
|
|
69
|
-
const
|
|
83
|
+
const { featureEnabled } = useFeatures()
|
|
84
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
85
|
+
const messagesWithSeparators = groupMessages({
|
|
86
|
+
ms: messages,
|
|
87
|
+
inReplyScreen: !!reply_root_id,
|
|
88
|
+
repliesEnabled,
|
|
89
|
+
})
|
|
70
90
|
const noMessages = messagesWithSeparators.length === 0
|
|
71
91
|
|
|
72
92
|
const { repliesDisabled, memberAbility, badges, title } = conversation
|
|
@@ -164,6 +184,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
164
184
|
conversation_id={conversation_id}
|
|
165
185
|
latestReadMessageSortKey={conversation?.latestReadMessageSortKey}
|
|
166
186
|
inReplyScreen={!!reply_root_id}
|
|
187
|
+
repliesEnabled={repliesEnabled}
|
|
167
188
|
/>
|
|
168
189
|
)
|
|
169
190
|
}}
|
|
@@ -172,8 +193,8 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
172
193
|
/>
|
|
173
194
|
)}
|
|
174
195
|
<JumpToBottomButton onPress={handleReturnToBottom} visible={showJumpToBottomButton} />
|
|
175
|
-
{!noMessages && <TypingIndicator
|
|
176
|
-
{showLeaderDisabledReplyBanner && <
|
|
196
|
+
{!noMessages && <TypingIndicator />}
|
|
197
|
+
{showLeaderDisabledReplyBanner && <LeaderMessagesDisabledBanner />}
|
|
177
198
|
{canReply ? (
|
|
178
199
|
<MessageForm.Root
|
|
179
200
|
replyRootAuthorFirstName={replyRootAuthorFirstName}
|
|
@@ -194,7 +215,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
194
215
|
<MessageForm.SubmitButton />
|
|
195
216
|
</MessageForm.Root>
|
|
196
217
|
) : (
|
|
197
|
-
<
|
|
218
|
+
<MemberMessagesDisabledBanner />
|
|
198
219
|
)}
|
|
199
220
|
</KeyboardView>
|
|
200
221
|
</View>
|
|
@@ -252,9 +273,14 @@ type ReplyShadowMessage = {
|
|
|
252
273
|
interface GroupMessagesProps {
|
|
253
274
|
ms: MessageResource[]
|
|
254
275
|
inReplyScreen?: boolean
|
|
276
|
+
repliesEnabled?: boolean
|
|
255
277
|
}
|
|
256
278
|
|
|
257
|
-
export const groupMessages = ({
|
|
279
|
+
export const groupMessages = ({
|
|
280
|
+
ms,
|
|
281
|
+
inReplyScreen,
|
|
282
|
+
repliesEnabled = false,
|
|
283
|
+
}: GroupMessagesProps) => {
|
|
258
284
|
let enrichedMessages: (MessageResource | DateSeparator | ReplyShadowMessage)[] = []
|
|
259
285
|
let encounteredOneOfMyMessages = false
|
|
260
286
|
|
|
@@ -284,6 +310,24 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
284
310
|
message.replyRootId &&
|
|
285
311
|
!threadRoot &&
|
|
286
312
|
(prevMessageDifferentThread || prevMessageIsDateSeparator)
|
|
313
|
+
const lastInGroup =
|
|
314
|
+
!nextMessage ||
|
|
315
|
+
nextMessageDifferentAuthor ||
|
|
316
|
+
nextMessageMoreThan5Minutes ||
|
|
317
|
+
nextMessageDifferentThread ||
|
|
318
|
+
nextMessageIsDateSeparator
|
|
319
|
+
const renderAuthor =
|
|
320
|
+
!message.mine &&
|
|
321
|
+
(!prevMessage ||
|
|
322
|
+
prevMessageDifferentAuthor ||
|
|
323
|
+
prevMessageMoreThan5Minutes ||
|
|
324
|
+
prevMessageDifferentThread ||
|
|
325
|
+
prevMessageIsDateSeparator)
|
|
326
|
+
const nextIsReplyShadowMessage =
|
|
327
|
+
repliesEnabled &&
|
|
328
|
+
nextMessageInThread &&
|
|
329
|
+
!nextMessageThreadRoot &&
|
|
330
|
+
(nextMessageDifferentThread || nextMessageIsDateSeparator)
|
|
287
331
|
|
|
288
332
|
if (message.mine && !encounteredOneOfMyMessages) {
|
|
289
333
|
encounteredOneOfMyMessages = true
|
|
@@ -291,17 +335,12 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
291
335
|
} else {
|
|
292
336
|
message.myLatestInConversation = false
|
|
293
337
|
}
|
|
294
|
-
message.lastInGroup =
|
|
295
|
-
message.renderAuthor =
|
|
296
|
-
!message.mine && (!prevMessage || prevMessageDifferentAuthor || prevMessageMoreThan5Minutes)
|
|
338
|
+
message.lastInGroup = lastInGroup
|
|
339
|
+
message.renderAuthor = renderAuthor
|
|
297
340
|
message.threadPosition = null
|
|
298
341
|
message.nextRendersAuthor = nextMessage?.renderAuthor
|
|
299
342
|
message.isReplyShadowMessage = false
|
|
300
|
-
message.nextIsReplyShadowMessage =
|
|
301
|
-
REPLIES_FEATURE_ENABLED &&
|
|
302
|
-
nextMessageInThread &&
|
|
303
|
-
!nextMessageThreadRoot &&
|
|
304
|
-
(nextMessageDifferentThread || nextMessageIsDateSeparator)
|
|
343
|
+
message.nextIsReplyShadowMessage = nextIsReplyShadowMessage
|
|
305
344
|
|
|
306
345
|
if (!inReplyScreen && inThread) {
|
|
307
346
|
message.prevIsMyReply = prevMessage?.mine
|
|
@@ -314,13 +353,12 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
314
353
|
message.threadPosition = null // ensures we don't render a connector for root replies that aren't immediately followed up a reply
|
|
315
354
|
else if (firstInThread) message.threadPosition = 'first'
|
|
316
355
|
else if (lastInThread) message.threadPosition = 'last'
|
|
317
|
-
else
|
|
318
|
-
message.threadPosition = 'center'
|
|
356
|
+
else message.threadPosition = 'center'
|
|
319
357
|
}
|
|
320
358
|
|
|
321
359
|
enrichedMessages.push(message)
|
|
322
360
|
|
|
323
|
-
if (insertReplyShadowMessage &&
|
|
361
|
+
if (insertReplyShadowMessage && repliesEnabled) {
|
|
324
362
|
enrichedMessages.push({
|
|
325
363
|
type: 'ReplyShadowMessage',
|
|
326
364
|
id: `${message.id}-${message.replyRootId}`,
|
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}>
|
|
@@ -14,7 +14,7 @@ import { useMessageReactionToggle } from '../hooks/use_message_reaction_toggle'
|
|
|
14
14
|
import { ReactionCountResource } from '../types/resources/reaction'
|
|
15
15
|
import { Clipboard, Haptic } from '../utils/native_adapters'
|
|
16
16
|
import { MessageResource } from '../types'
|
|
17
|
-
import {
|
|
17
|
+
import { availableFeatures, useFeatures } from '../hooks/use_features'
|
|
18
18
|
|
|
19
19
|
export const MessageActionsScreenOptions = getFormSheetScreenOptions({
|
|
20
20
|
sheetAllowedDetents: [0.5],
|
|
@@ -76,6 +76,8 @@ function MessageActionsScreenContent({
|
|
|
76
76
|
const navigation = useNavigation()
|
|
77
77
|
const apiClient = useApiClient()
|
|
78
78
|
const styles = useStyles()
|
|
79
|
+
const { featureEnabled } = useFeatures()
|
|
80
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
79
81
|
|
|
80
82
|
const myReactions = message?.reactionCounts
|
|
81
83
|
.filter(reaction => reaction.mine)
|
|
@@ -190,7 +192,7 @@ function MessageActionsScreenContent({
|
|
|
190
192
|
))}
|
|
191
193
|
</View>
|
|
192
194
|
<View style={styles.actions}>
|
|
193
|
-
{
|
|
195
|
+
{repliesEnabled && !inReplyScreen && (
|
|
194
196
|
<FormSheet.Action
|
|
195
197
|
onPress={handleReplyPress}
|
|
196
198
|
title="Reply to message"
|
|
@@ -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({
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api'
|
|
2
|
+
|
|
3
|
+
export const getFeaturesRequestArgs = () => {
|
|
4
|
+
const url = '/me/features'
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
url,
|
|
8
|
+
data: {
|
|
9
|
+
perPage: 100,
|
|
10
|
+
fields: {
|
|
11
|
+
Feature: ['name', 'enabled'],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getFeaturesQueryKey = () => {
|
|
18
|
+
const requestArgs = getFeaturesRequestArgs()
|
|
19
|
+
return getRequestQueryKey(requestArgs)
|
|
20
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"disabled_replies_banners.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/disabled_replies_banners.tsx"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,mCAEvC,CAAA;AAED,eAAO,MAAM,2BAA2B,mCAQvC,CAAA"}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { StyleSheet, View } from 'react-native';
|
|
2
|
-
import { useTheme } from '../../hooks';
|
|
3
|
-
import { Text } from '../display';
|
|
4
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
|
-
export const LeaderDisabledRepliesBanner = () => {
|
|
6
|
-
return <DisabledRepliesBanner description="Replies are frozen for everyone else."/>;
|
|
7
|
-
};
|
|
8
|
-
export const MemberDisabledRepliesBanner = () => {
|
|
9
|
-
const styles = useStyles();
|
|
10
|
-
return (<DisabledRepliesBanner description="Replies have been disabled by a leader, but you can still add reactions." style={styles.memberBanner}/>);
|
|
11
|
-
};
|
|
12
|
-
const DisabledRepliesBanner = ({ description, style }) => {
|
|
13
|
-
const styles = useStyles();
|
|
14
|
-
return (<View style={[styles.baseBanner, style]}>
|
|
15
|
-
<Text style={styles.text} variant="tertiary">
|
|
16
|
-
{description}
|
|
17
|
-
</Text>
|
|
18
|
-
</View>);
|
|
19
|
-
};
|
|
20
|
-
const useStyles = () => {
|
|
21
|
-
const { colors } = useTheme();
|
|
22
|
-
const { bottom } = useSafeAreaInsets();
|
|
23
|
-
return StyleSheet.create({
|
|
24
|
-
baseBanner: {
|
|
25
|
-
paddingHorizontal: 16,
|
|
26
|
-
paddingVertical: 4,
|
|
27
|
-
backgroundColor: colors.statusNeutralComposedBackground,
|
|
28
|
-
borderTopWidth: 1,
|
|
29
|
-
borderTopColor: colors.borderColorDefaultBase,
|
|
30
|
-
},
|
|
31
|
-
text: {
|
|
32
|
-
textAlign: 'center',
|
|
33
|
-
},
|
|
34
|
-
memberBanner: {
|
|
35
|
-
paddingTop: 16,
|
|
36
|
-
paddingBottom: 16 + bottom,
|
|
37
|
-
marginBottom: -bottom,
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
};
|
|
41
|
-
//# sourceMappingURL=disabled_replies_banners.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"disabled_replies_banners.js","sourceRoot":"","sources":["../../../src/components/conversation/disabled_replies_banners.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAA;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAElE,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,EAAE;IAC9C,OAAO,CAAC,qBAAqB,CAAC,WAAW,CAAC,uCAAuC,EAAG,CAAA;AACtF,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,EAAE;IAC9C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,OAAO,CACL,CAAC,qBAAqB,CACpB,WAAW,CAAC,0EAA0E,CACtF,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAC3B,CACH,CAAA;AACH,CAAC,CAAA;AAOD,MAAM,qBAAqB,GAAG,CAAC,EAAE,WAAW,EAAE,KAAK,EAA8B,EAAE,EAAE;IACnF,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,OAAO,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CACtC;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAC1C;QAAA,CAAC,WAAW,CACd;MAAA,EAAE,IAAI,CACR;IAAA,EAAE,IAAI,CAAC,CACR,CAAA;AACH,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC7B,MAAM,EAAE,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAA;IAEtC,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,UAAU,EAAE;YACV,iBAAiB,EAAE,EAAE;YACrB,eAAe,EAAE,CAAC;YAClB,eAAe,EAAE,MAAM,CAAC,+BAA+B;YACvD,cAAc,EAAE,CAAC;YACjB,cAAc,EAAE,MAAM,CAAC,sBAAsB;SAC9C;QACD,IAAI,EAAE;YACJ,SAAS,EAAE,QAAQ;SACpB;QACD,YAAY,EAAE;YACZ,UAAU,EAAE,EAAE;YACd,aAAa,EAAE,EAAE,GAAG,MAAM;YAC1B,YAAY,EAAE,CAAC,MAAM;SACtB;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import { StyleSheet, View, type ViewStyle } from 'react-native'\nimport { useTheme } from '../../hooks'\nimport { Text } from '../display'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\n\nexport const LeaderDisabledRepliesBanner = () => {\n return <DisabledRepliesBanner description=\"Replies are frozen for everyone else.\" />\n}\n\nexport const MemberDisabledRepliesBanner = () => {\n const styles = useStyles()\n return (\n <DisabledRepliesBanner\n description=\"Replies have been disabled by a leader, but you can still add reactions.\"\n style={styles.memberBanner}\n />\n )\n}\n\ninterface DisabledRepliesBannerProps {\n description: string\n style?: ViewStyle\n}\n\nconst DisabledRepliesBanner = ({ description, style }: DisabledRepliesBannerProps) => {\n const styles = useStyles()\n\n return (\n <View style={[styles.baseBanner, style]}>\n <Text style={styles.text} variant=\"tertiary\">\n {description}\n </Text>\n </View>\n )\n}\n\nconst useStyles = () => {\n const { colors } = useTheme()\n const { bottom } = useSafeAreaInsets()\n\n return StyleSheet.create({\n baseBanner: {\n paddingHorizontal: 16,\n paddingVertical: 4,\n backgroundColor: colors.statusNeutralComposedBackground,\n borderTopWidth: 1,\n borderTopColor: colors.borderColorDefaultBase,\n },\n text: {\n textAlign: 'center',\n },\n memberBanner: {\n paddingTop: 16,\n paddingBottom: 16 + bottom,\n marginBottom: -bottom,\n },\n })\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"replies_local_feature_flag.d.ts","sourceRoot":"","sources":["../../src/utils/replies_local_feature_flag.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,uBAAuB,QAAQ,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"replies_local_feature_flag.js","sourceRoot":"","sources":["../../src/utils/replies_local_feature_flag.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAA","sourcesContent":["// TODO: Replace this whole file with the flipper flag\nexport const REPLIES_FEATURE_ENABLED = false\n"]}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { StyleSheet, View, type ViewStyle } from 'react-native'
|
|
2
|
-
import { useTheme } from '../../hooks'
|
|
3
|
-
import { Text } from '../display'
|
|
4
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
5
|
-
|
|
6
|
-
export const LeaderDisabledRepliesBanner = () => {
|
|
7
|
-
return <DisabledRepliesBanner description="Replies are frozen for everyone else." />
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const MemberDisabledRepliesBanner = () => {
|
|
11
|
-
const styles = useStyles()
|
|
12
|
-
return (
|
|
13
|
-
<DisabledRepliesBanner
|
|
14
|
-
description="Replies have been disabled by a leader, but you can still add reactions."
|
|
15
|
-
style={styles.memberBanner}
|
|
16
|
-
/>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface DisabledRepliesBannerProps {
|
|
21
|
-
description: string
|
|
22
|
-
style?: ViewStyle
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DisabledRepliesBanner = ({ description, style }: DisabledRepliesBannerProps) => {
|
|
26
|
-
const styles = useStyles()
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<View style={[styles.baseBanner, style]}>
|
|
30
|
-
<Text style={styles.text} variant="tertiary">
|
|
31
|
-
{description}
|
|
32
|
-
</Text>
|
|
33
|
-
</View>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const useStyles = () => {
|
|
38
|
-
const { colors } = useTheme()
|
|
39
|
-
const { bottom } = useSafeAreaInsets()
|
|
40
|
-
|
|
41
|
-
return StyleSheet.create({
|
|
42
|
-
baseBanner: {
|
|
43
|
-
paddingHorizontal: 16,
|
|
44
|
-
paddingVertical: 4,
|
|
45
|
-
backgroundColor: colors.statusNeutralComposedBackground,
|
|
46
|
-
borderTopWidth: 1,
|
|
47
|
-
borderTopColor: colors.borderColorDefaultBase,
|
|
48
|
-
},
|
|
49
|
-
text: {
|
|
50
|
-
textAlign: 'center',
|
|
51
|
-
},
|
|
52
|
-
memberBanner: {
|
|
53
|
-
paddingTop: 16,
|
|
54
|
-
paddingBottom: 16 + bottom,
|
|
55
|
-
marginBottom: -bottom,
|
|
56
|
-
},
|
|
57
|
-
})
|
|
58
|
-
}
|