@droppii-org/chat-mobile 0.2.78 → 0.2.80
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/lib/module/components/messages/BubbleTail.js +31 -0
- package/lib/module/components/messages/BubbleTail.js.map +1 -0
- package/lib/module/components/messages/BubbleTailRow.js +46 -0
- package/lib/module/components/messages/BubbleTailRow.js.map +1 -0
- package/lib/module/components/messages/bubbleTail.utils.js +19 -0
- package/lib/module/components/messages/bubbleTail.utils.js.map +1 -0
- package/lib/module/components/messages/fileMessage/index.js +72 -65
- package/lib/module/components/messages/fileMessage/index.js.map +1 -1
- package/lib/module/components/messages/imageMessage/index.js +23 -17
- package/lib/module/components/messages/imageMessage/index.js.map +1 -1
- package/lib/module/components/messages/linkMessage/index.js +11 -23
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/mergedMessage/index.js +14 -7
- package/lib/module/components/messages/mergedMessage/index.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/index.js +11 -23
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/components/messages/revokedMessage/index.js +14 -7
- package/lib/module/components/messages/revokedMessage/index.js.map +1 -1
- package/lib/module/components/messages/stickerMessage/index.js +13 -7
- package/lib/module/components/messages/stickerMessage/index.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +11 -26
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/components/messages/types.js.map +1 -1
- package/lib/module/components/messages/videoMessage/index.js +16 -8
- package/lib/module/components/messages/videoMessage/index.js.map +1 -1
- package/lib/module/core/useChatListener.js +11 -0
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/hooks/groups/useDismissGroup.js +2 -1
- package/lib/module/hooks/groups/useDismissGroup.js.map +1 -1
- package/lib/module/hooks/groups/useGroupAccessGuard.js +145 -0
- package/lib/module/hooks/groups/useGroupAccessGuard.js.map +1 -0
- package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js +4 -1
- package/lib/module/screens/ChatInfo/ChatInfoDescriptionRow.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +12 -10
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetailHeader.js +3 -9
- package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +71 -21
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatDay.js +8 -2
- package/lib/module/screens/chat-detail/legend/LegendChatDay.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +38 -13
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/inbox/CommunityTab.js +5 -2
- package/lib/module/screens/inbox/CommunityTab.js.map +1 -1
- package/lib/module/screens/inbox/MessagesTab.js +2 -2
- package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
- package/lib/module/store/conversation.js +18 -20
- package/lib/module/store/conversation.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +4 -1
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/utils/conversation.js +4 -1
- package/lib/module/utils/conversation.js.map +1 -1
- package/lib/module/utils/legendListMessage.js +86 -12
- package/lib/module/utils/legendListMessage.js.map +1 -1
- package/lib/module/utils/message.js +19 -1
- package/lib/module/utils/message.js.map +1 -1
- package/lib/module/utils/messageUtils.js +7 -1
- package/lib/module/utils/messageUtils.js.map +1 -1
- package/lib/typescript/src/components/messages/BubbleTail.d.ts +13 -0
- package/lib/typescript/src/components/messages/BubbleTail.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/BubbleTailRow.d.ts +25 -0
- package/lib/typescript/src/components/messages/BubbleTailRow.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/bubbleTail.utils.d.ts +11 -0
- package/lib/typescript/src/components/messages/bubbleTail.utils.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/imageMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/imageMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/mergedMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/mergedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/revokedMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/revokedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/types.d.ts +3 -0
- package/lib/typescript/src/components/messages/types.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/videoMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/videoMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/hooks/groups/useDismissGroup.d.ts.map +1 -1
- package/lib/typescript/src/hooks/groups/useGroupAccessGuard.d.ts +10 -0
- package/lib/typescript/src/hooks/groups/useGroupAccessGuard.d.ts.map +1 -0
- package/lib/typescript/src/screens/ChatInfo/ChatInfoDescriptionRow.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts +6 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +14 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +0 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/store/conversation.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
- package/lib/typescript/src/utils/legendListMessage.d.ts +19 -4
- package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
- package/lib/typescript/src/utils/message.d.ts +17 -0
- package/lib/typescript/src/utils/message.d.ts.map +1 -1
- package/lib/typescript/src/utils/messageUtils.d.ts +1 -0
- package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/messages/BubbleTail.tsx +35 -0
- package/src/components/messages/BubbleTailRow.tsx +64 -0
- package/src/components/messages/bubbleTail.utils.ts +21 -0
- package/src/components/messages/fileMessage/index.tsx +90 -83
- package/src/components/messages/imageMessage/index.tsx +26 -23
- package/src/components/messages/linkMessage/index.tsx +6 -18
- package/src/components/messages/mergedMessage/index.tsx +27 -15
- package/src/components/messages/quotedMessage/index.tsx +7 -19
- package/src/components/messages/revokedMessage/index.tsx +23 -14
- package/src/components/messages/stickerMessage/index.tsx +16 -11
- package/src/components/messages/textMessage/index.tsx +6 -22
- package/src/components/messages/types.ts +3 -0
- package/src/components/messages/videoMessage/index.tsx +13 -10
- package/src/core/useChatListener.ts +14 -0
- package/src/hooks/groups/useDismissGroup.tsx +1 -0
- package/src/hooks/groups/useGroupAccessGuard.ts +182 -0
- package/src/screens/ChatInfo/ChatInfoDescriptionRow.tsx +5 -1
- package/src/screens/chat-detail/ChatDetail.tsx +11 -13
- package/src/screens/chat-detail/ChatDetailHeader.tsx +2 -6
- package/src/screens/chat-detail/ChatListLegend.tsx +97 -28
- package/src/screens/chat-detail/legend/LegendChatDay.tsx +25 -15
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +62 -12
- package/src/screens/chat-detail/types.ts +0 -1
- package/src/screens/inbox/CommunityTab.tsx +2 -2
- package/src/screens/inbox/MessagesTab.tsx +1 -1
- package/src/store/conversation.ts +14 -18
- package/src/translation/resources/i18n.ts +4 -0
- package/src/utils/conversation.ts +6 -3
- package/src/utils/legendListMessage.ts +123 -14
- package/src/utils/message.ts +19 -1
- package/src/utils/messageUtils.ts +9 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { MessageType, type MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
2
|
+
import { GROUP_NOTIFICATION_TYPES } from './messageUtils';
|
|
3
|
+
import { getMessageRenderId, getMessageTime } from './message';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Extract message text based on content type
|
|
@@ -39,41 +41,148 @@ export const getMessageText = (message: MessageItem): string => {
|
|
|
39
41
|
}
|
|
40
42
|
};
|
|
41
43
|
|
|
44
|
+
/** Where a message sits inside its run of consecutive same-sender messages. */
|
|
45
|
+
export type GroupPosition = 'first' | 'middle' | 'last' | 'solo';
|
|
46
|
+
|
|
42
47
|
export interface PrecomputedMessageData {
|
|
43
48
|
messageId: string;
|
|
44
49
|
dayStart: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* More than GROUP_TIME_GAP has passed since the previous message. The list
|
|
52
|
+
* marks these with a time label: the cluster break is real but, unlike a day
|
|
53
|
+
* change, nothing else on screen would show why the run stopped.
|
|
54
|
+
*/
|
|
55
|
+
timeBreak: boolean;
|
|
45
56
|
createdAt: number;
|
|
57
|
+
groupPosition: GroupPosition;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Two messages further apart than this never share a cluster. */
|
|
61
|
+
const GROUP_TIME_GAP = 300_000; // 5 minutes
|
|
62
|
+
|
|
63
|
+
/** Virtual sender for bot conversations — see getGroupKey. */
|
|
64
|
+
const BOT_GROUP_KEY = '__bot__';
|
|
65
|
+
|
|
66
|
+
const startOfDay = (timestamp: number): number => {
|
|
67
|
+
const date = new Date(timestamp);
|
|
68
|
+
date.setHours(0, 0, 0, 0);
|
|
69
|
+
return date.getTime();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* System banners (group created/renamed/member changes) and anything without a
|
|
74
|
+
* sender are not user-authored, so they never join a cluster and always break
|
|
75
|
+
* the runs on both sides of them.
|
|
76
|
+
*/
|
|
77
|
+
const isSystemMessage = (message: MessageItem): boolean =>
|
|
78
|
+
GROUP_NOTIFICATION_TYPES.includes(message.contentType) || !message.sendID;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Identity two messages must share to cluster together. In a bot conversation
|
|
82
|
+
* every incoming message is treated as one virtual sender: replies come from
|
|
83
|
+
* the bot and from human agents under different sendIDs, but read as a single
|
|
84
|
+
* continuous voice.
|
|
85
|
+
*/
|
|
86
|
+
const getGroupKey = (
|
|
87
|
+
message: MessageItem,
|
|
88
|
+
isBotConversation: boolean,
|
|
89
|
+
currentUserId?: string
|
|
90
|
+
): string => {
|
|
91
|
+
const isOutgoing = message.sendID === currentUserId;
|
|
92
|
+
if (isBotConversation && !isOutgoing) return BOT_GROUP_KEY;
|
|
93
|
+
return message.sendID;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export interface PrecomputeOptions {
|
|
97
|
+
isBotConversation?: boolean;
|
|
98
|
+
currentUserId?: string;
|
|
46
99
|
}
|
|
47
100
|
|
|
48
101
|
/**
|
|
49
|
-
* Precompute
|
|
50
|
-
*
|
|
102
|
+
* Precompute per-message render data (day dividers + cluster position) so
|
|
103
|
+
* renderItem stays allocation-free.
|
|
51
104
|
*
|
|
52
|
-
*
|
|
105
|
+
* Runs in two passes: day starts first, because cluster boundaries depend on
|
|
106
|
+
* them — a divider between two messages separates them however close in time
|
|
107
|
+
* they are.
|
|
53
108
|
*/
|
|
54
109
|
export const precomputeLegendListData = (
|
|
55
|
-
messages: MessageItem[]
|
|
110
|
+
messages: MessageItem[],
|
|
111
|
+
{ isBotConversation = false, currentUserId }: PrecomputeOptions = {}
|
|
56
112
|
): Map<string, PrecomputedMessageData> => {
|
|
57
113
|
const dataMap = new Map<string, PrecomputedMessageData>();
|
|
114
|
+
|
|
115
|
+
// Pass 1 — identity, timestamp, day divider.
|
|
116
|
+
const dayStarts: boolean[] = [];
|
|
117
|
+
const ids: string[] = [];
|
|
118
|
+
// Resolved timestamps, reused by pass 2. Shared with the list's sort key so
|
|
119
|
+
// cluster boundaries are computed over the same ordering that gets rendered.
|
|
120
|
+
const timestamps: number[] = [];
|
|
58
121
|
let lastDay: number | null = null;
|
|
59
122
|
|
|
60
123
|
messages.forEach((message, index) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
124
|
+
const messageId = getMessageRenderId(message, index);
|
|
125
|
+
|
|
126
|
+
// A message still in flight can reach us with no usable timestamp at all.
|
|
127
|
+
// Left as 0 it lands on the epoch, which reads as a different calendar day
|
|
128
|
+
// from its neighbours — that inserts a bogus divider AND splits the cluster,
|
|
129
|
+
// stranding a tail on the message before it. Anchor it to the previous
|
|
130
|
+
// message instead, which is where it belongs in both senses.
|
|
131
|
+
const rawCreatedAt = getMessageTime(message);
|
|
132
|
+
const createdAt =
|
|
133
|
+
rawCreatedAt > 0 ? rawCreatedAt : (timestamps[index - 1] ?? rawCreatedAt);
|
|
134
|
+
const day = startOfDay(createdAt);
|
|
66
135
|
|
|
67
|
-
const
|
|
68
|
-
|
|
136
|
+
const prevTimestamp = timestamps[index - 1];
|
|
137
|
+
|
|
138
|
+
ids[index] = messageId;
|
|
139
|
+
timestamps[index] = createdAt;
|
|
140
|
+
dayStarts[index] = lastDay !== day;
|
|
141
|
+
lastDay = day;
|
|
69
142
|
|
|
70
143
|
dataMap.set(messageId, {
|
|
71
144
|
messageId,
|
|
72
|
-
dayStart:
|
|
145
|
+
dayStart: dayStarts[index]!,
|
|
146
|
+
timeBreak:
|
|
147
|
+
prevTimestamp !== undefined &&
|
|
148
|
+
createdAt - prevTimestamp > GROUP_TIME_GAP,
|
|
73
149
|
createdAt,
|
|
150
|
+
groupPosition: 'solo',
|
|
74
151
|
});
|
|
152
|
+
});
|
|
75
153
|
|
|
76
|
-
|
|
154
|
+
// Pass 2 — cluster position, now that every day divider is known.
|
|
155
|
+
messages.forEach((message, index) => {
|
|
156
|
+
const entry = dataMap.get(ids[index]!);
|
|
157
|
+
if (!entry) return;
|
|
158
|
+
if (isSystemMessage(message)) return; // stays 'solo'
|
|
159
|
+
|
|
160
|
+
const key = getGroupKey(message, isBotConversation, currentUserId);
|
|
161
|
+
const prev = messages[index - 1];
|
|
162
|
+
const next = messages[index + 1];
|
|
163
|
+
|
|
164
|
+
const joinsPrev =
|
|
165
|
+
!!prev &&
|
|
166
|
+
!isSystemMessage(prev) &&
|
|
167
|
+
getGroupKey(prev, isBotConversation, currentUserId) === key &&
|
|
168
|
+
timestamps[index]! - timestamps[index - 1]! <= GROUP_TIME_GAP &&
|
|
169
|
+
// A divider above THIS message separates it from the one before it.
|
|
170
|
+
!dayStarts[index];
|
|
171
|
+
|
|
172
|
+
const joinsNext =
|
|
173
|
+
!!next &&
|
|
174
|
+
!isSystemMessage(next) &&
|
|
175
|
+
getGroupKey(next, isBotConversation, currentUserId) === key &&
|
|
176
|
+
timestamps[index + 1]! - timestamps[index]! <= GROUP_TIME_GAP &&
|
|
177
|
+
!dayStarts[index + 1];
|
|
178
|
+
|
|
179
|
+
entry.groupPosition = joinsPrev
|
|
180
|
+
? joinsNext
|
|
181
|
+
? 'middle'
|
|
182
|
+
: 'last'
|
|
183
|
+
: joinsNext
|
|
184
|
+
? 'first'
|
|
185
|
+
: 'solo';
|
|
77
186
|
});
|
|
78
187
|
|
|
79
188
|
return dataMap;
|
|
@@ -90,7 +199,7 @@ export const computeDayStarts = (
|
|
|
90
199
|
let lastDay: number | null = null;
|
|
91
200
|
|
|
92
201
|
messages.forEach((message) => {
|
|
93
|
-
const day =
|
|
202
|
+
const day = startOfDay(getMessageTime(message));
|
|
94
203
|
const messageId = message.clientMsgID || message.serverMsgID || '';
|
|
95
204
|
dayStartMap.set(messageId, lastDay !== day);
|
|
96
205
|
lastDay = day;
|
package/src/utils/message.ts
CHANGED
|
@@ -6,12 +6,30 @@ import {
|
|
|
6
6
|
} from '@droppii/openim-rn-client-sdk';
|
|
7
7
|
import { getConversationID } from './conversation';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Canonical timestamp for a message. `sendTime` is 0 until the SDK stamps an
|
|
11
|
+
* outgoing message, so `createTime` covers the in-flight window. This is the
|
|
12
|
+
* key the list is sorted by — anything reasoning about message order must use
|
|
13
|
+
* it too, or it will disagree with the order actually rendered.
|
|
14
|
+
*/
|
|
15
|
+
export const getMessageTime = (message: MessageItem) =>
|
|
10
16
|
message.sendTime || message.createTime || 0;
|
|
11
17
|
|
|
12
18
|
const getMessageKey = (message: MessageItem) =>
|
|
13
19
|
message.clientMsgID || message.serverMsgID;
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Stable id for a message inside a rendered list.
|
|
23
|
+
*
|
|
24
|
+
* A message that has not round-tripped through the server yet may carry
|
|
25
|
+
* neither id, so position and timestamp stand in. Every consumer that looks a
|
|
26
|
+
* message up by id must derive it the same way — the precompute map is keyed
|
|
27
|
+
* on this, so a divergent formula silently misses and the message renders
|
|
28
|
+
* without its day divider or cluster position.
|
|
29
|
+
*/
|
|
30
|
+
export const getMessageRenderId = (message: MessageItem, index: number) =>
|
|
31
|
+
getMessageKey(message) || `temp-${getMessageTime(message)}-${index}`;
|
|
32
|
+
|
|
15
33
|
export const mergeMessages = (
|
|
16
34
|
current: MessageItem[],
|
|
17
35
|
incoming: MessageItem[]
|
|
@@ -92,6 +92,14 @@ export const GROUP_NOTIFICATION_TEXT: Partial<
|
|
|
92
92
|
},
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
+
// The content types above, as a plain list. Both the chat list (to decide
|
|
96
|
+
// whether a message renders as a system banner) and the grouping precompute
|
|
97
|
+
// (system messages never join a sender cluster) need this, so it is derived
|
|
98
|
+
// once here rather than in each consumer.
|
|
99
|
+
export const GROUP_NOTIFICATION_TYPES = Object.keys(
|
|
100
|
+
GROUP_NOTIFICATION_TEXT
|
|
101
|
+
).map(Number) as MessageType[];
|
|
102
|
+
|
|
95
103
|
const parseNotificationDetail = (message: MessageItem): any => {
|
|
96
104
|
if (!message.notificationElem?.detail) return undefined;
|
|
97
105
|
try {
|
|
@@ -232,7 +240,7 @@ export const getMessagePreview = (message?: MessageItem): string => {
|
|
|
232
240
|
case MessageType.FileMessage: {
|
|
233
241
|
const fileName = message?.fileElem?.fileName;
|
|
234
242
|
if (fileName) {
|
|
235
|
-
return `[File] ${fileName}`;
|
|
243
|
+
return `[File] ${truncateMiddle(fileName, 20)}`;
|
|
236
244
|
}
|
|
237
245
|
return '[File]';
|
|
238
246
|
}
|