@droppii-org/chat-mobile 0.2.80 → 0.2.82
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/MentionBadge.js +24 -0
- package/lib/module/components/ThreadCard/MentionBadge.js.map +1 -0
- package/lib/module/components/ThreadCard/ThreadCard.js +5 -1
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/components/messages/linkMessage/LinkPreviewCard.js +67 -0
- package/lib/module/components/messages/linkMessage/LinkPreviewCard.js.map +1 -0
- package/lib/module/components/messages/linkMessage/index.js +21 -68
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/QuotedMessagePreviewStrip.js +152 -0
- package/lib/module/components/messages/quotedMessage/QuotedMessagePreviewStrip.js.map +1 -0
- package/lib/module/components/messages/textMessage/index.js +102 -8
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/config/api-endpoints.js +2 -1
- package/lib/module/config/api-endpoints.js.map +1 -1
- package/lib/module/core/useChatListener.js +1 -1
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/hooks/contacts/useSearchContacts.js +50 -0
- package/lib/module/hooks/contacts/useSearchContacts.js.map +1 -0
- package/lib/module/hooks/message/useSendMessage.js +64 -3
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/query-keys.js +8 -0
- package/lib/module/hooks/query-keys.js.map +1 -1
- package/lib/module/hooks/useChatCompose.js +3 -2
- package/lib/module/hooks/useChatCompose.js.map +1 -1
- package/lib/module/screens/SearchConversation/ContactResultItem.js +119 -0
- package/lib/module/screens/SearchConversation/ContactResultItem.js.map +1 -0
- package/lib/module/screens/SearchConversation/SearchAllResult.js +26 -20
- package/lib/module/screens/SearchConversation/SearchAllResult.js.map +1 -1
- package/lib/module/screens/SearchConversation/SearchCommunityResult.js +79 -0
- package/lib/module/screens/SearchConversation/SearchCommunityResult.js.map +1 -0
- package/lib/module/screens/SearchConversation/SearchContactsResult.js +117 -0
- package/lib/module/screens/SearchConversation/SearchContactsResult.js.map +1 -0
- package/lib/module/screens/SearchConversation/SearchEmpty.js +2 -0
- package/lib/module/screens/SearchConversation/SearchEmpty.js.map +1 -1
- package/lib/module/screens/SearchConversation/SearchMessagesResult.js +1 -8
- package/lib/module/screens/SearchConversation/SearchMessagesResult.js.map +1 -1
- package/lib/module/screens/SearchConversation/SearchResultItem.js +2 -0
- package/lib/module/screens/SearchConversation/SearchResultItem.js.map +1 -1
- package/lib/module/screens/SearchConversation/TabResult.js +24 -7
- package/lib/module/screens/SearchConversation/TabResult.js.map +1 -1
- package/lib/module/screens/SearchConversation/index.js +6 -2
- package/lib/module/screens/SearchConversation/index.js.map +1 -1
- package/lib/module/screens/SearchConversation/types.js +1 -0
- package/lib/module/screens/SearchConversation/types.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +170 -48
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +1 -0
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/components/ChatMessageInput.js +23 -2
- package/lib/module/screens/chat-detail/components/ChatMessageInput.js.map +1 -1
- package/lib/module/screens/chat-detail/components/MentionHighlightAdapter.js +51 -0
- package/lib/module/screens/chat-detail/components/MentionHighlightAdapter.js.map +1 -0
- package/lib/module/screens/chat-detail/components/MentionSuggestionItem.js +49 -0
- package/lib/module/screens/chat-detail/components/MentionSuggestionItem.js.map +1 -0
- package/lib/module/screens/chat-detail/components/MentionSuggestionList.js +114 -0
- package/lib/module/screens/chat-detail/components/MentionSuggestionList.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +8 -0
- package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/useMentionComposer.js +251 -0
- package/lib/module/screens/chat-detail/hooks/useMentionComposer.js.map +1 -0
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +5 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +11 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -1
- package/lib/module/services/endpoints.js +2 -1
- package/lib/module/services/endpoints.js.map +1 -1
- package/lib/module/services/mentionSearch.js +49 -0
- package/lib/module/services/mentionSearch.js.map +1 -0
- package/lib/module/services/message.js +6 -1
- package/lib/module/services/message.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +11 -0
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js +8 -0
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/types/mention.js +2 -0
- package/lib/module/types/mention.js.map +1 -0
- package/lib/module/utils/dateTimeUtils.js +10 -0
- package/lib/module/utils/dateTimeUtils.js.map +1 -1
- package/lib/module/utils/highlightSearch.js +6 -2
- package/lib/module/utils/highlightSearch.js.map +1 -1
- package/lib/module/utils/mentionSerializer.js +113 -0
- package/lib/module/utils/mentionSerializer.js.map +1 -0
- package/lib/module/utils/mentions/index.js +7 -0
- package/lib/module/utils/mentions/index.js.map +1 -0
- package/lib/module/utils/mentions/mentionDetector.js +49 -0
- package/lib/module/utils/mentions/mentionDetector.js.map +1 -0
- package/lib/module/utils/mentions/mentionDiff.js +66 -0
- package/lib/module/utils/mentions/mentionDiff.js.map +1 -0
- package/lib/module/utils/mentions/mentionRange.js +188 -0
- package/lib/module/utils/mentions/mentionRange.js.map +1 -0
- package/lib/module/utils/mentions/mentionTokenize.js +50 -0
- package/lib/module/utils/mentions/mentionTokenize.js.map +1 -0
- package/lib/module/utils/messageUtils.js +16 -3
- package/lib/module/utils/messageUtils.js.map +1 -1
- package/lib/module/utils/url.js +33 -0
- package/lib/module/utils/url.js.map +1 -1
- package/lib/typescript/src/components/ThreadCard/MentionBadge.d.ts +7 -0
- package/lib/typescript/src/components/ThreadCard/MentionBadge.d.ts.map +1 -0
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/linkMessage/LinkPreviewCard.d.ts +16 -0
- package/lib/typescript/src/components/messages/linkMessage/LinkPreviewCard.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.d.ts +13 -0
- package/lib/typescript/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/textMessage/index.d.ts +3 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
- package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
- package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts +221 -0
- package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts.map +1 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -2
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/query-keys.d.ts +8 -0
- package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useChatCompose.d.ts +2 -1
- package/lib/typescript/src/hooks/useChatCompose.d.ts.map +1 -1
- package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts +9 -0
- package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts.map +1 -0
- package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts +3 -2
- package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts.map +1 -1
- package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts +7 -0
- package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts.map +1 -0
- package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts +8 -0
- package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts.map +1 -0
- package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts.map +1 -1
- package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts.map +1 -1
- package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts.map +1 -1
- package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts +4 -2
- package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts.map +1 -1
- package/lib/typescript/src/screens/SearchConversation/index.d.ts +4 -2
- package/lib/typescript/src/screens/SearchConversation/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/SearchConversation/types.d.ts +2 -1
- package/lib/typescript/src/screens/SearchConversation/types.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.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/components/ChatMessageInput.d.ts +10 -2
- package/lib/typescript/src/screens/chat-detail/components/ChatMessageInput.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/components/MentionHighlightAdapter.d.ts +29 -0
- package/lib/typescript/src/screens/chat-detail/components/MentionHighlightAdapter.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionItem.d.ts +8 -0
- package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionItem.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionList.d.ts +12 -0
- package/lib/typescript/src/screens/chat-detail/components/MentionSuggestionList.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useMentionComposer.d.ts +48 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useMentionComposer.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -1
- package/lib/typescript/src/services/endpoints.d.ts +1 -0
- package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
- package/lib/typescript/src/services/mentionSearch.d.ts +15 -0
- package/lib/typescript/src/services/mentionSearch.d.ts.map +1 -0
- package/lib/typescript/src/services/message.d.ts +1 -1
- package/lib/typescript/src/services/message.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +50 -0
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/mention.d.ts +65 -0
- package/lib/typescript/src/types/mention.d.ts.map +1 -0
- package/lib/typescript/src/utils/dateTimeUtils.d.ts +2 -0
- package/lib/typescript/src/utils/dateTimeUtils.d.ts.map +1 -1
- package/lib/typescript/src/utils/highlightSearch.d.ts.map +1 -1
- package/lib/typescript/src/utils/mentionSerializer.d.ts +78 -0
- package/lib/typescript/src/utils/mentionSerializer.d.ts.map +1 -0
- package/lib/typescript/src/utils/mentions/index.d.ts +5 -0
- package/lib/typescript/src/utils/mentions/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/mentions/mentionDetector.d.ts +18 -0
- package/lib/typescript/src/utils/mentions/mentionDetector.d.ts.map +1 -0
- package/lib/typescript/src/utils/mentions/mentionDiff.d.ts +20 -0
- package/lib/typescript/src/utils/mentions/mentionDiff.d.ts.map +1 -0
- package/lib/typescript/src/utils/mentions/mentionRange.d.ts +86 -0
- package/lib/typescript/src/utils/mentions/mentionRange.d.ts.map +1 -0
- package/lib/typescript/src/utils/mentions/mentionTokenize.d.ts +29 -0
- package/lib/typescript/src/utils/mentions/mentionTokenize.d.ts.map +1 -0
- package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
- package/lib/typescript/src/utils/url.d.ts +12 -0
- package/lib/typescript/src/utils/url.d.ts.map +1 -1
- package/package.json +3 -1
- package/src/build-ignore.d.ts +2 -0
- package/src/components/ThreadCard/MentionBadge.tsx +28 -0
- package/src/components/ThreadCard/ThreadCard.tsx +3 -0
- package/src/components/messages/linkMessage/LinkPreviewCard.tsx +92 -0
- package/src/components/messages/linkMessage/index.tsx +25 -89
- package/src/components/messages/quotedMessage/QuotedMessagePreviewStrip.tsx +168 -0
- package/src/components/messages/textMessage/index.tsx +124 -4
- package/src/config/api-endpoints.ts +2 -0
- package/src/core/useChatListener.ts +1 -1
- package/src/hooks/contacts/useSearchContacts.ts +66 -0
- package/src/hooks/message/useSendMessage.ts +60 -1
- package/src/hooks/query-keys.ts +12 -0
- package/src/hooks/useChatCompose.ts +115 -107
- package/src/screens/SearchConversation/ContactResultItem.tsx +128 -0
- package/src/screens/SearchConversation/SearchAllResult.tsx +38 -24
- package/src/screens/SearchConversation/SearchCommunityResult.tsx +96 -0
- package/src/screens/SearchConversation/SearchContactsResult.tsx +155 -0
- package/src/screens/SearchConversation/SearchEmpty.tsx +2 -0
- package/src/screens/SearchConversation/SearchMessagesResult.tsx +1 -6
- package/src/screens/SearchConversation/SearchResultItem.tsx +6 -1
- package/src/screens/SearchConversation/TabResult.tsx +40 -5
- package/src/screens/SearchConversation/index.tsx +10 -2
- package/src/screens/SearchConversation/types.tsx +1 -0
- package/src/screens/chat-detail/ChatComposer.tsx +201 -67
- package/src/screens/chat-detail/ChatListLegend.tsx +1 -0
- package/src/screens/chat-detail/components/ChatMessageInput.tsx +35 -2
- package/src/screens/chat-detail/components/MentionHighlightAdapter.tsx +57 -0
- package/src/screens/chat-detail/components/MentionSuggestionItem.tsx +53 -0
- package/src/screens/chat-detail/components/MentionSuggestionList.tsx +133 -0
- package/src/screens/chat-detail/hooks/useChatComposerState.ts +7 -1
- package/src/screens/chat-detail/hooks/useMentionComposer.ts +344 -0
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +7 -1
- package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +22 -4
- package/src/services/endpoints.ts +1 -0
- package/src/services/mentionSearch.ts +57 -0
- package/src/services/message.ts +9 -1
- package/src/translation/resources/i18n.ts +12 -0
- package/src/types/chat.ts +52 -0
- package/src/types/mention.ts +70 -0
- package/src/utils/dateTimeUtils.ts +15 -0
- package/src/utils/highlightSearch.ts +12 -2
- package/src/utils/mentionSerializer.ts +155 -0
- package/src/utils/mentions/index.ts +16 -0
- package/src/utils/mentions/mentionDetector.ts +58 -0
- package/src/utils/mentions/mentionDiff.ts +95 -0
- package/src/utils/mentions/mentionRange.ts +207 -0
- package/src/utils/mentions/mentionTokenize.ts +53 -0
- package/src/utils/messageUtils.ts +21 -10
- package/src/utils/url.ts +30 -0
|
@@ -54,7 +54,7 @@ export const useChatListener = () => {
|
|
|
54
54
|
|
|
55
55
|
const onConversationChanged = useCallback(
|
|
56
56
|
(conversations: ConversationItem[]) => {
|
|
57
|
-
|
|
57
|
+
console.log('[Listener] OnConversationChanged', conversations);
|
|
58
58
|
|
|
59
59
|
// return; // TEMPORARILY DISABLED: OnConversationChanged/OnNewConversation can
|
|
60
60
|
const oldMap = useConversationStore.getState().map;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { contactQueryKeys } from '../query-keys';
|
|
4
|
+
import { apiInstance } from '../../services/apis';
|
|
5
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
6
|
+
import type {
|
|
7
|
+
ContactCategory,
|
|
8
|
+
DChatApplicationType,
|
|
9
|
+
IContactSearchItem,
|
|
10
|
+
IContactSearchRequest,
|
|
11
|
+
} from '../../types/chat';
|
|
12
|
+
import type { BaseResponse } from '../../types/auth';
|
|
13
|
+
import { PAGE_SIZE } from '../../constants';
|
|
14
|
+
|
|
15
|
+
interface IUseSearchContactsProps {
|
|
16
|
+
query: string;
|
|
17
|
+
applicationType: DChatApplicationType;
|
|
18
|
+
type?: ContactCategory;
|
|
19
|
+
pageSize?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useSearchContacts = ({
|
|
23
|
+
query,
|
|
24
|
+
applicationType,
|
|
25
|
+
type,
|
|
26
|
+
pageSize = PAGE_SIZE,
|
|
27
|
+
}: IUseSearchContactsProps) => {
|
|
28
|
+
const { data, ...rest } = useInfiniteQuery({
|
|
29
|
+
queryKey: [contactQueryKeys.search(query.trim(), type), pageSize],
|
|
30
|
+
queryFn: async ({ pageParam = 1 }) => {
|
|
31
|
+
const params: IContactSearchRequest = {
|
|
32
|
+
page: pageParam,
|
|
33
|
+
pageSize,
|
|
34
|
+
applicationType,
|
|
35
|
+
searchTerm: query.trim(),
|
|
36
|
+
type,
|
|
37
|
+
};
|
|
38
|
+
const res = await apiInstance?.post<BaseResponse<IContactSearchItem[]>>(
|
|
39
|
+
ENDPOINTS.chatService.searchContacts(),
|
|
40
|
+
params
|
|
41
|
+
);
|
|
42
|
+
return res?.data;
|
|
43
|
+
},
|
|
44
|
+
getNextPageParam: (lastPage) => {
|
|
45
|
+
const currentPage = lastPage?.pageable?.pageNumber || 1;
|
|
46
|
+
const dataLength = lastPage?.data?.length || 0;
|
|
47
|
+
return dataLength < pageSize ? undefined : currentPage + 1;
|
|
48
|
+
},
|
|
49
|
+
initialPageParam: 1,
|
|
50
|
+
enabled: !!query.trim() && !!applicationType,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const dataFlatten = useMemo<IContactSearchItem[]>(() => {
|
|
54
|
+
if (!query.trim() || !data) return [];
|
|
55
|
+
return (
|
|
56
|
+
data?.pages
|
|
57
|
+
?.flatMap?.((page) => page?.data)
|
|
58
|
+
?.filter((item): item is IContactSearchItem => item !== undefined) || []
|
|
59
|
+
);
|
|
60
|
+
}, [data, query]);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
dataFlatten,
|
|
64
|
+
...rest,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
|
|
2
2
|
import { useConversationStore } from '../../store/conversation';
|
|
3
3
|
import { useUserStore } from '../../store/user';
|
|
4
4
|
import type {
|
|
5
|
+
AtMsgParams,
|
|
5
6
|
CustomMsgParams,
|
|
6
7
|
MessageItem,
|
|
7
8
|
OfflinePush,
|
|
@@ -35,6 +36,8 @@ import { stickerQueryKeys } from '../query-keys';
|
|
|
35
36
|
import { getMessagePreview } from '../../utils/messageUtils';
|
|
36
37
|
import { useCanSendMessage } from '../useGroupPermission';
|
|
37
38
|
import { UIUtils } from '../../utils/UIUtils';
|
|
39
|
+
import type { Mention } from '../../types/mention';
|
|
40
|
+
import { serializeMentionsForSend } from '../../utils/mentionSerializer';
|
|
38
41
|
|
|
39
42
|
const buildSendParams = (
|
|
40
43
|
message: MessageItem,
|
|
@@ -98,12 +101,14 @@ export const useSendMessage = () => {
|
|
|
98
101
|
flow,
|
|
99
102
|
customMessage,
|
|
100
103
|
sticker,
|
|
104
|
+
mentions,
|
|
101
105
|
}: {
|
|
102
106
|
plainText?: string;
|
|
103
107
|
urlMetadata?: IUrlMetadata;
|
|
104
108
|
flow?: IRunConversationFlowPayload;
|
|
105
109
|
customMessage?: CustomMessagePayload;
|
|
106
110
|
sticker?: StickerItem;
|
|
111
|
+
mentions?: Mention[];
|
|
107
112
|
}) => {
|
|
108
113
|
if (!isSendMessageAllowed) {
|
|
109
114
|
UIUtils.toast.open({
|
|
@@ -124,9 +129,11 @@ export const useSendMessage = () => {
|
|
|
124
129
|
if (!recvID && !groupID) return;
|
|
125
130
|
|
|
126
131
|
const messageList: MessageItem[] = [];
|
|
132
|
+
let exMentions: IMessageItemEx['mentions'];
|
|
127
133
|
|
|
128
134
|
const hasText = !!plainText && plainText.trim() !== '';
|
|
129
135
|
const hasPreview = !!urlMetadata?.url;
|
|
136
|
+
const hasMentions = !!mentions && mentions.length > 0;
|
|
130
137
|
|
|
131
138
|
//text msg
|
|
132
139
|
if (hasText) {
|
|
@@ -134,12 +141,51 @@ export const useSendMessage = () => {
|
|
|
134
141
|
const isUrlMessage = urls.length > 0 || hasPreview;
|
|
135
142
|
let message: MessageItem | null = null;
|
|
136
143
|
|
|
137
|
-
|
|
144
|
+
// Mention always wins the content type over a URL preview: AtText
|
|
145
|
+
// and UrlText are mutually exclusive OpenIM content types (no field
|
|
146
|
+
// for both), but `ex.urlMetadata` is attached unconditionally below
|
|
147
|
+
// regardless of which branch builds `message` — so the URL card
|
|
148
|
+
// still rides along via `ex` on an AtTextMessage. TextMessage's
|
|
149
|
+
// render side reads it and shows the card next to the
|
|
150
|
+
// mention-highlighted text (see its doc comment). Other clients
|
|
151
|
+
// without that rendering just see plain "@Name ... https://..."
|
|
152
|
+
// text — mention intact, only the rich card is missing.
|
|
153
|
+
if (quotedMessage && hasMentions && groupID) {
|
|
154
|
+
// Native mention+quote: AtMsgParams.message embeds the quoted
|
|
155
|
+
// message into AtTextElem.quoteMessage server/core-side (verified
|
|
156
|
+
// down to openimsdk-core/internal/conversation_msg/
|
|
157
|
+
// create_message.go:76-101 — QuoteMessage: qs is a real field
|
|
158
|
+
// assignment, not just a bridge pass-through) — keeps the mention's
|
|
159
|
+
// native tag/notify semantics instead of falling back to a plain
|
|
160
|
+
// QuoteMessage, which has no atUserIDList at all.
|
|
161
|
+
const serialized = serializeMentionsForSend(mentions);
|
|
162
|
+
exMentions = { offsets: serialized.exMentions };
|
|
163
|
+
message = await createTextAtMessage({
|
|
164
|
+
text: plainText || '',
|
|
165
|
+
atUserIDList: serialized.atUserIDList,
|
|
166
|
+
atUsersInfo: serialized.atUsersInfo,
|
|
167
|
+
message: quotedMessage,
|
|
168
|
+
});
|
|
169
|
+
setQuotedMessage(undefined);
|
|
170
|
+
} else if (quotedMessage) {
|
|
138
171
|
message = await createQuotedMessage({
|
|
139
172
|
text: plainText || '',
|
|
140
173
|
message: quotedMessage,
|
|
141
174
|
});
|
|
142
175
|
setQuotedMessage(undefined);
|
|
176
|
+
} else if (hasMentions && groupID) {
|
|
177
|
+
// "Tất cả" serializes to OpenIM's own AT_ALL_TAG sentinel, not an
|
|
178
|
+
// expanded member list — no group-member fetch needed here, see
|
|
179
|
+
// AT_ALL_TAG's doc comment in mentionSerializer.ts for why.
|
|
180
|
+
const serialized = serializeMentionsForSend(mentions);
|
|
181
|
+
// Wrapped under `offsets` to match the wire shape other Droppii
|
|
182
|
+
// clients already send (see IMessageItemEx.mentions doc comment).
|
|
183
|
+
exMentions = { offsets: serialized.exMentions };
|
|
184
|
+
message = await createTextAtMessage({
|
|
185
|
+
text: plainText || '',
|
|
186
|
+
atUserIDList: serialized.atUserIDList,
|
|
187
|
+
atUsersInfo: serialized.atUsersInfo,
|
|
188
|
+
});
|
|
143
189
|
} else if (isUrlMessage) {
|
|
144
190
|
message = await createUrlTextMessage(plainText || '', urls);
|
|
145
191
|
} else if (hasText) {
|
|
@@ -178,6 +224,7 @@ export const useSendMessage = () => {
|
|
|
178
224
|
urlMetadata,
|
|
179
225
|
flow,
|
|
180
226
|
stickerId: sticker?.id,
|
|
227
|
+
mentions: exMentions,
|
|
181
228
|
};
|
|
182
229
|
|
|
183
230
|
await dispatchSend(
|
|
@@ -330,6 +377,18 @@ export const createUrlTextMessage = async (text: string, urls: string[]) => {
|
|
|
330
377
|
return urlTextMessage;
|
|
331
378
|
};
|
|
332
379
|
|
|
380
|
+
export const createTextAtMessage = async (params: AtMsgParams) => {
|
|
381
|
+
let atMessage = await OpenIMSDK.createTextAtMessage(params, uuid.v4())
|
|
382
|
+
.then((data) => {
|
|
383
|
+
return data;
|
|
384
|
+
})
|
|
385
|
+
.catch(({ errCode, errMsg }) => {
|
|
386
|
+
console.error('createTextAtMessage', errCode, errMsg);
|
|
387
|
+
return null;
|
|
388
|
+
});
|
|
389
|
+
return atMessage;
|
|
390
|
+
};
|
|
391
|
+
|
|
333
392
|
export const createCustomMessage = async (params: CustomMsgParams) => {
|
|
334
393
|
let customMessage = await OpenIMSDK.createCustomMessage(params, uuid.v4())
|
|
335
394
|
.then((data) => {
|
package/src/hooks/query-keys.ts
CHANGED
|
@@ -62,6 +62,18 @@ export const messageQueryKeys = {
|
|
|
62
62
|
[...messageQueryKeys.all, 'search', query, conversationId] as const,
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
export const mentionQueryKeys = {
|
|
66
|
+
all: ['mentionSearch'] as const,
|
|
67
|
+
search: (groupID: string, keyword: string) =>
|
|
68
|
+
[...mentionQueryKeys.all, groupID, keyword] as const,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const contactQueryKeys = {
|
|
72
|
+
all: ['contacts'] as const,
|
|
73
|
+
search: (query: string, type?: string) =>
|
|
74
|
+
[...contactQueryKeys.all, 'search', query, type] as const,
|
|
75
|
+
};
|
|
76
|
+
|
|
65
77
|
export const groupQueryKeys = {
|
|
66
78
|
all: ['groups'] as const,
|
|
67
79
|
public: () => [...groupQueryKeys.all, 'public'] as const,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
generateAndUploadVideoThumbnails,
|
|
14
14
|
type VideoWithUploadStatus,
|
|
15
15
|
} from '../utils/videoThumbnailUpload';
|
|
16
|
+
import type { Mention } from '../types/mention';
|
|
16
17
|
|
|
17
18
|
export function useChatCompose() {
|
|
18
19
|
const [text, setText] = useState('');
|
|
@@ -45,123 +46,130 @@ export function useChatCompose() {
|
|
|
45
46
|
]
|
|
46
47
|
);
|
|
47
48
|
|
|
48
|
-
const send = useCallback(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const send = useCallback(
|
|
50
|
+
async (mentions?: Mention[]) => {
|
|
51
|
+
const { selectedImages, clearAllImages } = imageAttachment;
|
|
52
|
+
const { selectedFiles, clearAllFiles } = fileAttachment;
|
|
53
|
+
const urlMeta = linkPreview.isVisible ? linkPreview.metadata : undefined;
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
// --- Attachments ---
|
|
56
|
+
if (selectedImages.length > 0 || selectedFiles.length > 0) {
|
|
57
|
+
const actualImages = selectedImages.filter(
|
|
58
|
+
(img: ImageWithUploadStatus) => !isVideoFromPicker(img.image)
|
|
59
|
+
);
|
|
60
|
+
const videos = selectedImages.filter((img: ImageWithUploadStatus) =>
|
|
61
|
+
isVideoFromPicker(img.image)
|
|
62
|
+
);
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
const uploadedImages = actualImages.filter(
|
|
65
|
+
(img: ImageWithUploadStatus) => img.uploadedUrl
|
|
66
|
+
);
|
|
67
|
+
const uploadedVideos = videos.filter(
|
|
68
|
+
(v) => v.uploadedUrl
|
|
69
|
+
) as unknown as VideoWithUploadStatus[];
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
75
|
-
let videoThumbnails: Record<string, { url: string; size: number }> = {};
|
|
76
|
-
if (videosMissingThumbnail.length > 0) {
|
|
77
|
-
videoThumbnails = await generateAndUploadVideoThumbnails(
|
|
78
|
-
videosMissingThumbnail
|
|
71
|
+
// Thumbnails are generated + uploaded at pick time (useImageAttachment).
|
|
72
|
+
// Only fall back to generating here for videos that don't already have one
|
|
73
|
+
// (e.g. thumbnail generation failed or hadn't finished before send).
|
|
74
|
+
const videosMissingThumbnail = uploadedVideos.filter(
|
|
75
|
+
(v) => !v.thumbnailUrl
|
|
79
76
|
);
|
|
80
|
-
|
|
77
|
+
let videoThumbnails: Record<string, { url: string; size: number }> = {};
|
|
78
|
+
if (videosMissingThumbnail.length > 0) {
|
|
79
|
+
videoThumbnails = await generateAndUploadVideoThumbnails(
|
|
80
|
+
videosMissingThumbnail
|
|
81
|
+
);
|
|
82
|
+
}
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
84
|
+
// Send images + videos
|
|
85
|
+
if (uploadedImages.length > 0 || uploadedVideos.length > 0) {
|
|
86
|
+
const attachmentItems = [
|
|
87
|
+
...uploadedImages.map((img: ImageWithUploadStatus) => ({
|
|
88
|
+
...img,
|
|
89
|
+
type: 'image' as const,
|
|
90
|
+
})),
|
|
91
|
+
...uploadedVideos.map(
|
|
92
|
+
(vid: VideoWithUploadStatus, vidIdx: number) => {
|
|
93
|
+
const videoId = vid.uploadedUrl || `video-${vidIdx}`;
|
|
94
|
+
const thumb = videoThumbnails[videoId];
|
|
95
|
+
return {
|
|
96
|
+
type: 'video' as const,
|
|
97
|
+
video: {
|
|
98
|
+
path: vid.image.path,
|
|
99
|
+
filename: vid.image.filename ?? undefined,
|
|
100
|
+
size: vid.image.size ?? undefined,
|
|
101
|
+
width: vid.image.width ?? undefined,
|
|
102
|
+
height: vid.image.height ?? undefined,
|
|
103
|
+
duration: vid.image.duration ?? undefined,
|
|
104
|
+
},
|
|
105
|
+
uploadProgress: vid.uploadProgress,
|
|
106
|
+
error: vid.error,
|
|
107
|
+
isUploading: vid.isUploading,
|
|
108
|
+
uploadedUrl: vid.uploadedUrl,
|
|
109
|
+
thumbnailUrl: vid.thumbnailUrl ?? thumb?.url,
|
|
110
|
+
thumbnailSize: vid.thumbnailSize ?? thumb?.size,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
];
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
// Clear composer immediately — message is already in chat list with Sending status.
|
|
117
|
+
// User retries from the bubble, not the composer.
|
|
118
|
+
ImagePicker.clean().catch(() => {});
|
|
119
|
+
clearAllImages();
|
|
120
|
+
// Awaited so files (lower priority per attachment-priority config)
|
|
121
|
+
// only start sending after images/videos are done.
|
|
122
|
+
await sendAttachments(attachmentItems, undefined);
|
|
123
|
+
}
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
125
|
+
// Send each file as its own message (never merged), in priority order
|
|
126
|
+
// after images/videos, one at a time to preserve pick order.
|
|
127
|
+
const uploadedFiles = selectedFiles.filter(
|
|
128
|
+
(f) => f.uploadedUrl && !f.error
|
|
129
|
+
);
|
|
130
|
+
if (uploadedFiles.length > 0) {
|
|
131
|
+
clearAllFiles();
|
|
132
|
+
for (const file of uploadedFiles) {
|
|
133
|
+
await sendAttachments(
|
|
134
|
+
[
|
|
135
|
+
{
|
|
136
|
+
type: 'file' as const,
|
|
137
|
+
file: {
|
|
138
|
+
path: file.uri,
|
|
139
|
+
filename: file.name,
|
|
140
|
+
size: file.size,
|
|
141
|
+
},
|
|
142
|
+
uploadProgress: 100,
|
|
143
|
+
error: null,
|
|
144
|
+
isUploading: false,
|
|
145
|
+
uploadedUrl: file.uploadedUrl,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
undefined
|
|
149
|
+
);
|
|
150
|
+
}
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
|
-
}
|
|
147
153
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
154
|
+
// --- Text / link preview ---
|
|
155
|
+
if (text.trim() || urlMeta) {
|
|
156
|
+
const plainText = text.trim();
|
|
157
|
+
// Clear composer immediately — message is already in chat list with Sending status.
|
|
158
|
+
// User retries from the bubble, not the composer.
|
|
159
|
+
setText('');
|
|
160
|
+
await sendMessage({ plainText, urlMetadata: urlMeta, mentions });
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
[
|
|
164
|
+
imageAttachment,
|
|
165
|
+
fileAttachment,
|
|
166
|
+
linkPreview.isVisible,
|
|
167
|
+
linkPreview.metadata,
|
|
168
|
+
sendAttachments,
|
|
169
|
+
sendMessage,
|
|
170
|
+
text,
|
|
171
|
+
]
|
|
172
|
+
);
|
|
165
173
|
|
|
166
174
|
return {
|
|
167
175
|
// Text
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KColors,
|
|
3
|
+
KContainer,
|
|
4
|
+
KImage,
|
|
5
|
+
KLabel,
|
|
6
|
+
KSpacingValue,
|
|
7
|
+
} from '@droppii/libs';
|
|
8
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
9
|
+
import { StyleSheet } from 'react-native';
|
|
10
|
+
import { PeerType } from '@droppii/openim-rn-client-sdk';
|
|
11
|
+
import { ContactCategory, type IContactSearchItem } from '../../types/chat';
|
|
12
|
+
import { AvatarSection, NamePrefixIcon } from '../../components/ThreadCard';
|
|
13
|
+
import { formatLastOnlineTime } from '../../utils/dateTimeUtils';
|
|
14
|
+
import {
|
|
15
|
+
highlightSearch,
|
|
16
|
+
type HighlightResult,
|
|
17
|
+
} from '../../utils/highlightSearch';
|
|
18
|
+
import { trans } from '../../translation';
|
|
19
|
+
import isEmpty from 'lodash/isEmpty';
|
|
20
|
+
|
|
21
|
+
const CONTACT_PEER_TYPE: Record<ContactCategory, PeerType> = {
|
|
22
|
+
[ContactCategory.Customer]: PeerType.Customer,
|
|
23
|
+
[ContactCategory.Seller]: PeerType.User,
|
|
24
|
+
[ContactCategory.Pdp]: PeerType.Bot,
|
|
25
|
+
[ContactCategory.Group]: PeerType.Group,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface IContactResultItemProps {
|
|
29
|
+
item: IContactSearchItem;
|
|
30
|
+
onPressSearchContact?: (item: IContactSearchItem) => void;
|
|
31
|
+
searchTerm?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ContactResultItem = memo((props: IContactResultItemProps) => {
|
|
35
|
+
const { item, onPressSearchContact, searchTerm } = props || {};
|
|
36
|
+
const peerType = CONTACT_PEER_TYPE[item?.type];
|
|
37
|
+
|
|
38
|
+
const subtitle = useMemo(() => {
|
|
39
|
+
if (item?.type === ContactCategory.Group) {
|
|
40
|
+
const description = !isEmpty(item?.description?.trim())
|
|
41
|
+
? item?.description
|
|
42
|
+
: trans('group:no_description');
|
|
43
|
+
return `${trans('group:member_count', { count: item?.memberCount ?? 0 })} • ${description}`;
|
|
44
|
+
}
|
|
45
|
+
if (item?.type === ContactCategory.Pdp) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
return formatLastOnlineTime(item?.lastOnlineTime ?? null);
|
|
49
|
+
}, [item?.type, item?.description, item?.memberCount, item?.lastOnlineTime]);
|
|
50
|
+
|
|
51
|
+
const highlightResult: HighlightResult | null = useMemo(() => {
|
|
52
|
+
if (isEmpty(searchTerm?.trim())) return null;
|
|
53
|
+
return highlightSearch(item?.targetName || '', searchTerm || '');
|
|
54
|
+
}, [item?.targetName, searchTerm]);
|
|
55
|
+
|
|
56
|
+
const handlePressItem = useCallback(() => {
|
|
57
|
+
onPressSearchContact?.(item);
|
|
58
|
+
}, [onPressSearchContact, item]);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<KContainer.Touchable
|
|
62
|
+
row
|
|
63
|
+
justifyContent="space-between"
|
|
64
|
+
alignItems
|
|
65
|
+
paddingV={'0.75rem'}
|
|
66
|
+
style={styles.container}
|
|
67
|
+
onPress={handlePressItem}
|
|
68
|
+
brBW={1}
|
|
69
|
+
brBC={KColors.border.light}
|
|
70
|
+
>
|
|
71
|
+
<KContainer.View row alignItems flex>
|
|
72
|
+
<AvatarSection
|
|
73
|
+
avatar={item?.targetAvatar ?? null}
|
|
74
|
+
fullName={item?.targetName}
|
|
75
|
+
peerType={peerType}
|
|
76
|
+
size="nm"
|
|
77
|
+
/>
|
|
78
|
+
<KContainer.View flex marginL={'0.5rem'}>
|
|
79
|
+
<KContainer.View row alignItems>
|
|
80
|
+
<NamePrefixIcon peerType={peerType} />
|
|
81
|
+
{highlightResult != null ? (
|
|
82
|
+
<KLabel.Text typo="TextMdMedium" numberOfLines={1} flex>
|
|
83
|
+
{highlightResult?.before || ''}
|
|
84
|
+
<KLabel.Text typo="TextMdMedium" color={KColors.primary.normal}>
|
|
85
|
+
{highlightResult?.match || ''}
|
|
86
|
+
</KLabel.Text>
|
|
87
|
+
<KLabel.Text typo="TextMdMedium">
|
|
88
|
+
{highlightResult?.after || ''}
|
|
89
|
+
</KLabel.Text>
|
|
90
|
+
</KLabel.Text>
|
|
91
|
+
) : (
|
|
92
|
+
<KLabel.Text typo="TextMdMedium" numberOfLines={1} flex>
|
|
93
|
+
{item?.targetName || ''}
|
|
94
|
+
</KLabel.Text>
|
|
95
|
+
)}
|
|
96
|
+
</KContainer.View>
|
|
97
|
+
<KContainer.VisibleView visible={!!subtitle}>
|
|
98
|
+
<KLabel.Text
|
|
99
|
+
typo="TextXsNormal"
|
|
100
|
+
color={KColors.gray.light}
|
|
101
|
+
numberOfLines={1}
|
|
102
|
+
>
|
|
103
|
+
{subtitle}
|
|
104
|
+
</KLabel.Text>
|
|
105
|
+
</KContainer.VisibleView>
|
|
106
|
+
</KContainer.View>
|
|
107
|
+
</KContainer.View>
|
|
108
|
+
|
|
109
|
+
<KContainer.VisibleView visible={false}>
|
|
110
|
+
<KContainer.View center>
|
|
111
|
+
<KImage.VectorIcons
|
|
112
|
+
name="more-horizontal-b"
|
|
113
|
+
size={24}
|
|
114
|
+
color={KColors.gray.normal}
|
|
115
|
+
/>
|
|
116
|
+
</KContainer.View>
|
|
117
|
+
</KContainer.VisibleView>
|
|
118
|
+
</KContainer.Touchable>
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export default ContactResultItem;
|
|
123
|
+
|
|
124
|
+
const styles = StyleSheet.create({
|
|
125
|
+
container: {
|
|
126
|
+
gap: KSpacingValue['1rem'],
|
|
127
|
+
},
|
|
128
|
+
});
|