@planningcenter/chat-react-native 3.17.0-rc.0 → 3.17.0
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 +27 -7
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts +3 -2
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +6 -4
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/reply_connectors.d.ts +1 -0
- package/build/components/conversation/reply_connectors.d.ts.map +1 -1
- package/build/components/conversation/reply_connectors.js +33 -14
- package/build/components/conversation/reply_connectors.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.d.ts +12 -0
- package/build/components/conversation/reply_shadow_message.d.ts.map +1 -0
- package/build/components/conversation/{shadow_message.js → reply_shadow_message.js} +36 -6
- package/build/components/conversation/reply_shadow_message.js.map +1 -0
- package/build/hooks/use_conversation_message.d.ts +12 -0
- package/build/hooks/use_conversation_message.d.ts.map +1 -0
- package/build/hooks/use_conversation_message.js +11 -0
- package/build/hooks/use_conversation_message.js.map +1 -0
- package/build/hooks/use_conversation_messages.d.ts +1 -20
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +2 -33
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_message_create_or_update.d.ts +1 -1
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +1 -1
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/hooks/use_message_reaction_toggle.js +1 -1
- package/build/hooks/use_message_reaction_toggle.js.map +1 -1
- package/build/navigation/index.d.ts +6 -2
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +3 -3
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +11 -3
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +46 -10
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/message_actions_screen.d.ts +1 -0
- package/build/screens/message_actions_screen.d.ts.map +1 -1
- package/build/screens/message_actions_screen.js +5 -5
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/types/resources/message.d.ts +3 -0
- package/build/types/resources/message.d.ts.map +1 -1
- package/build/types/resources/message.js.map +1 -1
- package/build/utils/cache/optimistically_create_message.js +1 -1
- package/build/utils/cache/optimistically_create_message.js.map +1 -1
- package/build/utils/cache/optimistically_update_message.js +1 -1
- package/build/utils/cache/optimistically_update_message.js.map +1 -1
- package/build/utils/pluralize.js +1 -1
- package/build/utils/pluralize.js.map +1 -1
- package/build/utils/request/get_message.d.ts +20 -0
- package/build/utils/request/get_message.d.ts.map +1 -0
- package/build/utils/request/get_message.js +18 -0
- package/build/utils/request/get_message.js.map +1 -0
- package/build/utils/request/get_messages.d.ts +20 -0
- package/build/utils/request/get_messages.d.ts.map +1 -0
- package/build/utils/request/get_messages.js +20 -0
- package/build/utils/request/get_messages.js.map +1 -0
- package/build/utils/request/messages_data_options.d.ts +7 -0
- package/build/utils/request/messages_data_options.d.ts.map +1 -0
- package/build/utils/request/messages_data_options.js +18 -0
- package/build/utils/request/messages_data_options.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/utils/pluralize.tsx +3 -0
- package/src/components/conversation/message.tsx +31 -7
- package/src/components/conversation/message_form.tsx +10 -4
- package/src/components/conversation/reply_connectors.tsx +42 -20
- package/src/components/conversation/{shadow_message.tsx → reply_shadow_message.tsx} +55 -6
- package/src/hooks/use_conversation_message.ts +25 -0
- package/src/hooks/use_conversation_messages.ts +3 -53
- package/src/hooks/use_conversation_messages_jolt_events.ts +1 -1
- package/src/hooks/use_message_create_or_update.ts +2 -2
- package/src/hooks/use_message_reaction_toggle.ts +1 -1
- package/src/navigation/index.tsx +3 -3
- package/src/screens/conversation_screen.tsx +68 -12
- package/src/screens/message_actions_screen.tsx +13 -3
- package/src/types/resources/message.ts +3 -0
- package/src/utils/cache/optimistically_create_message.ts +1 -1
- package/src/utils/cache/optimistically_update_message.ts +1 -1
- package/src/utils/pluralize.ts +1 -1
- package/src/utils/request/get_message.ts +32 -0
- package/src/utils/request/get_messages.ts +34 -0
- package/src/utils/request/messages_data_options.ts +18 -0
- package/build/components/conversation/shadow_message.d.ts +0 -8
- package/build/components/conversation/shadow_message.d.ts.map +0 -1
- package/build/components/conversation/shadow_message.js.map +0 -1
- package/build/utils/request/messages.d.ts +0 -15
- package/build/utils/request/messages.d.ts.map +0 -1
- package/build/utils/request/messages.js +0 -22
- package/build/utils/request/messages.js.map +0 -1
- package/src/utils/request/messages.ts +0 -21
|
@@ -10,6 +10,8 @@ import { REPLIES_FEATURE_ENABLED } from '../../utils'
|
|
|
10
10
|
|
|
11
11
|
const MY_REPLY_CONNECTOR_WIDTH = 38
|
|
12
12
|
const CONNECTOR_BORDER_WIDTH = 4
|
|
13
|
+
const OFFSET_CONNECTOR_CONTAINER = CONNECTOR_BORDER_WIDTH / 2 // Accounts for the border width ensuring the connectors are centered under the avatar
|
|
14
|
+
export const AVATAR_CONNECTOR_SPACING = 8
|
|
13
15
|
|
|
14
16
|
interface ReplyConnectorProps {
|
|
15
17
|
message: MessageResource
|
|
@@ -18,8 +20,10 @@ interface ReplyConnectorProps {
|
|
|
18
20
|
|
|
19
21
|
export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) {
|
|
20
22
|
const styles = useStyles()
|
|
23
|
+
const { nextRendersAuthor, threadPosition, renderAuthor, isReplyShadowMessage } = message
|
|
21
24
|
|
|
22
25
|
if (!REPLIES_FEATURE_ENABLED) return null
|
|
26
|
+
if (messageBubbleHeight === 0) return null // Prevents UI shifting
|
|
23
27
|
|
|
24
28
|
const connectorMap: Record<string, React.ReactNode | null> = {
|
|
25
29
|
shortTailCurve: <ShortTailCurveConnector height={messageBubbleHeight / 2} />,
|
|
@@ -29,11 +33,13 @@ export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConne
|
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
const getConnectorKey = () => {
|
|
32
|
-
const { threadPosition, renderAuthor } = message
|
|
33
|
-
|
|
34
36
|
if (threadPosition === 'first' && !renderAuthor) return 'shortHeadCurve'
|
|
35
37
|
if (threadPosition === 'last' && !renderAuthor) return 'shortTailCurve'
|
|
36
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
(threadPosition === 'first' && renderAuthor) ||
|
|
40
|
+
threadPosition === 'center' ||
|
|
41
|
+
isReplyShadowMessage
|
|
42
|
+
)
|
|
37
43
|
return 'verticalLine'
|
|
38
44
|
return 'default'
|
|
39
45
|
}
|
|
@@ -41,13 +47,20 @@ export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConne
|
|
|
41
47
|
const ConnectorComponent = connectorMap[getConnectorKey()]
|
|
42
48
|
if (!ConnectorComponent) return null
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
const spacerStyle = nextRendersAuthor ? styles.theirReplyConnectorSpacer : null
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View style={[styles.theirReplyConnectorContainer, spacerStyle]}>{ConnectorComponent}</View>
|
|
54
|
+
)
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) {
|
|
48
58
|
const styles = useStyles()
|
|
59
|
+
const { nextRendersAuthor, threadPosition, prevIsMyReply, nextIsMyReply, isReplyShadowMessage } =
|
|
60
|
+
message
|
|
49
61
|
|
|
50
62
|
if (!REPLIES_FEATURE_ENABLED) return null
|
|
63
|
+
if (messageBubbleHeight === 0) return null // Prevents UI shifting
|
|
51
64
|
|
|
52
65
|
const connectorMap: Record<string, React.ReactNode | null> = {
|
|
53
66
|
longTailCurve: (
|
|
@@ -63,14 +76,12 @@ export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnecto
|
|
|
63
76
|
/>
|
|
64
77
|
),
|
|
65
78
|
verticalLine: <VerticalLineConnector style={styles.myReplyConnectorPosition} />,
|
|
66
|
-
swirl: <SwirlConnector style={styles.myReplyConnectorPosition} />,
|
|
79
|
+
swirl: <SwirlConnector style={styles.myReplyConnectorPosition} height={messageBubbleHeight} />,
|
|
67
80
|
default: null,
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
const getConnectorKey = () => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (threadPosition === 'first') return 'longHeadCurve'
|
|
84
|
+
if (threadPosition === 'first' || isReplyShadowMessage) return 'longHeadCurve'
|
|
74
85
|
if (threadPosition === 'center' && prevIsMyReply && nextIsMyReply) return 'verticalLine'
|
|
75
86
|
if (threadPosition === 'center' && (!prevIsMyReply || !nextIsMyReply)) return 'swirl'
|
|
76
87
|
if (threadPosition === 'last') return 'longTailCurve'
|
|
@@ -81,7 +92,9 @@ export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnecto
|
|
|
81
92
|
const ConnectorComponent = connectorMap[getConnectorKey()]
|
|
82
93
|
if (!ConnectorComponent) return null
|
|
83
94
|
|
|
84
|
-
|
|
95
|
+
const spacerStyle = nextRendersAuthor ? styles.myReplyConnectorSpacer : null
|
|
96
|
+
|
|
97
|
+
return <View style={[styles.myReplyConnectorContainer, spacerStyle]}>{ConnectorComponent}</View>
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
function VerticalLineConnector({ style }: { style?: ViewStyle }) {
|
|
@@ -109,21 +122,23 @@ function ShortTailCurveConnector({ height }: { height: number }) {
|
|
|
109
122
|
return <View style={[styles.shortTailCurveConnector, { height }]} />
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
function SwirlConnector({ style }: { style?: ViewStyle }) {
|
|
125
|
+
function SwirlConnector({ style, height }: { style?: ViewStyle; height: number }) {
|
|
113
126
|
const styles = useStyles()
|
|
114
127
|
const { colors } = useTheme()
|
|
115
|
-
const borderColor = colors.borderColorDefaultBase
|
|
116
128
|
|
|
117
129
|
return (
|
|
118
130
|
<View style={[styles.swirlConnectorContainer, style]}>
|
|
119
|
-
<View style={
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
<View style={{ height }}>
|
|
132
|
+
<View style={styles.swirlConnectorVerticalLineHead} />
|
|
133
|
+
<Svg width={27} height={34} fill="none">
|
|
134
|
+
<Path
|
|
135
|
+
stroke={colors.borderColorDefaultBase}
|
|
136
|
+
strokeWidth={CONNECTOR_BORDER_WIDTH}
|
|
137
|
+
d="M2.07 34c0-6.142 1.201-12.283 3.343-17m0 0c2.305-5.075 5.699-8.5 9.857-8.5 4.86 0 8.8 3.806 8.8 8.5s-3.94 8.5-8.8 8.5c-4.158 0-7.552-3.425-9.857-8.5zm0 0C3.271 12.283 2.07 6.142 2.07 0"
|
|
138
|
+
/>
|
|
139
|
+
</Svg>
|
|
140
|
+
<View style={styles.swirlConnectorVerticalLineTail} />
|
|
141
|
+
</View>
|
|
127
142
|
<View style={styles.swirlConnectorVerticalLineTail} />
|
|
128
143
|
</View>
|
|
129
144
|
)
|
|
@@ -136,19 +151,26 @@ const useStyles = () => {
|
|
|
136
151
|
return StyleSheet.create({
|
|
137
152
|
theirReplyConnectorContainer: {
|
|
138
153
|
width: '50%',
|
|
154
|
+
marginRight: OFFSET_CONNECTOR_CONTAINER,
|
|
139
155
|
alignSelf: 'flex-end',
|
|
140
156
|
flex: 1,
|
|
141
157
|
},
|
|
158
|
+
theirReplyConnectorSpacer: {
|
|
159
|
+
paddingBottom: AVATAR_CONNECTOR_SPACING,
|
|
160
|
+
},
|
|
142
161
|
myReplyConnectorContainer: {
|
|
143
162
|
width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
144
163
|
position: 'absolute',
|
|
145
|
-
left: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
164
|
+
left: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL - OFFSET_CONNECTOR_CONTAINER,
|
|
146
165
|
top: 0,
|
|
147
166
|
bottom: 0,
|
|
148
167
|
},
|
|
149
168
|
myReplyConnectorPosition: {
|
|
150
169
|
left: '50%',
|
|
151
170
|
},
|
|
171
|
+
myReplyConnectorSpacer: {
|
|
172
|
+
bottom: AVATAR_CONNECTOR_SPACING,
|
|
173
|
+
},
|
|
152
174
|
verticalLineConnector: {
|
|
153
175
|
flex: 1,
|
|
154
176
|
borderLeftWidth: CONNECTOR_BORDER_WIDTH,
|
|
@@ -22,14 +22,45 @@ import {
|
|
|
22
22
|
} from '../../utils/styles'
|
|
23
23
|
import Animated from 'react-native-reanimated'
|
|
24
24
|
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
25
|
-
import { assertKeysAreNumbers } from '../../utils'
|
|
25
|
+
import { assertKeysAreNumbers, pluralize } from '../../utils'
|
|
26
26
|
import { useNavigation } from '@react-navigation/native'
|
|
27
|
+
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
27
28
|
|
|
28
|
-
interface
|
|
29
|
+
interface ReplyShadowMessageProps extends MessageResource {
|
|
30
|
+
messageId: string
|
|
29
31
|
conversation_id: number
|
|
32
|
+
inReplyScreen?: boolean
|
|
33
|
+
isReplyShadowMessage: boolean
|
|
34
|
+
nextRendersAuthor: boolean
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
export function
|
|
37
|
+
export function ReplyShadowMessage({
|
|
38
|
+
conversation_id,
|
|
39
|
+
inReplyScreen,
|
|
40
|
+
messageId,
|
|
41
|
+
isReplyShadowMessage,
|
|
42
|
+
nextRendersAuthor,
|
|
43
|
+
}: ReplyShadowMessageProps) {
|
|
44
|
+
const { message, isError, isLoading } = useConversationMessage({ conversation_id, messageId })
|
|
45
|
+
|
|
46
|
+
if (inReplyScreen) return null
|
|
47
|
+
if (isLoading) return <ShadowMessageFallback text="Loading..." />
|
|
48
|
+
if (isError || !message) return <ShadowMessageFallback text="Message not found" />
|
|
49
|
+
|
|
50
|
+
const enrichedMessage = {
|
|
51
|
+
...message,
|
|
52
|
+
isReplyShadowMessage,
|
|
53
|
+
nextRendersAuthor,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return <ShadowMessageContent conversation_id={conversation_id} {...enrichedMessage} />
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ShadowMessageContentProps extends MessageResource {
|
|
60
|
+
conversation_id: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageContentProps) {
|
|
33
64
|
const { text } = message
|
|
34
65
|
const styles = useStyles(message)
|
|
35
66
|
const { colors } = useTheme()
|
|
@@ -39,12 +70,13 @@ export function ShadowMessage({ conversation_id, ...message }: ShadowMessageProp
|
|
|
39
70
|
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
40
71
|
useAnimatedMessageBackgroundColor()
|
|
41
72
|
const scalableNumberOfLines = useScalableNumberOfLines(2)
|
|
73
|
+
const replyCountText = pluralize(message.replyCount, 'reply')
|
|
42
74
|
|
|
43
75
|
const handleNavigateToReplies = () => {
|
|
44
76
|
navigation.navigate('ConversationReply', {
|
|
45
77
|
conversation_id,
|
|
46
|
-
reply_root_id: message.
|
|
47
|
-
|
|
78
|
+
reply_root_id: message.replyRootId,
|
|
79
|
+
reply_root_author_name: message.author.name,
|
|
48
80
|
})
|
|
49
81
|
}
|
|
50
82
|
|
|
@@ -89,7 +121,7 @@ export function ShadowMessage({ conversation_id, ...message }: ShadowMessageProp
|
|
|
89
121
|
</View>
|
|
90
122
|
<View style={styles.messageMeta}>
|
|
91
123
|
<Text variant="footnote" style={styles.replyCountText}>
|
|
92
|
-
{
|
|
124
|
+
{replyCountText}
|
|
93
125
|
</Text>
|
|
94
126
|
</View>
|
|
95
127
|
</View>
|
|
@@ -203,6 +235,23 @@ function MessageAttachmentIcon({ iconName }: { iconName: IconProps['name'] }) {
|
|
|
203
235
|
)
|
|
204
236
|
}
|
|
205
237
|
|
|
238
|
+
// TODO: The mine prop is not used yet, but in the future we will be adding a way to find the `mine` value for this fallback.
|
|
239
|
+
function ShadowMessageFallback({ text, mine }: { text: string; mine?: boolean }) {
|
|
240
|
+
const styles = useStyles({ mine })
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<View style={styles.message}>
|
|
244
|
+
<View style={styles.messageContent}>
|
|
245
|
+
<View style={styles.messageBubble}>
|
|
246
|
+
<Text variant="footnote" style={styles.messageText}>
|
|
247
|
+
{text}
|
|
248
|
+
</Text>
|
|
249
|
+
</View>
|
|
250
|
+
</View>
|
|
251
|
+
</View>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
206
255
|
interface StylesProps {
|
|
207
256
|
imageWidth?: number
|
|
208
257
|
imageHeight?: number
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MessageResource } from '../types'
|
|
2
|
+
import { useApiGet } from './use_api'
|
|
3
|
+
import { getMessageRequestArgs, getMessageQueryKey } from '../utils/request/get_message'
|
|
4
|
+
|
|
5
|
+
export const useConversationMessage = ({
|
|
6
|
+
conversation_id,
|
|
7
|
+
messageId,
|
|
8
|
+
enabled,
|
|
9
|
+
}: {
|
|
10
|
+
conversation_id: number
|
|
11
|
+
messageId: string
|
|
12
|
+
enabled?: boolean
|
|
13
|
+
}) => {
|
|
14
|
+
const {
|
|
15
|
+
data: message,
|
|
16
|
+
isError,
|
|
17
|
+
isLoading,
|
|
18
|
+
} = useApiGet<MessageResource>({
|
|
19
|
+
...getMessageRequestArgs({ conversation_id, messageId }),
|
|
20
|
+
enabled,
|
|
21
|
+
})
|
|
22
|
+
const queryKey = getMessageQueryKey({ conversation_id, messageId })
|
|
23
|
+
|
|
24
|
+
return { message, isError, isLoading, queryKey }
|
|
25
|
+
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { MessageResource } from '../types'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
SuspensePaginatorOptions,
|
|
6
|
-
useSuspensePaginator,
|
|
7
|
-
} from './use_suspense_api'
|
|
3
|
+
import { SuspensePaginatorOptions, useSuspensePaginator } from './use_suspense_api'
|
|
4
|
+
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
8
5
|
|
|
9
6
|
export const useConversationMessages = (
|
|
10
|
-
{ conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string },
|
|
7
|
+
{ conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string | null },
|
|
11
8
|
opts?: SuspensePaginatorOptions
|
|
12
9
|
) => {
|
|
13
10
|
const { data, refetch, isRefetching, fetchNextPage } = useSuspensePaginator<MessageResource>(
|
|
@@ -27,50 +24,3 @@ export const useConversationMessages = (
|
|
|
27
24
|
|
|
28
25
|
return { messages, refetch, isRefetching, fetchNextPage, queryKey }
|
|
29
26
|
}
|
|
30
|
-
|
|
31
|
-
export const getMessagesRequestArgs = ({
|
|
32
|
-
conversation_id,
|
|
33
|
-
reply_root_id,
|
|
34
|
-
}: {
|
|
35
|
-
conversation_id: number
|
|
36
|
-
reply_root_id?: string
|
|
37
|
-
}) => {
|
|
38
|
-
const url = reply_root_id
|
|
39
|
-
? `/me/conversations/${conversation_id}/messages/${reply_root_id}/replies`
|
|
40
|
-
: `/me/conversations/${conversation_id}/messages`
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
url,
|
|
44
|
-
data: {
|
|
45
|
-
perPage: 25,
|
|
46
|
-
fields: {
|
|
47
|
-
Message: [
|
|
48
|
-
'text',
|
|
49
|
-
'text_edited_at',
|
|
50
|
-
'mine',
|
|
51
|
-
'attachments',
|
|
52
|
-
'created_at',
|
|
53
|
-
'deleted_at',
|
|
54
|
-
'author',
|
|
55
|
-
'reaction_counts',
|
|
56
|
-
'reply_count',
|
|
57
|
-
'reply_root',
|
|
58
|
-
],
|
|
59
|
-
Person: ['name', 'avatar'],
|
|
60
|
-
ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids'],
|
|
61
|
-
},
|
|
62
|
-
include: ['author', 'reaction_counts'],
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const getMessagesQueryKey = ({
|
|
68
|
-
conversation_id,
|
|
69
|
-
reply_root_id,
|
|
70
|
-
}: {
|
|
71
|
-
conversation_id: number
|
|
72
|
-
reply_root_id?: string
|
|
73
|
-
}) => {
|
|
74
|
-
const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id })
|
|
75
|
-
return getRequestQueryKey(requestArgs)
|
|
76
|
-
}
|
|
@@ -12,7 +12,7 @@ import { transformMessageEventDataToMessageResource } from '../utils/jolt/transf
|
|
|
12
12
|
import { getRequestQueryKey } from './use_suspense_api'
|
|
13
13
|
import { JoltReactionEvent, JoltTypingEvent } from '../types/jolt_events'
|
|
14
14
|
import { transformReactionEventDataToReactionCountResource } from '../utils/jolt/transform_reaction_event_data_to_reaction_count_resource'
|
|
15
|
-
import { getMessagesRequestArgs } from '../utils/request/
|
|
15
|
+
import { getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
16
16
|
import { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'
|
|
17
17
|
import { isTemporaryMessageId } from './use_message_create_or_update'
|
|
18
18
|
import { completeMessageCreationTracking } from '../utils/performance_tracking'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InfiniteData, useMutation } from '@tanstack/react-query'
|
|
2
|
-
import { getMessagesQueryKey, getMessagesRequestArgs } from '
|
|
2
|
+
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
3
3
|
import { useApiClient } from './use_api_client'
|
|
4
4
|
import { ApiCollection, ApiResource, MessageResource } from '../types'
|
|
5
5
|
import { chatQueryClient } from '../contexts/api_provider'
|
|
@@ -18,7 +18,7 @@ import { startMessageCreationTracking } from '../utils/performance_tracking'
|
|
|
18
18
|
interface Props {
|
|
19
19
|
conversationId: number
|
|
20
20
|
message?: MessageResource
|
|
21
|
-
replyRootId?: string
|
|
21
|
+
replyRootId?: string | null
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {
|
|
@@ -2,7 +2,7 @@ import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query
|
|
|
2
2
|
import { useCallback } from 'react'
|
|
3
3
|
import { Alert } from 'react-native'
|
|
4
4
|
import { useApiClient } from './use_api_client'
|
|
5
|
-
import { getMessagesQueryKey, getMessagesRequestArgs } from '
|
|
5
|
+
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
6
6
|
import { ApiCollection, ApiResource, MessageResource } from '../types'
|
|
7
7
|
import { ReactionCountResource } from '../types/resources/reaction'
|
|
8
8
|
import { updateRecordInPagesData } from '../utils'
|
package/src/navigation/index.tsx
CHANGED
|
@@ -187,9 +187,9 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
187
187
|
},
|
|
188
188
|
ConversationReply: {
|
|
189
189
|
screen: ConversationScreen,
|
|
190
|
-
options: {
|
|
191
|
-
title: 'Reply',
|
|
192
|
-
},
|
|
190
|
+
options: ({ route }) => ({
|
|
191
|
+
title: (route.params as ConversationRouteProps)?.title ?? 'Reply',
|
|
192
|
+
}),
|
|
193
193
|
},
|
|
194
194
|
TeamConversation: {
|
|
195
195
|
screen: TeamConversationScreen,
|
|
@@ -34,11 +34,13 @@ import { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'
|
|
|
34
34
|
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
|
+
import { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'
|
|
38
|
+
import { REPLIES_FEATURE_ENABLED } from '../utils'
|
|
37
39
|
|
|
38
40
|
export type ConversationRouteProps = {
|
|
39
41
|
conversation_id: number
|
|
40
|
-
reply_root_id?: string
|
|
41
|
-
|
|
42
|
+
reply_root_id?: string | null
|
|
43
|
+
reply_root_author_name?: string
|
|
42
44
|
chat_group_graph_id?: string
|
|
43
45
|
clear_input?: boolean
|
|
44
46
|
editing_message_id?: number | null
|
|
@@ -53,7 +55,8 @@ export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
|
|
|
53
55
|
export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
54
56
|
const styles = useStyles()
|
|
55
57
|
const navigation = useNavigation()
|
|
56
|
-
const { conversation_id, editing_message_id, reply_root_id } =
|
|
58
|
+
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
|
|
59
|
+
route.params
|
|
57
60
|
const { data: conversation } = useConversation(route.params)
|
|
58
61
|
const { messages, refetch, isRefetching, fetchNextPage } = useConversationMessages({
|
|
59
62
|
conversation_id,
|
|
@@ -71,6 +74,10 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
71
74
|
const showLeaderDisabledReplyBanner = canReply && repliesDisabled
|
|
72
75
|
const canDeleteNonAuthoredMessages = memberAbility?.canDeleteNonAuthoredMessages ?? false
|
|
73
76
|
const currentlyEditingMessage = messages.find(m => String(m.id) === String(editing_message_id))
|
|
77
|
+
const replyRootAuthorFirstName = reply_root_author_name?.split(' ')[0]
|
|
78
|
+
const replyHeaderTitle = replyRootAuthorFirstName
|
|
79
|
+
? `Reply to ${replyRootAuthorFirstName}`
|
|
80
|
+
: 'Reply'
|
|
74
81
|
|
|
75
82
|
const listRef = useRef<FlatList>(null)
|
|
76
83
|
const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)
|
|
@@ -87,14 +94,18 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
87
94
|
}, [])
|
|
88
95
|
|
|
89
96
|
useEffect(() => {
|
|
90
|
-
if (reply_root_id)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
if (reply_root_id) {
|
|
98
|
+
navigation.setParams({
|
|
99
|
+
title: replyHeaderTitle,
|
|
100
|
+
})
|
|
101
|
+
} else {
|
|
102
|
+
navigation.setParams({
|
|
103
|
+
title: title,
|
|
104
|
+
badge: badges?.[0],
|
|
105
|
+
deleted: conversation?.deleted,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}, [navigation, title, badges, conversation?.deleted, reply_root_id, replyHeaderTitle])
|
|
98
109
|
|
|
99
110
|
if (!conversation || conversation.deleted) {
|
|
100
111
|
return (
|
|
@@ -136,6 +147,16 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
136
147
|
return <InlineDateSeparator {...item} />
|
|
137
148
|
}
|
|
138
149
|
|
|
150
|
+
if (item.type === 'ReplyShadowMessage') {
|
|
151
|
+
return (
|
|
152
|
+
<ReplyShadowMessage
|
|
153
|
+
{...item}
|
|
154
|
+
conversation_id={conversation_id}
|
|
155
|
+
inReplyScreen={!!reply_root_id}
|
|
156
|
+
/>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
139
160
|
return (
|
|
140
161
|
<Message
|
|
141
162
|
{...item}
|
|
@@ -155,6 +176,7 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
155
176
|
{showLeaderDisabledReplyBanner && <LeaderDisabledRepliesBanner />}
|
|
156
177
|
{canReply ? (
|
|
157
178
|
<MessageForm.Root
|
|
179
|
+
replyRootAuthorFirstName={replyRootAuthorFirstName}
|
|
158
180
|
conversation={conversation}
|
|
159
181
|
replyRootId={reply_root_id}
|
|
160
182
|
currentlyEditingMessage={currentlyEditingMessage}
|
|
@@ -219,13 +241,21 @@ const useDateSeparatorStyles = () => {
|
|
|
219
241
|
})
|
|
220
242
|
}
|
|
221
243
|
|
|
244
|
+
type ReplyShadowMessage = {
|
|
245
|
+
type: 'ReplyShadowMessage'
|
|
246
|
+
id: string
|
|
247
|
+
messageId: string
|
|
248
|
+
isReplyShadowMessage: boolean
|
|
249
|
+
nextRendersAuthor: boolean
|
|
250
|
+
}
|
|
251
|
+
|
|
222
252
|
interface GroupMessagesProps {
|
|
223
253
|
ms: MessageResource[]
|
|
224
254
|
inReplyScreen?: boolean
|
|
225
255
|
}
|
|
226
256
|
|
|
227
257
|
export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
228
|
-
let enrichedMessages: (MessageResource | DateSeparator)[] = []
|
|
258
|
+
let enrichedMessages: (MessageResource | DateSeparator | ReplyShadowMessage)[] = []
|
|
229
259
|
let encounteredOneOfMyMessages = false
|
|
230
260
|
|
|
231
261
|
ms.forEach((message, i) => {
|
|
@@ -233,7 +263,9 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
233
263
|
const nextMessage = ms[i - 1]
|
|
234
264
|
const date = moment(message.createdAt).format('YYYY-MM-DD')
|
|
235
265
|
const inThread = message.replyRootId !== null
|
|
266
|
+
const nextMessageInThread = nextMessage?.replyRootId !== null
|
|
236
267
|
const threadRoot = message.replyRootId === message.id
|
|
268
|
+
const nextMessageThreadRoot = nextMessage?.replyRootId === nextMessage?.id
|
|
237
269
|
const prevMessageDifferentThread = message.replyRootId !== prevMessage?.replyRootId
|
|
238
270
|
const nextMessageDifferentThread = message.replyRootId !== nextMessage?.replyRootId
|
|
239
271
|
const prevMessageDifferentAuthor = message.author?.id !== prevMessage?.author?.id
|
|
@@ -244,8 +276,14 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
244
276
|
const nextMessageMoreThan5Minutes =
|
|
245
277
|
nextMessage &&
|
|
246
278
|
new Date(nextMessage.createdAt).getTime() - new Date(message.createdAt).getTime() > 60000 * 5
|
|
279
|
+
const prevMessageIsDateSeparator =
|
|
280
|
+
prevMessage && date !== moment(prevMessage.createdAt).format('YYYY-MM-DD')
|
|
247
281
|
const nextMessageIsDateSeparator =
|
|
248
282
|
nextMessage && date !== moment(nextMessage.createdAt).format('YYYY-MM-DD')
|
|
283
|
+
const insertReplyShadowMessage =
|
|
284
|
+
message.replyRootId &&
|
|
285
|
+
!threadRoot &&
|
|
286
|
+
(prevMessageDifferentThread || prevMessageIsDateSeparator)
|
|
249
287
|
|
|
250
288
|
if (message.mine && !encounteredOneOfMyMessages) {
|
|
251
289
|
encounteredOneOfMyMessages = true
|
|
@@ -257,6 +295,13 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
257
295
|
message.renderAuthor =
|
|
258
296
|
!message.mine && (!prevMessage || prevMessageDifferentAuthor || prevMessageMoreThan5Minutes)
|
|
259
297
|
message.threadPosition = null
|
|
298
|
+
message.nextRendersAuthor = nextMessage?.renderAuthor
|
|
299
|
+
message.isReplyShadowMessage = false
|
|
300
|
+
message.nextIsReplyShadowMessage =
|
|
301
|
+
REPLIES_FEATURE_ENABLED &&
|
|
302
|
+
nextMessageInThread &&
|
|
303
|
+
!nextMessageThreadRoot &&
|
|
304
|
+
(nextMessageDifferentThread || nextMessageIsDateSeparator)
|
|
260
305
|
|
|
261
306
|
if (!inReplyScreen && inThread) {
|
|
262
307
|
message.prevIsMyReply = prevMessage?.mine
|
|
@@ -274,6 +319,17 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
274
319
|
}
|
|
275
320
|
|
|
276
321
|
enrichedMessages.push(message)
|
|
322
|
+
|
|
323
|
+
if (insertReplyShadowMessage && REPLIES_FEATURE_ENABLED) {
|
|
324
|
+
enrichedMessages.push({
|
|
325
|
+
type: 'ReplyShadowMessage',
|
|
326
|
+
id: `${message.id}-${message.replyRootId}`,
|
|
327
|
+
messageId: message.replyRootId!,
|
|
328
|
+
isReplyShadowMessage: true,
|
|
329
|
+
nextRendersAuthor: message?.renderAuthor,
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
277
333
|
if (!prevMessage || date !== moment(prevMessage.createdAt).format('YYYY-MM-DD')) {
|
|
278
334
|
enrichedMessages.push({ type: 'DateSeparator', id: `day-divider-${message.id}`, date })
|
|
279
335
|
}
|
|
@@ -23,13 +23,20 @@ export const MessageActionsScreenOptions = getFormSheetScreenOptions({
|
|
|
23
23
|
|
|
24
24
|
export type MessageActionsScreenProps = StaticScreenProps<{
|
|
25
25
|
message_id: string
|
|
26
|
+
reply_root_author_name?: string
|
|
26
27
|
conversation_id: number
|
|
27
28
|
canDeleteNonAuthoredMessages?: boolean
|
|
28
29
|
inReplyScreen?: boolean
|
|
29
30
|
}>
|
|
30
31
|
|
|
31
32
|
export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
32
|
-
const {
|
|
33
|
+
const {
|
|
34
|
+
conversation_id,
|
|
35
|
+
message_id,
|
|
36
|
+
canDeleteNonAuthoredMessages,
|
|
37
|
+
inReplyScreen,
|
|
38
|
+
reply_root_author_name,
|
|
39
|
+
} = route.params
|
|
33
40
|
|
|
34
41
|
const { messages, refetch } = useConversationMessages(
|
|
35
42
|
{ conversation_id },
|
|
@@ -46,6 +53,7 @@ export function MessageActionsScreen({ route }: MessageActionsScreenProps) {
|
|
|
46
53
|
canDeleteNonAuthoredMessages={canDeleteNonAuthoredMessages || false}
|
|
47
54
|
refetchMessages={refetch}
|
|
48
55
|
inReplyScreen={inReplyScreen}
|
|
56
|
+
replyRootAuthorName={reply_root_author_name}
|
|
49
57
|
/>
|
|
50
58
|
)
|
|
51
59
|
}
|
|
@@ -56,12 +64,14 @@ function MessageActionsScreenContent({
|
|
|
56
64
|
canDeleteNonAuthoredMessages,
|
|
57
65
|
refetchMessages,
|
|
58
66
|
inReplyScreen,
|
|
67
|
+
replyRootAuthorName,
|
|
59
68
|
}: {
|
|
60
69
|
message: MessageResource
|
|
61
70
|
conversation_id: number
|
|
62
71
|
canDeleteNonAuthoredMessages: boolean
|
|
63
72
|
refetchMessages: () => void
|
|
64
73
|
inReplyScreen?: boolean
|
|
74
|
+
replyRootAuthorName?: string
|
|
65
75
|
}) {
|
|
66
76
|
const navigation = useNavigation()
|
|
67
77
|
const apiClient = useApiClient()
|
|
@@ -95,10 +105,10 @@ function MessageActionsScreenContent({
|
|
|
95
105
|
navigation.navigate('ConversationReply', {
|
|
96
106
|
conversation_id,
|
|
97
107
|
reply_root_id: message.replyRootId || message.id,
|
|
98
|
-
|
|
108
|
+
reply_root_author_name: replyRootAuthorName,
|
|
99
109
|
})
|
|
100
110
|
})
|
|
101
|
-
}, [navigation, conversation_id, message.id, message.replyRootId])
|
|
111
|
+
}, [navigation, conversation_id, message.id, message.replyRootId, replyRootAuthorName])
|
|
102
112
|
|
|
103
113
|
const handleCopyPress = () => {
|
|
104
114
|
Clipboard.setStringAsync(message?.text || attachmentForCopy() || '')
|
|
@@ -26,6 +26,9 @@ export interface MessageResource {
|
|
|
26
26
|
threadPosition?: 'first' | 'center' | 'last' | null
|
|
27
27
|
prevIsMyReply?: boolean
|
|
28
28
|
nextIsMyReply?: boolean
|
|
29
|
+
nextRendersAuthor?: boolean
|
|
30
|
+
isReplyShadowMessage?: boolean
|
|
31
|
+
nextIsReplyShadowMessage?: boolean
|
|
29
32
|
|
|
30
33
|
// Properties for mutation state
|
|
31
34
|
pending?: boolean // Indicates if the message is optimistically created and pending server response
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InfiniteData } from '@tanstack/react-query'
|
|
2
2
|
import { ApiCollection, CurrentPersonResource, MessageResource } from '../../types'
|
|
3
|
-
import { getMessagesQueryKey } from '../../
|
|
3
|
+
import { getMessagesQueryKey } from '../../utils/request/get_messages'
|
|
4
4
|
import { chatQueryClient } from '../../contexts/api_provider'
|
|
5
5
|
import { updateOrCreateRecordInPagesData } from './page_mutations'
|
|
6
6
|
import { convertAttachmentsForCreate } from '../convert_attachments_for_create'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InfiniteData } from '@tanstack/react-query'
|
|
2
2
|
import { ApiCollection, MessageResource } from '../../types'
|
|
3
|
-
import { getMessagesQueryKey } from '../../
|
|
3
|
+
import { getMessagesQueryKey } from '../../utils/request/get_messages'
|
|
4
4
|
import { chatQueryClient } from '../../contexts/api_provider'
|
|
5
5
|
import { updateOrCreateRecordInPagesData } from './page_mutations'
|
|
6
6
|
|
package/src/utils/pluralize.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const irregularInflections: Record<string, string> = { person: 'people' }
|
|
1
|
+
const irregularInflections: Record<string, string> = { person: 'people', reply: 'replies' }
|
|
2
2
|
|
|
3
3
|
export function pluralize(count: number, singularWord: string, includeCount = true) {
|
|
4
4
|
const plural = count !== 1
|