@planningcenter/chat-react-native 3.38.0-rc.8 → 3.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/jump_to_bottom_button.d.ts +1 -2
- package/build/components/conversation/jump_to_bottom_button.d.ts.map +1 -1
- package/build/components/conversation/jump_to_bottom_button.js +7 -39
- package/build/components/conversation/jump_to_bottom_button.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.d.ts +2 -1
- package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/contexts/conversation_context.d.ts +1 -8
- package/build/contexts/conversation_context.d.ts.map +1 -1
- package/build/contexts/conversation_context.js +3 -21
- package/build/contexts/conversation_context.js.map +1 -1
- package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
- package/build/hooks/groups/use_group_chat_conversation_payload.js +1 -0
- package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +6 -15
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +9 -62
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +4 -4
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_conversations_actions.d.ts +0 -5
- package/build/hooks/use_conversations_actions.d.ts.map +1 -1
- package/build/hooks/use_conversations_actions.js +0 -12
- package/build/hooks/use_conversations_actions.js.map +1 -1
- package/build/hooks/use_features.d.ts +0 -1
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +0 -1
- package/build/hooks/use_features.js.map +1 -1
- package/build/hooks/use_mark_latest_message_read.d.ts +1 -1
- package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
- package/build/hooks/use_mark_latest_message_read.js +1 -17
- package/build/hooks/use_mark_latest_message_read.js.map +1 -1
- package/build/hooks/use_suspense_api.d.ts +0 -1
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +1 -1
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/screens/conversation_filter_recipients/components/header_row.d.ts.map +1 -1
- package/build/screens/conversation_filter_recipients/components/header_row.js +3 -2
- package/build/screens/conversation_filter_recipients/components/header_row.js.map +1 -1
- package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.d.ts.map +1 -1
- package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js +47 -18
- package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js.map +1 -1
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +2 -1
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +23 -26
- package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
- package/build/screens/conversation_filter_recipients/types.d.ts +1 -1
- package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
- package/build/screens/conversation_filter_recipients/types.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +0 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +48 -95
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts +1 -1
- package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/components/recipient_link_row.js +3 -3
- package/build/screens/conversation_select_recipients/components/recipient_link_row.js.map +1 -1
- package/build/screens/conversation_select_recipients/components/team_recipient_row.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/components/team_recipient_row.js +1 -1
- package/build/screens/conversation_select_recipients/components/team_recipient_row.js.map +1 -1
- package/build/utils/cache/messages_cache.d.ts +0 -1
- package/build/utils/cache/messages_cache.d.ts.map +1 -1
- package/build/utils/cache/messages_cache.js +0 -4
- package/build/utils/cache/messages_cache.js.map +1 -1
- package/build/utils/group_messages.d.ts +2 -9
- package/build/utils/group_messages.d.ts.map +1 -1
- package/build/utils/group_messages.js +1 -20
- package/build/utils/group_messages.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/hooks/use_group_chat_conversation_payload.test.tsx +50 -0
- package/src/components/conversation/jump_to_bottom_button.tsx +8 -57
- package/src/components/conversation/reply_shadow_message.tsx +1 -1
- package/src/contexts/conversation_context.tsx +2 -30
- package/src/hooks/groups/use_group_chat_conversation_payload.ts +1 -0
- package/src/hooks/use_conversation_messages.ts +20 -120
- package/src/hooks/use_conversation_messages_jolt_events.ts +3 -4
- package/src/hooks/use_conversations_actions.ts +0 -15
- package/src/hooks/use_features.ts +0 -1
- package/src/hooks/use_mark_latest_message_read.ts +2 -16
- package/src/hooks/use_suspense_api.ts +1 -1
- package/src/screens/conversation_filter_recipients/components/header_row.tsx +3 -2
- package/src/screens/conversation_filter_recipients/hooks/__tests__/use_service_types_with_teams.test.ts +108 -0
- package/src/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.tsx +46 -19
- package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +31 -29
- package/src/screens/conversation_filter_recipients/types.tsx +1 -1
- package/src/screens/conversation_screen.tsx +76 -184
- package/src/screens/conversation_select_recipients/components/recipient_link_row.tsx +6 -4
- package/src/screens/conversation_select_recipients/components/team_recipient_row.tsx +2 -1
- package/src/utils/__tests__/group_messages.test.ts +0 -71
- package/src/utils/cache/messages_cache.ts +0 -5
- package/src/utils/group_messages.ts +2 -42
- package/build/components/conversation/unread_divider.d.ts +0 -6
- package/build/components/conversation/unread_divider.d.ts.map +0 -1
- package/build/components/conversation/unread_divider.js +0 -59
- package/build/components/conversation/unread_divider.js.map +0 -1
- package/build/hooks/use_flat_list_viewability.d.ts +0 -20
- package/build/hooks/use_flat_list_viewability.d.ts.map +0 -1
- package/build/hooks/use_flat_list_viewability.js +0 -30
- package/build/hooks/use_flat_list_viewability.js.map +0 -1
- package/build/hooks/use_jump_to_bottom_action.d.ts +0 -9
- package/build/hooks/use_jump_to_bottom_action.d.ts.map +0 -1
- package/build/hooks/use_jump_to_bottom_action.js +0 -62
- package/build/hooks/use_jump_to_bottom_action.js.map +0 -1
- package/build/hooks/use_jump_to_unread_anchor.d.ts +0 -20
- package/build/hooks/use_jump_to_unread_anchor.d.ts.map +0 -1
- package/build/hooks/use_jump_to_unread_anchor.js +0 -53
- package/build/hooks/use_jump_to_unread_anchor.js.map +0 -1
- package/build/hooks/use_jump_to_unread_gates.d.ts +0 -5
- package/build/hooks/use_jump_to_unread_gates.d.ts.map +0 -1
- package/build/hooks/use_jump_to_unread_gates.js +0 -10
- package/build/hooks/use_jump_to_unread_gates.js.map +0 -1
- package/build/hooks/use_scroll_tracking.d.ts +0 -13
- package/build/hooks/use_scroll_tracking.d.ts.map +0 -1
- package/build/hooks/use_scroll_tracking.js +0 -45
- package/build/hooks/use_scroll_tracking.js.map +0 -1
- package/build/hooks/use_track_highest_seen_message.d.ts +0 -4
- package/build/hooks/use_track_highest_seen_message.d.ts.map +0 -1
- package/build/hooks/use_track_highest_seen_message.js +0 -35
- package/build/hooks/use_track_highest_seen_message.js.map +0 -1
- package/build/utils/conversation_messages.d.ts +0 -10
- package/build/utils/conversation_messages.d.ts.map +0 -1
- package/build/utils/conversation_messages.js +0 -22
- package/build/utils/conversation_messages.js.map +0 -1
- package/build/utils/highest_seen_tracker.d.ts +0 -12
- package/build/utils/highest_seen_tracker.d.ts.map +0 -1
- package/build/utils/highest_seen_tracker.js +0 -37
- package/build/utils/highest_seen_tracker.js.map +0 -1
- package/build/utils/message_viewability.d.ts +0 -24
- package/build/utils/message_viewability.d.ts.map +0 -1
- package/build/utils/message_viewability.js +0 -29
- package/build/utils/message_viewability.js.map +0 -1
- package/build/utils/unread_divider_helpers.d.ts +0 -18
- package/build/utils/unread_divider_helpers.d.ts.map +0 -1
- package/build/utils/unread_divider_helpers.js +0 -13
- package/build/utils/unread_divider_helpers.js.map +0 -1
- package/src/__tests__/hooks/use_conversation_messages.test.tsx +0 -109
- package/src/__tests__/hooks/use_mark_latest_message_read.test.tsx +0 -154
- package/src/__tests__/utils/cache/messages_cache.test.ts +0 -54
- package/src/components/conversation/unread_divider.tsx +0 -90
- package/src/hooks/use_flat_list_viewability.ts +0 -50
- package/src/hooks/use_jump_to_bottom_action.ts +0 -75
- package/src/hooks/use_jump_to_unread_anchor.ts +0 -68
- package/src/hooks/use_jump_to_unread_gates.ts +0 -10
- package/src/hooks/use_scroll_tracking.ts +0 -64
- package/src/hooks/use_track_highest_seen_message.ts +0 -43
- package/src/utils/__tests__/conversation_messages.test.ts +0 -105
- package/src/utils/__tests__/highest_seen_tracker.test.ts +0 -82
- package/src/utils/__tests__/message_viewability.test.ts +0 -168
- package/src/utils/__tests__/unread_divider_helpers.test.ts +0 -85
- package/src/utils/conversation_messages.ts +0 -37
- package/src/utils/highest_seen_tracker.ts +0 -42
- package/src/utils/message_viewability.ts +0 -49
- package/src/utils/unread_divider_helpers.ts +0 -25
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import dayjs from './dayjs';
|
|
2
2
|
import { isSystemMessage } from './system_messages';
|
|
3
3
|
const FIVE_MINUTES_MS = 5 * 60 * 1000;
|
|
4
|
-
export
|
|
5
|
-
export function groupMessages({ ms, inReplyScreen, jumpToUnreadActive, initialMessageId, }) {
|
|
4
|
+
export function groupMessages({ ms, inReplyScreen }) {
|
|
6
5
|
const items = [];
|
|
7
6
|
let myLatestSeen = false;
|
|
8
7
|
let nextNeighborEnriched;
|
|
@@ -12,9 +11,6 @@ export function groupMessages({ ms, inReplyScreen, jumpToUnreadActive, initialMe
|
|
|
12
11
|
if (isSystemMessage(message)) {
|
|
13
12
|
const enriched = enrichSystemMessage(message, next);
|
|
14
13
|
items.push(enriched);
|
|
15
|
-
if (crossesUnreadBoundary(message, prev, jumpToUnreadActive, initialMessageId)) {
|
|
16
|
-
items.push(unreadDivider());
|
|
17
|
-
}
|
|
18
14
|
if (datesDifferBetween(message, prev))
|
|
19
15
|
items.push(dateSeparator(message));
|
|
20
16
|
nextNeighborEnriched = enriched;
|
|
@@ -25,9 +21,6 @@ export function groupMessages({ ms, inReplyScreen, jumpToUnreadActive, initialMe
|
|
|
25
21
|
myLatestSeen = true;
|
|
26
22
|
const enriched = enrichRegularMessage(message, prev, next, isMyLatest, !!inReplyScreen);
|
|
27
23
|
items.push(enriched);
|
|
28
|
-
if (crossesUnreadBoundary(message, prev, jumpToUnreadActive, initialMessageId)) {
|
|
29
|
-
items.push(unreadDivider());
|
|
30
|
-
}
|
|
31
24
|
const shadow = replyShadowFor(enriched, prev);
|
|
32
25
|
if (shadow)
|
|
33
26
|
items.push(shadow);
|
|
@@ -38,18 +31,6 @@ export function groupMessages({ ms, inReplyScreen, jumpToUnreadActive, initialMe
|
|
|
38
31
|
});
|
|
39
32
|
return items;
|
|
40
33
|
}
|
|
41
|
-
function crossesUnreadBoundary(message, prev, jumpToUnreadActive, initialMessageId) {
|
|
42
|
-
if (!jumpToUnreadActive)
|
|
43
|
-
return false;
|
|
44
|
-
if (!initialMessageId)
|
|
45
|
-
return false;
|
|
46
|
-
if (!prev)
|
|
47
|
-
return false;
|
|
48
|
-
return (prev.id.localeCompare(initialMessageId) <= 0 && message.id.localeCompare(initialMessageId) > 0);
|
|
49
|
-
}
|
|
50
|
-
function unreadDivider() {
|
|
51
|
-
return { type: 'UnreadDivider', id: UNREAD_DIVIDER_KEY };
|
|
52
|
-
}
|
|
53
34
|
function neighborsOf(arr, i) {
|
|
54
35
|
return { prev: arr[i + 1], next: arr[i - 1] };
|
|
55
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group_messages.js","sourceRoot":"","sources":["../../src/utils/group_messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageResource } from '../types/resources/message'\nimport dayjs from './dayjs'\nimport { isSystemMessage } from './system_messages'\n\nconst FIVE_MINUTES_MS = 5 * 60 * 1000\n\nexport const UNREAD_DIVIDER_KEY = 'unread-divider'\n\nexport type DateSeparator = { type: 'DateSeparator'; id: string; date: string }\n\nexport type UnreadDividerItem = { type: 'UnreadDivider'; id: typeof UNREAD_DIVIDER_KEY }\n\nexport type ReplyShadowMessage = {\n type: 'ReplyShadowMessage'\n id: string\n messageId: string\n isReplyShadowMessage: boolean\n nextRendersAuthor: boolean\n}\n\nexport type EnrichedMessage =\n | MessageResource\n | DateSeparator\n | UnreadDividerItem\n | ReplyShadowMessage\n\ninterface GroupMessagesProps {\n ms: MessageResource[]\n inReplyScreen?: boolean\n jumpToUnreadActive?: boolean\n initialMessageId?: string | null\n}\n\nexport function groupMessages({\n ms,\n inReplyScreen,\n jumpToUnreadActive,\n initialMessageId,\n}: GroupMessagesProps): EnrichedMessage[] {\n const items: EnrichedMessage[] = []\n let myLatestSeen = false\n let nextNeighborEnriched: MessageResource | undefined\n\n ms.forEach((message, i) => {\n const { prev } = neighborsOf(ms, i)\n const next = nextNeighborEnriched\n\n if (isSystemMessage(message)) {\n const enriched = enrichSystemMessage(message, next)\n items.push(enriched)\n if (crossesUnreadBoundary(message, prev, jumpToUnreadActive, initialMessageId)) {\n items.push(unreadDivider())\n }\n if (datesDifferBetween(message, prev)) items.push(dateSeparator(message))\n nextNeighborEnriched = enriched\n return\n }\n\n const isMyLatest = !myLatestSeen && message.mine\n if (isMyLatest) myLatestSeen = true\n\n const enriched = enrichRegularMessage(message, prev, next, isMyLatest, !!inReplyScreen)\n items.push(enriched)\n\n if (crossesUnreadBoundary(message, prev, jumpToUnreadActive, initialMessageId)) {\n items.push(unreadDivider())\n }\n\n const shadow = replyShadowFor(enriched, prev)\n if (shadow) items.push(shadow)\n\n if (!prev || datesDifferBetween(message, prev)) {\n items.push(dateSeparator(message))\n }\n\n nextNeighborEnriched = enriched\n })\n\n return items\n}\n\nfunction crossesUnreadBoundary(\n message: MessageResource,\n prev: MessageResource | undefined,\n jumpToUnreadActive: boolean | undefined,\n initialMessageId: string | null | undefined\n): boolean {\n if (!jumpToUnreadActive) return false\n if (!initialMessageId) return false\n if (!prev) return false\n return (\n prev.id.localeCompare(initialMessageId) <= 0 && message.id.localeCompare(initialMessageId) > 0\n )\n}\n\nfunction unreadDivider(): UnreadDividerItem {\n return { type: 'UnreadDivider', id: UNREAD_DIVIDER_KEY }\n}\n\nfunction neighborsOf<T>(arr: T[], i: number): { prev: T | undefined; next: T | undefined } {\n return { prev: arr[i + 1], next: arr[i - 1] }\n}\n\nfunction enrichSystemMessage(\n message: MessageResource,\n next: MessageResource | undefined\n): MessageResource {\n return {\n ...message,\n myLatestInConversation: false,\n lastInGroup: true,\n renderAuthor: false,\n nextRendersAuthor: next?.renderAuthor,\n isReplyShadowMessage: false,\n nextIsReplyShadowMessage: false,\n threadPosition: null,\n }\n}\n\nfunction enrichRegularMessage(\n message: MessageResource,\n prev: MessageResource | undefined,\n next: MessageResource | undefined,\n isMyLatest: boolean,\n inReplyScreen: boolean\n): MessageResource {\n const inThread = message.replyRootId !== null\n const showThreadDetails = !inReplyScreen && inThread\n\n return {\n ...message,\n myLatestInConversation: isMyLatest,\n lastInGroup: !next || startsNewGroup(next, message),\n renderAuthor: !message.mine && (!prev || startsNewGroup(message, prev)),\n nextRendersAuthor: next?.renderAuthor,\n isReplyShadowMessage: false,\n nextIsReplyShadowMessage: nextIntroducesReplyShadow(next, message),\n threadPosition: showThreadDetails ? threadPositionFor(message, next) : null,\n prevIsMyReply: showThreadDetails ? prev?.mine : undefined,\n nextIsMyReply: showThreadDetails ? next?.mine : undefined,\n }\n}\n\nfunction startsNewGroup(a: MessageResource, b: MessageResource): boolean {\n return (\n a.author?.id !== b.author?.id ||\n differsByMoreThan5Min(a, b) ||\n a.replyRootId !== b.replyRootId ||\n datesDifferBetween(a, b)\n )\n}\n\nfunction differsByMoreThan5Min(a: MessageResource, b: MessageResource): boolean {\n return Math.abs(toMillis(a.createdAt) - toMillis(b.createdAt)) > FIVE_MINUTES_MS\n}\n\nfunction toMillis(iso: string): number {\n return new Date(iso).getTime()\n}\n\nfunction datesDifferBetween(a: MessageResource, b: MessageResource | undefined): boolean {\n if (!b) return false\n return dateKey(a) !== dateKey(b)\n}\n\nfunction dateKey(message: MessageResource): string {\n return dayjs(message.createdAt).format('YYYY-MM-DD')\n}\n\nfunction dateSeparator(message: MessageResource): DateSeparator {\n return { type: 'DateSeparator', id: `day-divider-${message.id}`, date: dateKey(message) }\n}\n\nfunction threadPositionFor(\n message: MessageResource,\n next: MessageResource | undefined\n): 'first' | 'center' | 'last' | null {\n const isThreadRoot = message.replyRootId === message.id\n const isLast =\n !next || next.replyRootId !== message.replyRootId || datesDifferBetween(next, message)\n\n if (isThreadRoot && isLast) return null\n if (isThreadRoot) return 'first'\n if (isLast) return 'last'\n return 'center'\n}\n\nfunction nextIntroducesReplyShadow(\n next: MessageResource | undefined,\n current: MessageResource\n): boolean {\n if (!next) return false\n const nextInThread = next.replyRootId !== null\n const nextIsThreadRoot = next.replyRootId === next.id\n const differentThread = next.replyRootId !== current.replyRootId\n return nextInThread && !nextIsThreadRoot && (differentThread || datesDifferBetween(next, current))\n}\n\nfunction replyShadowFor(\n message: MessageResource,\n prev: MessageResource | undefined\n): ReplyShadowMessage | undefined {\n if (!message.replyRootId) return undefined\n if (message.replyRootId === message.id) return undefined\n\n const enteringNewThread =\n !prev || prev.replyRootId !== message.replyRootId || datesDifferBetween(prev, message)\n if (!enteringNewThread) return undefined\n\n return {\n type: 'ReplyShadowMessage',\n id: `${message.id}-${message.replyRootId}`,\n messageId: message.replyRootId,\n isReplyShadowMessage: true,\n nextRendersAuthor: message.renderAuthor ?? false,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"group_messages.js","sourceRoot":"","sources":["../../src/utils/group_messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MessageResource } from '../types/resources/message'\nimport dayjs from './dayjs'\nimport { isSystemMessage } from './system_messages'\n\nconst FIVE_MINUTES_MS = 5 * 60 * 1000\n\nexport type DateSeparator = { type: 'DateSeparator'; id: string; date: string }\n\nexport type ReplyShadowMessage = {\n type: 'ReplyShadowMessage'\n id: string\n messageId: string\n isReplyShadowMessage: boolean\n nextRendersAuthor: boolean\n}\n\nexport type EnrichedMessage = MessageResource | DateSeparator | ReplyShadowMessage\n\ninterface GroupMessagesProps {\n ms: MessageResource[]\n inReplyScreen?: boolean\n}\n\nexport function groupMessages({ ms, inReplyScreen }: GroupMessagesProps): EnrichedMessage[] {\n const items: EnrichedMessage[] = []\n let myLatestSeen = false\n let nextNeighborEnriched: MessageResource | undefined\n\n ms.forEach((message, i) => {\n const { prev } = neighborsOf(ms, i)\n const next = nextNeighborEnriched\n\n if (isSystemMessage(message)) {\n const enriched = enrichSystemMessage(message, next)\n items.push(enriched)\n if (datesDifferBetween(message, prev)) items.push(dateSeparator(message))\n nextNeighborEnriched = enriched\n return\n }\n\n const isMyLatest = !myLatestSeen && message.mine\n if (isMyLatest) myLatestSeen = true\n\n const enriched = enrichRegularMessage(message, prev, next, isMyLatest, !!inReplyScreen)\n items.push(enriched)\n\n const shadow = replyShadowFor(enriched, prev)\n if (shadow) items.push(shadow)\n\n if (!prev || datesDifferBetween(message, prev)) {\n items.push(dateSeparator(message))\n }\n\n nextNeighborEnriched = enriched\n })\n\n return items\n}\n\nfunction neighborsOf<T>(arr: T[], i: number): { prev: T | undefined; next: T | undefined } {\n return { prev: arr[i + 1], next: arr[i - 1] }\n}\n\nfunction enrichSystemMessage(\n message: MessageResource,\n next: MessageResource | undefined\n): MessageResource {\n return {\n ...message,\n myLatestInConversation: false,\n lastInGroup: true,\n renderAuthor: false,\n nextRendersAuthor: next?.renderAuthor,\n isReplyShadowMessage: false,\n nextIsReplyShadowMessage: false,\n threadPosition: null,\n }\n}\n\nfunction enrichRegularMessage(\n message: MessageResource,\n prev: MessageResource | undefined,\n next: MessageResource | undefined,\n isMyLatest: boolean,\n inReplyScreen: boolean\n): MessageResource {\n const inThread = message.replyRootId !== null\n const showThreadDetails = !inReplyScreen && inThread\n\n return {\n ...message,\n myLatestInConversation: isMyLatest,\n lastInGroup: !next || startsNewGroup(next, message),\n renderAuthor: !message.mine && (!prev || startsNewGroup(message, prev)),\n nextRendersAuthor: next?.renderAuthor,\n isReplyShadowMessage: false,\n nextIsReplyShadowMessage: nextIntroducesReplyShadow(next, message),\n threadPosition: showThreadDetails ? threadPositionFor(message, next) : null,\n prevIsMyReply: showThreadDetails ? prev?.mine : undefined,\n nextIsMyReply: showThreadDetails ? next?.mine : undefined,\n }\n}\n\nfunction startsNewGroup(a: MessageResource, b: MessageResource): boolean {\n return (\n a.author?.id !== b.author?.id ||\n differsByMoreThan5Min(a, b) ||\n a.replyRootId !== b.replyRootId ||\n datesDifferBetween(a, b)\n )\n}\n\nfunction differsByMoreThan5Min(a: MessageResource, b: MessageResource): boolean {\n return Math.abs(toMillis(a.createdAt) - toMillis(b.createdAt)) > FIVE_MINUTES_MS\n}\n\nfunction toMillis(iso: string): number {\n return new Date(iso).getTime()\n}\n\nfunction datesDifferBetween(a: MessageResource, b: MessageResource | undefined): boolean {\n if (!b) return false\n return dateKey(a) !== dateKey(b)\n}\n\nfunction dateKey(message: MessageResource): string {\n return dayjs(message.createdAt).format('YYYY-MM-DD')\n}\n\nfunction dateSeparator(message: MessageResource): DateSeparator {\n return { type: 'DateSeparator', id: `day-divider-${message.id}`, date: dateKey(message) }\n}\n\nfunction threadPositionFor(\n message: MessageResource,\n next: MessageResource | undefined\n): 'first' | 'center' | 'last' | null {\n const isThreadRoot = message.replyRootId === message.id\n const isLast =\n !next || next.replyRootId !== message.replyRootId || datesDifferBetween(next, message)\n\n if (isThreadRoot && isLast) return null\n if (isThreadRoot) return 'first'\n if (isLast) return 'last'\n return 'center'\n}\n\nfunction nextIntroducesReplyShadow(\n next: MessageResource | undefined,\n current: MessageResource\n): boolean {\n if (!next) return false\n const nextInThread = next.replyRootId !== null\n const nextIsThreadRoot = next.replyRootId === next.id\n const differentThread = next.replyRootId !== current.replyRootId\n return nextInThread && !nextIsThreadRoot && (differentThread || datesDifferBetween(next, current))\n}\n\nfunction replyShadowFor(\n message: MessageResource,\n prev: MessageResource | undefined\n): ReplyShadowMessage | undefined {\n if (!message.replyRootId) return undefined\n if (message.replyRootId === message.id) return undefined\n\n const enteringNewThread =\n !prev || prev.replyRootId !== message.replyRootId || datesDifferBetween(prev, message)\n if (!enteringNewThread) return undefined\n\n return {\n type: 'ReplyShadowMessage',\n id: `${message.id}-${message.replyRootId}`,\n messageId: message.replyRootId,\n isReplyShadowMessage: true,\n nextRendersAuthor: message.renderAuthor ?? false,\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.38.0
|
|
3
|
+
"version": "3.38.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "./src/index.tsx",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
|
28
28
|
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
|
29
|
-
"@planningcenter/emoji-keyboard": "3.38.0
|
|
29
|
+
"@planningcenter/emoji-keyboard": "3.38.0",
|
|
30
30
|
"lodash-inflection": "^1.5.0",
|
|
31
31
|
"react-compiler-runtime": "^1.0.0"
|
|
32
32
|
},
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"react-native-url-polyfill": "^2.0.0",
|
|
73
73
|
"typescript": "~5.9.2"
|
|
74
74
|
},
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "8b9d4026df5abb9838772a28e85ded7b059020b5"
|
|
76
76
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
2
|
+
import { renderHook, waitFor } from '@testing-library/react-native'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
5
|
+
import { useGroupChatConversationPayload } from '../../hooks/groups/use_group_chat_conversation_payload'
|
|
6
|
+
import * as useApiClientModule from '../../hooks/use_api_client'
|
|
7
|
+
|
|
8
|
+
const mockGroupsPost = (impl: jest.Mock) => {
|
|
9
|
+
jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({
|
|
10
|
+
groups: { post: impl },
|
|
11
|
+
} as unknown as ReturnType<typeof useApiClientModule.useApiClient>)
|
|
12
|
+
return impl
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const resolveWith = (value: string) =>
|
|
16
|
+
jest.fn().mockResolvedValue({
|
|
17
|
+
data: { type: 'GroupChatConversationPayload', id: '1', value },
|
|
18
|
+
links: {},
|
|
19
|
+
meta: {},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('useGroupChatConversationPayload', () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
jest.restoreAllMocks()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('refetches on mount even when a fresh payload is already cached', async () => {
|
|
28
|
+
// Prime the cache as if a payload was fetched moments ago. The hook's 25-min staleTime
|
|
29
|
+
// means the default refetchOnMount would treat this as fresh and skip the request, so a
|
|
30
|
+
// group composition change on web would stay invisible until the cache is dropped (app
|
|
31
|
+
// restart). refetchOnMount: 'always' must override that and refetch on every mount.
|
|
32
|
+
const queryClient = buildTestQueryClient()
|
|
33
|
+
queryClient.setQueryData(['groups', '/me/groups/1/chat_conversation_payload'], {
|
|
34
|
+
data: { type: 'GroupChatConversationPayload', id: '1', value: 'stale' },
|
|
35
|
+
links: {},
|
|
36
|
+
meta: {},
|
|
37
|
+
})
|
|
38
|
+
const post = mockGroupsPost(resolveWith('fresh'))
|
|
39
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
40
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const { result } = renderHook(() => useGroupChatConversationPayload({ groupId: 1 }), {
|
|
44
|
+
wrapper,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
await waitFor(() => expect(post).toHaveBeenCalledTimes(1))
|
|
48
|
+
await waitFor(() => expect(result.current.payload).toBe('fresh'))
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { View, Pressable, StyleSheet } from 'react-native'
|
|
3
3
|
import Animated, {
|
|
4
|
-
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
5
6
|
interpolate,
|
|
7
|
+
Extrapolation,
|
|
6
8
|
ReduceMotion,
|
|
7
|
-
useAnimatedStyle,
|
|
8
|
-
useSharedValue,
|
|
9
9
|
withSpring,
|
|
10
|
-
withTiming,
|
|
11
10
|
} from 'react-native-reanimated'
|
|
12
11
|
import { useTheme } from '../../hooks'
|
|
13
12
|
import { platformFontWeightMedium } from '../../utils'
|
|
@@ -16,17 +15,11 @@ import { Icon, Text } from '../display'
|
|
|
16
15
|
interface JumpToBottomButtonProps {
|
|
17
16
|
onPress: () => void
|
|
18
17
|
visible: boolean
|
|
19
|
-
loading?: boolean
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
export const JumpToBottomButton = ({
|
|
23
|
-
onPress,
|
|
24
|
-
visible,
|
|
25
|
-
loading = false,
|
|
26
|
-
}: JumpToBottomButtonProps) => {
|
|
20
|
+
export const JumpToBottomButton = ({ onPress, visible }: JumpToBottomButtonProps) => {
|
|
27
21
|
const styles = useStyles()
|
|
28
22
|
const progress = useSharedValue(0)
|
|
29
|
-
const loadingProgress = useSharedValue(0)
|
|
30
23
|
|
|
31
24
|
useEffect(() => {
|
|
32
25
|
progress.value = withSpring(visible ? 1 : 0, {
|
|
@@ -38,13 +31,6 @@ export const JumpToBottomButton = ({
|
|
|
38
31
|
})
|
|
39
32
|
}, [visible, progress])
|
|
40
33
|
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
loadingProgress.value = withTiming(loading ? 1 : 0, {
|
|
43
|
-
duration: 750,
|
|
44
|
-
reduceMotion: ReduceMotion.System,
|
|
45
|
-
})
|
|
46
|
-
}, [loading, loadingProgress])
|
|
47
|
-
|
|
48
34
|
const animatedStyle = useAnimatedStyle(() => {
|
|
49
35
|
return {
|
|
50
36
|
opacity: progress.value,
|
|
@@ -59,34 +45,16 @@ export const JumpToBottomButton = ({
|
|
|
59
45
|
}
|
|
60
46
|
})
|
|
61
47
|
|
|
62
|
-
const iconStyle = useAnimatedStyle(() => ({ opacity: 1 - loadingProgress.value }))
|
|
63
|
-
const spinnerStyle = useAnimatedStyle(() => ({ opacity: loadingProgress.value }))
|
|
64
|
-
|
|
65
48
|
return (
|
|
66
49
|
<View>
|
|
67
|
-
<Animated.View
|
|
68
|
-
style={[styles.container, animatedStyle]}
|
|
69
|
-
pointerEvents={visible ? 'auto' : 'none'}
|
|
70
|
-
>
|
|
50
|
+
<Animated.View style={[styles.container, animatedStyle]}>
|
|
71
51
|
<Pressable
|
|
72
52
|
onPress={onPress}
|
|
73
|
-
disabled={loading}
|
|
74
|
-
accessibilityRole="button"
|
|
75
|
-
accessibilityLabel="Jump to most recent message"
|
|
76
|
-
accessibilityState={{ busy: loading }}
|
|
77
|
-
hitSlop={hitSlop}
|
|
78
53
|
style={({ pressed }) => [styles.button, pressed && styles.pressed]}
|
|
79
54
|
>
|
|
80
|
-
<
|
|
81
|
-
<Animated.View style={[styles.glyphLayer, iconStyle]}>
|
|
82
|
-
<Icon name="general.downArrow" style={styles.icon} />
|
|
83
|
-
</Animated.View>
|
|
84
|
-
<Animated.View style={[styles.glyphLayer, spinnerStyle]}>
|
|
85
|
-
<ActivityIndicator size="small" color={styles.icon.color} style={styles.spinner} />
|
|
86
|
-
</Animated.View>
|
|
87
|
-
</View>
|
|
55
|
+
<Icon name="general.downArrow" style={styles.icon} />
|
|
88
56
|
<Text variant="tertiary" style={styles.text}>
|
|
89
|
-
|
|
57
|
+
Jump to bottom
|
|
90
58
|
</Text>
|
|
91
59
|
</Pressable>
|
|
92
60
|
</Animated.View>
|
|
@@ -94,8 +62,6 @@ export const JumpToBottomButton = ({
|
|
|
94
62
|
)
|
|
95
63
|
}
|
|
96
64
|
|
|
97
|
-
const hitSlop = { top: 12, bottom: 12, left: 12, right: 12 }
|
|
98
|
-
|
|
99
65
|
const useStyles = () => {
|
|
100
66
|
const { colors } = useTheme()
|
|
101
67
|
|
|
@@ -127,25 +93,10 @@ const useStyles = () => {
|
|
|
127
93
|
color: colors.fillColorNeutral100Inverted,
|
|
128
94
|
fontWeight: platformFontWeightMedium,
|
|
129
95
|
},
|
|
130
|
-
glyph: {
|
|
131
|
-
width: 14,
|
|
132
|
-
height: 14,
|
|
133
|
-
alignItems: 'center',
|
|
134
|
-
justifyContent: 'center',
|
|
135
|
-
},
|
|
136
|
-
glyphLayer: {
|
|
137
|
-
position: 'absolute',
|
|
138
|
-
alignItems: 'center',
|
|
139
|
-
justifyContent: 'center',
|
|
140
|
-
},
|
|
141
96
|
icon: {
|
|
142
97
|
color: colors.fillColorNeutral100Inverted,
|
|
143
98
|
fontSize: 14,
|
|
144
99
|
},
|
|
145
|
-
spinner: {
|
|
146
|
-
width: 14,
|
|
147
|
-
height: 14,
|
|
148
|
-
},
|
|
149
100
|
pressed: {
|
|
150
101
|
transform: [{ scale: 0.95 }],
|
|
151
102
|
},
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { Avatar, Icon, IconProps, Image, Text } from '../display'
|
|
29
29
|
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
30
30
|
|
|
31
|
-
interface ReplyShadowMessageProps {
|
|
31
|
+
interface ReplyShadowMessageProps extends MessageResource {
|
|
32
32
|
messageId: string
|
|
33
33
|
conversation_id: number
|
|
34
34
|
inReplyScreen?: boolean
|
|
@@ -1,59 +1,31 @@
|
|
|
1
|
-
import React, { createContext, PropsWithChildren, useContext, useMemo
|
|
1
|
+
import React, { createContext, PropsWithChildren, useContext, useMemo } from 'react'
|
|
2
2
|
|
|
3
3
|
interface ConversationContextValue {
|
|
4
4
|
conversationId: number
|
|
5
5
|
currentPageReplyRootId: string | null
|
|
6
|
-
initialMessageId: string | null
|
|
7
|
-
setInitialMessageId: (id: string | null) => void
|
|
8
|
-
initialMessageIdIsAnchor: boolean
|
|
9
|
-
atEndOfMessageHistory: boolean
|
|
10
|
-
setAtEndOfMessageHistory: (atEnd: boolean) => void
|
|
11
6
|
}
|
|
12
7
|
|
|
13
8
|
interface ConversationContextProviderProps extends PropsWithChildren {
|
|
14
9
|
conversationId: number
|
|
15
10
|
currentPageReplyRootId: string | null
|
|
16
|
-
initialMessageId?: string | null
|
|
17
|
-
initialMessageIdIsAnchor?: boolean
|
|
18
11
|
}
|
|
19
12
|
|
|
20
13
|
const ConversationContext = createContext<ConversationContextValue>({
|
|
21
14
|
conversationId: 0,
|
|
22
15
|
currentPageReplyRootId: null,
|
|
23
|
-
initialMessageId: null,
|
|
24
|
-
setInitialMessageId: () => {},
|
|
25
|
-
initialMessageIdIsAnchor: false,
|
|
26
|
-
atEndOfMessageHistory: false,
|
|
27
|
-
setAtEndOfMessageHistory: () => {},
|
|
28
16
|
})
|
|
29
17
|
|
|
30
18
|
export const ConversationContextProvider = ({
|
|
31
19
|
children,
|
|
32
20
|
conversationId,
|
|
33
21
|
currentPageReplyRootId,
|
|
34
|
-
initialMessageId: initialMessageIdProp = null,
|
|
35
|
-
initialMessageIdIsAnchor = false,
|
|
36
22
|
}: ConversationContextProviderProps) => {
|
|
37
|
-
const [initialMessageId, setInitialMessageId] = useState(initialMessageIdProp)
|
|
38
|
-
const [atEndOfMessageHistory, setAtEndOfMessageHistory] = useState(false)
|
|
39
|
-
|
|
40
23
|
const value = useMemo(
|
|
41
24
|
() => ({
|
|
42
25
|
conversationId,
|
|
43
26
|
currentPageReplyRootId,
|
|
44
|
-
initialMessageId,
|
|
45
|
-
setInitialMessageId,
|
|
46
|
-
initialMessageIdIsAnchor,
|
|
47
|
-
atEndOfMessageHistory,
|
|
48
|
-
setAtEndOfMessageHistory,
|
|
49
27
|
}),
|
|
50
|
-
[
|
|
51
|
-
conversationId,
|
|
52
|
-
currentPageReplyRootId,
|
|
53
|
-
initialMessageId,
|
|
54
|
-
initialMessageIdIsAnchor,
|
|
55
|
-
atEndOfMessageHistory,
|
|
56
|
-
]
|
|
28
|
+
[conversationId, currentPageReplyRootId]
|
|
57
29
|
)
|
|
58
30
|
|
|
59
31
|
return <ConversationContext.Provider value={value}>{children}</ConversationContext.Provider>
|
|
@@ -1,128 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
InfiniteData,
|
|
4
|
-
useQueryClient,
|
|
5
|
-
useSuspenseInfiniteQuery,
|
|
6
|
-
useSuspenseQueries,
|
|
7
|
-
} from '@tanstack/react-query'
|
|
8
|
-
import { useCallback, useMemo } from 'react'
|
|
9
|
-
import { useConversationContext } from '../contexts/conversation_context'
|
|
10
|
-
import { ApiCollection, MessageResource } from '../types'
|
|
11
|
-
import {
|
|
12
|
-
anchoredSeedPageParams,
|
|
13
|
-
MessagesPageParam,
|
|
14
|
-
newerPageParam,
|
|
15
|
-
olderPageParam,
|
|
16
|
-
sortAndFilterMessages,
|
|
17
|
-
} from '../utils/conversation_messages'
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { MessageResource } from '../types'
|
|
18
3
|
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
19
|
-
import {
|
|
20
|
-
import { throwResponseError } from './use_suspense_api'
|
|
21
|
-
|
|
22
|
-
type Args = { conversation_id: number; reply_root_id?: string | null }
|
|
23
|
-
|
|
24
|
-
export type ConversationMessagesOptions = Omit<
|
|
25
|
-
AnyUseSuspenseInfiniteQueryOptions,
|
|
26
|
-
| 'getNextPageParam'
|
|
27
|
-
| 'getPreviousPageParam'
|
|
28
|
-
| 'initialData'
|
|
29
|
-
| 'initialPageParam'
|
|
30
|
-
| 'queryFn'
|
|
31
|
-
| 'queryKey'
|
|
32
|
-
>
|
|
4
|
+
import { SuspensePaginatorOptions, useSuspensePaginator } from './use_suspense_api'
|
|
33
5
|
|
|
34
6
|
export const useConversationMessages = (
|
|
35
|
-
{ conversation_id, reply_root_id }:
|
|
36
|
-
opts?:
|
|
7
|
+
{ conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string | null },
|
|
8
|
+
opts?: SuspensePaginatorOptions
|
|
37
9
|
) => {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const requestArgs = useMemo(
|
|
43
|
-
() => getMessagesRequestArgs({ conversation_id, reply_root_id }),
|
|
44
|
-
[conversation_id, reply_root_id]
|
|
10
|
+
const { data, refetch, isRefetching, fetchNextPage } = useSuspensePaginator<MessageResource>(
|
|
11
|
+
getMessagesRequestArgs({ conversation_id, reply_root_id }),
|
|
12
|
+
opts
|
|
45
13
|
)
|
|
46
|
-
const queryKey =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return apiClient.chat
|
|
58
|
-
.get<ApiCollection<MessageResource>>({ url: requestArgs.url, data })
|
|
59
|
-
.catch(throwResponseError)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const seedPageParams = anchored ? anchoredSeedPageParams(initialMessageId) : []
|
|
63
|
-
const seedQueries = useSuspenseQueries({
|
|
64
|
-
queries: seedPageParams.map((pageParam, index) => ({
|
|
65
|
-
queryKey: [...queryKey, 'seed', index],
|
|
66
|
-
queryFn: () => fetchPage(pageParam),
|
|
67
|
-
staleTime: Infinity,
|
|
68
|
-
gcTime: 0,
|
|
69
|
-
})),
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const initialData: InfiniteData<ApiCollection<MessageResource>, MessagesPageParam> | undefined =
|
|
73
|
-
anchored
|
|
74
|
-
? {
|
|
75
|
-
pages: seedQueries.map(q => q.data),
|
|
76
|
-
pageParams: seedPageParams,
|
|
77
|
-
}
|
|
78
|
-
: undefined
|
|
79
|
-
|
|
80
|
-
const initialPageParam: MessagesPageParam = anchored ? seedPageParams[0] : {}
|
|
81
|
-
|
|
82
|
-
const {
|
|
83
|
-
data,
|
|
84
|
-
refetch,
|
|
85
|
-
isRefetching,
|
|
86
|
-
fetchNextPage,
|
|
87
|
-
hasNextPage,
|
|
88
|
-
fetchPreviousPage,
|
|
89
|
-
hasPreviousPage,
|
|
90
|
-
isFetchingPreviousPage,
|
|
91
|
-
} = useSuspenseInfiniteQuery<
|
|
92
|
-
ApiCollection<MessageResource>,
|
|
93
|
-
Response,
|
|
94
|
-
InfiniteData<ApiCollection<MessageResource>, MessagesPageParam>,
|
|
95
|
-
typeof queryKey,
|
|
96
|
-
MessagesPageParam
|
|
97
|
-
>({
|
|
98
|
-
queryKey,
|
|
99
|
-
queryFn: ({ pageParam }) => fetchPage(pageParam),
|
|
100
|
-
initialPageParam,
|
|
101
|
-
initialData,
|
|
102
|
-
getNextPageParam: olderPageParam,
|
|
103
|
-
getPreviousPageParam: anchored ? newerPageParam : () => undefined,
|
|
104
|
-
...(opts || {}),
|
|
105
|
-
...(anchored ? { staleTime: Infinity, refetchOnMount: false } : {}),
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
const messages = useMemo(() => sortAndFilterMessages(data.pages), [data.pages])
|
|
109
|
-
|
|
110
|
-
const queryClient = useQueryClient()
|
|
111
|
-
const cancelFetchNewerMessages = useCallback(
|
|
112
|
-
() => queryClient.cancelQueries({ queryKey }),
|
|
113
|
-
[queryClient, queryKey]
|
|
14
|
+
const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id })
|
|
15
|
+
const messages = useMemo(
|
|
16
|
+
() =>
|
|
17
|
+
data
|
|
18
|
+
.filter(
|
|
19
|
+
message =>
|
|
20
|
+
(!message.deletedAt || message.replyRootId) &&
|
|
21
|
+
(message.attachments?.length || message.text?.length)
|
|
22
|
+
)
|
|
23
|
+
.sort((a, b) => -a.id.localeCompare(b.id)),
|
|
24
|
+
[data]
|
|
114
25
|
)
|
|
115
26
|
|
|
116
|
-
return {
|
|
117
|
-
messages,
|
|
118
|
-
refetch,
|
|
119
|
-
isRefetching,
|
|
120
|
-
fetchOlderMessages: fetchNextPage,
|
|
121
|
-
hasMoreOlderMessages: hasNextPage,
|
|
122
|
-
fetchNewerMessages: fetchPreviousPage,
|
|
123
|
-
hasMoreNewerMessages: hasPreviousPage,
|
|
124
|
-
isFetchingNewerMessages: isFetchingPreviousPage,
|
|
125
|
-
cancelFetchNewerMessages,
|
|
126
|
-
queryKey,
|
|
127
|
-
}
|
|
27
|
+
return { messages, refetch, isRefetching, fetchNextPage, queryKey }
|
|
128
28
|
}
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
updateCacheWithIndividualMessage,
|
|
13
13
|
updateCacheWithReaction,
|
|
14
14
|
getThreadedMessagesQueryKey,
|
|
15
|
-
hasUnloadedNewerPages,
|
|
16
15
|
} from '../utils/cache/messages_cache'
|
|
17
16
|
import { transformMessageEventDataToMessageResource } from '../utils/jolt/transform_message_event_data_to_message_resource'
|
|
18
17
|
import { completeMessageCreationTracking } from '../utils/performance_tracking'
|
|
@@ -53,10 +52,10 @@ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
|
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
55
|
+
// Update the main conversation cache
|
|
56
|
+
updateCacheWithMessage(queryClient, messagesQueryKey, message, e.event)
|
|
59
57
|
|
|
58
|
+
// If message has a reply_root_id, also update the threaded cache
|
|
60
59
|
if (data.reply_root_id) {
|
|
61
60
|
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(
|
|
62
61
|
conversationId,
|
|
@@ -98,21 +98,6 @@ export const useConversationsMute = ({ conversation }: { conversation: Conversat
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
export const useConversationsMarkReadUpTo = ({ conversationId }: { conversationId: number }) => {
|
|
102
|
-
const apiClient = useApiClient()
|
|
103
|
-
|
|
104
|
-
return useMutation({
|
|
105
|
-
mutationKey: ['markReadUpTo', conversationId],
|
|
106
|
-
mutationFn: async ({ sortKey }: { sortKey: string }) =>
|
|
107
|
-
apiClient.chat.post({
|
|
108
|
-
url: `/me/conversations/${conversationId}/mark_read_up_to`,
|
|
109
|
-
data: {
|
|
110
|
-
data: { type: 'Conversation', attributes: { sort_key: sortKey } },
|
|
111
|
-
},
|
|
112
|
-
}),
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
|
|
116
101
|
export const useMarkAllRead = () => {
|
|
117
102
|
const apiClient = useApiClient()
|
|
118
103
|
const { args } = useConversationsContext()
|
|
@@ -40,7 +40,6 @@ export const availableFeatures = {
|
|
|
40
40
|
message_reporting: 'ROLLOUT_MOBILE_message_reporting',
|
|
41
41
|
granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',
|
|
42
42
|
custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
|
|
43
|
-
jump_to_unread: 'ROLLOUT_jump_to_unread',
|
|
44
43
|
conversation_safety_lock: 'ROLLOUT_conversation_safety_lock',
|
|
45
44
|
video_moderation: 'ROLLOUT_MOBILE_video_moderation',
|
|
46
45
|
} as const satisfies Record<string, `ROLLOUT_${string}`>
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import { debounce } from 'lodash'
|
|
2
2
|
import { useEffect, useMemo, useRef } from 'react'
|
|
3
|
-
import { useConversationContext } from '../contexts/conversation_context'
|
|
4
3
|
import { ConversationResource, MessageResource } from '../types'
|
|
5
4
|
import { useAppState } from './use_app_state'
|
|
6
5
|
import { useConversationsMarkRead } from './use_conversations_actions'
|
|
7
|
-
import { useJumpToUnreadGates } from './use_jump_to_unread_gates'
|
|
8
6
|
|
|
9
7
|
interface Props {
|
|
10
8
|
conversation: ConversationResource
|
|
11
|
-
messages
|
|
9
|
+
messages: MessageResource[]
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
export function useMarkLatestMessageRead({ conversation }: Props) {
|
|
15
|
-
const { jumpToUnreadActive } = useJumpToUnreadGates()
|
|
16
|
-
const { currentPageReplyRootId, atEndOfMessageHistory } = useConversationContext()
|
|
17
13
|
const firedOnce = useRef<boolean>(false)
|
|
18
14
|
const { markRead } = useConversationsMarkRead({ conversation })
|
|
19
15
|
const debouncedMarkRead = useMemo(
|
|
@@ -29,20 +25,10 @@ export function useMarkLatestMessageRead({ conversation }: Props) {
|
|
|
29
25
|
|
|
30
26
|
useEffect(() => {
|
|
31
27
|
if (!isActive || !shouldMarkRead) return
|
|
32
|
-
if (currentPageReplyRootId) return
|
|
33
|
-
if (jumpToUnreadActive && !atEndOfMessageHistory) return
|
|
34
28
|
|
|
35
29
|
firedOnce.current = true
|
|
36
30
|
|
|
37
31
|
debouncedMarkRead(true)
|
|
38
32
|
// keeping unreadReactionCount in the dependency array to watch for changes
|
|
39
|
-
}, [
|
|
40
|
-
debouncedMarkRead,
|
|
41
|
-
isActive,
|
|
42
|
-
shouldMarkRead,
|
|
43
|
-
unreadReactionCount,
|
|
44
|
-
currentPageReplyRootId,
|
|
45
|
-
jumpToUnreadActive,
|
|
46
|
-
atEndOfMessageHistory,
|
|
47
|
-
])
|
|
33
|
+
}, [debouncedMarkRead, isActive, shouldMarkRead, unreadReactionCount])
|
|
48
34
|
}
|
|
@@ -90,7 +90,7 @@ export const useSuspensePaginator = <T extends ResourceObject>(
|
|
|
90
90
|
return { ...query, data, totalCount }
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
const throwResponseError = (error: unknown) => {
|
|
94
94
|
if (error instanceof Response) {
|
|
95
95
|
throw new ResponseError(error as FailedResponse)
|
|
96
96
|
}
|
|
@@ -24,6 +24,7 @@ export const HeaderRow = ({ data, nativeID, style, setTeamFilters }: HeaderRowPr
|
|
|
24
24
|
const route = useRoute<RouteProp<ConversationFilterRecipientsScreenProps['route']>>()
|
|
25
25
|
|
|
26
26
|
const { serviceTypeName, teamIdsForServiceType } = data
|
|
27
|
+
const displayName = serviceTypeName ?? 'No service type'
|
|
27
28
|
const { team_ids: currentTeamIds = [] } = route.params
|
|
28
29
|
|
|
29
30
|
const newTeamIdsAdded = [...new Set([...currentTeamIds, ...teamIdsForServiceType])]
|
|
@@ -34,7 +35,7 @@ export const HeaderRow = ({ data, nativeID, style, setTeamFilters }: HeaderRowPr
|
|
|
34
35
|
const selectLabel = allTeamsSelected ? 'Deselect' : 'Select'
|
|
35
36
|
|
|
36
37
|
const headingAccessibilityHint = `${pluralize(teamIdsForServiceType.length, 'team')} available to select`
|
|
37
|
-
const selectAllAccessibilityLabel = `${selectLabel} ${pluralize(teamIdsForServiceType.length, 'team')} for ${
|
|
38
|
+
const selectAllAccessibilityLabel = `${selectLabel} ${pluralize(teamIdsForServiceType.length, 'team')} for ${displayName}`
|
|
38
39
|
|
|
39
40
|
const handleSelectAll = () => {
|
|
40
41
|
setTeamFilters({
|
|
@@ -53,7 +54,7 @@ export const HeaderRow = ({ data, nativeID, style, setTeamFilters }: HeaderRowPr
|
|
|
53
54
|
nativeID={nativeID}
|
|
54
55
|
accessibilityHint={headingAccessibilityHint}
|
|
55
56
|
>
|
|
56
|
-
{
|
|
57
|
+
{displayName}
|
|
57
58
|
</Heading>
|
|
58
59
|
</View>
|
|
59
60
|
|