@ermis-network/ermis-chat-react 1.0.9 → 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 +15288 -4203
- 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 +15238 -4179
- 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 +126 -7
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ermis-network/ermis-chat-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "React UI components for Ermis Chat",
|
|
5
5
|
"author": "Ermis",
|
|
6
6
|
"homepage": "https://ermis.network/",
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
"/src"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@ermis-network/ermis-chat-sdk": "
|
|
24
|
-
"
|
|
23
|
+
"@ermis-network/ermis-chat-sdk": "*",
|
|
24
|
+
"react-ts-audio-recorder": "^1.1.4",
|
|
25
|
+
"virtua": "^0.48.8",
|
|
26
|
+
"frimousse": "^0.3.0"
|
|
25
27
|
},
|
|
26
28
|
"peerDependencies": {
|
|
27
29
|
"react": ">=18.0.0",
|
|
@@ -30,13 +32,16 @@
|
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"@types/react": "^18.2.0",
|
|
32
34
|
"@types/react-dom": "^18.2.0",
|
|
35
|
+
"lucide-react": "^0.474.0",
|
|
36
|
+
"motion": "^12.0.0",
|
|
33
37
|
"react": "^18.2.0",
|
|
34
38
|
"react-dom": "^18.2.0",
|
|
39
|
+
"react-virtuoso": "^4.12.5",
|
|
35
40
|
"tsup": "^8.0.0",
|
|
36
41
|
"typescript": "^5.9.3"
|
|
37
42
|
},
|
|
38
43
|
"scripts": {
|
|
39
|
-
"build": "
|
|
44
|
+
"build": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && tsup",
|
|
40
45
|
"dev": "tsup --watch"
|
|
41
46
|
}
|
|
42
47
|
}
|
package/src/channelTypeUtils.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function isDirectChannel(channel: Channel | null | undefined): boolean {
|
|
|
22
22
|
|
|
23
23
|
/** Channel is a topic (sub-channel of a group channel) */
|
|
24
24
|
export function isTopicChannel(channel: Channel | null | undefined): boolean {
|
|
25
|
-
return channel ?
|
|
25
|
+
return channel ? channel.type === 'topic' || Boolean(channel.data?.parent_cid) : false;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** Channel is a public group that users can join without invite */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import { MediaLightbox } from './MediaLightbox';
|
|
3
|
+
import { getAvatarGradient } from '../utils/avatarColors';
|
|
3
4
|
import type { AvatarProps } from '../types';
|
|
4
5
|
|
|
5
6
|
export type { AvatarProps } from '../types';
|
|
@@ -85,7 +86,7 @@ export const Avatar: React.FC<AvatarProps> = React.memo(({
|
|
|
85
86
|
{/* 1. Underlying Fallback (Placeholder) */}
|
|
86
87
|
<div
|
|
87
88
|
className="ermis-avatar ermis-avatar--fallback"
|
|
88
|
-
style={contentStyle}
|
|
89
|
+
style={{ ...contentStyle, background: getAvatarGradient(name) }}
|
|
89
90
|
title={name}
|
|
90
91
|
>
|
|
91
92
|
{initials}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useChatClient } from '../hooks/useChatClient';
|
|
3
|
+
import { useChatComponents } from '../context/ChatComponentsContext';
|
|
3
4
|
import { useBannedState } from '../hooks/useBannedState';
|
|
4
5
|
import { useBlockedState } from '../hooks/useBlockedState';
|
|
5
6
|
import { ForwardMessageModal } from './ForwardMessageModal';
|
|
@@ -25,9 +26,12 @@ export const Channel: React.FC<ChannelProps> = React.memo(({
|
|
|
25
26
|
className,
|
|
26
27
|
EmptyStateIndicator = DefaultEmpty,
|
|
27
28
|
HeaderComponent,
|
|
28
|
-
ForwardMessageModalComponent
|
|
29
|
+
ForwardMessageModalComponent: ForwardMessageModalProp,
|
|
29
30
|
}) => {
|
|
30
31
|
const { activeChannel, client, forwardingMessage, setForwardingMessage } = useChatClient();
|
|
32
|
+
const { ForwardMessageModalComponent: ForwardMessageModalContext } = useChatComponents();
|
|
33
|
+
|
|
34
|
+
const ForwardMessageModalView = ForwardMessageModalProp || ForwardMessageModalContext || ForwardMessageModal;
|
|
31
35
|
const { isBanned } = useBannedState(activeChannel, client.userID);
|
|
32
36
|
const { isBlocked } = useBlockedState(activeChannel, client.userID);
|
|
33
37
|
|
|
@@ -64,7 +68,7 @@ export const Channel: React.FC<ChannelProps> = React.memo(({
|
|
|
64
68
|
{HeaderComponent && headerData && <HeaderComponent {...headerData} />}
|
|
65
69
|
{children}
|
|
66
70
|
{forwardingMessage && (
|
|
67
|
-
<
|
|
71
|
+
<ForwardMessageModalView
|
|
68
72
|
message={forwardingMessage}
|
|
69
73
|
onDismiss={() => setForwardingMessage(null)}
|
|
70
74
|
/>
|
|
@@ -32,6 +32,8 @@ export function computeDefaultActions(
|
|
|
32
32
|
onAddTopic?: (channel: Channel) => void;
|
|
33
33
|
onEditTopic?: (channel: Channel) => void;
|
|
34
34
|
onToggleCloseTopic?: (channel: Channel, isClosed: boolean) => void;
|
|
35
|
+
onDeleteTopic?: (channel: Channel) => void;
|
|
36
|
+
onTruncateChannel?: (channel: Channel) => void;
|
|
35
37
|
isBlocked?: boolean;
|
|
36
38
|
actionLabels?: ChannelActionLabels;
|
|
37
39
|
actionIcons?: ChannelActionIcons;
|
|
@@ -99,6 +101,25 @@ export function computeDefaultActions(
|
|
|
99
101
|
}
|
|
100
102
|
},
|
|
101
103
|
});
|
|
104
|
+
|
|
105
|
+
// Direct channel: Truncate / Clear history
|
|
106
|
+
actions.push({
|
|
107
|
+
id: 'truncate',
|
|
108
|
+
label: actionLabels?.truncateChannel || 'Clear history',
|
|
109
|
+
icon: actionIcons?.TruncateChannelIcon || <TrashIcon />,
|
|
110
|
+
isDanger: true,
|
|
111
|
+
onClick: async (ch) => {
|
|
112
|
+
if (options?.onTruncateChannel) {
|
|
113
|
+
await options.onTruncateChannel(ch);
|
|
114
|
+
} else {
|
|
115
|
+
try {
|
|
116
|
+
await ch.truncate();
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error('Error clearing channel history', e);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
102
123
|
} else if (isTopic) {
|
|
103
124
|
// Topic: Edit topic (owner & moder only)
|
|
104
125
|
if (canManageChannel(role)) {
|
|
@@ -118,8 +139,46 @@ export function computeDefaultActions(
|
|
|
118
139
|
label: isClosed ? (actionLabels?.reopenTopic || 'Reopen topic') : (actionLabels?.closeTopic || 'Close topic'),
|
|
119
140
|
icon: isClosed ? (actionIcons?.ReopenTopicIcon || <UnlockIcon />) : (actionIcons?.CloseTopicIcon || <LockIcon />),
|
|
120
141
|
isDanger: !isClosed,
|
|
121
|
-
onClick: (ch) => {
|
|
122
|
-
options?.onToggleCloseTopic
|
|
142
|
+
onClick: async (ch) => {
|
|
143
|
+
if (options?.onToggleCloseTopic) {
|
|
144
|
+
await options.onToggleCloseTopic(ch, isClosed);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Default behavior: call SDK API directly
|
|
148
|
+
const parentCid = ch.data?.parent_cid as string | undefined;
|
|
149
|
+
if (!parentCid) return;
|
|
150
|
+
try {
|
|
151
|
+
const client = ch.getClient();
|
|
152
|
+
const parentChannel = client.activeChannels[parentCid];
|
|
153
|
+
if (!parentChannel) return;
|
|
154
|
+
if (isClosed) {
|
|
155
|
+
await parentChannel.reopenTopic(ch.cid);
|
|
156
|
+
} else {
|
|
157
|
+
await parentChannel.closeTopic(ch.cid);
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.error('Failed to toggle topic close state', err);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Topic: Delete (owner only)
|
|
166
|
+
if (role === CHANNEL_ROLES.OWNER) {
|
|
167
|
+
actions.push({
|
|
168
|
+
id: 'delete_topic',
|
|
169
|
+
label: actionLabels?.deleteTopic || 'Delete topic',
|
|
170
|
+
icon: actionIcons?.DeleteTopicIcon || <TrashIcon />,
|
|
171
|
+
isDanger: true,
|
|
172
|
+
onClick: async (ch) => {
|
|
173
|
+
if (options?.onDeleteTopic) {
|
|
174
|
+
await options.onDeleteTopic(ch);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
await ch.delete();
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error('Failed to delete topic', err);
|
|
181
|
+
}
|
|
123
182
|
},
|
|
124
183
|
});
|
|
125
184
|
}
|
|
@@ -59,9 +59,23 @@ export const ChannelHeader: React.FC<ChannelHeaderProps> = React.memo(({
|
|
|
59
59
|
|
|
60
60
|
useEffect(() => {
|
|
61
61
|
if (!activeChannel) return;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const handleUpdate = () => setChannelUpdateCount((c) => c + 1);
|
|
63
|
+
|
|
64
|
+
const sub1 = activeChannel.on('channel.updated', handleUpdate);
|
|
65
|
+
|
|
66
|
+
// Also listen for client-level notifications that might affect this channel's roles/members
|
|
67
|
+
// We only care about this for messaging (direct) channels to update online status
|
|
68
|
+
const sub2 = client.on('notification.invite_accepted', (event) => {
|
|
69
|
+
if (event.cid === activeChannel.cid && isDirectChannel(activeChannel)) {
|
|
70
|
+
handleUpdate();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
sub1.unsubscribe();
|
|
76
|
+
sub2.unsubscribe();
|
|
77
|
+
};
|
|
78
|
+
}, [activeChannel, client]);
|
|
65
79
|
|
|
66
80
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
81
|
const channelName = useMemo(() =>
|
|
@@ -108,13 +122,13 @@ export const ChannelHeader: React.FC<ChannelHeaderProps> = React.memo(({
|
|
|
108
122
|
if (memberId !== currentUserId) return memberId;
|
|
109
123
|
}
|
|
110
124
|
return undefined;
|
|
111
|
-
}, [activeChannel, currentUserId]);
|
|
125
|
+
}, [activeChannel, currentUserId, channelUpdateCount]);
|
|
112
126
|
|
|
113
127
|
// Check if this is a friend channel (both members are owner).
|
|
114
128
|
const isFriend = useMemo(() => {
|
|
115
129
|
if (!otherUserId || !currentUserId || !activeChannel) return false;
|
|
116
130
|
return isFriendChannel(activeChannel, otherUserId, currentUserId);
|
|
117
|
-
}, [activeChannel, otherUserId, currentUserId]);
|
|
131
|
+
}, [activeChannel, otherUserId, currentUserId, channelUpdateCount]);
|
|
118
132
|
|
|
119
133
|
// Derive online status from watchers + subscribe to realtime events.
|
|
120
134
|
const [onlineStatus, setOnlineStatus] = useState<OnlineStatus>('unknown');
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useState, useMemo, useCallback } from 'react';
|
|
2
|
-
import { Modal } from '../Modal';
|
|
2
|
+
import { Modal as DefaultModal } from '../Modal';
|
|
3
3
|
import { UserPicker } from '../UserPicker';
|
|
4
4
|
import { Avatar } from '../Avatar';
|
|
5
|
+
import { useChatComponents } from '../../context/ChatComponentsContext';
|
|
5
6
|
import type { AddMemberModalProps, UserPickerUser } from '../../types';
|
|
6
7
|
|
|
7
8
|
export const AddMemberModal: React.FC<AddMemberModalProps> = ({
|
|
@@ -19,6 +20,8 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({
|
|
|
19
20
|
UserItemComponent,
|
|
20
21
|
SearchInputComponent,
|
|
21
22
|
}) => {
|
|
23
|
+
const { ModalComponent } = useChatComponents();
|
|
24
|
+
const Modal = ModalComponent || DefaultModal;
|
|
22
25
|
const [selectedUsers, setSelectedUsers] = useState<UserPickerUser[]>([]);
|
|
23
26
|
const [isAdding, setIsAdding] = useState(false);
|
|
24
27
|
|
|
@@ -62,6 +65,7 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({
|
|
|
62
65
|
<Modal isOpen onClose={onClose} title={title} maxWidth="480px" footer={footer}>
|
|
63
66
|
<UserPicker
|
|
64
67
|
mode="checkbox"
|
|
68
|
+
friendsOnly={true}
|
|
65
69
|
onSelectionChange={handleSelectionChange}
|
|
66
70
|
excludeUserIds={excludeUserIds}
|
|
67
71
|
pageSize={30}
|