@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.
Files changed (137) hide show
  1. package/build/components/conversation/message.d.ts +2 -1
  2. package/build/components/conversation/message.d.ts.map +1 -1
  3. package/build/components/conversation/message.js +53 -26
  4. package/build/components/conversation/message.js.map +1 -1
  5. package/build/components/conversation/message_form.d.ts.map +1 -1
  6. package/build/components/conversation/message_form.js +7 -6
  7. package/build/components/conversation/message_form.js.map +1 -1
  8. package/build/components/conversation/messages_disabled_banners.d.ts +3 -0
  9. package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -0
  10. package/build/components/conversation/messages_disabled_banners.js +53 -0
  11. package/build/components/conversation/messages_disabled_banners.js.map +1 -0
  12. package/build/components/conversation/reply_connectors.d.ts.map +1 -1
  13. package/build/components/conversation/reply_connectors.js +0 -5
  14. package/build/components/conversation/reply_connectors.js.map +1 -1
  15. package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
  16. package/build/components/conversation/reply_shadow_message.js +8 -3
  17. package/build/components/conversation/reply_shadow_message.js.map +1 -1
  18. package/build/components/conversation/typing_indicator.d.ts +1 -5
  19. package/build/components/conversation/typing_indicator.d.ts.map +1 -1
  20. package/build/components/conversation/typing_indicator.js +2 -2
  21. package/build/components/conversation/typing_indicator.js.map +1 -1
  22. package/build/components/conversations/conversation_actions.d.ts.map +1 -1
  23. package/build/components/conversations/conversation_actions.js +1 -2
  24. package/build/components/conversations/conversation_actions.js.map +1 -1
  25. package/build/components/conversations/conversations.d.ts.map +1 -1
  26. package/build/components/conversations/conversations.js +2 -3
  27. package/build/components/conversations/conversations.js.map +1 -1
  28. package/build/contexts/conversation_context.d.ts +13 -0
  29. package/build/contexts/conversation_context.d.ts.map +1 -0
  30. package/build/contexts/conversation_context.js +14 -0
  31. package/build/contexts/conversation_context.js.map +1 -0
  32. package/build/hooks/use_broadcast_typing_status.d.ts +1 -1
  33. package/build/hooks/use_broadcast_typing_status.d.ts.map +1 -1
  34. package/build/hooks/use_broadcast_typing_status.js +7 -3
  35. package/build/hooks/use_broadcast_typing_status.js.map +1 -1
  36. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  37. package/build/hooks/use_conversation_messages.js +2 -1
  38. package/build/hooks/use_conversation_messages.js.map +1 -1
  39. package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
  40. package/build/hooks/use_conversation_messages_jolt_events.js +23 -70
  41. package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
  42. package/build/hooks/use_features.d.ts +9 -0
  43. package/build/hooks/use_features.d.ts.map +1 -0
  44. package/build/hooks/use_features.js +35 -0
  45. package/build/hooks/use_features.js.map +1 -0
  46. package/build/hooks/use_message_create_or_update.d.ts +0 -2
  47. package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
  48. package/build/hooks/use_message_create_or_update.js +10 -8
  49. package/build/hooks/use_message_create_or_update.js.map +1 -1
  50. package/build/hooks/use_typing_indicators.d.ts +1 -1
  51. package/build/hooks/use_typing_indicators.d.ts.map +1 -1
  52. package/build/hooks/use_typing_indicators.js +16 -3
  53. package/build/hooks/use_typing_indicators.js.map +1 -1
  54. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  55. package/build/screens/conversation_details_screen.js +9 -6
  56. package/build/screens/conversation_details_screen.js.map +1 -1
  57. package/build/screens/conversation_new/components/form_list.d.ts +2 -2
  58. package/build/screens/conversation_new/components/form_list.d.ts.map +1 -1
  59. package/build/screens/conversation_new/components/form_list.js +2 -3
  60. package/build/screens/conversation_new/components/form_list.js.map +1 -1
  61. package/build/screens/conversation_screen.d.ts +2 -1
  62. package/build/screens/conversation_screen.d.ts.map +1 -1
  63. package/build/screens/conversation_screen.js +41 -18
  64. package/build/screens/conversation_screen.js.map +1 -1
  65. package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.d.ts.map +1 -1
  66. package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js +2 -3
  67. package/build/screens/conversation_select_recipients/conversation_select_group_recipients_screen.js.map +1 -1
  68. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.d.ts.map +1 -1
  69. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js +2 -3
  70. package/build/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.js.map +1 -1
  71. package/build/screens/message_actions_screen.js +4 -2
  72. package/build/screens/message_actions_screen.js.map +1 -1
  73. package/build/types/jolt_events/reaction_events.d.ts +1 -0
  74. package/build/types/jolt_events/reaction_events.d.ts.map +1 -1
  75. package/build/types/jolt_events/reaction_events.js.map +1 -1
  76. package/build/types/jolt_events/typing_events.d.ts +1 -0
  77. package/build/types/jolt_events/typing_events.d.ts.map +1 -1
  78. package/build/types/jolt_events/typing_events.js.map +1 -1
  79. package/build/types/resources/feature_resource.d.ts +7 -0
  80. package/build/types/resources/feature_resource.d.ts.map +1 -0
  81. package/build/types/resources/feature_resource.js +2 -0
  82. package/build/types/resources/feature_resource.js.map +1 -0
  83. package/build/utils/cache/messages_cache.d.ts +9 -0
  84. package/build/utils/cache/messages_cache.d.ts.map +1 -0
  85. package/build/utils/cache/messages_cache.js +89 -0
  86. package/build/utils/cache/messages_cache.js.map +1 -0
  87. package/build/utils/cache/optimistically_create_message.d.ts +2 -1
  88. package/build/utils/cache/optimistically_create_message.d.ts.map +1 -1
  89. package/build/utils/cache/optimistically_create_message.js +6 -3
  90. package/build/utils/cache/optimistically_create_message.js.map +1 -1
  91. package/build/utils/index.d.ts +0 -1
  92. package/build/utils/index.d.ts.map +1 -1
  93. package/build/utils/index.js +0 -1
  94. package/build/utils/index.js.map +1 -1
  95. package/build/utils/request/get_features.d.ts +11 -0
  96. package/build/utils/request/get_features.d.ts.map +1 -0
  97. package/build/utils/request/get_features.js +18 -0
  98. package/build/utils/request/get_features.js.map +1 -0
  99. package/package.json +2 -3
  100. package/src/components/conversation/message.tsx +80 -29
  101. package/src/components/conversation/message_form.tsx +6 -11
  102. package/src/components/conversation/messages_disabled_banners.tsx +69 -0
  103. package/src/components/conversation/reply_connectors.tsx +0 -3
  104. package/src/components/conversation/reply_shadow_message.tsx +9 -2
  105. package/src/components/conversation/typing_indicator.tsx +2 -6
  106. package/src/components/conversations/conversation_actions.tsx +1 -1
  107. package/src/components/conversations/conversations.tsx +7 -9
  108. package/src/contexts/conversation_context.tsx +34 -0
  109. package/src/hooks/use_broadcast_typing_status.ts +7 -3
  110. package/src/hooks/use_conversation_messages.ts +3 -1
  111. package/src/hooks/use_conversation_messages_jolt_events.ts +39 -81
  112. package/src/hooks/use_features.ts +47 -0
  113. package/src/hooks/use_message_create_or_update.ts +10 -9
  114. package/src/hooks/use_typing_indicators.ts +15 -3
  115. package/src/screens/conversation_details_screen.tsx +9 -6
  116. package/src/screens/conversation_new/components/form_list.tsx +3 -5
  117. package/src/screens/conversation_screen.tsx +58 -20
  118. package/src/screens/conversation_select_recipients/conversation_select_group_recipients_screen.tsx +2 -4
  119. package/src/screens/conversation_select_recipients/conversation_select_teams_i_lead_recipients_screen.tsx +2 -4
  120. package/src/screens/message_actions_screen.tsx +4 -2
  121. package/src/types/jolt_events/reaction_events.ts +1 -0
  122. package/src/types/jolt_events/typing_events.ts +1 -0
  123. package/src/types/resources/feature_resource.ts +6 -0
  124. package/src/utils/cache/messages_cache.ts +113 -0
  125. package/src/utils/cache/optimistically_create_message.ts +7 -2
  126. package/src/utils/index.ts +0 -1
  127. package/src/utils/request/get_features.ts +20 -0
  128. package/build/components/conversation/disabled_replies_banners.d.ts +0 -3
  129. package/build/components/conversation/disabled_replies_banners.d.ts.map +0 -1
  130. package/build/components/conversation/disabled_replies_banners.js +0 -41
  131. package/build/components/conversation/disabled_replies_banners.js.map +0 -1
  132. package/build/utils/replies_local_feature_flag.d.ts +0 -2
  133. package/build/utils/replies_local_feature_flag.d.ts.map +0 -1
  134. package/build/utils/replies_local_feature_flag.js +0 -3
  135. package/build/utils/replies_local_feature_flag.js.map +0 -1
  136. package/src/components/conversation/disabled_replies_banners.tsx +0 -58
  137. 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":"AAKA;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,mBAAoB,MAAM,GAAG,MAAM,eAwBvE,CAAA"}
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 = (conversationId) => {
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: { data: { type: 'TypingStatus', attributes: {} } },
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;AAE/C,MAAM,iBAAiB,GAAG,IAAI,CAAA,CAAC,YAAY;AAE3C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,cAA+B,EAAE,EAAE;IAC1E,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,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE;SACzD,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,EAAE,cAAc,CAAC,CAAC,CAAA;IAE/B,OAAO,qBAAqB,CAAA;AAC9B,CAAC,CAAA","sourcesContent":["import { useCallback, useRef } from 'react'\nimport { useApiClient } from './use_api_client'\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 = (conversationId: string | number) => {\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: { data: { type: 'TypingStatus', attributes: {} } },\n })\n .catch(error => {\n console.error('Failed to broadcast typing status:', error)\n })\n }, [apiClient, conversationId])\n\n return broadcastTypingStatus\n}\n"]}
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;;;;;;CAkBhC,CAAA"}
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 && (message.attachments?.length || message.text?.length))
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,CACvF;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 => !message.deletedAt && (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
+ {"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":"AAoBA,UAAU,KAAK;IACb,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,iCAAiC,CAAC,EAAE,cAAc,EAAE,EAAE,KAAK,QA+H1E"}
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, updateOrCreateRecordInPagesData, updateRecordInPagesData, } from '../utils';
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
- queryClient.setQueryData(messagesQueryKey, prev => {
34
- if (e.event === 'message.created') {
35
- // Before adding the new message, remove any pending temporary messages
36
- // with matching text to prevent duplicates from race conditions
37
- let dataAfterTempRemoval = prev;
38
- if (prev && message.text && message.mine) {
39
- dataAfterTempRemoval = deleteRecordInPagesData({
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
- const { data } = e.data;
78
- const message = { id: data.message_sort_key };
79
- queryClient.setQueryData(messagesQueryKey, prev => updateRecordInPagesData({
80
- data: prev,
81
- record: message,
82
- processRecord: (record, oldMessage) => {
83
- const reactionCounts = oldMessage.reactionCounts || [];
84
- let foundMatch = false;
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;AAMxH,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;;;GAuE5D;AAED,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAEvE;AACD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAE/D"}
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({ conversation_id: conversationId });
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({ conversation_id: conversationId });
93
+ const queryKey = getMessagesQueryKey({
94
+ conversation_id: conversationId,
95
+ reply_root_id: replyRootId,
96
+ });
89
97
  // First remove the optimistic message if it exists
90
98
  if (optimisticMessage) {
91
99
  chatQueryClient.setQueryData(queryKey, data => deleteRecordInPagesData({
@@ -102,12 +110,6 @@ export function useMessageCreateOrUpdate({ conversationId, message, replyRootId
102
110
  });
103
111
  return mutation;
104
112
  }
105
- export function isTemporaryMessageId(messageId) {
106
- return !!messageId && messageId.endsWith('-temp');
107
- }
108
- export function isNewMessage(message) {
109
- return !message?.id || isTemporaryMessageId(message.id);
110
- }
111
113
  /**
112
114
  * Generate a random UUID (v4) for idempotent keys.
113
115
  * Uses Math.random, which is not cryptographically secure.
@@ -1 +1 @@
1
- {"version":3,"file":"use_message_create_or_update.js","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"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","sourcesContent":["import { InfiniteData, useMutation } from '@tanstack/react-query'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { useApiClient } from './use_api_client'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { chatQueryClient } from '../contexts/api_provider'\nimport {\n deleteRecordInPagesData,\n Haptic,\n updateOrCreateRecordInPagesData,\n updateRecordInPagesData,\n} from '../utils'\nimport { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'\nimport { useCurrentPerson } from './use_current_person'\nimport { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message'\nimport { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message'\nimport { startMessageCreationTracking } from '../utils/performance_tracking'\n\ninterface Props {\n conversationId: number\n message?: MessageResource\n replyRootId?: string | null\n}\n\nexport function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {\n const messageId = message?.id || null\n const isEditing = !isNewMessage(message)\n const apiClient = useApiClient()\n const currentPerson = useCurrentPerson()\n\n const mutation = useMutation({\n mutationFn: ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n const requestParams = getMessagesRequestArgs({ conversation_id: conversationId })\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n let attributes: any = {\n text,\n ...(attachments ? { attachments } : {}),\n ...(replyRootId ? { reply_root: { id: replyRootId } } : {}),\n }\n if (!isEditing) {\n const idempotentKey = insecureUUID()\n attributes.idempotent_key = idempotentKey\n startMessageCreationTracking(idempotentKey)\n }\n const data = {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes,\n },\n fields: fieldsWithValueJoined,\n }\n\n if (isEditing) {\n return apiClient.chat.patch<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages/${messageId}`,\n data,\n })\n } else {\n return apiClient.chat.post<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages`,\n data,\n })\n }\n },\n onMutate: async ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n if (message && isEditing) {\n const optimisticMessage = optimisticallyUpdateMessage({\n conversationId,\n message,\n text,\n })\n\n return { message: optimisticMessage }\n }\n\n const optimisticMessage = optimisticallyCreateMessage({\n conversationId,\n message,\n text,\n attachments,\n currentPerson,\n })\n\n return { message: optimisticMessage }\n },\n onError: (error, variables, context) => {\n const { message: optimisticMessage } = context || {}\n Haptic.notificationError()\n\n // Add error to the optimistic message from the cache on error\n if (optimisticMessage) {\n const queryKey = getMessagesQueryKey({ conversation_id: conversationId })\n chatQueryClient.setQueryData(\n queryKey,\n (data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>\n updateRecordInPagesData({\n data,\n record: optimisticMessage,\n processRecord: (_next, prev) => ({\n ...prev,\n error: error.message || 'Failed to send message',\n pending: false, // Mark as no longer pending\n }),\n })\n )\n }\n },\n onSuccess: (result: ApiResource<MessageResource>, variables, context) => {\n const { message: optimisticMessage } = context || {}\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({ conversation_id: conversationId })\n\n // First remove the optimistic message if it exists\n if (optimisticMessage) {\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n deleteRecordInPagesData({\n data,\n record: optimisticMessage,\n })\n )\n }\n\n // Then add the real message\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: updatedMessage,\n })\n )\n },\n })\n\n return mutation\n}\n\nexport function isTemporaryMessageId(messageId?: string | null): boolean {\n return !!messageId && messageId.endsWith('-temp')\n}\nexport function isNewMessage(message?: MessageResource): boolean {\n return !message?.id || isTemporaryMessageId(message.id)\n}\n\n/**\n * Generate a random UUID (v4) for idempotent keys.\n * Uses Math.random, which is not cryptographically secure.\n * An actual crypto library requires native dependencies.\n * This is OK for now since idempotent keys are not security-sensitive\n * or need to be guaranteed unique.\n * They are short lived and we use it in combination with the message's creator_id so\n * their impact is scoped only the current user.\n */\nfunction insecureUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx'\n .replace(/x/g, () => Math.floor(Math.random() * 16).toString(16))\n .replace(/N/g, () => (Math.floor(Math.random() * 4) + 8).toString(16))\n}\n"]}
1
+ {"version":3,"file":"use_message_create_or_update.js","sourceRoot":"","sources":["../../src/hooks/use_message_create_or_update.ts"],"names":[],"mappings":"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","sourcesContent":["import { InfiniteData, useMutation } from '@tanstack/react-query'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { useApiClient } from './use_api_client'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { chatQueryClient } from '../contexts/api_provider'\nimport {\n deleteRecordInPagesData,\n Haptic,\n updateOrCreateRecordInPagesData,\n updateRecordInPagesData,\n} from '../utils'\nimport { DenormalizedAttachmentResourceForCreate } from '../types/resources/denormalized_attachment_resource_for_create'\nimport { useCurrentPerson } from './use_current_person'\nimport { optimisticallyUpdateMessage } from '../utils/cache/optimistically_update_message'\nimport { optimisticallyCreateMessage } from '../utils/cache/optimistically_create_message'\nimport { startMessageCreationTracking } from '../utils/performance_tracking'\nimport { isNewMessage } from '../utils/cache/messages_cache'\n\ninterface Props {\n conversationId: number\n message?: MessageResource\n replyRootId?: string | null\n}\n\nexport function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {\n const messageId = message?.id || null\n const isEditing = !isNewMessage(message)\n const apiClient = useApiClient()\n const currentPerson = useCurrentPerson()\n\n const mutation = useMutation({\n mutationFn: ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n const requestParams = getMessagesRequestArgs({ conversation_id: conversationId })\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n let attributes: any = {\n text,\n ...(attachments ? { attachments } : {}),\n ...(replyRootId ? { reply_root: { id: replyRootId } } : {}),\n }\n if (!isEditing) {\n const idempotentKey = insecureUUID()\n attributes.idempotent_key = idempotentKey\n startMessageCreationTracking(idempotentKey)\n }\n const data = {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes,\n },\n fields: fieldsWithValueJoined,\n }\n\n if (isEditing) {\n return apiClient.chat.patch<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages/${messageId}`,\n data,\n })\n } else {\n return apiClient.chat.post<ApiResource<MessageResource>>({\n url: `/me/conversations/${conversationId}/messages`,\n data,\n })\n }\n },\n onMutate: async ({\n text,\n attachments,\n }: {\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n }) => {\n if (message && isEditing) {\n const optimisticMessage = optimisticallyUpdateMessage({\n conversationId,\n message,\n text,\n })\n\n return { message: optimisticMessage }\n }\n\n const optimisticMessage = optimisticallyCreateMessage({\n conversationId,\n message,\n text,\n attachments,\n currentPerson,\n replyRootId,\n })\n\n return { message: optimisticMessage }\n },\n onError: (error, variables, context) => {\n const { message: optimisticMessage } = context || {}\n Haptic.notificationError()\n\n // Add error to the optimistic message from the cache on error\n if (optimisticMessage) {\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n chatQueryClient.setQueryData(\n queryKey,\n (data: InfiniteData<ApiCollection<MessageResource>> | undefined) =>\n updateRecordInPagesData({\n data,\n record: optimisticMessage,\n processRecord: (_next, prev) => ({\n ...prev,\n error: error.message || 'Failed to send message',\n pending: false, // Mark as no longer pending\n }),\n })\n )\n }\n },\n onSuccess: (result: ApiResource<MessageResource>, variables, context) => {\n const { message: optimisticMessage } = context || {}\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n // First remove the optimistic message if it exists\n if (optimisticMessage) {\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n deleteRecordInPagesData({\n data,\n record: optimisticMessage,\n })\n )\n }\n\n // Then add the real message\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: updatedMessage,\n })\n )\n },\n })\n\n return mutation\n}\n\n/**\n * Generate a random UUID (v4) for idempotent keys.\n * Uses Math.random, which is not cryptographically secure.\n * An actual crypto library requires native dependencies.\n * This is OK for now since idempotent keys are not security-sensitive\n * or need to be guaranteed unique.\n * They are short lived and we use it in combination with the message's creator_id so\n * their impact is scoped only the current user.\n */\nfunction insecureUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx'\n .replace(/x/g, () => Math.floor(Math.random() * 16).toString(16))\n .replace(/N/g, () => (Math.floor(Math.random() * 4) + 8).toString(16))\n}\n"]}
@@ -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: (conversationId: number) => PersonTyping[];
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;AAGxD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,mBAAoB,MAAM,mBAYzD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,WAAY,YAAY,EAAE,KAAG,MAU/D,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 = (conversationId) => {
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
- // Filter out expired entries and the current user
20
- return data.filter(person => person.expires > now && person.author_id !== currentPerson?.id);
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;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,cAAsB,EAAE,EAAE;IAC5D,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,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,kDAAkD;IAClD,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,IAAI,MAAM,CAAC,SAAS,KAAK,aAAa,EAAE,EAAE,CAAC,CAAA;AAC9F,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'\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 = (conversationId: number) => {\n const cacheKey = useMemo(() => ['conversationTyping', String(conversationId)], [conversationId])\n const currentPerson = useCurrentPerson()\n const now = Date.now()\n const { data } = useQuery({\n queryKey: cacheKey,\n queryFn: () => stableArray,\n initialData: stableArray,\n })\n\n // Filter out expired entries and the current user\n return data.filter(person => person.expires > now && person.author_id !== currentPerson?.id)\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
+ {"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;AAwEd,MAAM,MAAM,8BAA8B,GAAG,iBAAiB,CAAC;IAC7D,eAAe,EAAE,MAAM,CAAA;CACxB,CAAC,CAAA;AAEF,wBAAgB,yBAAyB,CAAC,EAAE,KAAK,EAAE,EAAE,8BAA8B,qBAwSlF"}
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
- <FlashList data={listData} estimatedItemSize={52} contentContainerStyle={styles.contentContainer} renderItem={({ item, index }) => {
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, }) {