@ermis-network/ermis-chat-react 1.0.8 → 2.0.0
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/dist/index.cjs +15295 -4209
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +701 -195
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +862 -94
- package/dist/index.d.ts +862 -94
- package/dist/index.mjs +15246 -4186
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -4
- package/src/channelTypeUtils.ts +1 -1
- package/src/components/Avatar.tsx +2 -1
- package/src/components/Channel.tsx +6 -2
- package/src/components/ChannelActions.tsx +61 -2
- package/src/components/ChannelHeader.tsx +19 -5
- package/src/components/ChannelInfo/AddMemberModal.tsx +5 -1
- package/src/components/ChannelInfo/ChannelInfo.tsx +330 -187
- package/src/components/ChannelInfo/ChannelInfoTabs.tsx +59 -297
- package/src/components/ChannelInfo/ChannelSettingsPanel.tsx +30 -174
- package/src/components/ChannelInfo/EditChannelModal.tsx +4 -1
- package/src/components/ChannelInfo/MediaGridItem.tsx +12 -2
- package/src/components/ChannelInfo/MemberListItem.tsx +2 -3
- package/src/components/ChannelInfo/MessageSearchPanel.tsx +27 -126
- package/src/components/ChannelInfo/States.tsx +1 -1
- package/src/components/ChannelInfo/index.ts +3 -0
- package/src/components/ChannelInfo/useChannelInfoTabs.tsx +386 -0
- package/src/components/ChannelInfo/useChannelSettings.ts +212 -0
- package/src/components/ChannelInfo/useMessageSearch.tsx +141 -0
- package/src/components/ChannelList.tsx +177 -290
- package/src/components/CreateChannelModal.tsx +166 -88
- package/src/components/Dropdown.tsx +1 -16
- package/src/components/EditPreview.tsx +1 -0
- package/src/components/ErmisCallProvider.tsx +72 -17
- package/src/components/ErmisCallUI.tsx +43 -20
- package/src/components/FlatTopicGroupItem.tsx +232 -0
- package/src/components/ForwardMessageModal.tsx +31 -77
- package/src/components/MediaLightbox.tsx +62 -40
- package/src/components/MentionSuggestions.tsx +47 -35
- package/src/components/MessageActionsBox.tsx +4 -1
- package/src/components/MessageInput.tsx +137 -16
- package/src/components/MessageInputDefaults.tsx +127 -1
- package/src/components/MessageItem.tsx +93 -26
- package/src/components/MessageQuickReactions.tsx +153 -26
- package/src/components/MessageReactions.tsx +2 -1
- package/src/components/MessageRenderers.tsx +111 -39
- package/src/components/Panel.tsx +1 -14
- package/src/components/PinnedMessages.tsx +17 -5
- package/src/components/PreviewOverlay.tsx +24 -0
- package/src/components/ReadReceipts.tsx +2 -1
- package/src/components/TopicList.tsx +221 -0
- package/src/components/TopicModal.tsx +4 -1
- package/src/components/TypingIndicator.tsx +14 -5
- package/src/components/UserPicker.tsx +87 -10
- package/src/components/VirtualMessageList.tsx +106 -20
- package/src/context/ChatComponentsContext.tsx +14 -0
- package/src/context/ChatProvider.tsx +18 -14
- package/src/context/ErmisCallContext.tsx +4 -0
- package/src/hooks/useChannelCapabilities.ts +7 -4
- package/src/hooks/useChannelData.ts +10 -3
- package/src/hooks/useChannelListUpdates.ts +72 -20
- package/src/hooks/useChannelMessages.ts +72 -10
- package/src/hooks/useChannelRowUpdates.ts +24 -5
- package/src/hooks/useChatUser.ts +31 -0
- package/src/hooks/useContactChannels.ts +45 -0
- package/src/hooks/useContactCount.ts +50 -0
- package/src/hooks/useDownloadHandler.ts +36 -0
- package/src/hooks/useDragAndDrop.ts +79 -0
- package/src/hooks/useForwardMessage.ts +112 -0
- package/src/hooks/useInviteChannels.ts +88 -0
- package/src/hooks/useInviteCount.ts +104 -0
- package/src/hooks/useMentions.ts +0 -1
- package/src/hooks/useMessageActions.ts +13 -10
- package/src/hooks/usePendingState.ts +21 -4
- package/src/hooks/usePreviewState.ts +69 -0
- package/src/hooks/useStickerPicker.ts +62 -0
- package/src/hooks/useTopicGroupUpdates.ts +197 -0
- package/src/index.ts +56 -6
- package/src/messageTypeUtils.ts +13 -1
- package/src/styles/_base.css +0 -1
- package/src/styles/_call-ui.css +59 -2
- package/src/styles/_channel-info.css +41 -4
- package/src/styles/_channel-list.css +97 -57
- package/src/styles/_create-channel-modal.css +10 -0
- package/src/styles/_forward-modal.css +16 -1
- package/src/styles/_media-lightbox.css +32 -0
- package/src/styles/_mentions.css +1 -1
- package/src/styles/_message-actions.css +3 -4
- package/src/styles/_message-bubble.css +286 -107
- package/src/styles/_message-input.css +131 -0
- package/src/styles/_message-list.css +33 -17
- package/src/styles/_message-quick-reactions.css +40 -9
- package/src/styles/_message-reactions.css +4 -0
- package/src/styles/_modal.css +2 -1
- package/src/styles/_preview-overlay.css +38 -0
- package/src/styles/_tokens.css +17 -15
- package/src/styles/_typing-indicator.css +7 -1
- package/src/styles/index.css +1 -0
- package/src/types.ts +362 -14
- package/src/utils/avatarColors.ts +48 -0
- package/src/utils.ts +193 -10
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import type { Channel } from '@ermis-network/ermis-chat-sdk';
|
|
3
|
+
import { isPendingMember, isSkippedMember } from '../channelRoleUtils';
|
|
4
|
+
import { isDirectChannel } from '../channelTypeUtils';
|
|
5
|
+
import { getLastMessagePreview } from '../utils';
|
|
6
|
+
import { SystemMessageTranslations, SignalMessageTranslations } from '@ermis-network/ermis-chat-sdk';
|
|
7
|
+
|
|
8
|
+
/** Preview data for the most recent message across the topic group */
|
|
9
|
+
export type LatestMessagePreview = {
|
|
10
|
+
text: React.ReactNode;
|
|
11
|
+
user: string;
|
|
12
|
+
timestamp?: string | Date;
|
|
13
|
+
/** Topic name if the message came from a sub-topic, null if from general/parent */
|
|
14
|
+
sourceName: string | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type TopicGroupUpdatesOptions = {
|
|
18
|
+
deletedMessageLabel?: React.ReactNode;
|
|
19
|
+
stickerMessageLabel?: React.ReactNode;
|
|
20
|
+
photoMessageLabel?: React.ReactNode;
|
|
21
|
+
videoMessageLabel?: React.ReactNode;
|
|
22
|
+
voiceRecordingMessageLabel?: React.ReactNode;
|
|
23
|
+
fileMessageLabel?: React.ReactNode;
|
|
24
|
+
systemMessageTranslations?: SystemMessageTranslations;
|
|
25
|
+
signalMessageTranslations?: SignalMessageTranslations;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hook encapsulating realtime logic for a topic-enabled channel group.
|
|
30
|
+
*
|
|
31
|
+
* Subscribes to message and pin events on the parent channel AND all its
|
|
32
|
+
* topics to compute:
|
|
33
|
+
* - sorted topics list (pinned first, then by last activity)
|
|
34
|
+
* - aggregated unread count across parent + all topics
|
|
35
|
+
* - boolean flag indicating if any unread exists
|
|
36
|
+
* - latest message preview across parent + all topics
|
|
37
|
+
*/
|
|
38
|
+
export function useTopicGroupUpdates(
|
|
39
|
+
channel: Channel,
|
|
40
|
+
currentUserId?: string,
|
|
41
|
+
options?: TopicGroupUpdatesOptions
|
|
42
|
+
): {
|
|
43
|
+
topics: Channel[];
|
|
44
|
+
aggregatedUnreadCount: number;
|
|
45
|
+
hasUnread: boolean;
|
|
46
|
+
updateCount: number;
|
|
47
|
+
latestMessagePreview: LatestMessagePreview | null;
|
|
48
|
+
} {
|
|
49
|
+
const [updateCount, setUpdateCount] = useState(0);
|
|
50
|
+
const bump = useCallback(() => setUpdateCount((c) => c + 1), []);
|
|
51
|
+
|
|
52
|
+
// Subscribe to realtime events on parent + all topics
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const subs: { unsubscribe: () => void }[] = [];
|
|
55
|
+
|
|
56
|
+
// Parent channel events
|
|
57
|
+
subs.push(channel.on('message.new', bump));
|
|
58
|
+
subs.push(channel.on('message.read', bump));
|
|
59
|
+
subs.push(channel.on('message.deleted', bump));
|
|
60
|
+
subs.push(channel.on('channel.updated', bump));
|
|
61
|
+
subs.push(channel.on('channel.topic.created', bump));
|
|
62
|
+
subs.push(channel.on('channel.pinned', bump));
|
|
63
|
+
subs.push(channel.on('channel.unpinned', bump));
|
|
64
|
+
|
|
65
|
+
// Topic children events
|
|
66
|
+
const currentTopics = channel.state?.topics || [];
|
|
67
|
+
currentTopics.forEach((t: Channel) => {
|
|
68
|
+
subs.push(t.on('message.new', bump));
|
|
69
|
+
subs.push(t.on('message.read', bump));
|
|
70
|
+
subs.push(t.on('message.deleted', bump));
|
|
71
|
+
subs.push(t.on('channel.updated', bump));
|
|
72
|
+
subs.push(t.on('channel.deleted', bump));
|
|
73
|
+
subs.push(t.on('channel.pinned', bump));
|
|
74
|
+
subs.push(t.on('channel.unpinned', bump));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return () => {
|
|
78
|
+
subs.forEach((s) => s.unsubscribe());
|
|
79
|
+
};
|
|
80
|
+
}, [channel, channel.state?.topics, channel.state?.topics?.length, bump]);
|
|
81
|
+
|
|
82
|
+
// Helper: get sort timestamp for a channel/topic
|
|
83
|
+
const getTopicTime = (t: Channel): number => {
|
|
84
|
+
const lastMsg = t.state?.latestMessages?.slice(-1)[0];
|
|
85
|
+
if (lastMsg?.created_at) return new Date(lastMsg.created_at).getTime();
|
|
86
|
+
if (t.data?.last_message_at) return new Date(t.data.last_message_at as string | Date).getTime();
|
|
87
|
+
if (t.data?.created_at) return new Date(t.data.created_at as string | Date).getTime();
|
|
88
|
+
return 0;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Helper: check if user is excluded from unread counting
|
|
92
|
+
const isExcludedUser = (ch: Channel): boolean => {
|
|
93
|
+
const ms = ch.state?.membership as Record<string, unknown> | undefined;
|
|
94
|
+
if (!ms) return false;
|
|
95
|
+
const isBannedSelf = Boolean(ms.banned);
|
|
96
|
+
|
|
97
|
+
// Topic support: check parent channel's ban status
|
|
98
|
+
const parentCid = ch.data?.parent_cid as string | undefined;
|
|
99
|
+
const parentChannel = parentCid ? ch.getClient().activeChannels[parentCid] : undefined;
|
|
100
|
+
const isBannedParent = Boolean(parentChannel?.state?.membership?.banned);
|
|
101
|
+
|
|
102
|
+
const isBanned = isBannedSelf || isBannedParent;
|
|
103
|
+
const isBlocked = isDirectChannel(ch) && Boolean(ms.blocked);
|
|
104
|
+
const isPending = isPendingMember(ms.channel_role as string);
|
|
105
|
+
const isSkipped = isSkippedMember(ms.channel_role as string);
|
|
106
|
+
return isBanned || isBlocked || isPending || isSkipped;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Helper: get unread count for a channel (reads from SDK state directly)
|
|
110
|
+
const getUnreadCount = (ch: Channel): number => {
|
|
111
|
+
if (!currentUserId || isExcludedUser(ch)) return 0;
|
|
112
|
+
// Primary: use the SDK's tracked unreadCount
|
|
113
|
+
const state = ch.state as unknown as Record<string, unknown> | undefined;
|
|
114
|
+
const count = (state?.unreadCount as number) ?? 0;
|
|
115
|
+
return count;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Sort topics: pinned first → last activity descending
|
|
119
|
+
const topics = useMemo(() => {
|
|
120
|
+
const allTopics = channel.state?.topics || [];
|
|
121
|
+
return [...allTopics].sort((a: Channel, b: Channel) => {
|
|
122
|
+
const aPinned = a.data?.is_pinned === true;
|
|
123
|
+
const bPinned = b.data?.is_pinned === true;
|
|
124
|
+
if (aPinned && !bPinned) return -1;
|
|
125
|
+
if (!aPinned && bPinned) return 1;
|
|
126
|
+
return getTopicTime(b) - getTopicTime(a);
|
|
127
|
+
});
|
|
128
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
129
|
+
}, [channel.state?.topics, updateCount]);
|
|
130
|
+
|
|
131
|
+
// Aggregated unread count across parent + all topics
|
|
132
|
+
const aggregatedUnreadCount = useMemo(() => {
|
|
133
|
+
let total = getUnreadCount(channel);
|
|
134
|
+
|
|
135
|
+
const allTopics = channel.state?.topics || [];
|
|
136
|
+
allTopics.forEach((topic: Channel) => {
|
|
137
|
+
total += getUnreadCount(topic);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return total;
|
|
141
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
142
|
+
}, [channel, channel.state?.topics, currentUserId, updateCount]);
|
|
143
|
+
|
|
144
|
+
const hasUnread = aggregatedUnreadCount > 0;
|
|
145
|
+
|
|
146
|
+
// Latest message preview across parent + all topics (Option B: prefix topic name)
|
|
147
|
+
const latestMessagePreview = useMemo((): LatestMessagePreview | null => {
|
|
148
|
+
// If banned from the main group, hide previews for all sub-items
|
|
149
|
+
if (isExcludedUser(channel)) return null;
|
|
150
|
+
|
|
151
|
+
const allChannels = [channel, ...(channel.state?.topics || [])];
|
|
152
|
+
|
|
153
|
+
let bestTime = 0;
|
|
154
|
+
let bestChannel: Channel | null = null;
|
|
155
|
+
|
|
156
|
+
for (const ch of allChannels) {
|
|
157
|
+
const time = getTopicTime(ch);
|
|
158
|
+
if (time > bestTime) {
|
|
159
|
+
bestTime = time;
|
|
160
|
+
bestChannel = ch;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!bestChannel) return null;
|
|
165
|
+
|
|
166
|
+
const preview = getLastMessagePreview(bestChannel, currentUserId, options);
|
|
167
|
+
if (!preview.text && !preview.user) return null;
|
|
168
|
+
|
|
169
|
+
// sourceName is non-null only when the message comes from a sub-topic (not the parent/general)
|
|
170
|
+
const isFromSubTopic = bestChannel !== channel;
|
|
171
|
+
const sourceName = isFromSubTopic ? (bestChannel.data?.name as string || null) : null;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
text: preview.text,
|
|
175
|
+
user: preview.user,
|
|
176
|
+
timestamp: preview.timestamp,
|
|
177
|
+
sourceName,
|
|
178
|
+
};
|
|
179
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
180
|
+
}, [
|
|
181
|
+
channel,
|
|
182
|
+
channel.state?.topics,
|
|
183
|
+
currentUserId,
|
|
184
|
+
updateCount,
|
|
185
|
+
options?.deletedMessageLabel,
|
|
186
|
+
options?.stickerMessageLabel,
|
|
187
|
+
options?.photoMessageLabel,
|
|
188
|
+
options?.videoMessageLabel,
|
|
189
|
+
options?.voiceRecordingMessageLabel,
|
|
190
|
+
options?.fileMessageLabel,
|
|
191
|
+
options?.systemMessageTranslations,
|
|
192
|
+
options?.signalMessageTranslations,
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
return { topics, aggregatedUnreadCount, hasUnread, updateCount, latestMessagePreview };
|
|
196
|
+
}
|
|
197
|
+
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,11 @@ export type { ChatProviderProps, ChatContextValue, Theme } from './context/ChatP
|
|
|
7
7
|
|
|
8
8
|
// Hooks
|
|
9
9
|
export { useChatClient } from './hooks/useChatClient';
|
|
10
|
+
export { useChatUser } from './hooks/useChatUser';
|
|
11
|
+
export { useInviteChannels } from './hooks/useInviteChannels';
|
|
12
|
+
export { useContactChannels } from './hooks/useContactChannels';
|
|
13
|
+
export { useInviteCount } from './hooks/useInviteCount';
|
|
14
|
+
export { useContactCount } from './hooks/useContactCount';
|
|
10
15
|
export { useChannel } from './hooks/useChannel';
|
|
11
16
|
export type { UseChannelReturn } from './hooks/useChannel';
|
|
12
17
|
export { useChannelListUpdates } from './hooks/useChannelListUpdates';
|
|
@@ -17,14 +22,29 @@ export { useOnlineStatus } from './hooks/useOnlineStatus';
|
|
|
17
22
|
export type { OnlineStatus } from './hooks/useOnlineStatus';
|
|
18
23
|
export { useOnlineUsers } from './hooks/useOnlineUsers';
|
|
19
24
|
export { usePendingState } from './hooks/usePendingState';
|
|
25
|
+
export { usePreviewState } from './hooks/usePreviewState';
|
|
26
|
+
export { useTopicGroupUpdates } from './hooks/useTopicGroupUpdates';
|
|
27
|
+
export { useDragAndDrop } from './hooks/useDragAndDrop';
|
|
28
|
+
export { useMessageSend } from './hooks/useMessageSend';
|
|
29
|
+
export { useFileUpload } from './hooks/useFileUpload';
|
|
30
|
+
export { useEmojiPicker } from './hooks/useEmojiPicker';
|
|
31
|
+
export { useStickerPicker } from './hooks/useStickerPicker';
|
|
32
|
+
export type { UseStickerPickerOptions } from './hooks/useStickerPicker';
|
|
33
|
+
export { useChannelCapabilities } from './hooks/useChannelCapabilities';
|
|
34
|
+
export { useChannelMembers, useChannelProfile } from './hooks/useChannelData';
|
|
20
35
|
|
|
21
36
|
// Components
|
|
22
37
|
export { Avatar } from './components/Avatar';
|
|
23
38
|
export type { AvatarProps } from './components/Avatar';
|
|
24
39
|
|
|
25
|
-
export { ChannelList, ChannelItem,
|
|
40
|
+
export { ChannelList, ChannelItem, ChannelRow, DefaultPinnedIcon } from './components/ChannelList';
|
|
26
41
|
export type { ChannelListProps, ChannelItemProps } from './components/ChannelList';
|
|
27
42
|
|
|
43
|
+
export { FlatTopicGroupItem } from './components/FlatTopicGroupItem';
|
|
44
|
+
export type { TopicPillProps, TopicListProps } from './types';
|
|
45
|
+
|
|
46
|
+
export { TopicList } from './components/TopicList';
|
|
47
|
+
|
|
28
48
|
export { DefaultChannelActions, computeDefaultActions } from './components/ChannelActions';
|
|
29
49
|
export type { ChannelAction, ChannelActionsProps } from './types';
|
|
30
50
|
|
|
@@ -54,7 +74,7 @@ export { MessageActionsBox } from './components/MessageActionsBox';
|
|
|
54
74
|
export type { MessageActionsBoxProps } from './types';
|
|
55
75
|
|
|
56
76
|
export { Dropdown, closeAllDropdowns } from './components/Dropdown';
|
|
57
|
-
export type { DropdownProps } from './
|
|
77
|
+
export type { DropdownProps } from './types';
|
|
58
78
|
|
|
59
79
|
export { MessageReactions } from './components/MessageReactions';
|
|
60
80
|
export type { MessageReactionsProps, ReactionUser, LatestReaction } from './types';
|
|
@@ -63,7 +83,19 @@ export { MessageQuickReactions } from './components/MessageQuickReactions';
|
|
|
63
83
|
|
|
64
84
|
export { useMessageActions } from './hooks/useMessageActions';
|
|
65
85
|
|
|
66
|
-
export {
|
|
86
|
+
export {
|
|
87
|
+
formatTime,
|
|
88
|
+
getDateKey,
|
|
89
|
+
formatDateLabel,
|
|
90
|
+
getMessageUserId,
|
|
91
|
+
replaceMentionsForPreview,
|
|
92
|
+
getLastMessagePreview,
|
|
93
|
+
buildUserMap,
|
|
94
|
+
removeAccents,
|
|
95
|
+
formatRelativeDate,
|
|
96
|
+
countWords,
|
|
97
|
+
} from './utils';
|
|
98
|
+
export { getAvatarGradient } from './utils/avatarColors';
|
|
67
99
|
export {
|
|
68
100
|
isGroupChannel,
|
|
69
101
|
isDirectChannel,
|
|
@@ -100,8 +132,10 @@ export {
|
|
|
100
132
|
isLinkPreviewAttachment,
|
|
101
133
|
isImage,
|
|
102
134
|
isVideo,
|
|
135
|
+
MESSAGE_DISPLAY_TYPES,
|
|
136
|
+
isDeletedDisplayMessage,
|
|
103
137
|
} from './messageTypeUtils';
|
|
104
|
-
export type { MessageType, AttachmentType } from './messageTypeUtils';
|
|
138
|
+
export type { MessageType, AttachmentType, MessageDisplayType } from './messageTypeUtils';
|
|
105
139
|
|
|
106
140
|
export {
|
|
107
141
|
defaultMessageRenderers,
|
|
@@ -134,7 +168,10 @@ export type { FilePreviewItem, FilesPreviewProps } from './components/FilesPrevi
|
|
|
134
168
|
export { MentionSuggestions } from './components/MentionSuggestions';
|
|
135
169
|
export type { MentionSuggestionsProps } from './components/MentionSuggestions';
|
|
136
170
|
|
|
137
|
-
export {
|
|
171
|
+
export { EditPreview } from './components/EditPreview';
|
|
172
|
+
export { PreviewOverlay } from './components/PreviewOverlay';
|
|
173
|
+
|
|
174
|
+
export { useMentions, getMentionHtml } from './hooks/useMentions';
|
|
138
175
|
export type { MentionMember, MentionPayload, UseMentionsOptions, UseMentionsReturn } from './hooks/useMentions';
|
|
139
176
|
|
|
140
177
|
export { useScrollToMessage } from './hooks/useScrollToMessage';
|
|
@@ -143,9 +180,11 @@ export type { UseScrollToMessageOptions, UseScrollToMessageReturn } from './hook
|
|
|
143
180
|
export { useLoadMessages, dedupMessages } from './hooks/useLoadMessages';
|
|
144
181
|
export type { UseLoadMessagesOptions, UseLoadMessagesReturn } from './hooks/useLoadMessages';
|
|
145
182
|
|
|
146
|
-
export { useChannelMessages } from './hooks/useChannelMessages';
|
|
183
|
+
export { useChannelMessages, markChannelAsFullyQueried } from './hooks/useChannelMessages';
|
|
147
184
|
export type { UseChannelMessagesOptions } from './hooks/useChannelMessages';
|
|
148
185
|
|
|
186
|
+
export { useForwardMessage } from './hooks/useForwardMessage';
|
|
187
|
+
|
|
149
188
|
export { QuotedMessagePreview } from './components/QuotedMessagePreview';
|
|
150
189
|
export type { QuotedMessagePreviewProps } from './components/QuotedMessagePreview';
|
|
151
190
|
export { ReplyPreview } from './components/ReplyPreview';
|
|
@@ -167,6 +206,11 @@ export {
|
|
|
167
206
|
DefaultChannelInfoCover,
|
|
168
207
|
DefaultChannelInfoActions,
|
|
169
208
|
DefaultChannelInfoTabs,
|
|
209
|
+
MessageSearchPanel,
|
|
210
|
+
HighlightedText,
|
|
211
|
+
useMessageSearch,
|
|
212
|
+
ChannelSettingsPanel,
|
|
213
|
+
useChannelSettings,
|
|
170
214
|
} from './components/ChannelInfo';
|
|
171
215
|
|
|
172
216
|
export { Modal } from './components/Modal';
|
|
@@ -177,6 +221,7 @@ export type {
|
|
|
177
221
|
ChannelInfoCoverProps,
|
|
178
222
|
ChannelInfoActionsProps,
|
|
179
223
|
ChannelInfoTabsProps,
|
|
224
|
+
ChannelInfoTabHeaderProps,
|
|
180
225
|
ChannelInfoMemberItemProps,
|
|
181
226
|
ChannelInfoMediaItemProps,
|
|
182
227
|
ChannelInfoLinkItemProps,
|
|
@@ -188,6 +233,11 @@ export type {
|
|
|
188
233
|
AddMemberModalProps,
|
|
189
234
|
AddMemberUserItemProps,
|
|
190
235
|
AddMemberButtonProps,
|
|
236
|
+
EditChannelModalProps,
|
|
237
|
+
EditChannelData,
|
|
238
|
+
TopicModalProps,
|
|
239
|
+
MessageSearchPanelProps,
|
|
240
|
+
ChannelSettingsPanelProps,
|
|
191
241
|
} from './types';
|
|
192
242
|
|
|
193
243
|
export { UserPicker } from './components/UserPicker';
|
package/src/messageTypeUtils.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function isSystemMessage(message: any): boolean {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function isStickerMessage(message: any): boolean {
|
|
27
|
-
return message?.type === MESSAGE_TYPES.STICKER;
|
|
27
|
+
return message?.type === MESSAGE_TYPES.STICKER || Boolean(message?.sticker_url);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function isRegularMessage(message: any): boolean {
|
|
@@ -62,3 +62,15 @@ export function isImage(attachment: any): boolean {
|
|
|
62
62
|
export function isVideo(attachment: any): boolean {
|
|
63
63
|
return !!(isVideoAttachment(attachment) || (!attachment.type && attachment.mime_type?.startsWith('video/')));
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
export const MESSAGE_DISPLAY_TYPES = {
|
|
67
|
+
NORMAL: 'normal',
|
|
68
|
+
DELETED: 'deleted',
|
|
69
|
+
} as const;
|
|
70
|
+
|
|
71
|
+
export type MessageDisplayType = (typeof MESSAGE_DISPLAY_TYPES)[keyof typeof MESSAGE_DISPLAY_TYPES] | string;
|
|
72
|
+
|
|
73
|
+
/** Check if a message was deleted for current user (display_type === 'deleted') */
|
|
74
|
+
export function isDeletedDisplayMessage(message: any): boolean {
|
|
75
|
+
return message?.display_type === MESSAGE_DISPLAY_TYPES.DELETED;
|
|
76
|
+
}
|
package/src/styles/_base.css
CHANGED
package/src/styles/_call-ui.css
CHANGED
|
@@ -187,6 +187,12 @@
|
|
|
187
187
|
transform: scale(0.95);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
.ermis-call-ui__action-circle:disabled {
|
|
191
|
+
opacity: 0.7;
|
|
192
|
+
cursor: not-allowed;
|
|
193
|
+
transform: none;
|
|
194
|
+
}
|
|
195
|
+
|
|
190
196
|
.ermis-call-ui__action-circle--reject {
|
|
191
197
|
background-color: var(--ermis-color-danger);
|
|
192
198
|
box-shadow: 0 4px 20px rgba(239, 68, 68, 0.35);
|
|
@@ -242,7 +248,7 @@
|
|
|
242
248
|
.ermis-call-ui__video-remote {
|
|
243
249
|
width: 100%;
|
|
244
250
|
height: 100%;
|
|
245
|
-
object-fit:
|
|
251
|
+
object-fit: contain;
|
|
246
252
|
}
|
|
247
253
|
|
|
248
254
|
.ermis-call-ui__video-local {
|
|
@@ -268,7 +274,7 @@
|
|
|
268
274
|
.ermis-call-ui__video-local-stream {
|
|
269
275
|
width: 100%;
|
|
270
276
|
height: 100%;
|
|
271
|
-
object-fit:
|
|
277
|
+
object-fit: contain;
|
|
272
278
|
transform: scaleX(-1);
|
|
273
279
|
}
|
|
274
280
|
|
|
@@ -325,6 +331,39 @@
|
|
|
325
331
|
background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
|
|
326
332
|
}
|
|
327
333
|
|
|
334
|
+
/* Video call status bar: mic-muted icon + duration timer in one row */
|
|
335
|
+
.ermis-call-ui__video-timer {
|
|
336
|
+
position: absolute;
|
|
337
|
+
top: 16px;
|
|
338
|
+
left: 16px;
|
|
339
|
+
z-index: 15;
|
|
340
|
+
display: flex;
|
|
341
|
+
align-items: center;
|
|
342
|
+
gap: 6px;
|
|
343
|
+
padding: 4px 12px;
|
|
344
|
+
border-radius: var(--ermis-radius-full);
|
|
345
|
+
background: rgba(0, 0, 0, 0.45);
|
|
346
|
+
backdrop-filter: blur(12px);
|
|
347
|
+
-webkit-backdrop-filter: blur(12px);
|
|
348
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
349
|
+
color: rgba(255, 255, 255, 0.85);
|
|
350
|
+
font-size: var(--ermis-font-size-sm);
|
|
351
|
+
font-variant-numeric: tabular-nums;
|
|
352
|
+
font-weight: 500;
|
|
353
|
+
user-select: none;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.ermis-call-ui__video-timer-mic {
|
|
357
|
+
display: flex;
|
|
358
|
+
align-items: center;
|
|
359
|
+
color: #f87171; /* red-400 */
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.ermis-call-ui__video-timer-mic svg {
|
|
363
|
+
width: 16px;
|
|
364
|
+
height: 16px;
|
|
365
|
+
}
|
|
366
|
+
|
|
328
367
|
/* -- Audio Layout -- */
|
|
329
368
|
.ermis-call-ui__audio-container {
|
|
330
369
|
text-align: center;
|
|
@@ -741,3 +780,21 @@
|
|
|
741
780
|
transform: none;
|
|
742
781
|
}
|
|
743
782
|
}
|
|
783
|
+
|
|
784
|
+
/* ============================================================
|
|
785
|
+
SPINNER
|
|
786
|
+
============================================================ */
|
|
787
|
+
.ermis-call-ui__spinner {
|
|
788
|
+
width: 20px;
|
|
789
|
+
height: 20px;
|
|
790
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
791
|
+
border-radius: 50%;
|
|
792
|
+
border-top-color: #ffffff;
|
|
793
|
+
animation: ermis-call-spin 0.8s linear infinite;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
@keyframes ermis-call-spin {
|
|
797
|
+
to {
|
|
798
|
+
transform: rotate(360deg);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
@@ -6,9 +6,17 @@
|
|
|
6
6
|
height: 100%;
|
|
7
7
|
background: var(--ermis-bg-primary);
|
|
8
8
|
border-left: 1px solid var(--ermis-border-base);
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ermis-channel-info__body {
|
|
16
|
+
flex: 1;
|
|
9
17
|
overflow-y: auto;
|
|
10
18
|
overflow-x: hidden;
|
|
11
|
-
|
|
19
|
+
scroll-behavior: smooth;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
.ermis-channel-info__header {
|
|
@@ -323,7 +331,6 @@
|
|
|
323
331
|
============================================ */
|
|
324
332
|
|
|
325
333
|
.ermis-channel-info__media-section {
|
|
326
|
-
flex: 1;
|
|
327
334
|
display: flex;
|
|
328
335
|
flex-direction: column;
|
|
329
336
|
padding: 0;
|
|
@@ -336,6 +343,10 @@
|
|
|
336
343
|
padding: 0 4px;
|
|
337
344
|
gap: 0;
|
|
338
345
|
flex-shrink: 0;
|
|
346
|
+
position: sticky;
|
|
347
|
+
top: 0;
|
|
348
|
+
z-index: 10;
|
|
349
|
+
background-color: var(--ermis-bg-primary);
|
|
339
350
|
}
|
|
340
351
|
|
|
341
352
|
.ermis-channel-info__media-tab {
|
|
@@ -393,8 +404,6 @@
|
|
|
393
404
|
============================================ */
|
|
394
405
|
|
|
395
406
|
.ermis-channel-info__media-content {
|
|
396
|
-
flex: 1;
|
|
397
|
-
overflow: hidden;
|
|
398
407
|
min-height: 120px;
|
|
399
408
|
}
|
|
400
409
|
|
|
@@ -960,3 +969,31 @@
|
|
|
960
969
|
font-weight: 500;
|
|
961
970
|
color: var(--ermis-text-secondary);
|
|
962
971
|
}
|
|
972
|
+
|
|
973
|
+
.ermis-channel-info__preview-actions {
|
|
974
|
+
display: flex;
|
|
975
|
+
justify-content: center;
|
|
976
|
+
padding: 12px 16px;
|
|
977
|
+
border-bottom: 1px solid var(--ermis-border);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.ermis-channel-info__join-btn {
|
|
981
|
+
width: 100%;
|
|
982
|
+
justify-content: center;
|
|
983
|
+
font-weight: 600;
|
|
984
|
+
padding: 10px 16px;
|
|
985
|
+
border-radius: var(--ermis-radius-lg);
|
|
986
|
+
background-color: var(--ermis-accent);
|
|
987
|
+
color: #ffffff;
|
|
988
|
+
border: none;
|
|
989
|
+
cursor: pointer;
|
|
990
|
+
transition: opacity 0.2s ease, transform 0.1s ease;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.ermis-channel-info__join-btn:hover {
|
|
994
|
+
opacity: 0.9;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.ermis-channel-info__join-btn:active {
|
|
998
|
+
transform: scale(0.98);
|
|
999
|
+
}
|