@droppii-org/chat-mobile 0.2.88 → 0.2.90
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/ThreadCard/ThreadCard.js +0 -1
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/components/messages/LinkifiedText.js +34 -0
- package/lib/module/components/messages/LinkifiedText.js.map +1 -0
- package/lib/module/components/messages/quotedMessage/index.js +14 -2
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +15 -22
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/core/useChatListener.js +2 -2
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/hooks/message/useMessages.js +6 -0
- package/lib/module/hooks/message/useMessages.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +10 -3
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/screens/ResourceCenter/ResourceCenterTabScene.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +2 -1
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +235 -123
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/PinnedMessagesSheet.js +4 -1
- package/lib/module/screens/chat-detail/PinnedMessagesSheet.js.map +1 -1
- package/lib/module/screens/chat-detail/PinnedMessagesSheetBackup.js +3 -1
- package/lib/module/screens/chat-detail/PinnedMessagesSheetBackup.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +11 -9
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +37 -4
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
- package/lib/module/services/resourceCenter.js +45 -22
- package/lib/module/services/resourceCenter.js.map +1 -1
- package/lib/module/store/message.js +8 -1
- package/lib/module/store/message.js.map +1 -1
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/LinkifiedText.d.ts +11 -0
- package/lib/typescript/src/components/messages/LinkifiedText.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts +1 -0
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useMessages.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/ResourceCenter/ResourceCenterTabScene.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/ChatListLegend.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheet.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheetBackup.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +5 -3
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +2 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
- package/lib/typescript/src/services/resourceCenter.d.ts.map +1 -1
- package/lib/typescript/src/store/message.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/ThreadCard/ThreadCard.tsx +0 -2
- package/src/components/messages/LinkifiedText.tsx +37 -0
- package/src/components/messages/quotedMessage/index.tsx +18 -2
- package/src/components/messages/textMessage/index.tsx +18 -27
- package/src/core/useChatListener.ts +2 -2
- package/src/hooks/message/useMessages.ts +6 -0
- package/src/hooks/message/useSendMessage.ts +7 -0
- package/src/screens/ResourceCenter/ResourceCenterTabScene.tsx +0 -1
- package/src/screens/chat-detail/ChatDetail.tsx +1 -0
- package/src/screens/chat-detail/ChatListLegend.tsx +236 -136
- package/src/screens/chat-detail/PinnedMessagesSheet.tsx +6 -1
- package/src/screens/chat-detail/PinnedMessagesSheetBackup.tsx +3 -1
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +16 -11
- package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +47 -2
- package/src/services/resourceCenter.ts +77 -36
- package/src/store/message.ts +7 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
forwardRef,
|
|
3
3
|
useCallback,
|
|
4
|
+
useEffect,
|
|
4
5
|
useImperativeHandle,
|
|
5
6
|
useMemo,
|
|
7
|
+
useRef,
|
|
6
8
|
useState,
|
|
7
9
|
} from 'react';
|
|
8
10
|
import { Modal, Pressable, StyleSheet } from 'react-native';
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
import type { MeasuredRect, MessageToolboxRef } from './types';
|
|
19
21
|
import { MessageToolboxWrapper } from './MessageToolboxWrapper';
|
|
20
22
|
import type { ReactNode } from 'react';
|
|
23
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
21
24
|
import { useSendMessage } from '../../../hooks/message/useSendMessage';
|
|
22
25
|
import { useChatDetailContext } from '../../../context';
|
|
23
26
|
import {
|
|
@@ -26,11 +29,25 @@ import {
|
|
|
26
29
|
} from '@droppii/openim-rn-client-sdk';
|
|
27
30
|
import { useHasGroupPermission } from '../../../hooks/useGroupPermission';
|
|
28
31
|
|
|
32
|
+
// Android only: this Modal's native window is added/removed asynchronously
|
|
33
|
+
// relative to its React state, and each of those transitions makes
|
|
34
|
+
// react-native-keyboard-controller's resize listener misfire a spurious
|
|
35
|
+
// zero-height "keyboard" event a short beat later — confirmed via adb logcat,
|
|
36
|
+
// the close-triggered one lands ~30-70ms after `hide()` runs. `freeze` is
|
|
37
|
+
// held past that window (not just released synchronously in `hide()`) so the
|
|
38
|
+
// delayed spurious event is still suppressed instead of replaying a stale
|
|
39
|
+
// scrollTo. See `show`/`hide` below for the observed sequence.
|
|
40
|
+
const UNFREEZE_DELAY_MS = 300;
|
|
41
|
+
|
|
29
42
|
interface MessageToolboxOverlayProps {
|
|
30
43
|
currentUserId?: string;
|
|
31
44
|
customMessageItemFactory?: (item: MessageItem) => ReactNode;
|
|
32
45
|
onPinMessage?: (message: MessageItem) => void;
|
|
33
46
|
onUnpinMessage?: (message: MessageItem) => void;
|
|
47
|
+
// Same SharedValue KeyboardAwareLegendList uses to suspend its own
|
|
48
|
+
// keyboard-driven scroll adjustments (see MessageToolboxOverlay's
|
|
49
|
+
// show/hide below for why this Modal needs it).
|
|
50
|
+
freeze?: SharedValue<boolean>;
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
export const MessageToolboxOverlay = forwardRef<
|
|
@@ -43,6 +60,7 @@ export const MessageToolboxOverlay = forwardRef<
|
|
|
43
60
|
customMessageItemFactory,
|
|
44
61
|
onPinMessage,
|
|
45
62
|
onUnpinMessage,
|
|
63
|
+
freeze,
|
|
46
64
|
},
|
|
47
65
|
ref
|
|
48
66
|
) => {
|
|
@@ -56,12 +74,31 @@ export const MessageToolboxOverlay = forwardRef<
|
|
|
56
74
|
);
|
|
57
75
|
const { retryMessage, revokeMessage } = useSendMessage();
|
|
58
76
|
const isPinAllowed = useHasGroupPermission(GroupPermission.PinMessage);
|
|
77
|
+
const unfreezeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
|
78
|
+
null
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const clearPendingUnfreeze = useCallback(() => {
|
|
82
|
+
if (unfreezeTimeoutRef.current) {
|
|
83
|
+
clearTimeout(unfreezeTimeoutRef.current);
|
|
84
|
+
unfreezeTimeoutRef.current = null;
|
|
85
|
+
}
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
useEffect(() => clearPendingUnfreeze, [clearPendingUnfreeze]);
|
|
59
89
|
|
|
60
90
|
const hide = useCallback(() => {
|
|
61
91
|
setVisible(false);
|
|
62
92
|
setMessage(undefined);
|
|
63
93
|
setRect(null);
|
|
64
|
-
|
|
94
|
+
if (freeze) {
|
|
95
|
+
clearPendingUnfreeze();
|
|
96
|
+
unfreezeTimeoutRef.current = setTimeout(() => {
|
|
97
|
+
freeze.value = false;
|
|
98
|
+
unfreezeTimeoutRef.current = null;
|
|
99
|
+
}, UNFREEZE_DELAY_MS);
|
|
100
|
+
}
|
|
101
|
+
}, [freeze, clearPendingUnfreeze]);
|
|
65
102
|
|
|
66
103
|
const show = useCallback(
|
|
67
104
|
(msg: MessageItem, msgRect: MeasuredRect) => {
|
|
@@ -72,11 +109,18 @@ export const MessageToolboxOverlay = forwardRef<
|
|
|
72
109
|
|
|
73
110
|
if (toolboxNotAvailable) return;
|
|
74
111
|
|
|
112
|
+
if (freeze) {
|
|
113
|
+
// Cancel a still-pending delayed unfreeze from a just-closed
|
|
114
|
+
// toolbox so it can't fire mid-way through this new one.
|
|
115
|
+
clearPendingUnfreeze();
|
|
116
|
+
freeze.value = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
setMessage(msg);
|
|
76
120
|
setRect(msgRect);
|
|
77
121
|
setVisible(true);
|
|
78
122
|
},
|
|
79
|
-
[currentUserId]
|
|
123
|
+
[currentUserId, freeze, clearPendingUnfreeze]
|
|
80
124
|
);
|
|
81
125
|
|
|
82
126
|
useImperativeHandle(ref, () => ({ show, hide }), [show, hide]);
|
|
@@ -170,6 +214,7 @@ export const MessageToolboxOverlay = forwardRef<
|
|
|
170
214
|
message={
|
|
171
215
|
<LegendChatMessage
|
|
172
216
|
message={message}
|
|
217
|
+
messageId={message.clientMsgID || message.serverMsgID || ''}
|
|
173
218
|
isOutgoing={isOutgoing}
|
|
174
219
|
createdAtTime={message.sendTime || message.createTime}
|
|
175
220
|
customMessageItemFactory={customMessageItemFactory}
|
|
@@ -60,13 +60,49 @@ const safeJsonParse = <T>(raw: string | undefined): T | undefined => {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
type LinkFields = Pick<
|
|
64
|
+
DThreadResource,
|
|
65
|
+
'linkUrl' | 'linkTitle' | 'linkDescription' | 'linkImageUrl' | 'linkDomain'
|
|
66
|
+
>;
|
|
67
|
+
|
|
68
|
+
// The Link tab's endpoint returns any message that contains a URL, not just
|
|
69
|
+
// dedicated URL_TEXT (160) messages — @mention text (106) and quoted replies
|
|
70
|
+
// (114) with a link show up here too, still carrying `content.urls` / `ex.urlMetadata`.
|
|
71
|
+
// A single message can carry MULTIPLE urls in `content.urls` (e.g. "check
|
|
72
|
+
// https://a.com and https://b.com") — BE only splits multi-url messages into
|
|
73
|
+
// separate rows for dedicated URL_TEXT (160); for other content types it stays
|
|
74
|
+
// one row, so every url must be turned into its own item here. `ex.urlMetadata`
|
|
75
|
+
// only ever describes one link (the one it matches by exact url), so the rest
|
|
76
|
+
// render as domain-only cards (no title/image) — the UI already supports that.
|
|
77
|
+
const parseLinkItems = (
|
|
78
|
+
chatLog: DThreadResourceRawRow['chatLog']
|
|
79
|
+
): LinkFields[] => {
|
|
80
|
+
const ex = safeJsonParse<RawUrlTextEx>(chatLog.ex);
|
|
81
|
+
const content = safeJsonParse<RawUrlTextContent>(chatLog.content);
|
|
82
|
+
const metadata = ex?.urlMetadata;
|
|
83
|
+
const urls = content?.urls?.length
|
|
84
|
+
? content.urls
|
|
85
|
+
: metadata?.url
|
|
86
|
+
? [metadata.url]
|
|
87
|
+
: [];
|
|
88
|
+
return urls.map((linkUrl) => {
|
|
89
|
+
const matchesMetadata = !!metadata?.url && metadata.url === linkUrl;
|
|
90
|
+
return {
|
|
91
|
+
linkUrl,
|
|
92
|
+
linkTitle: matchesMetadata ? metadata?.title : undefined,
|
|
93
|
+
linkDescription: matchesMetadata ? metadata?.description : undefined,
|
|
94
|
+
linkImageUrl: matchesMetadata ? metadata?.image : undefined,
|
|
95
|
+
linkDomain: extractDomain(linkUrl),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
63
100
|
const parseThreadResourceItem = (
|
|
64
101
|
row: DThreadResourceRawRow
|
|
65
|
-
): DThreadResource => {
|
|
102
|
+
): DThreadResource[] => {
|
|
66
103
|
const { chatLog } = row;
|
|
67
104
|
const contentType = chatLog.contentType as DThreadResourceContentType;
|
|
68
|
-
const base
|
|
69
|
-
id: chatLog.clientMsgID,
|
|
105
|
+
const base = {
|
|
70
106
|
contentType,
|
|
71
107
|
isRevoked: row.isRevoked,
|
|
72
108
|
createdAt: chatLog.sendTime,
|
|
@@ -75,47 +111,52 @@ const parseThreadResourceItem = (
|
|
|
75
111
|
switch (contentType) {
|
|
76
112
|
case DThreadResourceContentType.IMAGE: {
|
|
77
113
|
const content = safeJsonParse<RawPictureContent>(chatLog.content);
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
id: chatLog.clientMsgID,
|
|
117
|
+
...base,
|
|
118
|
+
sourceUrl: content?.bigPicture?.url,
|
|
119
|
+
thumbnailUrl: content?.snapshotPicture?.url,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
83
122
|
}
|
|
84
123
|
case DThreadResourceContentType.VIDEO: {
|
|
85
124
|
const content = safeJsonParse<RawVideoContent>(chatLog.content);
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
id: chatLog.clientMsgID,
|
|
128
|
+
...base,
|
|
129
|
+
sourceUrl: content?.videoUrl,
|
|
130
|
+
thumbnailUrl: content?.snapshotUrl,
|
|
131
|
+
duration: content?.duration,
|
|
132
|
+
},
|
|
133
|
+
];
|
|
92
134
|
}
|
|
93
135
|
case DThreadResourceContentType.FILE: {
|
|
94
136
|
const content = safeJsonParse<RawFileContent>(chatLog.content);
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
id: chatLog.clientMsgID,
|
|
140
|
+
...base,
|
|
141
|
+
sourceUrl: content?.sourceUrl,
|
|
142
|
+
fileName: content?.fileName,
|
|
143
|
+
fileSize: content?.fileSize,
|
|
144
|
+
mimeType: content?.fileType,
|
|
145
|
+
},
|
|
146
|
+
];
|
|
102
147
|
}
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
148
|
+
default: {
|
|
149
|
+
const linkItems = parseLinkItems(chatLog);
|
|
150
|
+
if (!linkItems.length) return [{ id: chatLog.clientMsgID, ...base }];
|
|
151
|
+
return linkItems.map((fields, index) => ({
|
|
152
|
+
id:
|
|
153
|
+
linkItems.length > 1
|
|
154
|
+
? `${chatLog.clientMsgID}_${index}`
|
|
155
|
+
: chatLog.clientMsgID,
|
|
109
156
|
...base,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
linkDescription: metadata?.description,
|
|
113
|
-
linkImageUrl: metadata?.image,
|
|
114
|
-
linkDomain: linkUrl ? extractDomain(linkUrl) : undefined,
|
|
115
|
-
};
|
|
157
|
+
...fields,
|
|
158
|
+
}));
|
|
116
159
|
}
|
|
117
|
-
default:
|
|
118
|
-
return base;
|
|
119
160
|
}
|
|
120
161
|
};
|
|
121
162
|
|
|
@@ -127,7 +168,7 @@ export namespace ResourceCenterService {
|
|
|
127
168
|
BaseResponse<DThreadResourceListResponse>
|
|
128
169
|
>(getApiEndpoint('messagesResources'), req);
|
|
129
170
|
const rows = res?.data?.data ?? [];
|
|
130
|
-
const items = rows.
|
|
171
|
+
const items = rows.flatMap(parseThreadResourceItem);
|
|
131
172
|
// `pageable.totalPages`/`totalElements` come back as `0` on this endpoint even
|
|
132
173
|
// when rows are present (confirmed against staging) — unreliable, don't use it.
|
|
133
174
|
// Item-count-vs-pageSize is the only trustworthy `hasMore` signal available.
|
package/src/store/message.ts
CHANGED
|
@@ -14,7 +14,13 @@ import type {
|
|
|
14
14
|
import { mergeMessages } from '../utils/message';
|
|
15
15
|
import { useConversationStore } from './conversation';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Smaller than a typical "load more" page size elsewhere in the app on
|
|
18
|
+
// purpose: each page becomes a single prepend batch in ChatListLegend, and a
|
|
19
|
+
// smaller batch means fewer newly-inserted rows still unmeasured by the time
|
|
20
|
+
// native layout catches up — shrinks both the size and duration of the
|
|
21
|
+
// visible settle right after a history-prepend flush (see
|
|
22
|
+
// project_chatlistlegend_scroll_debugging memory for the full context).
|
|
23
|
+
const PAGE_SIZE = 15;
|
|
18
24
|
|
|
19
25
|
export const useMessageStore = create<MessageStore>()((set, get) => ({
|
|
20
26
|
messages: [],
|