@planningcenter/chat-react-native 3.18.0-rc.6 → 3.18.0-rc.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +23 -12
- 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 +2 -4
- package/build/components/conversation/message_form.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/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_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +23 -70
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_message_create_or_update.d.ts +0 -2
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +10 -8
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/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_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +8 -1
- package/build/screens/conversation_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/utils/cache/messages_cache.d.ts +9 -0
- package/build/utils/cache/messages_cache.d.ts.map +1 -0
- package/build/utils/cache/messages_cache.js +89 -0
- package/build/utils/cache/messages_cache.js.map +1 -0
- package/build/utils/cache/optimistically_create_message.d.ts +2 -1
- package/build/utils/cache/optimistically_create_message.d.ts.map +1 -1
- package/build/utils/cache/optimistically_create_message.js +6 -3
- package/build/utils/cache/optimistically_create_message.js.map +1 -1
- package/package.json +2 -2
- package/src/components/conversation/message.tsx +34 -16
- package/src/components/conversation/message_form.tsx +2 -9
- package/src/components/conversation/typing_indicator.tsx +2 -6
- package/src/contexts/conversation_context.tsx +34 -0
- package/src/hooks/use_broadcast_typing_status.ts +7 -3
- package/src/hooks/use_conversation_messages_jolt_events.ts +39 -81
- package/src/hooks/use_message_create_or_update.ts +10 -9
- package/src/hooks/use_typing_indicators.ts +15 -3
- package/src/screens/conversation_screen.tsx +15 -1
- package/src/types/jolt_events/reaction_events.ts +1 -0
- package/src/types/jolt_events/typing_events.ts +1 -0
- package/src/utils/cache/messages_cache.ts +113 -0
- package/src/utils/cache/optimistically_create_message.ts +7 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reaction_events.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAA;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAE7D,UAAU,qBAAsB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,gBAAgB,EAAE,MAAM,CAAA;QACxB,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;QACrC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,kBAAkB,CAAA;IACzB,IAAI,EAAE,qBAAqB,CAAA;CAC5B;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,oBAAoB,CAAA;IAC3B,IAAI,EAAE,qBAAqB,CAAA;CAC5B"}
|
|
1
|
+
{"version":3,"file":"reaction_events.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAA;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAE7D,UAAU,qBAAsB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,gBAAgB,EAAE,MAAM,CAAA;QACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC7B,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;QACrC,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,kBAAkB,CAAA;IACzB,IAAI,EAAE,qBAAqB,CAAA;CAC5B;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,oBAAoB,CAAA;IAC3B,IAAI,EAAE,qBAAqB,CAAA;CAC5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reaction_events.js","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"","sourcesContent":["import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'\nimport { ReactionCountResource } from '../resources/reaction'\n\ninterface BaseReactionEventData extends Record<string, unknown> {\n data: {\n author_id: number\n conversation_id: number\n message_sort_key: string\n created_at: string\n organization_id: number\n value: ReactionCountResource['value']\n author_name?: string\n author_avatar?: string\n count: number\n }\n}\n\nexport interface ReactionCreatedEvent extends CustomMessage {\n event: 'reaction.created'\n data: BaseReactionEventData\n}\n\nexport interface ReactionDeletedEvent extends CustomMessage {\n event: 'reaction.destroyed'\n data: BaseReactionEventData\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reaction_events.js","sourceRoot":"","sources":["../../../src/types/jolt_events/reaction_events.ts"],"names":[],"mappings":"","sourcesContent":["import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'\nimport { ReactionCountResource } from '../resources/reaction'\n\ninterface BaseReactionEventData extends Record<string, unknown> {\n data: {\n author_id: number\n conversation_id: number\n message_sort_key: string\n reply_root_id?: string | null\n created_at: string\n organization_id: number\n value: ReactionCountResource['value']\n author_name?: string\n author_avatar?: string\n count: number\n }\n}\n\nexport interface ReactionCreatedEvent extends CustomMessage {\n event: 'reaction.created'\n data: BaseReactionEventData\n}\n\nexport interface ReactionDeletedEvent extends CustomMessage {\n event: 'reaction.destroyed'\n data: BaseReactionEventData\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typing_events.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/typing_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAA;AAE1F,UAAU,mBAAoB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,IAAI,EAAE,6BAA6B,CAAA;CACpC;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,kBAAkB,CAAA;IACzB,IAAI,EAAE,mBAAmB,CAAA;CAC1B;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,EAAE,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"typing_events.d.ts","sourceRoot":"","sources":["../../../src/types/jolt_events/typing_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAA;AAE1F,UAAU,mBAAoB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,IAAI,EAAE,6BAA6B,CAAA;CACpC;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,KAAK,EAAE,kBAAkB,CAAA;IACzB,IAAI,EAAE,mBAAmB,CAAA;CAC1B;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typing_events.js","sourceRoot":"","sources":["../../../src/types/jolt_events/typing_events.ts"],"names":[],"mappings":"","sourcesContent":["import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'\n\ninterface TypingBroadcastData extends Record<string, unknown> {\n data: TypingBroadcastDataAttributes\n}\n\nexport interface TypingBroadcastEvent extends CustomMessage {\n event: 'typing.broadcast'\n data: TypingBroadcastData\n}\n\nexport interface TypingBroadcastDataAttributes {\n author_id: number\n author_name: string\n id: string\n}\n"]}
|
|
1
|
+
{"version":3,"file":"typing_events.js","sourceRoot":"","sources":["../../../src/types/jolt_events/typing_events.ts"],"names":[],"mappings":"","sourcesContent":["import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'\n\ninterface TypingBroadcastData extends Record<string, unknown> {\n data: TypingBroadcastDataAttributes\n}\n\nexport interface TypingBroadcastEvent extends CustomMessage {\n event: 'typing.broadcast'\n data: TypingBroadcastData\n}\n\nexport interface TypingBroadcastDataAttributes {\n author_id: number\n author_name: string\n id: string\n reply_root_id: string | null\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { MessageResource } from '../../types';
|
|
3
|
+
import { JoltReactionEvent } from '../../types/jolt_events';
|
|
4
|
+
export declare function updateCacheWithMessage(queryClient: QueryClient, queryKey: unknown[], message: MessageResource, event: 'message.created' | 'message.updated'): void;
|
|
5
|
+
export declare function updateCacheWithReaction(queryClient: QueryClient, queryKey: unknown[], event: JoltReactionEvent, currentPersonId: number): void;
|
|
6
|
+
export declare function isTemporaryMessageId(messageId?: string | null): boolean;
|
|
7
|
+
export declare function isNewMessage(message?: MessageResource): boolean;
|
|
8
|
+
export declare function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string): import("../../hooks/use_suspense_api").RequestQueryKey;
|
|
9
|
+
//# sourceMappingURL=messages_cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages_cache.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,EAAiB,eAAe,EAAE,MAAM,aAAa,CAAA;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAK3D,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,QAsC7C;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,iBAAiB,EACxB,eAAe,EAAE,MAAM,QAuCxB;AAGD,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAEvE;AACD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,0DAMtF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { deleteRecordInPagesData } from './page_mutations';
|
|
2
|
+
import { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations';
|
|
3
|
+
import { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource';
|
|
4
|
+
import { getMessagesRequestArgs } from '../request/get_messages';
|
|
5
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api';
|
|
6
|
+
export function updateCacheWithMessage(queryClient, queryKey, message, event) {
|
|
7
|
+
queryClient.setQueryData(queryKey, prev => {
|
|
8
|
+
if (event === 'message.created') {
|
|
9
|
+
// Before adding the new message, remove any pending temporary messages
|
|
10
|
+
// with matching text to prevent duplicates from race conditions
|
|
11
|
+
let dataAfterTempRemoval = prev;
|
|
12
|
+
if (prev && message.text && message.mine) {
|
|
13
|
+
dataAfterTempRemoval = deleteRecordInPagesData({
|
|
14
|
+
data: prev,
|
|
15
|
+
record: message,
|
|
16
|
+
matchFn: (existingMessage, _record) => {
|
|
17
|
+
return (isTemporaryMessageId(existingMessage.id) &&
|
|
18
|
+
existingMessage.text === message.text &&
|
|
19
|
+
existingMessage.mine);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return updateOrCreateRecordInPagesData({
|
|
24
|
+
data: dataAfterTempRemoval,
|
|
25
|
+
record: message,
|
|
26
|
+
processRecord: (record, current) => {
|
|
27
|
+
return { ...current, ...record };
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return updateRecordInPagesData({
|
|
33
|
+
data: prev,
|
|
34
|
+
record: message,
|
|
35
|
+
processRecord: (record, current) => {
|
|
36
|
+
return { ...current, ...record };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function updateCacheWithReaction(queryClient, queryKey, event, currentPersonId) {
|
|
43
|
+
const message = { id: event.data.data.message_sort_key };
|
|
44
|
+
queryClient.setQueryData(queryKey, prev => updateRecordInPagesData({
|
|
45
|
+
data: prev,
|
|
46
|
+
record: message,
|
|
47
|
+
processRecord: (record, oldMessage) => {
|
|
48
|
+
const reactionCounts = oldMessage.reactionCounts || [];
|
|
49
|
+
let foundMatch = false;
|
|
50
|
+
let newReactionCounts = reactionCounts.map(reactionCount => {
|
|
51
|
+
if (reactionCount.value === event.data.data.value) {
|
|
52
|
+
foundMatch = true;
|
|
53
|
+
return transformReactionEventDataToReactionCountResource({
|
|
54
|
+
data: event.data.data,
|
|
55
|
+
oldData: reactionCount,
|
|
56
|
+
event: event.event,
|
|
57
|
+
currentPersonId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return reactionCount;
|
|
61
|
+
});
|
|
62
|
+
if (!foundMatch) {
|
|
63
|
+
const newReactionCount = transformReactionEventDataToReactionCountResource({
|
|
64
|
+
data: event.data.data,
|
|
65
|
+
event: event.event,
|
|
66
|
+
currentPersonId,
|
|
67
|
+
});
|
|
68
|
+
if (newReactionCount?.count) {
|
|
69
|
+
newReactionCounts = [...newReactionCounts, newReactionCount];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { ...oldMessage, reactionCounts: newReactionCounts };
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
export function isTemporaryMessageId(messageId) {
|
|
77
|
+
return !!messageId && messageId.endsWith('-temp');
|
|
78
|
+
}
|
|
79
|
+
export function isNewMessage(message) {
|
|
80
|
+
return !message?.id || isTemporaryMessageId(message.id);
|
|
81
|
+
}
|
|
82
|
+
export function getThreadedMessagesQueryKey(conversationId, replyRootId) {
|
|
83
|
+
const requestArgs = getMessagesRequestArgs({
|
|
84
|
+
conversation_id: conversationId,
|
|
85
|
+
reply_root_id: replyRootId,
|
|
86
|
+
});
|
|
87
|
+
return getRequestQueryKey(requestArgs);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=messages_cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages_cache.js","sourceRoot":"","sources":["../../../src/utils/cache/messages_cache.ts"],"names":[],"mappings":"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","sourcesContent":["import { InfiniteData, QueryClient } from '@tanstack/react-query'\nimport { ApiCollection, MessageResource } from '../../types'\nimport { deleteRecordInPagesData } from './page_mutations'\nimport { updateOrCreateRecordInPagesData, updateRecordInPagesData } from './page_mutations'\nimport { JoltReactionEvent } from '../../types/jolt_events'\nimport { transformReactionEventDataToReactionCountResource } from '../jolt/transform_reaction_event_data_to_reaction_count_resource'\nimport { getMessagesRequestArgs } from '../request/get_messages'\nimport { getRequestQueryKey } from '../../hooks/use_suspense_api'\n\nexport function updateCacheWithMessage(\n queryClient: QueryClient,\n queryKey: unknown[],\n message: MessageResource,\n event: 'message.created' | 'message.updated'\n) {\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev => {\n if (event === 'message.created') {\n // Before adding the new message, remove any pending temporary messages\n // with matching text to prevent duplicates from race conditions\n let dataAfterTempRemoval = prev\n if (prev && message.text && message.mine) {\n dataAfterTempRemoval = deleteRecordInPagesData({\n data: prev,\n record: message,\n matchFn: (existingMessage, _record) => {\n return (\n isTemporaryMessageId(existingMessage.id) &&\n existingMessage.text === message.text &&\n existingMessage.mine\n )\n },\n })\n }\n\n return updateOrCreateRecordInPagesData({\n data: dataAfterTempRemoval,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n } else {\n return updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, current) => {\n return { ...current, ...record }\n },\n })\n }\n })\n}\n\nexport function updateCacheWithReaction(\n queryClient: QueryClient,\n queryKey: unknown[],\n event: JoltReactionEvent,\n currentPersonId: number\n) {\n const message = { id: event.data.data.message_sort_key } as MessageResource\n queryClient.setQueryData<MessagesQueryData>(queryKey, prev =>\n updateRecordInPagesData({\n data: prev,\n record: message,\n processRecord: (record, oldMessage) => {\n const reactionCounts = oldMessage.reactionCounts || []\n let foundMatch = false\n let newReactionCounts = reactionCounts.map(reactionCount => {\n if (reactionCount.value === event.data.data.value) {\n foundMatch = true\n return transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n oldData: reactionCount,\n event: event.event,\n currentPersonId,\n })\n }\n return reactionCount\n })\n\n if (!foundMatch) {\n const newReactionCount = transformReactionEventDataToReactionCountResource({\n data: event.data.data,\n event: event.event,\n currentPersonId,\n })\n\n if (newReactionCount?.count) {\n newReactionCounts = [...newReactionCounts, newReactionCount]\n }\n }\n\n return { ...oldMessage, reactionCounts: newReactionCounts }\n },\n })\n )\n}\n\ntype MessagesQueryData = InfiniteData<ApiCollection<MessageResource>>\nexport function isTemporaryMessageId(messageId?: string | null): boolean {\n return !!messageId && messageId.endsWith('-temp')\n}\nexport function isNewMessage(message?: MessageResource): boolean {\n return !message?.id || isTemporaryMessageId(message.id)\n}\n\nexport function getThreadedMessagesQueryKey(conversationId: number, replyRootId: string) {\n const requestArgs = getMessagesRequestArgs({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n return getRequestQueryKey(requestArgs)\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CurrentPersonResource, MessageResource } from '../../types';
|
|
2
2
|
import { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create';
|
|
3
|
-
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, }: {
|
|
3
|
+
export declare function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, }: {
|
|
4
4
|
conversationId: number;
|
|
5
5
|
text: string;
|
|
6
6
|
attachments?: DenormalizedAttachmentResourceForCreate[];
|
|
7
7
|
currentPerson: CurrentPersonResource;
|
|
8
8
|
message?: MessageResource;
|
|
9
|
+
replyRootId?: string | null;
|
|
9
10
|
}): MessageResource;
|
|
10
11
|
//# sourceMappingURL=optimistically_create_message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAI3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.d.ts","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnF,OAAO,EAAE,uCAAuC,EAAE,MAAM,mEAAmE,CAAA;AAI3H,wBAAgB,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,GACZ,EAAE;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,uCAAuC,EAAE,CAAA;IACvD,aAAa,EAAE,qBAAqB,CAAA;IACpC,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,mBA+CA"}
|
|
@@ -3,7 +3,7 @@ import { chatQueryClient } from '../../contexts/api_provider';
|
|
|
3
3
|
import { updateOrCreateRecordInPagesData } from './page_mutations';
|
|
4
4
|
import { convertAttachmentsForCreate } from '../convert_attachments_for_create';
|
|
5
5
|
import { generatePlaceholderUlid } from '../generate_placeholder_ulid';
|
|
6
|
-
export function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, }) {
|
|
6
|
+
export function optimisticallyCreateMessage({ conversationId, text, attachments, currentPerson, message, replyRootId, }) {
|
|
7
7
|
const id = message?.id || generateTempMessageId();
|
|
8
8
|
// Convert attachments to denormalized format for optimistic UI
|
|
9
9
|
const decoratedAttachments = convertAttachmentsForCreate(attachments || []);
|
|
@@ -28,9 +28,12 @@ export function optimisticallyCreateMessage({ conversationId, text, attachments,
|
|
|
28
28
|
lastInGroup: true,
|
|
29
29
|
pending: true,
|
|
30
30
|
replyCount: 0,
|
|
31
|
-
replyRootId: null,
|
|
31
|
+
replyRootId: replyRootId || null,
|
|
32
32
|
};
|
|
33
|
-
const queryKey = getMessagesQueryKey({
|
|
33
|
+
const queryKey = getMessagesQueryKey({
|
|
34
|
+
conversation_id: conversationId,
|
|
35
|
+
reply_root_id: replyRootId,
|
|
36
|
+
});
|
|
34
37
|
chatQueryClient.setQueryData(queryKey, data => updateOrCreateRecordInPagesData({
|
|
35
38
|
data,
|
|
36
39
|
record: optimisticMessage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimistically_create_message.js","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAA;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,MAAM,UAAU,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,
|
|
1
|
+
{"version":3,"file":"optimistically_create_message.js","sourceRoot":"","sources":["../../../src/utils/cache/optimistically_create_message.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAA;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,MAAM,UAAU,2BAA2B,CAAC,EAC1C,cAAc,EACd,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,WAAW,GAQZ;IACC,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,qBAAqB,EAAE,CAAA;IAEjD,+DAA+D;IAC/D,MAAM,oBAAoB,GAAqC,2BAA2B,CACxF,WAAW,IAAI,EAAE,CAClB,CAAA;IAED,+BAA+B;IAC/B,MAAM,iBAAiB,GAAoB;QACzC,GAAG,OAAO;QACV,IAAI,EAAE,SAAS;QACf,EAAE;QACF,IAAI;QACJ,IAAI,EAAE,EAAE,EAAE,2BAA2B;QACrC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,IAAI;QACf,YAAY,EAAE,IAAI;QAClB,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,oBAAoB;QACjC,oBAAoB,EAAE,WAAW,IAAI,EAAE;QACvC,MAAM,EAAE,aAAa;QACrB,cAAc,EAAE,EAAE;QAClB,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,IAAI;QAChB,sBAAsB,EAAE,IAAI;QAC5B,WAAW,EAAE,IAAI;QACjB,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,WAAW,IAAI,IAAI;KACjC,CAAA;IAID,MAAM,QAAQ,GAAG,mBAAmB,CAAC;QACnC,eAAe,EAAE,cAAc;QAC/B,aAAa,EAAE,WAAW;KAC3B,CAAC,CAAA;IAEF,eAAe,CAAC,YAAY,CAAY,QAAQ,EAAE,IAAI,CAAC,EAAE,CACvD,+BAA+B,CAAC;QAC9B,IAAI;QACJ,MAAM,EAAE,iBAAoC;KAC7C,CAAC,CACH,CAAA;IAED,OAAO,iBAAiB,CAAA;AAC1B,CAAC;AAED,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACjC,0FAA0F;IAC1F,sEAAsE;IACtE,OAAO,GAAG,uBAAuB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,OAAO,CAAA;AAC9D,CAAC,CAAA","sourcesContent":["import { InfiniteData } from '@tanstack/react-query'\nimport { ApiCollection, CurrentPersonResource, MessageResource } from '../../types'\nimport { getMessagesQueryKey } from '../../utils/request/get_messages'\nimport { chatQueryClient } from '../../contexts/api_provider'\nimport { updateOrCreateRecordInPagesData } from './page_mutations'\nimport { convertAttachmentsForCreate } from '../convert_attachments_for_create'\nimport { DenormalizedAttachmentResourceForCreate } from '../../types/resources/denormalized_attachment_resource_for_create'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { generatePlaceholderUlid } from '../generate_placeholder_ulid'\n\nexport function optimisticallyCreateMessage({\n conversationId,\n text,\n attachments,\n currentPerson,\n message,\n replyRootId,\n}: {\n conversationId: number\n text: string\n attachments?: DenormalizedAttachmentResourceForCreate[]\n currentPerson: CurrentPersonResource\n message?: MessageResource\n replyRootId?: string | null\n}) {\n const id = message?.id || generateTempMessageId()\n\n // Convert attachments to denormalized format for optimistic UI\n const decoratedAttachments: DenormalizedAttachmentResource[] = convertAttachmentsForCreate(\n attachments || []\n )\n\n // Create an optimistic message\n const optimisticMessage: MessageResource = {\n ...message,\n type: 'Message',\n id,\n text,\n html: '', // Will be filled by server\n createdAt: new Date().toISOString(),\n deletedAt: null,\n textEditedAt: null,\n mine: true,\n attachments: decoratedAttachments,\n attachmentsForCreate: attachments || [],\n author: currentPerson,\n reactionCounts: [],\n renderAuthor: false,\n renderTime: true,\n myLatestInConversation: true,\n lastInGroup: true,\n pending: true,\n replyCount: 0,\n replyRootId: replyRootId || null,\n }\n\n // Add the optimistic message to the cache\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n const queryKey = getMessagesQueryKey({\n conversation_id: conversationId,\n reply_root_id: replyRootId,\n })\n\n chatQueryClient.setQueryData<QueryData>(queryKey, data =>\n updateOrCreateRecordInPagesData({\n data,\n record: optimisticMessage as MessageResource,\n })\n )\n\n return optimisticMessage\n}\n\nconst generateTempMessageId = () => {\n // Put it 5 seconds in the future to account for server lag of previously created messages\n // that are still pending. This gets overwritten by the server anyway.\n return `${generatePlaceholderUlid({ offsetMs: 5000 })}-temp`\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.18.0-rc.
|
|
3
|
+
"version": "3.18.0-rc.8",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"react-native-url-polyfill": "^2.0.0",
|
|
59
59
|
"typescript": "<5.6.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "fff0564227516dace7a52c0cb1896b23b6c0e395"
|
|
62
62
|
}
|
|
@@ -22,11 +22,12 @@ import {
|
|
|
22
22
|
import Animated from 'react-native-reanimated'
|
|
23
23
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
|
|
24
24
|
import { MessageReadReceipts } from './message_read_receipts'
|
|
25
|
-
import {
|
|
25
|
+
import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
26
26
|
import { Haptic } from '../../utils/native_adapters'
|
|
27
27
|
import { TheirReplyConnector, MyReplyConnector, AVATAR_CONNECTOR_SPACING } from './reply_connectors'
|
|
28
28
|
import { pluralize } from '../../utils'
|
|
29
29
|
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
30
|
+
import { isNewMessage } from '../../utils/cache/messages_cache'
|
|
30
31
|
|
|
31
32
|
/** Message
|
|
32
33
|
* Component for display of a message within a conversation list
|
|
@@ -79,7 +80,6 @@ export function Message({
|
|
|
79
80
|
const isReplyRootMessage = message.replyRootId === message.id
|
|
80
81
|
const isDeletedReplyRootMessage = isReplyRootMessage && !!message.deletedAt
|
|
81
82
|
|
|
82
|
-
const messageText = isDeletedReplyRootMessage ? 'Message deleted' : text
|
|
83
83
|
const replyCountText = pluralize(message.replyCount, 'reply')
|
|
84
84
|
const messagePendingLabel = isPersisted ? 'Saving' : 'Sending'
|
|
85
85
|
const replyRootAuthorName = message.replyRootId
|
|
@@ -117,6 +117,10 @@ export function Message({
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
const handleMessagePress = () => {
|
|
121
|
+
setShowMessageMetaToggle(!showMessageMetaToggle)
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
const handleMessageLongPress = () => {
|
|
121
125
|
if (!isPersisted) return
|
|
122
126
|
|
|
@@ -129,6 +133,7 @@ export function Message({
|
|
|
129
133
|
reply_root_author_name: replyRootAuthorName,
|
|
130
134
|
})
|
|
131
135
|
}
|
|
136
|
+
|
|
132
137
|
const handleReactionLongPress = (reaction: ReactionCountResource) => {
|
|
133
138
|
Haptic.impactLight()
|
|
134
139
|
navigation.navigate('Reactions', {
|
|
@@ -161,11 +166,12 @@ export function Message({
|
|
|
161
166
|
return (
|
|
162
167
|
<Pressable
|
|
163
168
|
onLongPress={handleMessageLongPress}
|
|
164
|
-
onPress={
|
|
169
|
+
onPress={handleMessagePress}
|
|
165
170
|
onPressIn={handleMessagePressIn}
|
|
166
171
|
onPressOut={handleMessagePressOut}
|
|
167
172
|
android_ripple={{ color: colors.androidRippleNeutral, borderless: false, foreground: true }}
|
|
168
173
|
accessibilityHint="Long press to view message actions like reacting and copying."
|
|
174
|
+
disabled={isDeletedReplyRootMessage}
|
|
169
175
|
>
|
|
170
176
|
<Animated.View style={[styles.message, animatedBackgroundColor]}>
|
|
171
177
|
{!message.mine && (
|
|
@@ -189,7 +195,7 @@ export function Message({
|
|
|
189
195
|
</View>
|
|
190
196
|
)}
|
|
191
197
|
<View style={[styles.messageContent, { marginBottom: messageBottomMargin }]}>
|
|
192
|
-
{renderAuthor && (
|
|
198
|
+
{renderAuthor && !isDeletedReplyRootMessage && (
|
|
193
199
|
<Text variant="footnote" style={styles.authorName}>
|
|
194
200
|
{message.author.name}
|
|
195
201
|
</Text>
|
|
@@ -198,18 +204,26 @@ export function Message({
|
|
|
198
204
|
style={styles.messageBubble}
|
|
199
205
|
onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
|
|
200
206
|
>
|
|
201
|
-
|
|
202
|
-
<MessageAttachments
|
|
203
|
-
attachments={message.attachments}
|
|
204
|
-
metaProps={metaProps}
|
|
205
|
-
onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
|
|
206
|
-
onMessageLongPress={handleMessageLongPress}
|
|
207
|
-
/>
|
|
208
|
-
</ErrorBoundary>
|
|
209
|
-
{messageText && (
|
|
207
|
+
{isDeletedReplyRootMessage ? (
|
|
210
208
|
<View style={styles.messageText}>
|
|
211
|
-
<
|
|
209
|
+
<Text style={styles.replyRootDeletedText}>Message deleted</Text>
|
|
212
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
|
+
</>
|
|
213
227
|
)}
|
|
214
228
|
</View>
|
|
215
229
|
{showReplyCountButton && (
|
|
@@ -223,7 +237,7 @@ export function Message({
|
|
|
223
237
|
{replyCountText}
|
|
224
238
|
</TextButton>
|
|
225
239
|
)}
|
|
226
|
-
{hasReactions && (
|
|
240
|
+
{hasReactions && !isDeletedReplyRootMessage && (
|
|
227
241
|
<View style={styles.messageReactions}>
|
|
228
242
|
{reactionCounts.map(reaction => (
|
|
229
243
|
<MessageReaction
|
|
@@ -236,7 +250,7 @@ export function Message({
|
|
|
236
250
|
))}
|
|
237
251
|
</View>
|
|
238
252
|
)}
|
|
239
|
-
{showMessageMeta && (
|
|
253
|
+
{showMessageMeta && !isDeletedReplyRootMessage && (
|
|
240
254
|
<View style={styles.messageMeta}>
|
|
241
255
|
{message.mine && !pending && !error && (
|
|
242
256
|
<MessageReadReceipts
|
|
@@ -364,5 +378,9 @@ const useMessageStyles = ({ mine }: MessageResource) => {
|
|
|
364
378
|
errorText: {
|
|
365
379
|
color: colors.statusErrorText,
|
|
366
380
|
},
|
|
381
|
+
replyRootDeletedText: {
|
|
382
|
+
color: colors.textColorDefaultSecondary,
|
|
383
|
+
fontStyle: 'italic',
|
|
384
|
+
},
|
|
367
385
|
})
|
|
368
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,
|
|
@@ -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)
|
|
@@ -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
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { createContext, PropsWithChildren, useContext, useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
interface ConversationContextValue {
|
|
4
|
+
conversationId: number
|
|
5
|
+
currentPageReplyRootId: string | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ConversationContextProviderProps extends PropsWithChildren {
|
|
9
|
+
conversationId: number
|
|
10
|
+
currentPageReplyRootId: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ConversationContext = createContext<ConversationContextValue>({
|
|
14
|
+
conversationId: 0,
|
|
15
|
+
currentPageReplyRootId: null,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const ConversationContextProvider = ({
|
|
19
|
+
children,
|
|
20
|
+
conversationId,
|
|
21
|
+
currentPageReplyRootId,
|
|
22
|
+
}: ConversationContextProviderProps) => {
|
|
23
|
+
const value = useMemo(
|
|
24
|
+
() => ({
|
|
25
|
+
conversationId,
|
|
26
|
+
currentPageReplyRootId,
|
|
27
|
+
}),
|
|
28
|
+
[conversationId, currentPageReplyRootId]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return <ConversationContext.Provider value={value}>{children}</ConversationContext.Provider>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const useConversationContext = () => useContext(ConversationContext)
|
|
@@ -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
|
|
|
4
5
|
const THROTTLE_INTERVAL = 3000 // 3 seconds
|
|
5
6
|
|
|
@@ -10,7 +11,8 @@ const THROTTLE_INTERVAL = 3000 // 3 seconds
|
|
|
10
11
|
* after receiving a typing event. This is how we can show a steady typing indicator even
|
|
11
12
|
* if the user types once every 2.9 seconds.
|
|
12
13
|
*/
|
|
13
|
-
export const useBroadcastTypingStatus = (
|
|
14
|
+
export const useBroadcastTypingStatus = () => {
|
|
15
|
+
const { conversationId, currentPageReplyRootId } = useConversationContext()
|
|
14
16
|
const apiClient = useApiClient()
|
|
15
17
|
const lastBroadcastTime = useRef<number>(0)
|
|
16
18
|
|
|
@@ -26,12 +28,14 @@ export const useBroadcastTypingStatus = (conversationId: string | number) => {
|
|
|
26
28
|
apiClient.chat
|
|
27
29
|
.post({
|
|
28
30
|
url: `/me/conversations/${conversationId}/broadcast_typing_status`,
|
|
29
|
-
data: {
|
|
31
|
+
data: {
|
|
32
|
+
data: { type: 'TypingStatus', attributes: { reply_root_id: currentPageReplyRootId } },
|
|
33
|
+
},
|
|
30
34
|
})
|
|
31
35
|
.catch(error => {
|
|
32
36
|
console.error('Failed to broadcast typing status:', error)
|
|
33
37
|
})
|
|
34
|
-
}, [apiClient, conversationId])
|
|
38
|
+
}, [apiClient.chat, conversationId, currentPageReplyRootId])
|
|
35
39
|
|
|
36
40
|
return broadcastTypingStatus
|
|
37
41
|
}
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { ApiCollection, MessageResource } from '../types'
|
|
2
2
|
import { useJoltChannel, useJoltEvent } from './use_jolt'
|
|
3
|
-
import {
|
|
4
|
-
deleteRecordInPagesData,
|
|
5
|
-
updateOrCreateRecordInPagesData,
|
|
6
|
-
updateRecordInPagesData,
|
|
7
|
-
} from '../utils'
|
|
3
|
+
import { deleteRecordInPagesData } from '../utils'
|
|
8
4
|
import { MessageCreatedEvent, MessageDeletedEvent } from '../types/jolt_events/message_events'
|
|
9
5
|
import { InfiniteData, useQueryClient } from '@tanstack/react-query'
|
|
10
6
|
import { useCurrentPerson } from './use_current_person'
|
|
11
7
|
import { transformMessageEventDataToMessageResource } from '../utils/jolt/transform_message_event_data_to_message_resource'
|
|
12
8
|
import { getRequestQueryKey } from './use_suspense_api'
|
|
13
9
|
import { JoltReactionEvent, JoltTypingEvent } from '../types/jolt_events'
|
|
14
|
-
import { transformReactionEventDataToReactionCountResource } from '../utils/jolt/transform_reaction_event_data_to_reaction_count_resource'
|
|
15
10
|
import { getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
16
11
|
import { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'
|
|
17
|
-
import { isTemporaryMessageId } from './use_message_create_or_update'
|
|
18
12
|
import { completeMessageCreationTracking } from '../utils/performance_tracking'
|
|
19
13
|
import { useApiClient } from './use_api_client'
|
|
14
|
+
import {
|
|
15
|
+
updateCacheWithMessage,
|
|
16
|
+
updateCacheWithReaction,
|
|
17
|
+
getThreadedMessagesQueryKey,
|
|
18
|
+
} from '../utils/cache/messages_cache'
|
|
20
19
|
|
|
21
20
|
interface Props {
|
|
22
21
|
conversationId: number
|
|
@@ -47,42 +46,17 @@ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
isTemporaryMessageId(existingMessage.id) &&
|
|
62
|
-
existingMessage.text === message.text &&
|
|
63
|
-
existingMessage.mine
|
|
64
|
-
)
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return updateOrCreateRecordInPagesData({
|
|
70
|
-
data: dataAfterTempRemoval,
|
|
71
|
-
record: message,
|
|
72
|
-
processRecord: (record, current) => {
|
|
73
|
-
return { ...current, ...record }
|
|
74
|
-
},
|
|
75
|
-
})
|
|
76
|
-
} else {
|
|
77
|
-
return updateRecordInPagesData({
|
|
78
|
-
data: prev,
|
|
79
|
-
record: message,
|
|
80
|
-
processRecord: (record, current) => {
|
|
81
|
-
return { ...current, ...record }
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
})
|
|
49
|
+
// Update the main conversation cache
|
|
50
|
+
updateCacheWithMessage(queryClient, messagesQueryKey, message, e.event)
|
|
51
|
+
|
|
52
|
+
// If message has a reply_root_id, also update the threaded cache
|
|
53
|
+
if (data.reply_root_id) {
|
|
54
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(
|
|
55
|
+
conversationId,
|
|
56
|
+
data.reply_root_id
|
|
57
|
+
)
|
|
58
|
+
updateCacheWithMessage(queryClient, threadedMessagesQueryKey, message, e.event)
|
|
59
|
+
}
|
|
86
60
|
}
|
|
87
61
|
|
|
88
62
|
const handleMessageDeleted = async (e: MessageDeletedEvent) => {
|
|
@@ -92,50 +66,34 @@ export function useConversationMessagesJoltEvents({ conversationId }: Props) {
|
|
|
92
66
|
currentPersonId: currentPerson.id,
|
|
93
67
|
})
|
|
94
68
|
|
|
69
|
+
// Update the main conversation cache
|
|
95
70
|
queryClient.setQueryData<QueryData>(messagesQueryKey, prev =>
|
|
96
71
|
deleteRecordInPagesData({ data: prev, record: message })
|
|
97
72
|
)
|
|
73
|
+
|
|
74
|
+
// If message has a reply_root_id, also update the threaded cache
|
|
75
|
+
if (data.reply_root_id) {
|
|
76
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(
|
|
77
|
+
conversationId,
|
|
78
|
+
data.reply_root_id
|
|
79
|
+
)
|
|
80
|
+
queryClient.setQueryData<QueryData>(threadedMessagesQueryKey, prev =>
|
|
81
|
+
deleteRecordInPagesData({ data: prev, record: message })
|
|
82
|
+
)
|
|
83
|
+
}
|
|
98
84
|
}
|
|
99
85
|
|
|
100
86
|
const handleReactionJoltEvent = async (e: JoltReactionEvent) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (reactionCount.value === data.value) {
|
|
112
|
-
foundMatch = true
|
|
113
|
-
return transformReactionEventDataToReactionCountResource({
|
|
114
|
-
data,
|
|
115
|
-
oldData: reactionCount,
|
|
116
|
-
event: e.event,
|
|
117
|
-
currentPersonId: currentPerson.id,
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
return reactionCount
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
if (!foundMatch) {
|
|
124
|
-
const newReactionCount = transformReactionEventDataToReactionCountResource({
|
|
125
|
-
data,
|
|
126
|
-
event: e.event,
|
|
127
|
-
currentPersonId: currentPerson.id,
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
if (newReactionCount?.count) {
|
|
131
|
-
newReactionCounts = [...newReactionCounts, newReactionCount]
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return { ...oldMessage, reactionCounts: newReactionCounts }
|
|
136
|
-
},
|
|
137
|
-
})
|
|
138
|
-
)
|
|
87
|
+
// Update the main conversation cache and capture the reply_root_id if present
|
|
88
|
+
updateCacheWithReaction(queryClient, messagesQueryKey, e, currentPerson.id)
|
|
89
|
+
|
|
90
|
+
const replyRootId = e.data.data.reply_root_id
|
|
91
|
+
|
|
92
|
+
// If the message has a reply_root_id, also update the threaded cache
|
|
93
|
+
if (replyRootId) {
|
|
94
|
+
const threadedMessagesQueryKey = getThreadedMessagesQueryKey(conversationId, replyRootId)
|
|
95
|
+
updateCacheWithReaction(queryClient, threadedMessagesQueryKey, e, currentPerson.id)
|
|
96
|
+
}
|
|
139
97
|
}
|
|
140
98
|
|
|
141
99
|
const handleTypingEvent = async (e: JoltTypingEvent) => {
|