@planningcenter/chat-react-native 3.18.0-rc.6 → 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/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/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 -2
- package/src/components/conversation/message.tsx +2 -1
- package/src/hooks/use_conversation_messages_jolt_events.ts +39 -81
- package/src/hooks/use_message_create_or_update.ts +10 -9
- 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoB,MAAM,OAAO,CAAA;AASxC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoB,MAAM,OAAO,CAAA;AASxC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAqB7C;;GAEG;AACH,UAAU,YAAa,SAAQ,eAAe;IAC5C,4BAA4B,EAAE,OAAO,CAAA;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,wBAAgB,OAAO,CAAC,EACtB,4BAA4B,EAC5B,eAAe,EACf,wBAAwB,EACxB,aAAa,EACb,cAAc,EACd,GAAG,OAAO,EACX,EAAE,YAAY,qBAsPd"}
|
|
@@ -11,11 +11,12 @@ import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL, MESSAGE_AUTHOR_AVATAR_COL
|
|
|
11
11
|
import Animated from 'react-native-reanimated';
|
|
12
12
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time';
|
|
13
13
|
import { MessageReadReceipts } from './message_read_receipts';
|
|
14
|
-
import {
|
|
14
|
+
import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update';
|
|
15
15
|
import { Haptic } from '../../utils/native_adapters';
|
|
16
16
|
import { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from './reply_connectors';
|
|
17
17
|
import { pluralize } from '../../utils';
|
|
18
18
|
import { useConversationMessage } from '../../hooks/use_conversation_message';
|
|
19
|
+
import { isNewMessage } from '../../utils/cache/messages_cache';
|
|
19
20
|
export function Message({ canDeleteNonAuthoredMessages, conversation_id, latestReadMessageSortKey, inReplyScreen, repliesEnabled, ...message }) {
|
|
20
21
|
const { text, reactionCounts, pending, error } = message;
|
|
21
22
|
const styles = useMessageStyles(message);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useNavigation } from '@react-navigation/native'\nimport React, { useEffect } from 'react'\nimport { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'\nimport { MessageReaction } from '../../components/conversation/message_reaction'\nimport { Avatar, Icon, Spinner, Text, TextButton, TextInlineButton } from '../../components/display'\nimport {\n useAnimatedMessageBackgroundColor,\n useInteractionGhostBackgroundColor,\n useTheme,\n} from '../../hooks'\nimport { MessageResource } from '../../types'\nimport { ReactionCountResource } from '../../types/resources/reaction'\nimport { MessageAttachments } from './message_attachments'\nimport ErrorBoundary from '../page/error_boundary'\nimport { MessageMarkdown } from './message_markdown'\nimport { DenormalizedMessageAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport {\n CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,\n platformFontWeightMedium,\n} from '../../utils/styles'\nimport Animated from 'react-native-reanimated'\nimport { useLiveRelativeTime } from '../../hooks/use_live_relative_time'\nimport { MessageReadReceipts } from './message_read_receipts'\nimport { isNewMessage, useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'\nimport { Haptic } from '../../utils/native_adapters'\nimport { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from './reply_connectors'\nimport { pluralize } from '../../utils'\nimport { useConversationMessage } from '../../hooks/use_conversation_message'\n\n/** Message\n * Component for display of a message within a conversation list\n */\ninterface MessageProps extends MessageResource {\n canDeleteNonAuthoredMessages: boolean\n conversation_id: number\n latestReadMessageSortKey?: string\n inReplyScreen?: boolean\n repliesEnabled?: boolean\n}\n\nexport function Message({\n canDeleteNonAuthoredMessages,\n conversation_id,\n latestReadMessageSortKey,\n inReplyScreen,\n repliesEnabled,\n ...message\n}: MessageProps) {\n const { text, reactionCounts, pending, error } = message\n const styles = useMessageStyles(message)\n const navigation = useNavigation()\n const { colors } = useTheme()\n const hasReactions = reactionCounts.length > 0\n const [showMessageMetaToggle, setShowMessageMetaToggle] = React.useState(false)\n const showMessageMeta =\n showMessageMetaToggle || message.myLatestInConversation || !!pending || !!error || false\n const timestamp = useLiveRelativeTime(message.createdAt)\n const wasEdited = Boolean(message.textEditedAt)\n const isPersisted = !isNewMessage(message)\n const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)\n const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)\n const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =\n useAnimatedMessageBackgroundColor()\n const { message: replyRootMessage } = useConversationMessage({\n conversation_id,\n messageId: message.replyRootId || '',\n enabled: !!message.replyRootId,\n })\n\n const metaProps = {\n authorName: message.author.name,\n createdAt: message.createdAt,\n }\n\n const renderAuthor = (!message.mine && message.renderAuthor) || false\n const showReplyCountButton =\n !inReplyScreen && message.replyRootId === message.id && repliesEnabled\n const isReplyRootMessage = message.replyRootId === message.id\n const isDeletedReplyRootMessage = isReplyRootMessage && !!message.deletedAt\n\n const messageText = isDeletedReplyRootMessage ? 'Message deleted' : text\n const replyCountText = pluralize(message.replyCount, 'reply')\n const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'\n const replyRootAuthorName = message.replyRootId\n ? replyRootMessage?.author.name\n : message.author.name\n\n const messageBottomMargin =\n message.lastInGroup || message.nextIsReplyShadowMessage\n ? 16\n : hasReactions || showMessageMeta || showReplyCountButton\n ? 8\n : 4\n\n useEffect(() => {\n if (pending) {\n const timer = setTimeout(() => {\n setTemporarilyHidePendingState(false)\n }, 2000)\n return () => clearTimeout(timer)\n }\n return () => {}\n }, [pending])\n\n const retryFailedSaveMutation = useMessageCreateOrUpdate({\n conversationId: conversation_id,\n message,\n })\n\n const handleRetry = () => {\n if (text && error) {\n retryFailedSaveMutation.mutateAsync({\n text,\n attachments: message.attachmentsForCreate,\n })\n }\n }\n\n const handleMessageLongPress = () => {\n if (!isPersisted) return\n\n Haptic.impactLight()\n navigation.navigate('MessageActions', {\n message_id: message.id,\n conversation_id,\n canDeleteNonAuthoredMessages,\n inReplyScreen,\n reply_root_author_name: replyRootAuthorName,\n })\n }\n const handleReactionLongPress = (reaction: ReactionCountResource) => {\n Haptic.impactLight()\n navigation.navigate('Reactions', {\n message_id: message.id,\n conversation_id,\n reaction_value: reaction.value,\n })\n }\n\n const handleMessageAttachmentLongPress = (attachment: DenormalizedMessageAttachmentResource) => {\n Haptic.impactLight()\n navigation.navigate('AttachmentActions', {\n attachmentId: attachment?.id,\n attachmentContentType: attachment?.attributes.contentType,\n attachmentUrl: attachment?.attributes.url,\n conversation_id,\n canDeleteNonAuthoredMessages,\n myMessage: message.mine,\n })\n }\n\n const handleNavigateToReplyPress = () => {\n navigation.navigate('ConversationReply', {\n conversation_id,\n reply_root_id: message.id,\n reply_root_author_name: message.author.name,\n })\n }\n\n return (\n <Pressable\n onLongPress={handleMessageLongPress}\n onPress={() => setShowMessageMetaToggle(!showMessageMetaToggle)}\n onPressIn={handleMessagePressIn}\n onPressOut={handleMessagePressOut}\n android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}\n accessibilityHint=\"Long press to view message actions like reacting and copying.\"\n >\n <Animated.View style={[styles.message, animatedBackgroundColor]}>\n {!message.mine && (\n <View>\n {renderAuthor ? (\n <Avatar\n size={'md'}\n sourceUri={message.author.avatar}\n style={styles.avatar}\n maxFontSizeMultiplier={1}\n minFontSizeMultiplier={1}\n fallbackIconName={isDeletedReplyRootMessage ? 'publishing.trash' : 'general.person'}\n showFallback={isDeletedReplyRootMessage}\n />\n ) : (\n <View style={styles.avatarPlaceholder} />\n )}\n {repliesEnabled && (\n <TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />\n )}\n </View>\n )}\n <View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>\n {renderAuthor && (\n <Text variant=\"footnote\" style={styles.authorName}>\n {message.author.name}\n </Text>\n )}\n <View\n style={styles.messageBubble}\n onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}\n >\n <ErrorBoundary>\n <MessageAttachments\n attachments={message.attachments}\n metaProps={metaProps}\n onMessageAttachmentLongPress={handleMessageAttachmentLongPress}\n onMessageLongPress={handleMessageLongPress}\n />\n </ErrorBoundary>\n {messageText && (\n <View style={styles.messageText}>\n <MessageMarkdown text={messageText} />\n </View>\n )}\n </View>\n {showReplyCountButton && (\n <TextButton\n variant=\"footnote\"\n onPress={handleNavigateToReplyPress}\n style={styles.messageReplyCount}\n accessibilityHint=\"Navigates to reply screen for this message\"\n accessibilityRole=\"link\"\n >\n {replyCountText}\n </TextButton>\n )}\n {hasReactions && (\n <View style={styles.messageReactions}>\n {reactionCounts.map(reaction => (\n <MessageReaction\n key={reaction.value}\n reaction={reaction}\n onLongPress={handleReactionLongPress}\n message={message}\n conversationId={conversation_id}\n />\n ))}\n </View>\n )}\n {showMessageMeta && (\n <View style={styles.messageMeta}>\n {message.mine && !pending && !error && (\n <MessageReadReceipts\n message={message}\n latestReadMessageSortKey={latestReadMessageSortKey}\n />\n )}\n {error ? (\n <View style={styles.errorContainer}>\n {retryFailedSaveMutation.isPending ? (\n <Spinner size={12} />\n ) : (\n <Icon name=\"general.exclamationTriangle\" size={12} style={styles.errorIcon} />\n )}\n <Text variant=\"footnote\" style={styles.errorText}>\n Failed to {isPersisted ? 'edit' : 'send'} |{' '}\n <TextInlineButton\n onPress={handleRetry}\n variant=\"footnote\"\n appearance=\"danger\"\n disabled={retryFailedSaveMutation.isPending}\n >\n Try again\n </TextInlineButton>\n </Text>\n </View>\n ) : pending && (!temporarilyHidePendingState || isPersisted) ? (\n <View style={styles.pendingIndicatorContainer}>\n <Icon\n name=\"general.outlinedClock\"\n size={12}\n color={colors.iconColorDefaultSecondary}\n />\n <Text variant=\"footnote\">{messagePendingLabel}</Text>\n </View>\n ) : isPersisted ? (\n <Text variant=\"footnote\">{timestamp}</Text>\n ) : null}\n {!pending && !error && wasEdited && (\n <Text variant=\"footnote\">\n |{' '}\n <Text variant=\"footnote\" style={styles.editedText}>\n Edited\n </Text>\n </Text>\n )}\n </View>\n )}\n </View>\n {repliesEnabled && message.mine && (\n <MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />\n )}\n </Animated.View>\n </Pressable>\n )\n}\n\nconst useMessageStyles = ({ mine }: MessageResource) => {\n const { colors } = useTheme()\n const myMessageBackgroundColor = useInteractionGhostBackgroundColor()\n const { width } = useWindowDimensions()\n const tabletWidth = width >= 744 // Smallest iPad Mini's width\n\n const messageBubbleBackgroundColor = mine ? myMessageBackgroundColor : colors.fillColorNeutral070\n\n return StyleSheet.create({\n message: {\n gap: 8,\n flexDirection: mine ? 'row-reverse' : 'row',\n paddingHorizontal: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n },\n messageContent: {\n flex: 1,\n gap: 4,\n },\n avatar: {\n marginBottom: AVATAR_CONNECTOR_SPACING,\n },\n avatarPlaceholder: {\n width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH, // Same width as avatar\n },\n authorName: {\n fontWeight: platformFontWeightMedium,\n },\n messageBubble: {\n backgroundColor: messageBubbleBackgroundColor,\n borderRadius: 8,\n maxWidth: tabletWidth ? 360 : '80%',\n alignSelf: mine ? 'flex-end' : 'flex-start',\n },\n messageText: {\n paddingVertical: 6,\n paddingHorizontal: 8,\n },\n messageReactions: {\n flexDirection: 'row',\n gap: 4,\n justifyContent: mine ? 'flex-end' : 'flex-start',\n },\n messageReplyCount: {\n alignSelf: mine ? 'flex-end' : 'flex-start',\n },\n messageMeta: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: mine ? 'flex-end' : 'flex-start',\n gap: 4,\n },\n editedText: {\n fontWeight: platformFontWeightMedium,\n },\n pendingIndicatorContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 4,\n },\n errorContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 4,\n },\n errorIcon: {\n color: colors.statusErrorIcon,\n },\n errorText: {\n color: colors.statusErrorText,\n },\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useNavigation } from '@react-navigation/native'\nimport React, { useEffect } from 'react'\nimport { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'\nimport { MessageReaction } from '../../components/conversation/message_reaction'\nimport { Avatar, Icon, Spinner, Text, TextButton, TextInlineButton } from '../../components/display'\nimport {\n useAnimatedMessageBackgroundColor,\n useInteractionGhostBackgroundColor,\n useTheme,\n} from '../../hooks'\nimport { MessageResource } from '../../types'\nimport { ReactionCountResource } from '../../types/resources/reaction'\nimport { MessageAttachments } from './message_attachments'\nimport ErrorBoundary from '../page/error_boundary'\nimport { MessageMarkdown } from './message_markdown'\nimport { DenormalizedMessageAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport {\n CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,\n platformFontWeightMedium,\n} from '../../utils/styles'\nimport Animated from 'react-native-reanimated'\nimport { useLiveRelativeTime } from '../../hooks/use_live_relative_time'\nimport { MessageReadReceipts } from './message_read_receipts'\nimport { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'\nimport { Haptic } from '../../utils/native_adapters'\nimport { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from './reply_connectors'\nimport { pluralize } from '../../utils'\nimport { useConversationMessage } from '../../hooks/use_conversation_message'\nimport { isNewMessage } from '../../utils/cache/messages_cache'\n\n/** Message\n * Component for display of a message within a conversation list\n */\ninterface MessageProps extends MessageResource {\n canDeleteNonAuthoredMessages: boolean\n conversation_id: number\n latestReadMessageSortKey?: string\n inReplyScreen?: boolean\n repliesEnabled?: boolean\n}\n\nexport function Message({\n canDeleteNonAuthoredMessages,\n conversation_id,\n latestReadMessageSortKey,\n inReplyScreen,\n repliesEnabled,\n ...message\n}: MessageProps) {\n const { text, reactionCounts, pending, error } = message\n const styles = useMessageStyles(message)\n const navigation = useNavigation()\n const { colors } = useTheme()\n const hasReactions = reactionCounts.length > 0\n const [showMessageMetaToggle, setShowMessageMetaToggle] = React.useState(false)\n const showMessageMeta =\n showMessageMetaToggle || message.myLatestInConversation || !!pending || !!error || false\n const timestamp = useLiveRelativeTime(message.createdAt)\n const wasEdited = Boolean(message.textEditedAt)\n const isPersisted = !isNewMessage(message)\n const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)\n const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)\n const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =\n useAnimatedMessageBackgroundColor()\n const { message: replyRootMessage } = useConversationMessage({\n conversation_id,\n messageId: message.replyRootId || '',\n enabled: !!message.replyRootId,\n })\n\n const metaProps = {\n authorName: message.author.name,\n createdAt: message.createdAt,\n }\n\n const renderAuthor = (!message.mine && message.renderAuthor) || false\n const showReplyCountButton =\n !inReplyScreen && message.replyRootId === message.id && repliesEnabled\n const isReplyRootMessage = message.replyRootId === message.id\n const isDeletedReplyRootMessage = isReplyRootMessage && !!message.deletedAt\n\n const messageText = isDeletedReplyRootMessage ? 'Message deleted' : text\n const replyCountText = pluralize(message.replyCount, 'reply')\n const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'\n const replyRootAuthorName = message.replyRootId\n ? replyRootMessage?.author.name\n : message.author.name\n\n const messageBottomMargin =\n message.lastInGroup || message.nextIsReplyShadowMessage\n ? 16\n : hasReactions || showMessageMeta || showReplyCountButton\n ? 8\n : 4\n\n useEffect(() => {\n if (pending) {\n const timer = setTimeout(() => {\n setTemporarilyHidePendingState(false)\n }, 2000)\n return () => clearTimeout(timer)\n }\n return () => {}\n }, [pending])\n\n const retryFailedSaveMutation = useMessageCreateOrUpdate({\n conversationId: conversation_id,\n message,\n })\n\n const handleRetry = () => {\n if (text && error) {\n retryFailedSaveMutation.mutateAsync({\n text,\n attachments: message.attachmentsForCreate,\n })\n }\n }\n\n const handleMessageLongPress = () => {\n if (!isPersisted) return\n\n Haptic.impactLight()\n navigation.navigate('MessageActions', {\n message_id: message.id,\n conversation_id,\n canDeleteNonAuthoredMessages,\n inReplyScreen,\n reply_root_author_name: replyRootAuthorName,\n })\n }\n const handleReactionLongPress = (reaction: ReactionCountResource) => {\n Haptic.impactLight()\n navigation.navigate('Reactions', {\n message_id: message.id,\n conversation_id,\n reaction_value: reaction.value,\n })\n }\n\n const handleMessageAttachmentLongPress = (attachment: DenormalizedMessageAttachmentResource) => {\n Haptic.impactLight()\n navigation.navigate('AttachmentActions', {\n attachmentId: attachment?.id,\n attachmentContentType: attachment?.attributes.contentType,\n attachmentUrl: attachment?.attributes.url,\n conversation_id,\n canDeleteNonAuthoredMessages,\n myMessage: message.mine,\n })\n }\n\n const handleNavigateToReplyPress = () => {\n navigation.navigate('ConversationReply', {\n conversation_id,\n reply_root_id: message.id,\n reply_root_author_name: message.author.name,\n })\n }\n\n return (\n <Pressable\n onLongPress={handleMessageLongPress}\n onPress={() => setShowMessageMetaToggle(!showMessageMetaToggle)}\n onPressIn={handleMessagePressIn}\n onPressOut={handleMessagePressOut}\n android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}\n accessibilityHint=\"Long press to view message actions like reacting and copying.\"\n >\n <Animated.View style={[styles.message, animatedBackgroundColor]}>\n {!message.mine && (\n <View>\n {renderAuthor ? (\n <Avatar\n size={'md'}\n sourceUri={message.author.avatar}\n style={styles.avatar}\n maxFontSizeMultiplier={1}\n minFontSizeMultiplier={1}\n fallbackIconName={isDeletedReplyRootMessage ? 'publishing.trash' : 'general.person'}\n showFallback={isDeletedReplyRootMessage}\n />\n ) : (\n <View style={styles.avatarPlaceholder} />\n )}\n {repliesEnabled && (\n <TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />\n )}\n </View>\n )}\n <View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>\n {renderAuthor && (\n <Text variant=\"footnote\" style={styles.authorName}>\n {message.author.name}\n </Text>\n )}\n <View\n style={styles.messageBubble}\n onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}\n >\n <ErrorBoundary>\n <MessageAttachments\n attachments={message.attachments}\n metaProps={metaProps}\n onMessageAttachmentLongPress={handleMessageAttachmentLongPress}\n onMessageLongPress={handleMessageLongPress}\n />\n </ErrorBoundary>\n {messageText && (\n <View style={styles.messageText}>\n <MessageMarkdown text={messageText} />\n </View>\n )}\n </View>\n {showReplyCountButton && (\n <TextButton\n variant=\"footnote\"\n onPress={handleNavigateToReplyPress}\n style={styles.messageReplyCount}\n accessibilityHint=\"Navigates to reply screen for this message\"\n accessibilityRole=\"link\"\n >\n {replyCountText}\n </TextButton>\n )}\n {hasReactions && (\n <View style={styles.messageReactions}>\n {reactionCounts.map(reaction => (\n <MessageReaction\n key={reaction.value}\n reaction={reaction}\n onLongPress={handleReactionLongPress}\n message={message}\n conversationId={conversation_id}\n />\n ))}\n </View>\n )}\n {showMessageMeta && (\n <View style={styles.messageMeta}>\n {message.mine && !pending && !error && (\n <MessageReadReceipts\n message={message}\n latestReadMessageSortKey={latestReadMessageSortKey}\n />\n )}\n {error ? (\n <View style={styles.errorContainer}>\n {retryFailedSaveMutation.isPending ? (\n <Spinner size={12} />\n ) : (\n <Icon name=\"general.exclamationTriangle\" size={12} style={styles.errorIcon} />\n )}\n <Text variant=\"footnote\" style={styles.errorText}>\n Failed to {isPersisted ? 'edit' : 'send'} |{' '}\n <TextInlineButton\n onPress={handleRetry}\n variant=\"footnote\"\n appearance=\"danger\"\n disabled={retryFailedSaveMutation.isPending}\n >\n Try again\n </TextInlineButton>\n </Text>\n </View>\n ) : pending && (!temporarilyHidePendingState || isPersisted) ? (\n <View style={styles.pendingIndicatorContainer}>\n <Icon\n name=\"general.outlinedClock\"\n size={12}\n color={colors.iconColorDefaultSecondary}\n />\n <Text variant=\"footnote\">{messagePendingLabel}</Text>\n </View>\n ) : isPersisted ? (\n <Text variant=\"footnote\">{timestamp}</Text>\n ) : null}\n {!pending && !error && wasEdited && (\n <Text variant=\"footnote\">\n |{' '}\n <Text variant=\"footnote\" style={styles.editedText}>\n Edited\n </Text>\n </Text>\n )}\n </View>\n )}\n </View>\n {repliesEnabled && message.mine && (\n <MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />\n )}\n </Animated.View>\n </Pressable>\n )\n}\n\nconst useMessageStyles = ({ mine }: MessageResource) => {\n const { colors } = useTheme()\n const myMessageBackgroundColor = useInteractionGhostBackgroundColor()\n const { width } = useWindowDimensions()\n const tabletWidth = width >= 744 // Smallest iPad Mini's width\n\n const messageBubbleBackgroundColor = mine ? myMessageBackgroundColor : colors.fillColorNeutral070\n\n return StyleSheet.create({\n message: {\n gap: 8,\n flexDirection: mine ? 'row-reverse' : 'row',\n paddingHorizontal: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n },\n messageContent: {\n flex: 1,\n gap: 4,\n },\n avatar: {\n marginBottom: AVATAR_CONNECTOR_SPACING,\n },\n avatarPlaceholder: {\n width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH, // Same width as avatar\n },\n authorName: {\n fontWeight: platformFontWeightMedium,\n },\n messageBubble: {\n backgroundColor: messageBubbleBackgroundColor,\n borderRadius: 8,\n maxWidth: tabletWidth ? 360 : '80%',\n alignSelf: mine ? 'flex-end' : 'flex-start',\n },\n messageText: {\n paddingVertical: 6,\n paddingHorizontal: 8,\n },\n messageReactions: {\n flexDirection: 'row',\n gap: 4,\n justifyContent: mine ? 'flex-end' : 'flex-start',\n },\n messageReplyCount: {\n alignSelf: mine ? 'flex-end' : 'flex-start',\n },\n messageMeta: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: mine ? 'flex-end' : 'flex-start',\n gap: 4,\n },\n editedText: {\n fontWeight: platformFontWeightMedium,\n },\n pendingIndicatorContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 4,\n },\n errorContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 4,\n },\n errorIcon: {\n color: colors.statusErrorIcon,\n },\n errorText: {\n color: colors.statusErrorText,\n },\n })\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_conversation_messages_jolt_events.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages_jolt_events.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use_conversation_messages_jolt_events.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages_jolt_events.ts"],"names":[],"mappings":"AAmBA,UAAU,KAAK;IACb,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,iCAAiC,CAAC,EAAE,cAAc,EAAE,EAAE,KAAK,QAsF1E"}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { useJoltChannel, useJoltEvent } from './use_jolt';
|
|
2
|
-
import { deleteRecordInPagesData
|
|
2
|
+
import { deleteRecordInPagesData } from '../utils';
|
|
3
3
|
import { useQueryClient } from '@tanstack/react-query';
|
|
4
4
|
import { useCurrentPerson } from './use_current_person';
|
|
5
5
|
import { transformMessageEventDataToMessageResource } from '../utils/jolt/transform_message_event_data_to_message_resource';
|
|
6
6
|
import { getRequestQueryKey } from './use_suspense_api';
|
|
7
|
-
import { transformReactionEventDataToReactionCountResource } from '../utils/jolt/transform_reaction_event_data_to_reaction_count_resource';
|
|
8
7
|
import { getMessagesRequestArgs } from '../utils/request/get_messages';
|
|
9
8
|
import { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache';
|
|
10
|
-
import { isTemporaryMessageId } from './use_message_create_or_update';
|
|
11
9
|
import { completeMessageCreationTracking } from '../utils/performance_tracking';
|
|
12
10
|
import { useApiClient } from './use_api_client';
|
|
11
|
+
import { updateCacheWithMessage, updateCacheWithReaction, getThreadedMessagesQueryKey, } from '../utils/cache/messages_cache';
|
|
13
12
|
export function useConversationMessagesJoltEvents({ conversationId }) {
|
|
14
13
|
const queryClient = useQueryClient();
|
|
15
14
|
const currentPerson = useCurrentPerson();
|
|
@@ -30,40 +29,13 @@ export function useConversationMessagesJoltEvents({ conversationId }) {
|
|
|
30
29
|
completeMessageCreationTracking({ apiClient, idempotentKey: data.idempotent_key });
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
data: prev,
|
|
41
|
-
record: message,
|
|
42
|
-
matchFn: (existingMessage, _record) => {
|
|
43
|
-
return (isTemporaryMessageId(existingMessage.id) &&
|
|
44
|
-
existingMessage.text === message.text &&
|
|
45
|
-
existingMessage.mine);
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
return updateOrCreateRecordInPagesData({
|
|
50
|
-
data: dataAfterTempRemoval,
|
|
51
|
-
record: message,
|
|
52
|
-
processRecord: (record, current) => {
|
|
53
|
-
return { ...current, ...record };
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
return updateRecordInPagesData({
|
|
59
|
-
data: prev,
|
|
60
|
-
record: message,
|
|
61
|
-
processRecord: (record, current) => {
|
|
62
|
-
return { ...current, ...record };
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
});
|
|
32
|
+
// Update the main conversation cache
|
|
33
|
+
updateCacheWithMessage(queryClient, messagesQueryKey, message, e.event);
|
|
34
|
+
// If message has a reply_root_id, also update the threaded cache
|
|
35
|
+
if (data.reply_root_id) {
|
|
36
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(conversationId, data.reply_root_id);
|
|
37
|
+
updateCacheWithMessage(queryClient, threadedMessagesQueryKey, message, e.event);
|
|
38
|
+
}
|
|
67
39
|
};
|
|
68
40
|
const handleMessageDeleted = async (e) => {
|
|
69
41
|
const { data } = e.data;
|
|
@@ -71,42 +43,23 @@ export function useConversationMessagesJoltEvents({ conversationId }) {
|
|
|
71
43
|
data,
|
|
72
44
|
currentPersonId: currentPerson.id,
|
|
73
45
|
});
|
|
46
|
+
// Update the main conversation cache
|
|
74
47
|
queryClient.setQueryData(messagesQueryKey, prev => deleteRecordInPagesData({ data: prev, record: message }));
|
|
48
|
+
// If message has a reply_root_id, also update the threaded cache
|
|
49
|
+
if (data.reply_root_id) {
|
|
50
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(conversationId, data.reply_root_id);
|
|
51
|
+
queryClient.setQueryData(threadedMessagesQueryKey, prev => deleteRecordInPagesData({ data: prev, record: message }));
|
|
52
|
+
}
|
|
75
53
|
};
|
|
76
54
|
const handleReactionJoltEvent = async (e) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let newReactionCounts = reactionCounts.map(reactionCount => {
|
|
86
|
-
if (reactionCount.value === data.value) {
|
|
87
|
-
foundMatch = true;
|
|
88
|
-
return transformReactionEventDataToReactionCountResource({
|
|
89
|
-
data,
|
|
90
|
-
oldData: reactionCount,
|
|
91
|
-
event: e.event,
|
|
92
|
-
currentPersonId: currentPerson.id,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return reactionCount;
|
|
96
|
-
});
|
|
97
|
-
if (!foundMatch) {
|
|
98
|
-
const newReactionCount = transformReactionEventDataToReactionCountResource({
|
|
99
|
-
data,
|
|
100
|
-
event: e.event,
|
|
101
|
-
currentPersonId: currentPerson.id,
|
|
102
|
-
});
|
|
103
|
-
if (newReactionCount?.count) {
|
|
104
|
-
newReactionCounts = [...newReactionCounts, newReactionCount];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return { ...oldMessage, reactionCounts: newReactionCounts };
|
|
108
|
-
},
|
|
109
|
-
}));
|
|
55
|
+
// Update the main conversation cache and capture the reply_root_id if present
|
|
56
|
+
updateCacheWithReaction(queryClient, messagesQueryKey, e, currentPerson.id);
|
|
57
|
+
const replyRootId = e.data.data.reply_root_id;
|
|
58
|
+
// If the message has a reply_root_id, also update the threaded cache
|
|
59
|
+
if (replyRootId) {
|
|
60
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(conversationId, replyRootId);
|
|
61
|
+
updateCacheWithReaction(queryClient, threadedMessagesQueryKey, e, currentPerson.id);
|
|
62
|
+
}
|
|
110
63
|
};
|
|
111
64
|
const handleTypingEvent = async (e) => {
|
|
112
65
|
const { data } = e.data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_conversation_messages_jolt_events.js","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages_jolt_events.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCollection, MessageResource } from '../types'\nimport { useJoltChannel, useJoltEvent } from './use_jolt'\nimport {\n deleteRecordInPagesData,\n updateOrCreateRecordInPagesData,\n updateRecordInPagesData,\n} from '../utils'\nimport { MessageCreatedEvent, MessageDeletedEvent } from '../types/jolt_events/message_events'\nimport { InfiniteData, useQueryClient } from '@tanstack/react-query'\nimport { useCurrentPerson } from './use_current_person'\nimport { transformMessageEventDataToMessageResource } from '../utils/jolt/transform_message_event_data_to_message_resource'\nimport { getRequestQueryKey } from './use_suspense_api'\nimport { JoltReactionEvent, JoltTypingEvent } from '../types/jolt_events'\nimport { transformReactionEventDataToReactionCountResource } from '../utils/jolt/transform_reaction_event_data_to_reaction_count_resource'\nimport { getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'\nimport { isTemporaryMessageId } from './use_message_create_or_update'\nimport { completeMessageCreationTracking } from '../utils/performance_tracking'\nimport { useApiClient } from './use_api_client'\n\ninterface Props {\n conversationId: number\n}\n\nexport function useConversationMessagesJoltEvents({ conversationId }: Props) {\n const queryClient = useQueryClient()\n const currentPerson = useCurrentPerson()\n const joltChannel = useJoltChannel(`chat.conversations.${conversationId}`)\n const messagesRequestArgs = getMessagesRequestArgs({ conversation_id: conversationId })\n const messagesQueryKey = getRequestQueryKey(messagesRequestArgs)\n const apiClient = useApiClient()\n\n const { addTypingEvent, removeTypingEventById, removeAllTypingEventsByAuthorId } =\n useTypingStatusCache(conversationId)\n\n const handleMessageUpdateOrCreate = async (e: MessageCreatedEvent) => {\n const { data } = e.data\n const message = transformMessageEventDataToMessageResource({\n data,\n currentPersonId: currentPerson.id,\n })\n\n if (e.event === 'message.created' && data.author_id) {\n removeAllTypingEventsByAuthorId(data.author_id)\n if (data.idempotent_key && data.author_id === currentPerson.id) {\n completeMessageCreationTracking({ apiClient, idempotentKey: data.idempotent_key })\n }\n }\n\n queryClient.setQueryData<QueryData>(messagesQueryKey, prev => {\n if (e.event === 'message.created') {\n // Before adding the new message, remove any pending temporary messages\n // with matching text to prevent duplicates from race conditions\n let dataAfterTempRemoval = prev\n if (prev && message.text && message.mine) {\n dataAfterTempRemoval = deleteRecordInPagesData({\n data: prev,\n record: message,\n matchFn: (existingMessage, _record) => {\n return (\n isTemporaryMessageId(existingMessage.id) &&\n existingMessage.text === message.text &&\n existingMessage.mine\n )\n },\n })\n }\n\n return updateOrCreateRecordInPagesData({\n data: dataAfterTempRemoval,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n } else {\n return updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n }\n })\n }\n\n const handleMessageDeleted = async (e: MessageDeletedEvent) => {\n const { data } = e.data\n const message = transformMessageEventDataToMessageResource({\n data,\n currentPersonId: currentPerson.id,\n })\n\n queryClient.setQueryData<QueryData>(messagesQueryKey, prev =>\n deleteRecordInPagesData({ data: prev, record: message })\n )\n }\n\n const handleReactionJoltEvent = async (e: JoltReactionEvent) => {\n const { data } = e.data\n const message = { id: data.message_sort_key } as MessageResource\n queryClient.setQueryData<QueryData>(messagesQueryKey, prev =>\n updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, oldMessage) => {\n const reactionCounts = oldMessage.reactionCounts || []\n let foundMatch = false\n let newReactionCounts = reactionCounts.map(reactionCount => {\n if (reactionCount.value === data.value) {\n foundMatch = true\n return transformReactionEventDataToReactionCountResource({\n data,\n oldData: reactionCount,\n event: e.event,\n currentPersonId: currentPerson.id,\n })\n }\n return reactionCount\n })\n\n if (!foundMatch) {\n const newReactionCount = transformReactionEventDataToReactionCountResource({\n data,\n event: e.event,\n currentPersonId: currentPerson.id,\n })\n\n if (newReactionCount?.count) {\n newReactionCounts = [...newReactionCounts, newReactionCount]\n }\n }\n\n return { ...oldMessage, reactionCounts: newReactionCounts }\n },\n })\n )\n }\n\n const handleTypingEvent = async (e: JoltTypingEvent) => {\n const { data } = e.data\n addTypingEvent(data)\n setTimeout(() => removeTypingEventById(data.id), TYPING_TIMEOUT_INTERVAL)\n }\n\n useJoltEvent(joltChannel, 'message.created', handleMessageUpdateOrCreate)\n useJoltEvent(joltChannel, 'message.updated', handleMessageUpdateOrCreate)\n useJoltEvent(joltChannel, 'message.destroyed', handleMessageDeleted)\n useJoltEvent(joltChannel, 'reaction.*', handleReactionJoltEvent)\n useJoltEvent(joltChannel, 'typing.broadcast', handleTypingEvent)\n}\n\ntype QueryData = InfiniteData<ApiCollection<MessageResource>>\n"]}
|
|
1
|
+
{"version":3,"file":"use_conversation_messages_jolt_events.js","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages_jolt_events.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCollection, MessageResource } from '../types'\nimport { useJoltChannel, useJoltEvent } from './use_jolt'\nimport { deleteRecordInPagesData } from '../utils'\nimport { MessageCreatedEvent, MessageDeletedEvent } from '../types/jolt_events/message_events'\nimport { InfiniteData, useQueryClient } from '@tanstack/react-query'\nimport { useCurrentPerson } from './use_current_person'\nimport { transformMessageEventDataToMessageResource } from '../utils/jolt/transform_message_event_data_to_message_resource'\nimport { getRequestQueryKey } from './use_suspense_api'\nimport { JoltReactionEvent, JoltTypingEvent } from '../types/jolt_events'\nimport { getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'\nimport { completeMessageCreationTracking } from '../utils/performance_tracking'\nimport { useApiClient } from './use_api_client'\nimport {\n updateCacheWithMessage,\n updateCacheWithReaction,\n getThreadedMessagesQueryKey,\n} from '../utils/cache/messages_cache'\n\ninterface Props {\n conversationId: number\n}\n\nexport function useConversationMessagesJoltEvents({ conversationId }: Props) {\n const queryClient = useQueryClient()\n const currentPerson = useCurrentPerson()\n const joltChannel = useJoltChannel(`chat.conversations.${conversationId}`)\n const messagesRequestArgs = getMessagesRequestArgs({ conversation_id: conversationId })\n const messagesQueryKey = getRequestQueryKey(messagesRequestArgs)\n const apiClient = useApiClient()\n\n const { addTypingEvent, removeTypingEventById, removeAllTypingEventsByAuthorId } =\n useTypingStatusCache(conversationId)\n\n const handleMessageUpdateOrCreate = async (e: MessageCreatedEvent) => {\n const { data } = e.data\n const message = transformMessageEventDataToMessageResource({\n data,\n currentPersonId: currentPerson.id,\n })\n\n if (e.event === 'message.created' && data.author_id) {\n removeAllTypingEventsByAuthorId(data.author_id)\n if (data.idempotent_key && data.author_id === currentPerson.id) {\n completeMessageCreationTracking({ apiClient, idempotentKey: data.idempotent_key })\n }\n }\n\n // Update the main conversation cache\n updateCacheWithMessage(queryClient, messagesQueryKey, message, e.event)\n\n // If message has a reply_root_id, also update the threaded cache\n if (data.reply_root_id) {\n const threadedMessagesQueryKey = getThreadedMessagesQueryKey(\n conversationId,\n data.reply_root_id\n )\n updateCacheWithMessage(queryClient, threadedMessagesQueryKey, message, e.event)\n }\n }\n\n const handleMessageDeleted = async (e: MessageDeletedEvent) => {\n const { data } = e.data\n const message = transformMessageEventDataToMessageResource({\n data,\n currentPersonId: currentPerson.id,\n })\n\n // Update the main conversation cache\n queryClient.setQueryData<QueryData>(messagesQueryKey, prev =>\n deleteRecordInPagesData({ data: prev, record: message })\n )\n\n // If message has a reply_root_id, also update the threaded cache\n if (data.reply_root_id) {\n const threadedMessagesQueryKey = getThreadedMessagesQueryKey(\n conversationId,\n data.reply_root_id\n )\n queryClient.setQueryData<QueryData>(threadedMessagesQueryKey, prev =>\n deleteRecordInPagesData({ data: prev, record: message })\n )\n }\n }\n\n const handleReactionJoltEvent = async (e: JoltReactionEvent) => {\n // Update the main conversation cache and capture the reply_root_id if present\n updateCacheWithReaction(queryClient, messagesQueryKey, e, currentPerson.id)\n\n const replyRootId = e.data.data.reply_root_id\n\n // If the message has a reply_root_id, also update the threaded cache\n if (replyRootId) {\n const threadedMessagesQueryKey = getThreadedMessagesQueryKey(conversationId, replyRootId)\n updateCacheWithReaction(queryClient, threadedMessagesQueryKey, e, currentPerson.id)\n }\n }\n\n const handleTypingEvent = async (e: JoltTypingEvent) => {\n const { data } = e.data\n addTypingEvent(data)\n setTimeout(() => removeTypingEventById(data.id), TYPING_TIMEOUT_INTERVAL)\n }\n\n useJoltEvent(joltChannel, 'message.created', handleMessageUpdateOrCreate)\n useJoltEvent(joltChannel, 'message.updated', handleMessageUpdateOrCreate)\n useJoltEvent(joltChannel, 'message.destroyed', handleMessageDeleted)\n useJoltEvent(joltChannel, 'reaction.*', handleReactionJoltEvent)\n useJoltEvent(joltChannel, 'typing.broadcast', handleTypingEvent)\n}\n\ntype QueryData = InfiniteData<ApiCollection<MessageResource>>\n"]}
|
|
@@ -11,7 +11,5 @@ export declare function useMessageCreateOrUpdate({ conversationId, message, repl
|
|
|
11
11
|
}, {
|
|
12
12
|
message: MessageResource;
|
|
13
13
|
}>;
|
|
14
|
-
export declare function isTemporaryMessageId(messageId?: string | null): boolean;
|
|
15
|
-
export declare function isNewMessage(message?: MessageResource): boolean;
|
|
16
14
|
export {};
|
|
17
15
|
//# sourceMappingURL=use_message_create_or_update.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_message_create_or_update.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,WAAW,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAQtE,OAAO,EAAE,uCAAuC,EAAE,MAAM,gEAAgE,CAAA;
|
|
1
|
+
{"version":3,"file":"use_message_create_or_update.d.ts","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,WAAW,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAQtE,OAAO,EAAE,uCAAuC,EAAE,MAAM,gEAAgE,CAAA;AAOxH,UAAU,KAAK;IACb,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,wBAAgB,wBAAwB,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK;UAqD5E,MAAM;kBACE,uCAAuC,EAAE;;;GA8E5D"}
|
|
@@ -7,6 +7,7 @@ import { useCurrentPerson } from './use_current_person';
|
|
|
7
7
|
import { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message';
|
|
8
8
|
import { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message';
|
|
9
9
|
import { startMessageCreationTracking } from '../utils/performance_tracking';
|
|
10
|
+
import { isNewMessage } from '../utils/cache/messages_cache';
|
|
10
11
|
export function useMessageCreateOrUpdate({ conversationId, message, replyRootId }) {
|
|
11
12
|
const messageId = message?.id || null;
|
|
12
13
|
const isEditing = !isNewMessage(message);
|
|
@@ -62,6 +63,7 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
62
63
|
text,
|
|
63
64
|
attachments,
|
|
64
65
|
currentPerson,
|
|
66
|
+
replyRootId,
|
|
65
67
|
});
|
|
66
68
|
return { message: optimisticMessage };
|
|
67
69
|
},
|
|
@@ -70,7 +72,10 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
70
72
|
Haptic.notificationError();
|
|
71
73
|
// Add error to the optimistic message from the cache on error
|
|
72
74
|
if (optimisticMessage) {
|
|
73
|
-
const queryKey = getMessagesQueryKey({
|
|
75
|
+
const queryKey = getMessagesQueryKey({
|
|
76
|
+
conversation_id: conversationId,
|
|
77
|
+
reply_root_id: replyRootId,
|
|
78
|
+
});
|
|
74
79
|
chatQueryClient.setQueryData(queryKey, (data) => updateRecordInPagesData({
|
|
75
80
|
data,
|
|
76
81
|
record: optimisticMessage,
|
|
@@ -85,7 +90,10 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
85
90
|
onSuccess: (result, variables, context) => {
|
|
86
91
|
const { message: optimisticMessage } = context || {};
|
|
87
92
|
const updatedMessage = result.data;
|
|
88
|
-
const queryKey = getMessagesQueryKey({
|
|
93
|
+
const queryKey = getMessagesQueryKey({
|
|
94
|
+
conversation_id: conversationId,
|
|
95
|
+
reply_root_id: replyRootId,
|
|
96
|
+
});
|
|
89
97
|
// First remove the optimistic message if it exists
|
|
90
98
|
if (optimisticMessage) {
|
|
91
99
|
chatQueryClient.setQueryData(queryKey, data => deleteRecordInPagesData({
|
|
@@ -102,12 +110,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
|
|
|
102
110
|
});
|
|
103
111
|
return mutation;
|
|
104
112
|
}
|
|
105
|
-
export function isTemporaryMessageId(messageId) {
|
|
106
|
-
return !!messageId && messageId.endsWith('-temp');
|
|
107
|
-
}
|
|
108
|
-
export function isNewMessage(message) {
|
|
109
|
-
return !message?.id || isTemporaryMessageId(message.id);
|
|
110
|
-
}
|
|
111
113
|
/**
|
|
112
114
|
* Generate a random UUID (v4) for idempotent keys.
|
|
113
115
|
* Uses Math.random, which is not cryptographically secure.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_message_create_or_update.js","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"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","sourcesContent":["import { InfiniteData, useMutation } from '@tanstack/react-query'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { useApiClient } from './use_api_client'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { chatQueryClient } from '../contexts/api_provider'\nimport {\n deleteRecordInPagesData,\n Haptic,\n updateOrCreateRecordInPagesData,\n updateRecordInPagesData,\n} from '../utils'\nimport { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'\nimport { useCurrentPerson } from './use_current_person'\nimport { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message'\nimport { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message'\nimport { startMessageCreationTracking } from '../utils/performance_tracking'\n\ninterface Props {\n conversationId: number\n message?: MessageResource\n replyRootId?: string | null\n}\n\nexport function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {\n const messageId = message?.id || null\n const isEditing = !isNewMessage(message)\n const apiClient = useApiClient()\n const currentPerson = useCurrentPerson()\n\n const mutation = useMutation({\n mutationFn: ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n const requestParams = getMessagesRequestArgs({ conversation_id: conversationId })\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n let attributes: any = {\n text,\n ...(attachments ? { attachments } : {}),\n ...(replyRootId ? { reply_root: { id: replyRootId } } : {}),\n }\n if (!isEditing) {\n const idempotentKey = insecureUUID()\n attributes.idempotent_key = idempotentKey\n startMessageCreationTracking(idempotentKey)\n }\n const data = {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes,\n },\n fields: fieldsWithValueJoined,\n }\n\n if (isEditing) {\n return apiClient.chat.patch<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages/${messageId}`,\n data,\n })\n } else {\n return apiClient.chat.post<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages`,\n data,\n })\n }\n },\n onMutate: async ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n if (message && isEditing) {\n const optimisticMessage = optimisticallyUpdateMessage({\n conversationId,\n message,\n text,\n })\n\n return { message: optimisticMessage }\n }\n\n const optimisticMessage = optimisticallyCreateMessage({\n conversationId,\n message,\n text,\n attachments,\n currentPerson,\n })\n\n return { message: optimisticMessage }\n },\n onError: (error, variables, context) => {\n const { message: optimisticMessage } = context || {}\n Haptic.notificationError()\n\n // Add error to the optimistic message from the cache on error\n if (optimisticMessage) {\n const queryKey = getMessagesQueryKey({ conversation_id: conversationId })\n chatQueryClient.setQueryData(\n queryKey,\n (data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>\n updateRecordInPagesData({\n data,\n record: optimisticMessage,\n processRecord: (_next, prev) => ({\n ...prev,\n error: error.message || 'Failed to send message',\n pending: false, // Mark as no longer pending\n }),\n })\n )\n }\n },\n onSuccess: (result: ApiResource<MessageResource>, variables, context) => {\n const { message: optimisticMessage } = context || {}\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({ conversation_id: conversationId })\n\n // First remove the optimistic message if it exists\n if (optimisticMessage) {\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n deleteRecordInPagesData({\n data,\n record: optimisticMessage,\n })\n )\n }\n\n // Then add the real message\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: updatedMessage,\n })\n )\n },\n })\n\n return mutation\n}\n\nexport function isTemporaryMessageId(messageId?: string | null): boolean {\n return !!messageId && messageId.endsWith('-temp')\n}\nexport function isNewMessage(message?: MessageResource): boolean {\n return !message?.id || isTemporaryMessageId(message.id)\n}\n\n/**\n * Generate a random UUID (v4) for idempotent keys.\n * Uses Math.random, which is not cryptographically secure.\n * An actual crypto library requires native dependencies.\n * This is OK for now since idempotent keys are not security-sensitive\n * or need to be guaranteed unique.\n * They are short lived and we use it in combination with the message's creator_id so\n * their impact is scoped only the current user.\n */\nfunction insecureUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx'\n .replace(/x/g, () => Math.floor(Math.random() * 16).toString(16))\n .replace(/N/g, () => (Math.floor(Math.random() * 4) + 8).toString(16))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use_message_create_or_update.js","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"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","sourcesContent":["import { InfiniteData, useMutation } from '@tanstack/react-query'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { useApiClient } from './use_api_client'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { chatQueryClient } from '../contexts/api_provider'\nimport {\n deleteRecordInPagesData,\n Haptic,\n updateOrCreateRecordInPagesData,\n updateRecordInPagesData,\n} from '../utils'\nimport { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'\nimport { useCurrentPerson } from './use_current_person'\nimport { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message'\nimport { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message'\nimport { startMessageCreationTracking } from '../utils/performance_tracking'\nimport { isNewMessage } from '../utils/cache/messages_cache'\n\ninterface Props {\n conversationId: number\n message?: MessageResource\n replyRootId?: string | null\n}\n\nexport function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {\n const messageId = message?.id || null\n const isEditing = !isNewMessage(message)\n const apiClient = useApiClient()\n const currentPerson = useCurrentPerson()\n\n const mutation = useMutation({\n mutationFn: ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n const requestParams = getMessagesRequestArgs({ conversation_id: conversationId })\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n let attributes: any = {\n text,\n ...(attachments ? { attachments } : {}),\n ...(replyRootId ? { reply_root: { id: replyRootId } } : {}),\n }\n if (!isEditing) {\n const idempotentKey = insecureUUID()\n attributes.idempotent_key = idempotentKey\n startMessageCreationTracking(idempotentKey)\n }\n const data = {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes,\n },\n fields: fieldsWithValueJoined,\n }\n\n if (isEditing) {\n return apiClient.chat.patch<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages/${messageId}`,\n data,\n })\n } else {\n return apiClient.chat.post<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages`,\n data,\n })\n }\n },\n onMutate: async ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n if (message && isEditing) {\n const optimisticMessage = optimisticallyUpdateMessage({\n conversationId,\n message,\n text,\n })\n\n return { message: optimisticMessage }\n }\n\n const optimisticMessage = optimisticallyCreateMessage({\n conversationId,\n message,\n text,\n attachments,\n currentPerson,\n replyRootId,\n })\n\n return { message: optimisticMessage }\n },\n onError: (error, variables, context) => {\n const { message: optimisticMessage } = context || {}\n Haptic.notificationError()\n\n // Add error to the optimistic message from the cache on error\n if (optimisticMessage) {\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n chatQueryClient.setQueryData(\n queryKey,\n (data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>\n updateRecordInPagesData({\n data,\n record: optimisticMessage,\n processRecord: (_next, prev) => ({\n ...prev,\n error: error.message || 'Failed to send message',\n pending: false, // Mark as no longer pending\n }),\n })\n )\n }\n },\n onSuccess: (result: ApiResource<MessageResource>, variables, context) => {\n const { message: optimisticMessage } = context || {}\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n // First remove the optimistic message if it exists\n if (optimisticMessage) {\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n deleteRecordInPagesData({\n data,\n record: optimisticMessage,\n })\n )\n }\n\n // Then add the real message\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: updatedMessage,\n })\n )\n },\n })\n\n return mutation\n}\n\n/**\n * Generate a random UUID (v4) for idempotent keys.\n * Uses Math.random, which is not cryptographically secure.\n * An actual crypto library requires native dependencies.\n * This is OK for now since idempotent keys are not security-sensitive\n * or need to be guaranteed unique.\n * They are short lived and we use it in combination with the message's creator_id so\n * their impact is scoped only the current user.\n */\nfunction insecureUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx'\n .replace(/x/g, () => Math.floor(Math.random() * 16).toString(16))\n .replace(/N/g, () => (Math.floor(Math.random() * 4) + 8).toString(16))\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reaction_events.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAA;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAE7D,UAAU,qBAAsB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,gBAAgB,EAAE,MAAM,CAAA;QACxB,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;QACrC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,kBAAkB,CAAA;IACzB,IAAI,EAAE,qBAAqB,CAAA;CAC5B;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,oBAAoB,CAAA;IAC3B,IAAI,EAAE,qBAAqB,CAAA;CAC5B"}
|
|
1
|
+
{"version":3,"file":"reaction_events.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAA;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAE7D,UAAU,qBAAsB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,gBAAgB,EAAE,MAAM,CAAA;QACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC7B,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;QACrC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,kBAAkB,CAAA;IACzB,IAAI,EAAE,qBAAqB,CAAA;CAC5B;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,oBAAoB,CAAA;IAC3B,IAAI,EAAE,qBAAqB,CAAA;CAC5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reaction_events.js","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"","sourcesContent":["import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'\nimport { ReactionCountResource } from '../resources/reaction'\n\ninterface BaseReactionEventData extends Record<string, unknown> {\n data: {\n author_id: number\n conversation_id: number\n message_sort_key: string\n created_at: string\n organization_id: number\n value: ReactionCountResource['value']\n author_name?: string\n author_avatar?: string\n count: number\n }\n}\n\nexport interface ReactionCreatedEvent extends CustomMessage {\n event: 'reaction.created'\n data: BaseReactionEventData\n}\n\nexport interface ReactionDeletedEvent extends CustomMessage {\n event: 'reaction.destroyed'\n data: BaseReactionEventData\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reaction_events.js","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"","sourcesContent":["import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'\nimport { ReactionCountResource } from '../resources/reaction'\n\ninterface BaseReactionEventData extends Record<string, unknown> {\n data: {\n author_id: number\n conversation_id: number\n message_sort_key: string\n reply_root_id?: string | null\n created_at: string\n organization_id: number\n value: ReactionCountResource['value']\n author_name?: string\n author_avatar?: string\n count: number\n }\n}\n\nexport interface ReactionCreatedEvent extends CustomMessage {\n event: 'reaction.created'\n data: BaseReactionEventData\n}\n\nexport interface ReactionDeletedEvent extends CustomMessage {\n event: 'reaction.destroyed'\n data: BaseReactionEventData\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { MessageResource } from '../../types';
|
|
3
|
+
import { JoltReactionEvent } from '../../types/jolt_events';
|
|
4
|
+
export declare function updateCacheWithMessage(queryClient: QueryClient, queryKey: unknown[], message: MessageResource, event: 'message.created' | 'message.updated'): void;
|
|
5
|
+
export declare function updateCacheWithReaction(queryClient: QueryClient, queryKey: unknown[], event: JoltReactionEvent, currentPersonId: number): void;
|
|
6
|
+
export declare function isTemporaryMessageId(messageId?: string | null): boolean;
|
|
7
|
+
export declare function isNewMessage(message?: MessageResource): boolean;
|
|
8
|
+
export declare function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string): import("../../hooks/use_suspense_api").RequestQueryKey;
|
|
9
|
+
//# sourceMappingURL=messages_cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages_cache.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAiB,eAAe,EAAE,MAAM,aAAa,CAAA;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAK3D,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,QAsC7C;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,iBAAiB,EACxB,eAAe,EAAE,MAAM,QAuCxB;AAGD,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAEvE;AACD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,0DAMtF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { deleteRecordInPagesData } from './page_mutations';
|
|
2
|
+
import { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations';
|
|
3
|
+
import { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource';
|
|
4
|
+
import { getMessagesRequestArgs } from '../request/get_messages';
|
|
5
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api';
|
|
6
|
+
export function updateCacheWithMessage(queryClient, queryKey, message, event) {
|
|
7
|
+
queryClient.setQueryData(queryKey, prev => {
|
|
8
|
+
if (event === 'message.created') {
|
|
9
|
+
// Before adding the new message, remove any pending temporary messages
|
|
10
|
+
// with matching text to prevent duplicates from race conditions
|
|
11
|
+
let dataAfterTempRemoval = prev;
|
|
12
|
+
if (prev && message.text && message.mine) {
|
|
13
|
+
dataAfterTempRemoval = deleteRecordInPagesData({
|
|
14
|
+
data: prev,
|
|
15
|
+
record: message,
|
|
16
|
+
matchFn: (existingMessage, _record) => {
|
|
17
|
+
return (isTemporaryMessageId(existingMessage.id) &&
|
|
18
|
+
existingMessage.text === message.text &&
|
|
19
|
+
existingMessage.mine);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return updateOrCreateRecordInPagesData({
|
|
24
|
+
data: dataAfterTempRemoval,
|
|
25
|
+
record: message,
|
|
26
|
+
processRecord: (record, current) => {
|
|
27
|
+
return { ...current, ...record };
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return updateRecordInPagesData({
|
|
33
|
+
data: prev,
|
|
34
|
+
record: message,
|
|
35
|
+
processRecord: (record, current) => {
|
|
36
|
+
return { ...current, ...record };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function updateCacheWithReaction(queryClient, queryKey, event, currentPersonId) {
|
|
43
|
+
const message = { id: event.data.data.message_sort_key };
|
|
44
|
+
queryClient.setQueryData(queryKey, prev => updateRecordInPagesData({
|
|
45
|
+
data: prev,
|
|
46
|
+
record: message,
|
|
47
|
+
processRecord: (record, oldMessage) => {
|
|
48
|
+
const reactionCounts = oldMessage.reactionCounts || [];
|
|
49
|
+
let foundMatch = false;
|
|
50
|
+
let newReactionCounts = reactionCounts.map(reactionCount => {
|
|
51
|
+
if (reactionCount.value === event.data.data.value) {
|
|
52
|
+
foundMatch = true;
|
|
53
|
+
return transformReactionEventDataToReactionCountResource({
|
|
54
|
+
data: event.data.data,
|
|
55
|
+
oldData: reactionCount,
|
|
56
|
+
event: event.event,
|
|
57
|
+
currentPersonId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return reactionCount;
|
|
61
|
+
});
|
|
62
|
+
if (!foundMatch) {
|
|
63
|
+
const newReactionCount = transformReactionEventDataToReactionCountResource({
|
|
64
|
+
data: event.data.data,
|
|
65
|
+
event: event.event,
|
|
66
|
+
currentPersonId,
|
|
67
|
+
});
|
|
68
|
+
if (newReactionCount?.count) {
|
|
69
|
+
newReactionCounts = [...newReactionCounts, newReactionCount];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { ...oldMessage, reactionCounts: newReactionCounts };
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
export function isTemporaryMessageId(messageId) {
|
|
77
|
+
return !!messageId && messageId.endsWith('-temp');
|
|
78
|
+
}
|
|
79
|
+
export function isNewMessage(message) {
|
|
80
|
+
return !message?.id || isTemporaryMessageId(message.id);
|
|
81
|
+
}
|
|
82
|
+
export function getThreadedMessagesQueryKey(conversationId, replyRootId) {
|
|
83
|
+
const requestArgs = getMessagesRequestArgs({
|
|
84
|
+
conversation_id: conversationId,
|
|
85
|
+
reply_root_id: replyRootId,
|
|
86
|
+
});
|
|
87
|
+
return getRequestQueryKey(requestArgs);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=messages_cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages_cache.js","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"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","sourcesContent":["import { InfiniteData, QueryClient } from '@tanstack/react-query'\nimport { ApiCollection, MessageResource } from '../../types'\nimport { deleteRecordInPagesData } from './page_mutations'\nimport { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations'\nimport { JoltReactionEvent } from '../../types/jolt_events'\nimport { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource'\nimport { getMessagesRequestArgs } from '../request/get_messages'\nimport { getRequestQueryKey } from '../../hooks/use_suspense_api'\n\nexport function updateCacheWithMessage(\n queryClient: QueryClient,\n queryKey: unknown[],\n message: MessageResource,\n event: 'message.created' | 'message.updated'\n) {\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev => {\n if (event === 'message.created') {\n // Before adding the new message, remove any pending temporary messages\n // with matching text to prevent duplicates from race conditions\n let dataAfterTempRemoval = prev\n if (prev && message.text && message.mine) {\n dataAfterTempRemoval = deleteRecordInPagesData({\n data: prev,\n record: message,\n matchFn: (existingMessage, _record) => {\n return (\n isTemporaryMessageId(existingMessage.id) &&\n existingMessage.text === message.text &&\n existingMessage.mine\n )\n },\n })\n }\n\n return updateOrCreateRecordInPagesData({\n data: dataAfterTempRemoval,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n } else {\n return updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n }\n })\n}\n\nexport function updateCacheWithReaction(\n queryClient: QueryClient,\n queryKey: unknown[],\n event: JoltReactionEvent,\n currentPersonId: number\n) {\n const message = { id: event.data.data.message_sort_key } as MessageResource\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev =>\n updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, oldMessage) => {\n const reactionCounts = oldMessage.reactionCounts || []\n let foundMatch = false\n let newReactionCounts = reactionCounts.map(reactionCount => {\n if (reactionCount.value === event.data.data.value) {\n foundMatch = true\n return transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n oldData: reactionCount,\n event: event.event,\n currentPersonId,\n })\n }\n return reactionCount\n })\n\n if (!foundMatch) {\n const newReactionCount = transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n event: event.event,\n currentPersonId,\n })\n\n if (newReactionCount?.count) {\n newReactionCounts = [...newReactionCounts, newReactionCount]\n }\n }\n\n return { ...oldMessage, reactionCounts: newReactionCounts }\n },\n })\n )\n}\n\ntype MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>\nexport function isTemporaryMessageId(messageId?: string | null): boolean {\n return !!messageId && messageId.endsWith('-temp')\n}\nexport function isNewMessage(message?: MessageResource): boolean {\n return !message?.id || isTemporaryMessageId(message.id)\n}\n\nexport function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string) {\n const requestArgs = getMessagesRequestArgs({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n return getRequestQueryKey(requestArgs)\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CurrentPersonResource, MessageResource } from '../../types';
|
|
2
2
|
import { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create';
|
|
3
|
-
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, }: {
|
|
3
|
+
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, }: {
|
|
4
4
|
conversationId: number;
|
|
5
5
|
text: string;
|
|
6
6
|
attachments?: DenormalizedAttachmentResourceForCreate[];
|
|
7
7
|
currentPerson: CurrentPersonResource;
|
|
8
8
|
message?: MessageResource;
|
|
9
|
+
replyRootId?: string | null;
|
|
9
10
|
}): MessageResource;
|
|
10
11
|
//# sourceMappingURL=optimistically_create_message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAI3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAI3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,GACZ,EAAE;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,uCAAuC,EAAE,CAAA;IACvD,aAAa,EAAE,qBAAqB,CAAA;IACpC,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,mBA+CA"}
|
|
@@ -3,7 +3,7 @@ import { chatQueryClient } from '../../contexts/api_provider';
|
|
|
3
3
|
import { updateOrCreateRecordInPagesData } from './page_mutations';
|
|
4
4
|
import { convertAttachmentsForCreate } from '../convert_attachments_for_create';
|
|
5
5
|
import { generatePlaceholderUlid } from '../generate_placeholder_ulid';
|
|
6
|
-
export function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, }) {
|
|
6
|
+
export function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, }) {
|
|
7
7
|
const id = message?.id || generateTempMessageId();
|
|
8
8
|
// Convert attachments to denormalized format for optimistic UI
|
|
9
9
|
const decoratedAttachments = convertAttachmentsForCreate(attachments || []);
|
|
@@ -28,9 +28,12 @@ export function optimisticallyCreateMessage({ conversationId, text, attachments,
|
|
|
28
28
|
lastInGroup: true,
|
|
29
29
|
pending: true,
|
|
30
30
|
replyCount: 0,
|
|
31
|
-
replyRootId: null,
|
|
31
|
+
replyRootId: replyRootId || null,
|
|
32
32
|
};
|
|
33
|
-
const queryKey = getMessagesQueryKey({
|
|
33
|
+
const queryKey = getMessagesQueryKey({
|
|
34
|
+
conversation_id: conversationId,
|
|
35
|
+
reply_root_id: replyRootId,
|
|
36
|
+
});
|
|
34
37
|
chatQueryClient.setQueryData(queryKey, data => updateOrCreateRecordInPagesData({
|
|
35
38
|
data,
|
|
36
39
|
record: optimisticMessage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.js","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAA;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,MAAM,UAAU,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.js","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAA;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,MAAM,UAAU,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,GAQZ;IACC,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,qBAAqB,EAAE,CAAA;IAEjD,+DAA+D;IAC/D,MAAM,oBAAoB,GAAqC,2BAA2B,CACxF,WAAW,IAAI,EAAE,CAClB,CAAA;IAED,+BAA+B;IAC/B,MAAM,iBAAiB,GAAoB;QACzC,GAAG,OAAO;QACV,IAAI,EAAE,SAAS;QACf,EAAE;QACF,IAAI;QACJ,IAAI,EAAE,EAAE,EAAE,2BAA2B;QACrC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,IAAI;QACf,YAAY,EAAE,IAAI;QAClB,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,oBAAoB;QACjC,oBAAoB,EAAE,WAAW,IAAI,EAAE;QACvC,MAAM,EAAE,aAAa;QACrB,cAAc,EAAE,EAAE;QAClB,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,IAAI;QAChB,sBAAsB,EAAE,IAAI;QAC5B,WAAW,EAAE,IAAI;QACjB,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,WAAW,IAAI,IAAI;KACjC,CAAA;IAID,MAAM,QAAQ,GAAG,mBAAmB,CAAC;QACnC,eAAe,EAAE,cAAc;QAC/B,aAAa,EAAE,WAAW;KAC3B,CAAC,CAAA;IAEF,eAAe,CAAC,YAAY,CAAY,QAAQ,EAAE,IAAI,CAAC,EAAE,CACvD,+BAA+B,CAAC;QAC9B,IAAI;QACJ,MAAM,EAAE,iBAAoC;KAC7C,CAAC,CACH,CAAA;IAED,OAAO,iBAAiB,CAAA;AAC1B,CAAC;AAED,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACjC,0FAA0F;IAC1F,sEAAsE;IACtE,OAAO,GAAG,uBAAuB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,OAAO,CAAA;AAC9D,CAAC,CAAA","sourcesContent":["import { InfiniteData } from '@tanstack/react-query'\nimport { ApiCollection, CurrentPersonResource, MessageResource } from '../../types'\nimport { getMessagesQueryKey } from '../../utils/request/get_messages'\nimport { chatQueryClient } from '../../contexts/api_provider'\nimport { updateOrCreateRecordInPagesData } from './page_mutations'\nimport { convertAttachmentsForCreate } from '../convert_attachments_for_create'\nimport { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { generatePlaceholderUlid } from '../generate_placeholder_ulid'\n\nexport function optimisticallyCreateMessage({\n conversationId,\n text,\n attachments,\n currentPerson,\n message,\n replyRootId,\n}: {\n conversationId: number\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n currentPerson: CurrentPersonResource\n message?: MessageResource\n replyRootId?: string | null\n}) {\n const id = message?.id || generateTempMessageId()\n\n // Convert attachments to denormalized format for optimistic UI\n const decoratedAttachments: DenormalizedAttachmentResource[] = convertAttachmentsForCreate(\n attachments || []\n )\n\n // Create an optimistic message\n const optimisticMessage: MessageResource = {\n ...message,\n type: 'Message',\n id,\n text,\n html: '', // Will be filled by server\n createdAt: new Date().toISOString(),\n deletedAt: null,\n textEditedAt: null,\n mine: true,\n attachments: decoratedAttachments,\n attachmentsForCreate: attachments || [],\n author: currentPerson,\n reactionCounts: [],\n renderAuthor: false,\n renderTime: true,\n myLatestInConversation: true,\n lastInGroup: true,\n pending: true,\n replyCount: 0,\n replyRootId: replyRootId || null,\n }\n\n // Add the optimistic message to the cache\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: optimisticMessage as MessageResource,\n })\n )\n\n return optimisticMessage\n}\n\nconst generateTempMessageId = () => {\n // Put it 5 seconds in the future to account for server lag of previously created messages\n // that are still pending. This gets overwritten by the server anyway.\n return `${generatePlaceholderUlid({ offsetMs: 5000 })}-temp`\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.18.0-rc.
|
|
3
|
+
"version": "3.18.0-rc.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"react-native-url-polyfill": "^2.0.0",
|
|
59
59
|
"typescript": "<5.6.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "6d7c028cd363cb5e85d262ff1e8f9df72f1cd9e7"
|
|
62
62
|
}
|
|
@@ -22,11 +22,12 @@ import {
|
|
|
22
22
|
import Animated from 'react-native-reanimated'
|
|
23
23
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
|
|
24
24
|
import { MessageReadReceipts } from './message_read_receipts'
|
|
25
|
-
import {
|
|
25
|
+
import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
26
26
|
import { Haptic } from '../../utils/native_adapters'
|
|
27
27
|
import { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from './reply_connectors'
|
|
28
28
|
import { pluralize } from '../../utils'
|
|
29
29
|
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
30
|
+
import { isNewMessage } from '../../utils/cache/messages_cache'
|
|
30
31
|
|
|
31
32
|
/** Message
|
|
32
33
|
* Component for display of a message within a conversation list
|
|
@@ -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.
|
|
@@ -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({
|