@planningcenter/chat-react-native 3.18.0-rc.1 → 3.18.0-rc.10
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 +28 -17
- 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/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 +42 -20
- 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/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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { MessageResource } from '../../types';
|
|
3
|
+
import { JoltReactionEvent } from '../../types/jolt_events';
|
|
4
|
+
export declare function updateCacheWithMessage(queryClient: QueryClient, queryKey: unknown[], message: MessageResource, event: 'message.created' | 'message.updated'): void;
|
|
5
|
+
export declare function updateCacheWithReaction(queryClient: QueryClient, queryKey: unknown[], event: JoltReactionEvent, currentPersonId: number): void;
|
|
6
|
+
export declare function isTemporaryMessageId(messageId?: string | null): boolean;
|
|
7
|
+
export declare function isNewMessage(message?: MessageResource): boolean;
|
|
8
|
+
export declare function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string): import("../../hooks/use_suspense_api").RequestQueryKey;
|
|
9
|
+
//# sourceMappingURL=messages_cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages_cache.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAiB,eAAe,EAAE,MAAM,aAAa,CAAA;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAK3D,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,QAsC7C;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,iBAAiB,EACxB,eAAe,EAAE,MAAM,QAuCxB;AAGD,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAEvE;AACD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,0DAMtF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { deleteRecordInPagesData } from './page_mutations';
|
|
2
|
+
import { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations';
|
|
3
|
+
import { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource';
|
|
4
|
+
import { getMessagesRequestArgs } from '../request/get_messages';
|
|
5
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api';
|
|
6
|
+
export function updateCacheWithMessage(queryClient, queryKey, message, event) {
|
|
7
|
+
queryClient.setQueryData(queryKey, prev => {
|
|
8
|
+
if (event === 'message.created') {
|
|
9
|
+
// Before adding the new message, remove any pending temporary messages
|
|
10
|
+
// with matching text to prevent duplicates from race conditions
|
|
11
|
+
let dataAfterTempRemoval = prev;
|
|
12
|
+
if (prev && message.text && message.mine) {
|
|
13
|
+
dataAfterTempRemoval = deleteRecordInPagesData({
|
|
14
|
+
data: prev,
|
|
15
|
+
record: message,
|
|
16
|
+
matchFn: (existingMessage, _record) => {
|
|
17
|
+
return (isTemporaryMessageId(existingMessage.id) &&
|
|
18
|
+
existingMessage.text === message.text &&
|
|
19
|
+
existingMessage.mine);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return updateOrCreateRecordInPagesData({
|
|
24
|
+
data: dataAfterTempRemoval,
|
|
25
|
+
record: message,
|
|
26
|
+
processRecord: (record, current) => {
|
|
27
|
+
return { ...current, ...record };
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return updateRecordInPagesData({
|
|
33
|
+
data: prev,
|
|
34
|
+
record: message,
|
|
35
|
+
processRecord: (record, current) => {
|
|
36
|
+
return { ...current, ...record };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function updateCacheWithReaction(queryClient, queryKey, event, currentPersonId) {
|
|
43
|
+
const message = { id: event.data.data.message_sort_key };
|
|
44
|
+
queryClient.setQueryData(queryKey, prev => updateRecordInPagesData({
|
|
45
|
+
data: prev,
|
|
46
|
+
record: message,
|
|
47
|
+
processRecord: (record, oldMessage) => {
|
|
48
|
+
const reactionCounts = oldMessage.reactionCounts || [];
|
|
49
|
+
let foundMatch = false;
|
|
50
|
+
let newReactionCounts = reactionCounts.map(reactionCount => {
|
|
51
|
+
if (reactionCount.value === event.data.data.value) {
|
|
52
|
+
foundMatch = true;
|
|
53
|
+
return transformReactionEventDataToReactionCountResource({
|
|
54
|
+
data: event.data.data,
|
|
55
|
+
oldData: reactionCount,
|
|
56
|
+
event: event.event,
|
|
57
|
+
currentPersonId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return reactionCount;
|
|
61
|
+
});
|
|
62
|
+
if (!foundMatch) {
|
|
63
|
+
const newReactionCount = transformReactionEventDataToReactionCountResource({
|
|
64
|
+
data: event.data.data,
|
|
65
|
+
event: event.event,
|
|
66
|
+
currentPersonId,
|
|
67
|
+
});
|
|
68
|
+
if (newReactionCount?.count) {
|
|
69
|
+
newReactionCounts = [...newReactionCounts, newReactionCount];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { ...oldMessage, reactionCounts: newReactionCounts };
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
export function isTemporaryMessageId(messageId) {
|
|
77
|
+
return !!messageId && messageId.endsWith('-temp');
|
|
78
|
+
}
|
|
79
|
+
export function isNewMessage(message) {
|
|
80
|
+
return !message?.id || isTemporaryMessageId(message.id);
|
|
81
|
+
}
|
|
82
|
+
export function getThreadedMessagesQueryKey(conversationId, replyRootId) {
|
|
83
|
+
const requestArgs = getMessagesRequestArgs({
|
|
84
|
+
conversation_id: conversationId,
|
|
85
|
+
reply_root_id: replyRootId,
|
|
86
|
+
});
|
|
87
|
+
return getRequestQueryKey(requestArgs);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=messages_cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages_cache.js","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"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","sourcesContent":["import { InfiniteData, QueryClient } from '@tanstack/react-query'\nimport { ApiCollection, MessageResource } from '../../types'\nimport { deleteRecordInPagesData } from './page_mutations'\nimport { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations'\nimport { JoltReactionEvent } from '../../types/jolt_events'\nimport { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource'\nimport { getMessagesRequestArgs } from '../request/get_messages'\nimport { getRequestQueryKey } from '../../hooks/use_suspense_api'\n\nexport function updateCacheWithMessage(\n queryClient: QueryClient,\n queryKey: unknown[],\n message: MessageResource,\n event: 'message.created' | 'message.updated'\n) {\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev => {\n if (event === 'message.created') {\n // Before adding the new message, remove any pending temporary messages\n // with matching text to prevent duplicates from race conditions\n let dataAfterTempRemoval = prev\n if (prev && message.text && message.mine) {\n dataAfterTempRemoval = deleteRecordInPagesData({\n data: prev,\n record: message,\n matchFn: (existingMessage, _record) => {\n return (\n isTemporaryMessageId(existingMessage.id) &&\n existingMessage.text === message.text &&\n existingMessage.mine\n )\n },\n })\n }\n\n return updateOrCreateRecordInPagesData({\n data: dataAfterTempRemoval,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n } else {\n return updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n }\n })\n}\n\nexport function updateCacheWithReaction(\n queryClient: QueryClient,\n queryKey: unknown[],\n event: JoltReactionEvent,\n currentPersonId: number\n) {\n const message = { id: event.data.data.message_sort_key } as MessageResource\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev =>\n updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, oldMessage) => {\n const reactionCounts = oldMessage.reactionCounts || []\n let foundMatch = false\n let newReactionCounts = reactionCounts.map(reactionCount => {\n if (reactionCount.value === event.data.data.value) {\n foundMatch = true\n return transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n oldData: reactionCount,\n event: event.event,\n currentPersonId,\n })\n }\n return reactionCount\n })\n\n if (!foundMatch) {\n const newReactionCount = transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n event: event.event,\n currentPersonId,\n })\n\n if (newReactionCount?.count) {\n newReactionCounts = [...newReactionCounts, newReactionCount]\n }\n }\n\n return { ...oldMessage, reactionCounts: newReactionCounts }\n },\n })\n )\n}\n\ntype MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>\nexport function isTemporaryMessageId(messageId?: string | null): boolean {\n return !!messageId && messageId.endsWith('-temp')\n}\nexport function isNewMessage(message?: MessageResource): boolean {\n return !message?.id || isTemporaryMessageId(message.id)\n}\n\nexport function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string) {\n const requestArgs = getMessagesRequestArgs({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n return getRequestQueryKey(requestArgs)\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CurrentPersonResource, MessageResource } from '../../types';
|
|
2
2
|
import { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create';
|
|
3
|
-
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, }: {
|
|
3
|
+
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, }: {
|
|
4
4
|
conversationId: number;
|
|
5
5
|
text: string;
|
|
6
6
|
attachments?: DenormalizedAttachmentResourceForCreate[];
|
|
7
7
|
currentPerson: CurrentPersonResource;
|
|
8
8
|
message?: MessageResource;
|
|
9
|
+
replyRootId?: string | null;
|
|
9
10
|
}): MessageResource;
|
|
10
11
|
//# sourceMappingURL=optimistically_create_message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAI3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAI3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,GACZ,EAAE;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,uCAAuC,EAAE,CAAA;IACvD,aAAa,EAAE,qBAAqB,CAAA;IACpC,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,mBA+CA"}
|
|
@@ -3,7 +3,7 @@ import { chatQueryClient } from '../../contexts/api_provider';
|
|
|
3
3
|
import { updateOrCreateRecordInPagesData } from './page_mutations';
|
|
4
4
|
import { convertAttachmentsForCreate } from '../convert_attachments_for_create';
|
|
5
5
|
import { generatePlaceholderUlid } from '../generate_placeholder_ulid';
|
|
6
|
-
export function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, }) {
|
|
6
|
+
export function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, }) {
|
|
7
7
|
const id = message?.id || generateTempMessageId();
|
|
8
8
|
// Convert attachments to denormalized format for optimistic UI
|
|
9
9
|
const decoratedAttachments = convertAttachmentsForCreate(attachments || []);
|
|
@@ -28,9 +28,12 @@ export function optimisticallyCreateMessage({ conversationId, text, attachments,
|
|
|
28
28
|
lastInGroup: true,
|
|
29
29
|
pending: true,
|
|
30
30
|
replyCount: 0,
|
|
31
|
-
replyRootId: null,
|
|
31
|
+
replyRootId: replyRootId || null,
|
|
32
32
|
};
|
|
33
|
-
const queryKey = getMessagesQueryKey({
|
|
33
|
+
const queryKey = getMessagesQueryKey({
|
|
34
|
+
conversation_id: conversationId,
|
|
35
|
+
reply_root_id: replyRootId,
|
|
36
|
+
});
|
|
34
37
|
chatQueryClient.setQueryData(queryKey, data => updateOrCreateRecordInPagesData({
|
|
35
38
|
data,
|
|
36
39
|
record: optimisticMessage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.js","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAA;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,MAAM,UAAU,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.js","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAA;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,MAAM,UAAU,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,GAQZ;IACC,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,qBAAqB,EAAE,CAAA;IAEjD,+DAA+D;IAC/D,MAAM,oBAAoB,GAAqC,2BAA2B,CACxF,WAAW,IAAI,EAAE,CAClB,CAAA;IAED,+BAA+B;IAC/B,MAAM,iBAAiB,GAAoB;QACzC,GAAG,OAAO;QACV,IAAI,EAAE,SAAS;QACf,EAAE;QACF,IAAI;QACJ,IAAI,EAAE,EAAE,EAAE,2BAA2B;QACrC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,IAAI;QACf,YAAY,EAAE,IAAI;QAClB,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,oBAAoB;QACjC,oBAAoB,EAAE,WAAW,IAAI,EAAE;QACvC,MAAM,EAAE,aAAa;QACrB,cAAc,EAAE,EAAE;QAClB,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,IAAI;QAChB,sBAAsB,EAAE,IAAI;QAC5B,WAAW,EAAE,IAAI;QACjB,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,WAAW,IAAI,IAAI;KACjC,CAAA;IAID,MAAM,QAAQ,GAAG,mBAAmB,CAAC;QACnC,eAAe,EAAE,cAAc;QAC/B,aAAa,EAAE,WAAW;KAC3B,CAAC,CAAA;IAEF,eAAe,CAAC,YAAY,CAAY,QAAQ,EAAE,IAAI,CAAC,EAAE,CACvD,+BAA+B,CAAC;QAC9B,IAAI;QACJ,MAAM,EAAE,iBAAoC;KAC7C,CAAC,CACH,CAAA;IAED,OAAO,iBAAiB,CAAA;AAC1B,CAAC;AAED,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACjC,0FAA0F;IAC1F,sEAAsE;IACtE,OAAO,GAAG,uBAAuB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,OAAO,CAAA;AAC9D,CAAC,CAAA","sourcesContent":["import { InfiniteData } from '@tanstack/react-query'\nimport { ApiCollection, CurrentPersonResource, MessageResource } from '../../types'\nimport { getMessagesQueryKey } from '../../utils/request/get_messages'\nimport { chatQueryClient } from '../../contexts/api_provider'\nimport { updateOrCreateRecordInPagesData } from './page_mutations'\nimport { convertAttachmentsForCreate } from '../convert_attachments_for_create'\nimport { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { generatePlaceholderUlid } from '../generate_placeholder_ulid'\n\nexport function optimisticallyCreateMessage({\n conversationId,\n text,\n attachments,\n currentPerson,\n message,\n replyRootId,\n}: {\n conversationId: number\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n currentPerson: CurrentPersonResource\n message?: MessageResource\n replyRootId?: string | null\n}) {\n const id = message?.id || generateTempMessageId()\n\n // Convert attachments to denormalized format for optimistic UI\n const decoratedAttachments: DenormalizedAttachmentResource[] = convertAttachmentsForCreate(\n attachments || []\n )\n\n // Create an optimistic message\n const optimisticMessage: MessageResource = {\n ...message,\n type: 'Message',\n id,\n text,\n html: '', // Will be filled by server\n createdAt: new Date().toISOString(),\n deletedAt: null,\n textEditedAt: null,\n mine: true,\n attachments: decoratedAttachments,\n attachmentsForCreate: attachments || [],\n author: currentPerson,\n reactionCounts: [],\n renderAuthor: false,\n renderTime: true,\n myLatestInConversation: true,\n lastInGroup: true,\n pending: true,\n replyCount: 0,\n replyRootId: replyRootId || null,\n }\n\n // Add the optimistic message to the cache\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: optimisticMessage as MessageResource,\n })\n )\n\n return optimisticMessage\n}\n\nconst generateTempMessageId = () => {\n // Put it 5 seconds in the future to account for server lag of previously created messages\n // that are still pending. This gets overwritten by the server anyway.\n return `${generatePlaceholderUlid({ offsetMs: 5000 })}-temp`\n}\n"]}
|
package/build/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA"}
|
package/build/utils/index.js
CHANGED
package/build/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\nexport * from './native_adapters'\nexport * from './pluralize'\nexport * from './destructure_chat_group_graph_id'\nexport * from './convert_attachments_for_create'\nexport * from './assert_keys_are_numbers'\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const getFeaturesRequestArgs: () => {
|
|
2
|
+
url: string;
|
|
3
|
+
data: {
|
|
4
|
+
perPage: number;
|
|
5
|
+
fields: {
|
|
6
|
+
Feature: string[];
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export declare const getFeaturesQueryKey: () => import("../../hooks/use_suspense_api").RequestQueryKey;
|
|
11
|
+
//# sourceMappingURL=get_features.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_features.d.ts","sourceRoot":"","sources":["../../../src/utils/request/get_features.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB;;;;;;;;CAYlC,CAAA;AAED,eAAO,MAAM,mBAAmB,8DAG/B,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api';
|
|
2
|
+
export const getFeaturesRequestArgs = () => {
|
|
3
|
+
const url = '/me/features';
|
|
4
|
+
return {
|
|
5
|
+
url,
|
|
6
|
+
data: {
|
|
7
|
+
perPage: 100,
|
|
8
|
+
fields: {
|
|
9
|
+
Feature: ['name', 'enabled'],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export const getFeaturesQueryKey = () => {
|
|
15
|
+
const requestArgs = getFeaturesRequestArgs();
|
|
16
|
+
return getRequestQueryKey(requestArgs);
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=get_features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_features.js","sourceRoot":"","sources":["../../../src/utils/request/get_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AAEjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,EAAE;IACzC,MAAM,GAAG,GAAG,cAAc,CAAA;IAE1B,OAAO;QACL,GAAG;QACH,IAAI,EAAE;YACJ,OAAO,EAAE,GAAG;YACZ,MAAM,EAAE;gBACN,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;aAC7B;SACF;KACF,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,EAAE;IACtC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAC5C,OAAO,kBAAkB,CAAC,WAAW,CAAC,CAAA;AACxC,CAAC,CAAA","sourcesContent":["import { getRequestQueryKey } from '../../hooks/use_suspense_api'\n\nexport const getFeaturesRequestArgs = () => {\n const url = '/me/features'\n\n return {\n url,\n data: {\n perPage: 100,\n fields: {\n Feature: ['name', 'enabled'],\n },\n },\n }\n}\n\nexport const getFeaturesQueryKey = () => {\n const requestArgs = getFeaturesRequestArgs()\n return getRequestQueryKey(requestArgs)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.18.0-rc.
|
|
3
|
+
"version": "3.18.0-rc.10",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"@react-navigation/elements": "*",
|
|
30
30
|
"@react-navigation/native": ">=7.0.0",
|
|
31
31
|
"@react-navigation/native-stack": ">=7.0.0",
|
|
32
|
-
"@shopify/flash-list": "<2.0.0",
|
|
33
32
|
"@tanstack/react-query": "^5.0.0",
|
|
34
33
|
"color": "^3.1.2",
|
|
35
34
|
"lodash": "*",
|
|
@@ -59,5 +58,5 @@
|
|
|
59
58
|
"react-native-url-polyfill": "^2.0.0",
|
|
60
59
|
"typescript": "<5.6.0"
|
|
61
60
|
},
|
|
62
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "20b1107e3efd3b9e108ccaada901e34ab49546fa"
|
|
63
62
|
}
|
|
@@ -22,11 +22,12 @@ import {
|
|
|
22
22
|
import Animated from 'react-native-reanimated'
|
|
23
23
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
|
|
24
24
|
import { MessageReadReceipts } from './message_read_receipts'
|
|
25
|
-
import {
|
|
25
|
+
import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
26
26
|
import { Haptic } from '../../utils/native_adapters'
|
|
27
27
|
import { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from './reply_connectors'
|
|
28
|
-
import { pluralize
|
|
28
|
+
import { pluralize } from '../../utils'
|
|
29
29
|
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
30
|
+
import { isNewMessage } from '../../utils/cache/messages_cache'
|
|
30
31
|
|
|
31
32
|
/** Message
|
|
32
33
|
* Component for display of a message within a conversation list
|
|
@@ -36,6 +37,7 @@ interface MessageProps extends MessageResource {
|
|
|
36
37
|
conversation_id: number
|
|
37
38
|
latestReadMessageSortKey?: string
|
|
38
39
|
inReplyScreen?: boolean
|
|
40
|
+
repliesEnabled?: boolean
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export function Message({
|
|
@@ -43,6 +45,7 @@ export function Message({
|
|
|
43
45
|
conversation_id,
|
|
44
46
|
latestReadMessageSortKey,
|
|
45
47
|
inReplyScreen,
|
|
48
|
+
repliesEnabled,
|
|
46
49
|
...message
|
|
47
50
|
}: MessageProps) {
|
|
48
51
|
const { text, reactionCounts, pending, error } = message
|
|
@@ -73,11 +76,10 @@ export function Message({
|
|
|
73
76
|
|
|
74
77
|
const renderAuthor = (!message.mine && message.renderAuthor) || false
|
|
75
78
|
const showReplyCountButton =
|
|
76
|
-
!inReplyScreen && message.replyRootId === message.id &&
|
|
79
|
+
!inReplyScreen && message.replyRootId === message.id && repliesEnabled
|
|
77
80
|
const isReplyRootMessage = message.replyRootId === message.id
|
|
78
81
|
const isDeletedReplyRootMessage = isReplyRootMessage && !!message.deletedAt
|
|
79
82
|
|
|
80
|
-
const messageText = isDeletedReplyRootMessage ? 'Message deleted' : text
|
|
81
83
|
const replyCountText = pluralize(message.replyCount, 'reply')
|
|
82
84
|
const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'
|
|
83
85
|
const replyRootAuthorName = message.replyRootId
|
|
@@ -115,6 +117,10 @@ export function Message({
|
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
const handleMessagePress = () => {
|
|
121
|
+
setShowMessageMetaToggle(!showMessageMetaToggle)
|
|
122
|
+
}
|
|
123
|
+
|
|
118
124
|
const handleMessageLongPress = () => {
|
|
119
125
|
if (!isPersisted) return
|
|
120
126
|
|
|
@@ -127,6 +133,7 @@ export function Message({
|
|
|
127
133
|
reply_root_author_name: replyRootAuthorName,
|
|
128
134
|
})
|
|
129
135
|
}
|
|
136
|
+
|
|
130
137
|
const handleReactionLongPress = (reaction: ReactionCountResource) => {
|
|
131
138
|
Haptic.impactLight()
|
|
132
139
|
navigation.navigate('Reactions', {
|
|
@@ -159,11 +166,12 @@ export function Message({
|
|
|
159
166
|
return (
|
|
160
167
|
<Pressable
|
|
161
168
|
onLongPress={handleMessageLongPress}
|
|
162
|
-
onPress={
|
|
169
|
+
onPress={handleMessagePress}
|
|
163
170
|
onPressIn={handleMessagePressIn}
|
|
164
171
|
onPressOut={handleMessagePressOut}
|
|
165
172
|
android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}
|
|
166
173
|
accessibilityHint="Long press to view message actions like reacting and copying."
|
|
174
|
+
disabled={isDeletedReplyRootMessage}
|
|
167
175
|
>
|
|
168
176
|
<Animated.View style={[styles.message, animatedBackgroundColor]}>
|
|
169
177
|
{!message.mine && (
|
|
@@ -181,11 +189,13 @@ export function Message({
|
|
|
181
189
|
) : (
|
|
182
190
|
<View style={styles.avatarPlaceholder} />
|
|
183
191
|
)}
|
|
184
|
-
|
|
192
|
+
{repliesEnabled && (
|
|
193
|
+
<TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
194
|
+
)}
|
|
185
195
|
</View>
|
|
186
196
|
)}
|
|
187
197
|
<View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>
|
|
188
|
-
{renderAuthor && (
|
|
198
|
+
{renderAuthor && !isDeletedReplyRootMessage && (
|
|
189
199
|
<Text variant="footnote" style={styles.authorName}>
|
|
190
200
|
{message.author.name}
|
|
191
201
|
</Text>
|
|
@@ -194,18 +204,26 @@ export function Message({
|
|
|
194
204
|
style={styles.messageBubble}
|
|
195
205
|
onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
|
|
196
206
|
>
|
|
197
|
-
|
|
198
|
-
<MessageAttachments
|
|
199
|
-
attachments={message.attachments}
|
|
200
|
-
metaProps={metaProps}
|
|
201
|
-
onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
|
|
202
|
-
onMessageLongPress={handleMessageLongPress}
|
|
203
|
-
/>
|
|
204
|
-
</ErrorBoundary>
|
|
205
|
-
{messageText && (
|
|
207
|
+
{isDeletedReplyRootMessage ? (
|
|
206
208
|
<View style={styles.messageText}>
|
|
207
|
-
<
|
|
209
|
+
<Text style={styles.replyRootDeletedText}>Message deleted</Text>
|
|
208
210
|
</View>
|
|
211
|
+
) : (
|
|
212
|
+
<>
|
|
213
|
+
<ErrorBoundary>
|
|
214
|
+
<MessageAttachments
|
|
215
|
+
attachments={message.attachments}
|
|
216
|
+
metaProps={metaProps}
|
|
217
|
+
onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
|
|
218
|
+
onMessageLongPress={handleMessageLongPress}
|
|
219
|
+
/>
|
|
220
|
+
</ErrorBoundary>
|
|
221
|
+
{text && (
|
|
222
|
+
<View style={styles.messageText}>
|
|
223
|
+
<MessageMarkdown text={text} />
|
|
224
|
+
</View>
|
|
225
|
+
)}
|
|
226
|
+
</>
|
|
209
227
|
)}
|
|
210
228
|
</View>
|
|
211
229
|
{showReplyCountButton && (
|
|
@@ -219,7 +237,7 @@ export function Message({
|
|
|
219
237
|
{replyCountText}
|
|
220
238
|
</TextButton>
|
|
221
239
|
)}
|
|
222
|
-
{hasReactions && (
|
|
240
|
+
{hasReactions && !isDeletedReplyRootMessage && (
|
|
223
241
|
<View style={styles.messageReactions}>
|
|
224
242
|
{reactionCounts.map(reaction => (
|
|
225
243
|
<MessageReaction
|
|
@@ -232,7 +250,7 @@ export function Message({
|
|
|
232
250
|
))}
|
|
233
251
|
</View>
|
|
234
252
|
)}
|
|
235
|
-
{showMessageMeta && (
|
|
253
|
+
{showMessageMeta && !isDeletedReplyRootMessage && (
|
|
236
254
|
<View style={styles.messageMeta}>
|
|
237
255
|
{message.mine && !pending && !error && (
|
|
238
256
|
<MessageReadReceipts
|
|
@@ -282,7 +300,7 @@ export function Message({
|
|
|
282
300
|
</View>
|
|
283
301
|
)}
|
|
284
302
|
</View>
|
|
285
|
-
{message.mine && (
|
|
303
|
+
{repliesEnabled && message.mine && (
|
|
286
304
|
<MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />
|
|
287
305
|
)}
|
|
288
306
|
</Animated.View>
|
|
@@ -360,5 +378,9 @@ const useMessageStyles = ({ mine }: MessageResource) => {
|
|
|
360
378
|
errorText: {
|
|
361
379
|
color: colors.statusErrorText,
|
|
362
380
|
},
|
|
381
|
+
replyRootDeletedText: {
|
|
382
|
+
color: colors.textColorDefaultSecondary,
|
|
383
|
+
fontStyle: 'italic',
|
|
384
|
+
},
|
|
363
385
|
})
|
|
364
386
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RouteProp,
|
|
3
|
-
useNavigation,
|
|
4
|
-
useTheme as useNavigationTheme,
|
|
5
|
-
useRoute,
|
|
6
|
-
} from '@react-navigation/native'
|
|
1
|
+
import { useNavigation, useTheme as useNavigationTheme, useRoute } from '@react-navigation/native'
|
|
7
2
|
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
|
8
3
|
import {
|
|
9
4
|
Platform,
|
|
@@ -30,10 +25,10 @@ import { ChatContext } from '../../contexts/chat_context'
|
|
|
30
25
|
import { Haptic, ImagePicker, ImagePickerResult } from '../../utils/native_adapters'
|
|
31
26
|
import {
|
|
32
27
|
MAX_FONT_SIZE_MULTIPLIER_LANDMARK,
|
|
33
|
-
REPLIES_FEATURE_ENABLED,
|
|
34
28
|
platformFontWeightMedium,
|
|
35
29
|
platformPressedOpacityStyle,
|
|
36
30
|
} from '../../utils'
|
|
31
|
+
import { availableFeatures, useFeatures } from '../../hooks/use_features'
|
|
37
32
|
import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
|
|
38
33
|
import { useMessageDraft } from '../../hooks/use_message_draft'
|
|
39
34
|
import {
|
|
@@ -319,9 +314,7 @@ function MessageFormInput() {
|
|
|
319
314
|
React.useContext(MessageFormContext)
|
|
320
315
|
const attachmentError = attachmentUploader?.errorMessage
|
|
321
316
|
|
|
322
|
-
const
|
|
323
|
-
const conversationId = route.params.conversation_id
|
|
324
|
-
const broadcastTypingStatus = useBroadcastTypingStatus(conversationId)
|
|
317
|
+
const broadcastTypingStatus = useBroadcastTypingStatus()
|
|
325
318
|
|
|
326
319
|
const handleTextChange = (newText: string) => {
|
|
327
320
|
setText(newText)
|
|
@@ -566,9 +559,11 @@ function EditingIndicator() {
|
|
|
566
559
|
function ReplyIndicator() {
|
|
567
560
|
const { replyRootId, replyRootAuthorFirstName } = React.useContext(MessageFormContext)
|
|
568
561
|
const navigation = useNavigation()
|
|
562
|
+
const { featureEnabled } = useFeatures()
|
|
563
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
569
564
|
const title = replyRootAuthorFirstName ? `Reply to ${replyRootAuthorFirstName}` : 'Reply'
|
|
570
565
|
|
|
571
|
-
if (!
|
|
566
|
+
if (!repliesEnabled || !replyRootId) return null
|
|
572
567
|
|
|
573
568
|
return (
|
|
574
569
|
<FormIndicatorRow
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { StyleSheet, View, type ViewStyle } from 'react-native'
|
|
2
|
+
import { useTheme } from '../../hooks'
|
|
3
|
+
import { Text } from '../display'
|
|
4
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
5
|
+
import { useFeatures } from '../../hooks/use_features'
|
|
6
|
+
import { availableFeatures } from '../../hooks/use_features'
|
|
7
|
+
|
|
8
|
+
export const LeaderMessagesDisabledBanner = () => {
|
|
9
|
+
const { featureEnabled } = useFeatures()
|
|
10
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
11
|
+
|
|
12
|
+
const description = repliesEnabled
|
|
13
|
+
? 'Only leaders can send messages in this conversation.'
|
|
14
|
+
: 'Replies are frozen for everyone else.'
|
|
15
|
+
|
|
16
|
+
return <MessagesDisabledBanner description={description} />
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const MemberMessagesDisabledBanner = () => {
|
|
20
|
+
const styles = useStyles()
|
|
21
|
+
const { featureEnabled } = useFeatures()
|
|
22
|
+
const repliesEnabled = featureEnabled(availableFeatures.threaded_replies)
|
|
23
|
+
|
|
24
|
+
const description = repliesEnabled
|
|
25
|
+
? 'Only leaders can send messages, but you can still add reactions.'
|
|
26
|
+
: 'Replies have been disabled by a leader, but you can still add reactions.'
|
|
27
|
+
|
|
28
|
+
return <MessagesDisabledBanner description={description} style={styles.memberBanner} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MessagesDisabledBannerProps {
|
|
32
|
+
description: string
|
|
33
|
+
style?: ViewStyle
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const MessagesDisabledBanner = ({ description, style }: MessagesDisabledBannerProps) => {
|
|
37
|
+
const styles = useStyles()
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View style={[styles.baseBanner, style]}>
|
|
41
|
+
<Text style={styles.text} variant="tertiary">
|
|
42
|
+
{description}
|
|
43
|
+
</Text>
|
|
44
|
+
</View>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const useStyles = () => {
|
|
49
|
+
const { colors } = useTheme()
|
|
50
|
+
const { bottom } = useSafeAreaInsets()
|
|
51
|
+
|
|
52
|
+
return StyleSheet.create({
|
|
53
|
+
baseBanner: {
|
|
54
|
+
paddingHorizontal: 16,
|
|
55
|
+
paddingVertical: 4,
|
|
56
|
+
backgroundColor: colors.statusNeutralComposedBackground,
|
|
57
|
+
borderTopWidth: 1,
|
|
58
|
+
borderTopColor: colors.borderColorDefaultBase,
|
|
59
|
+
},
|
|
60
|
+
text: {
|
|
61
|
+
textAlign: 'center',
|
|
62
|
+
},
|
|
63
|
+
memberBanner: {
|
|
64
|
+
paddingTop: 16,
|
|
65
|
+
paddingBottom: 16 + bottom,
|
|
66
|
+
marginBottom: -bottom,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
}
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
7
7
|
MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
8
8
|
} from '../../utils/styles'
|
|
9
|
-
import { REPLIES_FEATURE_ENABLED } from '../../utils'
|
|
10
9
|
|
|
11
10
|
const MY_REPLY_CONNECTOR_WIDTH = 38
|
|
12
11
|
const CONNECTOR_BORDER_WIDTH = 4
|
|
@@ -22,7 +21,6 @@ export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConne
|
|
|
22
21
|
const styles = useStyles()
|
|
23
22
|
const { nextRendersAuthor, threadPosition, renderAuthor, isReplyShadowMessage } = message
|
|
24
23
|
|
|
25
|
-
if (!REPLIES_FEATURE_ENABLED) return null
|
|
26
24
|
if (messageBubbleHeight === 0) return null // Prevents UI shifting
|
|
27
25
|
|
|
28
26
|
const connectorMap: Record<string, React.ReactNode | null> = {
|
|
@@ -59,7 +57,6 @@ export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnecto
|
|
|
59
57
|
const { nextRendersAuthor, threadPosition, prevIsMyReply, nextIsMyReply, isReplyShadowMessage } =
|
|
60
58
|
message
|
|
61
59
|
|
|
62
|
-
if (!REPLIES_FEATURE_ENABLED) return null
|
|
63
60
|
if (messageBubbleHeight === 0) return null // Prevents UI shifting
|
|
64
61
|
|
|
65
62
|
const connectorMap: Record<string, React.ReactNode | null> = {
|
|
@@ -41,16 +41,12 @@ const TypingDots = () => {
|
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
interface TypingIndicatorProps {
|
|
45
|
-
conversationId: number
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
/**
|
|
49
45
|
* Component to display typing indicators in a conversation
|
|
50
46
|
* Shows "X is typing..." with animated dots
|
|
51
47
|
*/
|
|
52
|
-
export const TypingIndicator = (
|
|
53
|
-
const typingPeople = useTypingIndicators(
|
|
48
|
+
export const TypingIndicator = () => {
|
|
49
|
+
const typingPeople = useTypingIndicators()
|
|
54
50
|
const styles = useStyles()
|
|
55
51
|
const enabled = typingPeople.length > 0
|
|
56
52
|
|
|
@@ -2,6 +2,7 @@ import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'reac
|
|
|
2
2
|
import {
|
|
3
3
|
AccessibilityActionEvent,
|
|
4
4
|
Platform,
|
|
5
|
+
Pressable,
|
|
5
6
|
StyleProp,
|
|
6
7
|
StyleSheet,
|
|
7
8
|
View,
|
|
@@ -10,7 +11,6 @@ import {
|
|
|
10
11
|
import ReanimatedSwipeable, {
|
|
11
12
|
SwipeableMethods,
|
|
12
13
|
} from 'react-native-gesture-handler/ReanimatedSwipeable'
|
|
13
|
-
import { Pressable } from 'react-native-gesture-handler'
|
|
14
14
|
import { useConversationsContext } from '../../contexts/conversations_context'
|
|
15
15
|
import { useTheme, useCreateAndroidRippleColor } from '../../hooks'
|
|
16
16
|
import {
|