@planningcenter/chat-react-native 3.18.0-rc.1 → 3.18.0-rc.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/message.d.ts +2 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +53 -26
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +7 -6
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/messages_disabled_banners.d.ts +3 -0
- package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -0
- package/build/components/conversation/messages_disabled_banners.js +53 -0
- package/build/components/conversation/messages_disabled_banners.js.map +1 -0
- package/build/components/conversation/reply_connectors.d.ts.map +1 -1
- package/build/components/conversation/reply_connectors.js +0 -5
- package/build/components/conversation/reply_connectors.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
- package/build/components/conversation/reply_shadow_message.js +8 -3
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/components/conversation/typing_indicator.d.ts +1 -5
- package/build/components/conversation/typing_indicator.d.ts.map +1 -1
- package/build/components/conversation/typing_indicator.js +2 -2
- package/build/components/conversation/typing_indicator.js.map +1 -1
- package/build/components/conversations/conversation_actions.d.ts.map +1 -1
- package/build/components/conversations/conversation_actions.js +1 -2
- package/build/components/conversations/conversation_actions.js.map +1 -1
- package/build/components/conversations/conversations.d.ts.map +1 -1
- package/build/components/conversations/conversations.js +2 -3
- package/build/components/conversations/conversations.js.map +1 -1
- package/build/contexts/conversation_context.d.ts +13 -0
- package/build/contexts/conversation_context.d.ts.map +1 -0
- package/build/contexts/conversation_context.js +14 -0
- package/build/contexts/conversation_context.js.map +1 -0
- package/build/hooks/use_broadcast_typing_status.d.ts +1 -1
- package/build/hooks/use_broadcast_typing_status.d.ts.map +1 -1
- package/build/hooks/use_broadcast_typing_status.js +7 -3
- package/build/hooks/use_broadcast_typing_status.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +2 -1
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +23 -70
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_features.d.ts +9 -0
- package/build/hooks/use_features.d.ts.map +1 -0
- package/build/hooks/use_features.js +35 -0
- package/build/hooks/use_features.js.map +1 -0
- package/build/hooks/use_message_create_or_update.d.ts +0 -2
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +10 -8
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/hooks/use_typing_indicators.d.ts +1 -1
- package/build/hooks/use_typing_indicators.d.ts.map +1 -1
- package/build/hooks/use_typing_indicators.js +16 -3
- package/build/hooks/use_typing_indicators.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +9 -6
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_new/components/form_list.d.ts +2 -2
- package/build/screens/conversation_new/components/form_list.d.ts.map +1 -1
- package/build/screens/conversation_new/components/form_list.js +2 -3
- package/build/screens/conversation_new/components/form_list.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +2 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +41 -18
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js +2 -3
- package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.d.ts.map +1 -1
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js +2 -3
- package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js.map +1 -1
- package/build/screens/message_actions_screen.js +4 -2
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/types/jolt_events/reaction_events.d.ts +1 -0
- package/build/types/jolt_events/reaction_events.d.ts.map +1 -1
- package/build/types/jolt_events/reaction_events.js.map +1 -1
- package/build/types/jolt_events/typing_events.d.ts +1 -0
- package/build/types/jolt_events/typing_events.d.ts.map +1 -1
- package/build/types/jolt_events/typing_events.js.map +1 -1
- package/build/types/resources/feature_resource.d.ts +7 -0
- package/build/types/resources/feature_resource.d.ts.map +1 -0
- package/build/types/resources/feature_resource.js +2 -0
- package/build/types/resources/feature_resource.js.map +1 -0
- package/build/utils/cache/messages_cache.d.ts +9 -0
- package/build/utils/cache/messages_cache.d.ts.map +1 -0
- package/build/utils/cache/messages_cache.js +89 -0
- package/build/utils/cache/messages_cache.js.map +1 -0
- package/build/utils/cache/optimistically_create_message.d.ts +2 -1
- package/build/utils/cache/optimistically_create_message.d.ts.map +1 -1
- package/build/utils/cache/optimistically_create_message.js +6 -3
- package/build/utils/cache/optimistically_create_message.js.map +1 -1
- package/build/utils/index.d.ts +0 -1
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +0 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/request/get_features.d.ts +11 -0
- package/build/utils/request/get_features.d.ts.map +1 -0
- package/build/utils/request/get_features.js +18 -0
- package/build/utils/request/get_features.js.map +1 -0
- package/package.json +2 -3
- package/src/components/conversation/message.tsx +80 -29
- package/src/components/conversation/message_form.tsx +6 -11
- package/src/components/conversation/messages_disabled_banners.tsx +69 -0
- package/src/components/conversation/reply_connectors.tsx +0 -3
- package/src/components/conversation/reply_shadow_message.tsx +9 -2
- package/src/components/conversation/typing_indicator.tsx +2 -6
- package/src/components/conversations/conversation_actions.tsx +1 -1
- package/src/components/conversations/conversations.tsx +7 -9
- package/src/contexts/conversation_context.tsx +34 -0
- package/src/hooks/use_broadcast_typing_status.ts +7 -3
- package/src/hooks/use_conversation_messages.ts +3 -1
- package/src/hooks/use_conversation_messages_jolt_events.ts +39 -81
- package/src/hooks/use_features.ts +47 -0
- package/src/hooks/use_message_create_or_update.ts +10 -9
- package/src/hooks/use_typing_indicators.ts +15 -3
- package/src/screens/conversation_details_screen.tsx +9 -6
- package/src/screens/conversation_new/components/form_list.tsx +3 -5
- package/src/screens/conversation_screen.tsx +58 -20
- package/src/screens/conversation_select_recipients/conversation_select_group_recipients_screen.tsx +2 -4
- package/src/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.tsx +2 -4
- package/src/screens/message_actions_screen.tsx +4 -2
- package/src/types/jolt_events/reaction_events.ts +1 -0
- package/src/types/jolt_events/typing_events.ts +1 -0
- package/src/types/resources/feature_resource.ts +6 -0
- package/src/utils/cache/messages_cache.ts +113 -0
- package/src/utils/cache/optimistically_create_message.ts +7 -2
- package/src/utils/index.ts +0 -1
- package/src/utils/request/get_features.ts +20 -0
- package/build/components/conversation/disabled_replies_banners.d.ts +0 -3
- package/build/components/conversation/disabled_replies_banners.d.ts.map +0 -1
- package/build/components/conversation/disabled_replies_banners.js +0 -41
- package/build/components/conversation/disabled_replies_banners.js.map +0 -1
- package/build/utils/replies_local_feature_flag.d.ts +0 -2
- package/build/utils/replies_local_feature_flag.d.ts.map +0 -1
- package/build/utils/replies_local_feature_flag.js +0 -3
- package/build/utils/replies_local_feature_flag.js.map +0 -1
- package/src/components/conversation/disabled_replies_banners.tsx +0 -58
- package/src/utils/replies_local_feature_flag.ts +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_broadcast_typing_status.d.ts","sourceRoot":"","sources":["../../src/hooks/use_broadcast_typing_status.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use_broadcast_typing_status.d.ts","sourceRoot":"","sources":["../../src/hooks/use_broadcast_typing_status.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,kBA2BpC,CAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useRef } from 'react';
|
|
2
2
|
import { useApiClient } from './use_api_client';
|
|
3
|
+
import { useConversationContext } from '../contexts/conversation_context';
|
|
3
4
|
const THROTTLE_INTERVAL = 3000; // 3 seconds
|
|
4
5
|
/**
|
|
5
6
|
* Hook for broadcasting typing status to other users in a conversation
|
|
@@ -8,7 +9,8 @@ const THROTTLE_INTERVAL = 3000; // 3 seconds
|
|
|
8
9
|
* after receiving a typing event. This is how we can show a steady typing indicator even
|
|
9
10
|
* if the user types once every 2.9 seconds.
|
|
10
11
|
*/
|
|
11
|
-
export const useBroadcastTypingStatus = (
|
|
12
|
+
export const useBroadcastTypingStatus = () => {
|
|
13
|
+
const { conversationId, currentPageReplyRootId } = useConversationContext();
|
|
12
14
|
const apiClient = useApiClient();
|
|
13
15
|
const lastBroadcastTime = useRef(0);
|
|
14
16
|
const broadcastTypingStatus = useCallback(() => {
|
|
@@ -20,12 +22,14 @@ export const useBroadcastTypingStatus = (conversationId) => {
|
|
|
20
22
|
apiClient.chat
|
|
21
23
|
.post({
|
|
22
24
|
url: `/me/conversations/${conversationId}/broadcast_typing_status`,
|
|
23
|
-
data: {
|
|
25
|
+
data: {
|
|
26
|
+
data: { type: 'TypingStatus', attributes: { reply_root_id: currentPageReplyRootId } },
|
|
27
|
+
},
|
|
24
28
|
})
|
|
25
29
|
.catch(error => {
|
|
26
30
|
console.error('Failed to broadcast typing status:', error);
|
|
27
31
|
});
|
|
28
|
-
}, [apiClient, conversationId]);
|
|
32
|
+
}, [apiClient.chat, conversationId, currentPageReplyRootId]);
|
|
29
33
|
return broadcastTypingStatus;
|
|
30
34
|
};
|
|
31
35
|
//# sourceMappingURL=use_broadcast_typing_status.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_broadcast_typing_status.js","sourceRoot":"","sources":["../../src/hooks/use_broadcast_typing_status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"use_broadcast_typing_status.js","sourceRoot":"","sources":["../../src/hooks/use_broadcast_typing_status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAEzE,MAAM,iBAAiB,GAAG,IAAI,CAAA,CAAC,YAAY;AAE3C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,EAAE;IAC3C,MAAM,EAAE,cAAc,EAAE,sBAAsB,EAAE,GAAG,sBAAsB,EAAE,CAAA;IAC3E,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,iBAAiB,GAAG,MAAM,CAAS,CAAC,CAAC,CAAA;IAE3C,MAAM,qBAAqB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEtB,IAAI,GAAG,GAAG,iBAAiB,CAAC,OAAO,GAAG,iBAAiB,EAAE,CAAC;YACxD,OAAM;QACR,CAAC;QAED,iBAAiB,CAAC,OAAO,GAAG,GAAG,CAAA;QAE/B,SAAS,CAAC,IAAI;aACX,IAAI,CAAC;YACJ,GAAG,EAAE,qBAAqB,cAAc,0BAA0B;YAClE,IAAI,EAAE;gBACJ,IAAI,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,EAAE,aAAa,EAAE,sBAAsB,EAAE,EAAE;aACtF;SACF,CAAC;aACD,KAAK,CAAC,KAAK,CAAC,EAAE;YACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAA;QAC5D,CAAC,CAAC,CAAA;IACN,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,sBAAsB,CAAC,CAAC,CAAA;IAE5D,OAAO,qBAAqB,CAAA;AAC9B,CAAC,CAAA","sourcesContent":["import { useCallback, useRef } from 'react'\nimport { useApiClient } from './use_api_client'\nimport { useConversationContext } from '../contexts/conversation_context'\n\nconst THROTTLE_INTERVAL = 3000 // 3 seconds\n\n/**\n * Hook for broadcasting typing status to other users in a conversation\n *\n * We only broadcast once every 3 seconds, but we show the typing indicator for 6 seconds\n * after receiving a typing event. This is how we can show a steady typing indicator even\n * if the user types once every 2.9 seconds.\n */\nexport const useBroadcastTypingStatus = () => {\n const { conversationId, currentPageReplyRootId } = useConversationContext()\n const apiClient = useApiClient()\n const lastBroadcastTime = useRef<number>(0)\n\n const broadcastTypingStatus = useCallback(() => {\n const now = Date.now()\n\n if (now - lastBroadcastTime.current < THROTTLE_INTERVAL) {\n return\n }\n\n lastBroadcastTime.current = now\n\n apiClient.chat\n .post({\n url: `/me/conversations/${conversationId}/broadcast_typing_status`,\n data: {\n data: { type: 'TypingStatus', attributes: { reply_root_id: currentPageReplyRootId } },\n },\n })\n .catch(error => {\n console.error('Failed to broadcast typing status:', error)\n })\n }, [apiClient.chat, conversationId, currentPageReplyRootId])\n\n return broadcastTypingStatus\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_conversation_messages.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,wBAAwB,EAAwB,MAAM,oBAAoB,CAAA;AAGnF,eAAO,MAAM,uBAAuB,uCACE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,SACvF,wBAAwB;;;;;;
|
|
1
|
+
{"version":3,"file":"use_conversation_messages.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,wBAAwB,EAAwB,MAAM,oBAAoB,CAAA;AAGnF,eAAO,MAAM,uBAAuB,uCACE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,SACvF,wBAAwB;;;;;;CAoBhC,CAAA"}
|
|
@@ -5,7 +5,8 @@ export const useConversationMessages = ({ conversation_id, reply_root_id }, opts
|
|
|
5
5
|
const { data, refetch, isRefetching, fetchNextPage } = useSuspensePaginator(getMessagesRequestArgs({ conversation_id, reply_root_id }), opts);
|
|
6
6
|
const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id });
|
|
7
7
|
const messages = useMemo(() => data
|
|
8
|
-
.filter(message => !message.deletedAt
|
|
8
|
+
.filter(message => (!message.deletedAt || message.replyRootId) &&
|
|
9
|
+
(message.attachments?.length || message.text?.length))
|
|
9
10
|
.sort((a, b) => -a.id.localeCompare(b.id)), [data]);
|
|
10
11
|
return { messages, refetch, isRefetching, fetchNextPage, queryKey };
|
|
11
12
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_conversation_messages.js","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAA4B,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAE3F,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,EAAE,eAAe,EAAE,aAAa,EAA8D,EAC9F,IAA+B,EAC/B,EAAE;IACF,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,oBAAoB,CACzE,sBAAsB,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,EAC1D,IAAI,CACL,CAAA;IACD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,CAAA;IACxE,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CACH,IAAI;SACD,MAAM,CACL,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"use_conversation_messages.js","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAA4B,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAE3F,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,EAAE,eAAe,EAAE,aAAa,EAA8D,EAC9F,IAA+B,EAC/B,EAAE;IACF,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,oBAAoB,CACzE,sBAAsB,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,EAC1D,IAAI,CACL,CAAA;IACD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,CAAA;IACxE,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CACH,IAAI;SACD,MAAM,CACL,OAAO,CAAC,EAAE,CACR,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;QAC3C,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CACxD;SACA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9C,CAAC,IAAI,CAAC,CACP,CAAA;IAED,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAA;AACrE,CAAC,CAAA","sourcesContent":["import { useMemo } from 'react'\nimport { MessageResource } from '../types'\nimport { SuspensePaginatorOptions, useSuspensePaginator } from './use_suspense_api'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\n\nexport const useConversationMessages = (\n { conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string | null },\n opts?: SuspensePaginatorOptions\n) => {\n const { data, refetch, isRefetching, fetchNextPage } = useSuspensePaginator<MessageResource>(\n getMessagesRequestArgs({ conversation_id, reply_root_id }),\n opts\n )\n const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id })\n const messages = useMemo(\n () =>\n data\n .filter(\n message =>\n (!message.deletedAt || message.replyRootId) &&\n (message.attachments?.length || message.text?.length)\n )\n .sort((a, b) => -a.id.localeCompare(b.id)),\n [data]\n )\n\n return { messages, refetch, isRefetching, fetchNextPage, queryKey }\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"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FeatureResource } from '../types/resources/feature_resource';
|
|
2
|
+
export declare function useFeatures(): {
|
|
3
|
+
features: FeatureResource[];
|
|
4
|
+
featureEnabled: (featureName: string) => boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare const availableFeatures: {
|
|
7
|
+
threaded_replies: string;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=use_features.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use_features.d.ts","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAG1E,wBAAgB,WAAW;;kCAiBT,MAAM;EASvB;AAED,eAAO,MAAM,iBAAiB;;CAE7B,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features';
|
|
4
|
+
import { useApiClient } from './use_api_client';
|
|
5
|
+
export function useFeatures() {
|
|
6
|
+
const apiClient = useApiClient();
|
|
7
|
+
const requestArgs = getFeaturesRequestArgs();
|
|
8
|
+
const { data } = useSuspenseQuery({
|
|
9
|
+
queryKey: getFeaturesQueryKey(),
|
|
10
|
+
queryFn: () => {
|
|
11
|
+
return apiClient.chat
|
|
12
|
+
.get(requestArgs)
|
|
13
|
+
.catch(() => stableEmptyFeatures);
|
|
14
|
+
},
|
|
15
|
+
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
16
|
+
});
|
|
17
|
+
const features = data.data;
|
|
18
|
+
const featureEnabled = useCallback((featureName) => features.some(feature => feature.name === featureName && feature.enabled), [features]);
|
|
19
|
+
return {
|
|
20
|
+
features,
|
|
21
|
+
featureEnabled,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export const availableFeatures = {
|
|
25
|
+
threaded_replies: 'QA_MOBILE_threaded_replies',
|
|
26
|
+
};
|
|
27
|
+
const stableEmptyFeatures = {
|
|
28
|
+
data: [],
|
|
29
|
+
links: {},
|
|
30
|
+
meta: {
|
|
31
|
+
count: 0,
|
|
32
|
+
totalCount: 0,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=use_features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use_features.js","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAI/C,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAiC,WAAW,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAA;QACrC,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,YAAY;KACvC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAE1B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,WAAmB,EAAE,EAAE,CACtB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,EAC3E,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,OAAO;QACL,QAAQ;QACR,cAAc;KACf,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,gBAAgB,EAAE,4BAA4B;CAC/C,CAAA;AAED,MAAM,mBAAmB,GAAmC;IAC1D,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,CAAC;KACd;CACF,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'\nimport { useApiClient } from './use_api_client'\nimport type { FeatureResource } from '../types/resources/feature_resource'\nimport { ApiCollection } from '../types'\n\nexport function useFeatures() {\n const apiClient = useApiClient()\n const requestArgs = getFeaturesRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getFeaturesQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiCollection<FeatureResource>>(requestArgs)\n .catch(() => stableEmptyFeatures)\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n\n const features = data.data\n\n const featureEnabled = useCallback(\n (featureName: string) =>\n features.some(feature => feature.name === featureName && feature.enabled),\n [features]\n )\n\n return {\n features,\n featureEnabled,\n }\n}\n\nexport const availableFeatures = {\n threaded_replies: 'QA_MOBILE_threaded_replies',\n}\n\nconst stableEmptyFeatures: ApiCollection<FeatureResource> = {\n data: [],\n links: {},\n meta: {\n count: 0,\n totalCount: 0,\n },\n}\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":"AAAA,OAAO,EAAgB,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EACL,uBAAuB,EACvB,MAAM,EACN,+BAA+B,EAC/B,uBAAuB,GACxB,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAA;AAC1F,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAA;AAC1F,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAA;AAQ5E,MAAM,UAAU,wBAAwB,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAS;IACtF,MAAM,SAAS,GAAG,OAAO,EAAE,EAAE,IAAI,IAAI,CAAA;IACrC,MAAM,SAAS,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAA;IAExC,MAAM,QAAQ,GAAG,WAAW,CAAC;QAC3B,UAAU,EAAE,CAAC,EACX,IAAI,EACJ,WAAW,GAIZ,EAAE,EAAE;YACH,MAAM,aAAa,GAAG,sBAAsB,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAA;YACjF,MAAM,qBAAqB,GAAG,MAAM,CAAC,WAAW,CAC9C,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAC5E,CAAA;YACD,IAAI,UAAU,GAAQ;gBACpB,IAAI;gBACJ,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5D,CAAA;YACD,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,aAAa,GAAG,YAAY,EAAE,CAAA;gBACpC,UAAU,CAAC,cAAc,GAAG,aAAa,CAAA;gBACzC,4BAA4B,CAAC,aAAa,CAAC,CAAA;YAC7C,CAAC;YACD,MAAM,IAAI,GAAG;gBACX,GAAG,aAAa,CAAC,IAAI;gBACrB,IAAI,EAAE;oBACJ,IAAI,EAAE,SAAS;oBACf,UAAU;iBACX;gBACD,MAAM,EAAE,qBAAqB;aAC9B,CAAA;YAED,IAAI,SAAS,EAAE,CAAC;gBACd,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,CAA+B;oBACxD,GAAG,EAAE,qBAAqB,cAAc,aAAa,SAAS,EAAE;oBAChE,IAAI;iBACL,CAAC,CAAA;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAA+B;oBACvD,GAAG,EAAE,qBAAqB,cAAc,WAAW;oBACnD,IAAI;iBACL,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QACD,QAAQ,EAAE,KAAK,EAAE,EACf,IAAI,EACJ,WAAW,GAIZ,EAAE,EAAE;YACH,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;gBACzB,MAAM,iBAAiB,GAAG,2BAA2B,CAAC;oBACpD,cAAc;oBACd,OAAO;oBACP,IAAI;iBACL,CAAC,CAAA;gBAEF,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAA;YACvC,CAAC;YAED,MAAM,iBAAiB,GAAG,2BAA2B,CAAC;gBACpD,cAAc;gBACd,OAAO;gBACP,IAAI;gBACJ,WAAW;gBACX,aAAa;aACd,CAAC,CAAA;YAEF,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAA;QACvC,CAAC;QACD,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;YACrC,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,OAAO,IAAI,EAAE,CAAA;YACpD,MAAM,CAAC,iBAAiB,EAAE,CAAA;YAE1B,8DAA8D;YAC9D,IAAI,iBAAiB,EAAE,CAAC;gBACtB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAA;gBACzE,eAAe,CAAC,YAAY,CAC1B,QAAQ,EACR,CAAC,IAA8D,EAAE,EAAE,CACjE,uBAAuB,CAAC;oBACtB,IAAI;oBACJ,MAAM,EAAE,iBAAiB;oBACzB,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;wBAC/B,GAAG,IAAI;wBACP,KAAK,EAAE,KAAK,CAAC,OAAO,IAAI,wBAAwB;wBAChD,OAAO,EAAE,KAAK,EAAE,4BAA4B;qBAC7C,CAAC;iBACH,CAAC,CACL,CAAA;YACH,CAAC;QACH,CAAC;QACD,SAAS,EAAE,CAAC,MAAoC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE;YACtE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,OAAO,IAAI,EAAE,CAAA;YACpD,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAA;YAElC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAA;YAEzE,mDAAmD;YACnD,IAAI,iBAAiB,EAAE,CAAC;gBACtB,eAAe,CAAC,YAAY,CAAY,QAAQ,EAAE,IAAI,CAAC,EAAE,CACvD,uBAAuB,CAAC;oBACtB,IAAI;oBACJ,MAAM,EAAE,iBAAiB;iBAC1B,CAAC,CACH,CAAA;YACH,CAAC;YAED,4BAA4B;YAC5B,eAAe,CAAC,YAAY,CAAY,QAAQ,EAAE,IAAI,CAAC,EAAE,CACvD,+BAA+B,CAAC;gBAC9B,IAAI;gBACJ,MAAM,EAAE,cAAc;aACvB,CAAC,CACH,CAAA;QACH,CAAC;KACF,CAAC,CAAA;IAEF,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAyB;IAC5D,OAAO,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;AACnD,CAAC;AACD,MAAM,UAAU,YAAY,CAAC,OAAyB;IACpD,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;AACzD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,YAAY;IACnB,OAAO,sCAAsC;SAC1C,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;SAChE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;AAC1E,CAAC","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"]}
|
|
@@ -5,7 +5,7 @@ import { PersonTyping } from './use_typing_status_cache';
|
|
|
5
5
|
* The query function itself doesn't do anything, but we add to the cache
|
|
6
6
|
* from useTypingStatusCache when we receive a typing event.
|
|
7
7
|
*/
|
|
8
|
-
export declare const useTypingIndicators: (
|
|
8
|
+
export declare const useTypingIndicators: () => PersonTyping[];
|
|
9
9
|
/**
|
|
10
10
|
* Format text for typing indicators based on the number of people typing
|
|
11
11
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_typing_indicators.d.ts","sourceRoot":"","sources":["../../src/hooks/use_typing_indicators.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"use_typing_indicators.d.ts","sourceRoot":"","sources":["../../src/hooks/use_typing_indicators.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAIxD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,sBAuB/B,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,WAAY,YAAY,EAAE,KAAG,MAU/D,CAAA"}
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
import { useQuery } from '@tanstack/react-query';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { useCurrentPerson } from './use_current_person';
|
|
4
|
+
import { useConversationContext } from '../contexts/conversation_context';
|
|
4
5
|
/**
|
|
5
6
|
* Hook for getting people currently typing in a conversation
|
|
6
7
|
*
|
|
7
8
|
* The query function itself doesn't do anything, but we add to the cache
|
|
8
9
|
* from useTypingStatusCache when we receive a typing event.
|
|
9
10
|
*/
|
|
10
|
-
export const useTypingIndicators = (
|
|
11
|
+
export const useTypingIndicators = () => {
|
|
12
|
+
const { conversationId, currentPageReplyRootId } = useConversationContext();
|
|
11
13
|
const cacheKey = useMemo(() => ['conversationTyping', String(conversationId)], [conversationId]);
|
|
12
14
|
const currentPerson = useCurrentPerson();
|
|
15
|
+
const isCurrentPerson = (authorId) => authorId === currentPerson?.id;
|
|
16
|
+
const inMainConversationView = !currentPageReplyRootId;
|
|
13
17
|
const now = Date.now();
|
|
14
18
|
const { data } = useQuery({
|
|
15
19
|
queryKey: cacheKey,
|
|
16
20
|
queryFn: () => stableArray,
|
|
17
21
|
initialData: stableArray,
|
|
18
22
|
});
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
return data.filter(({ author_id, expires, reply_root_id }) => {
|
|
24
|
+
if (isCurrentPerson(author_id))
|
|
25
|
+
return false;
|
|
26
|
+
if (now > expires)
|
|
27
|
+
return false;
|
|
28
|
+
// If you are in the main conversation view, you will see any message sent
|
|
29
|
+
if (inMainConversationView)
|
|
30
|
+
return true;
|
|
31
|
+
// If you are in a reply view, you will only see messages sent in this reply thread
|
|
32
|
+
return reply_root_id === currentPageReplyRootId;
|
|
33
|
+
});
|
|
21
34
|
};
|
|
22
35
|
/**
|
|
23
36
|
* Format text for typing indicators based on the number of people typing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_typing_indicators.js","sourceRoot":"","sources":["../../src/hooks/use_typing_indicators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"use_typing_indicators.js","sourceRoot":"","sources":["../../src/hooks/use_typing_indicators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAEzE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,EAAE;IACtC,MAAM,EAAE,cAAc,EAAE,sBAAsB,EAAE,GAAG,sBAAsB,EAAE,CAAA;IAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,oBAAoB,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAA;IAChG,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAA;IACxC,MAAM,eAAe,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,QAAQ,KAAK,aAAa,EAAE,EAAE,CAAA;IAC5E,MAAM,sBAAsB,GAAG,CAAC,sBAAsB,CAAA;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;QACxB,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW;QAC1B,WAAW,EAAE,WAAW;KACzB,CAAC,CAAA;IAEF,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE;QAC3D,IAAI,eAAe,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAA;QAC5C,IAAI,GAAG,GAAG,OAAO;YAAE,OAAO,KAAK,CAAA;QAE/B,0EAA0E;QAC1E,IAAI,sBAAsB;YAAE,OAAO,IAAI,CAAA;QAEvC,mFAAmF;QACnF,OAAO,aAAa,KAAK,sBAAsB,CAAA;IACjD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAsB,EAAU,EAAE;IACvE,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,CAAA;IAE7B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,eAAe,CAAA;IAChD,CAAC;SAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,QAAQ,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,gBAAgB,CAAA;IAC9E,CAAC;SAAM,CAAC;QACN,OAAO,8BAA8B,CAAA;IACvC,CAAC;AACH,CAAC,CAAA;AAED,MAAM,WAAW,GAAmB,EAAE,CAAA","sourcesContent":["import { useQuery } from '@tanstack/react-query'\nimport { useMemo } from 'react'\nimport { PersonTyping } from './use_typing_status_cache'\nimport { useCurrentPerson } from './use_current_person'\nimport { useConversationContext } from '../contexts/conversation_context'\n\n/**\n * Hook for getting people currently typing in a conversation\n *\n * The query function itself doesn't do anything, but we add to the cache\n * from useTypingStatusCache when we receive a typing event.\n */\nexport const useTypingIndicators = () => {\n const { conversationId, currentPageReplyRootId } = useConversationContext()\n const cacheKey = useMemo(() => ['conversationTyping', String(conversationId)], [conversationId])\n const currentPerson = useCurrentPerson()\n const isCurrentPerson = (authorId: number) => authorId === currentPerson?.id\n const inMainConversationView = !currentPageReplyRootId\n const now = Date.now()\n const { data } = useQuery({\n queryKey: cacheKey,\n queryFn: () => stableArray,\n initialData: stableArray,\n })\n\n return data.filter(({ author_id, expires, reply_root_id }) => {\n if (isCurrentPerson(author_id)) return false\n if (now > expires) return false\n\n // If you are in the main conversation view, you will see any message sent\n if (inMainConversationView) return true\n\n // If you are in a reply view, you will only see messages sent in this reply thread\n return reply_root_id === currentPageReplyRootId\n })\n}\n\n/**\n * Format text for typing indicators based on the number of people typing\n */\nexport const getTypingIndicatorText = (people: PersonTyping[]): string => {\n if (!people.length) return ''\n\n if (people.length === 1) {\n return `${people[0].author_name} is typing...`\n } else if (people.length === 2) {\n return `${people[0].author_name} and ${people[1].author_name} are typing...`\n } else {\n return 'Several people are typing...'\n }\n}\n\nconst stableArray: PersonTyping[] = []\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation_details_screen.d.ts","sourceRoot":"","sources":["../../src/screens/conversation_details_screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,iBAAiB,EAAiB,MAAM,0BAA0B,CAAA;AACzF,OAAO,KAQN,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"conversation_details_screen.d.ts","sourceRoot":"","sources":["../../src/screens/conversation_details_screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,iBAAiB,EAAiB,MAAM,0BAA0B,CAAA;AACzF,OAAO,KAQN,MAAM,OAAO,CAAA;AA0Ed,MAAM,MAAM,8BAA8B,GAAG,iBAAiB,CAAC;IAC7D,eAAe,EAAE,MAAM,CAAA;CACxB,CAAC,CAAA;AAEF,wBAAgB,yBAAyB,CAAC,EAAE,KAAK,EAAE,EAAE,8BAA8B,qBAySlF"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { StackActions, useNavigation } from '@react-navigation/native';
|
|
2
2
|
import React, { useCallback, useEffect, useState, useRef, } from 'react';
|
|
3
|
-
import { StyleSheet, TextInput, View, Pressable, Alert, Platform, } from 'react-native';
|
|
3
|
+
import { FlatList, StyleSheet, TextInput, View, Pressable, Alert, Platform, } from 'react-native';
|
|
4
4
|
import { HeaderTitle as ElementsHeaderTitle } from '@react-navigation/elements';
|
|
5
5
|
import { Badge, ChildNotice, Heading, Icon, Person, Switch, Text, TextButton, } from '../components';
|
|
6
6
|
import { useSuspensePaginator, useTheme } from '../hooks';
|
|
7
7
|
import { useConversation, useConversationDelete, useConversationDisableReplies, useConversationMute, useConversationUpdate, } from '../hooks/use_conversation';
|
|
8
8
|
import { isDefined } from '../types';
|
|
9
9
|
import { HeaderTextButton } from '../components/display/platform_modal_header_buttons';
|
|
10
|
-
import { FlashList } from '@shopify/flash-list';
|
|
11
10
|
import { tokens } from '../vendor/tapestry/tokens';
|
|
11
|
+
import { useFeatures } from '../hooks/use_features';
|
|
12
|
+
import { availableFeatures } from '../hooks/use_features';
|
|
12
13
|
// =========================================
|
|
13
14
|
// ====== Factory Constants & Types ========
|
|
14
15
|
// =========================================
|
|
@@ -30,6 +31,8 @@ export function ConversationDetailsScreen({ route }) {
|
|
|
30
31
|
const { repliesDisabled, setRepliesDisabled } = useConversationDisableReplies(route.params);
|
|
31
32
|
const { mutate: saveTitle } = useConversationUpdate(route.params);
|
|
32
33
|
const { mutate: deleteConversation } = useConversationDelete(route.params);
|
|
34
|
+
const { featureEnabled } = useFeatures();
|
|
35
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies);
|
|
33
36
|
const trimmedTitle = title.trim();
|
|
34
37
|
const emptyTitle = trimmedTitle === '' || title === null;
|
|
35
38
|
const canUpdate = conversation.memberAbility?.canUpdate || false;
|
|
@@ -139,8 +142,8 @@ export function ConversationDetailsScreen({ route }) {
|
|
|
139
142
|
{
|
|
140
143
|
type: canUpdate ? SectionTypes.setting : SectionTypes.hidden,
|
|
141
144
|
data: {
|
|
142
|
-
title: 'Freeze conversation',
|
|
143
|
-
subtitle: 'Disables replies for everyone except leaders.',
|
|
145
|
+
title: repliesEnabled ? 'Leader messages only' : 'Freeze conversation',
|
|
146
|
+
subtitle: repliesEnabled ? undefined : 'Disables replies for everyone except leaders.',
|
|
144
147
|
rightItem: (<Switch value={repliesDisabled} onChange={() => setRepliesDisabled(!repliesDisabled)}/>),
|
|
145
148
|
},
|
|
146
149
|
showBottomBorder: true,
|
|
@@ -184,7 +187,7 @@ export function ConversationDetailsScreen({ route }) {
|
|
|
184
187
|
.map(({ type }, index) => (type === SectionTypes.header ? index : undefined))
|
|
185
188
|
.filter(isDefined);
|
|
186
189
|
return (<View style={styles.listContainer}>
|
|
187
|
-
<
|
|
190
|
+
<FlatList data={listData} contentContainerStyle={styles.contentContainer} renderItem={({ item, index }) => {
|
|
188
191
|
const [isStart, isEnd] = [
|
|
189
192
|
index === 0 || headerIndices.includes(index),
|
|
190
193
|
index === listData.length - 1 || headerIndices.includes(index + 1),
|
|
@@ -213,7 +216,7 @@ export function ConversationDetailsScreen({ route }) {
|
|
|
213
216
|
default:
|
|
214
217
|
return null;
|
|
215
218
|
}
|
|
216
|
-
}} onEndReached={fetchNextPageOfMembers}/>
|
|
219
|
+
}} onEndReached={() => fetchNextPageOfMembers()}/>
|
|
217
220
|
</View>);
|
|
218
221
|
}
|
|
219
222
|
function ListSection({ isStart, isEnd, showBottomBorder, outerStyle, innerStyle, children, }) {
|