@ermis-network/ermis-chat-react 1.0.6 → 1.0.8
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 +3802 -1772
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +836 -25
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +304 -1
- package/dist/index.d.ts +304 -1
- package/dist/index.mjs +3755 -1761
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/channelRoleUtils.ts +73 -0
- package/src/channelTypeUtils.ts +46 -0
- package/src/components/Avatar.tsx +57 -31
- package/src/components/BannedOverlay.tsx +40 -0
- package/src/components/ChannelActions.tsx +233 -0
- package/src/components/ChannelHeader.tsx +126 -5
- package/src/components/ChannelInfo/ChannelInfo.tsx +128 -24
- package/src/components/ChannelInfo/ChannelInfoTabs.tsx +67 -28
- package/src/components/ChannelInfo/ChannelSettingsPanel.tsx +90 -1
- package/src/components/ChannelInfo/EditChannelModal.tsx +5 -4
- package/src/components/ChannelInfo/MemberListItem.tsx +2 -1
- package/src/components/ChannelList.tsx +514 -47
- package/src/components/ClosedTopicOverlay.tsx +38 -0
- package/src/components/CreateChannelModal.tsx +53 -16
- package/src/components/EditPreview.tsx +2 -1
- package/src/components/ForwardMessageModal.tsx +2 -1
- package/src/components/MediaLightbox.tsx +314 -0
- package/src/components/MessageInput.tsx +21 -3
- package/src/components/MessageItem.tsx +10 -12
- package/src/components/MessageQuickReactions.tsx +3 -2
- package/src/components/MessageReactions.tsx +8 -3
- package/src/components/MessageRenderers.tsx +174 -54
- package/src/components/PendingOverlay.tsx +51 -0
- package/src/components/PinnedMessages.tsx +2 -1
- package/src/components/ReplyPreview.tsx +2 -1
- package/src/components/SkippedOverlay.tsx +36 -0
- package/src/components/TopicModal.tsx +189 -0
- package/src/components/UserPicker.tsx +1 -1
- package/src/components/VirtualMessageList.tsx +162 -47
- package/src/hooks/useBannedState.ts +27 -3
- package/src/hooks/useBlockedState.ts +3 -2
- package/src/hooks/useChannelCapabilities.ts +10 -8
- package/src/hooks/useChannelData.ts +1 -1
- package/src/hooks/useChannelListUpdates.ts +28 -5
- package/src/hooks/useChannelMessages.ts +2 -3
- package/src/hooks/useChannelRowUpdates.ts +9 -2
- package/src/hooks/useMessageActions.ts +23 -9
- package/src/hooks/useOnlineStatus.ts +71 -0
- package/src/hooks/useOnlineUsers.ts +115 -0
- package/src/hooks/usePendingState.ts +8 -3
- package/src/index.ts +67 -10
- package/src/messageTypeUtils.ts +64 -0
- package/src/styles/_channel-info.css +21 -0
- package/src/styles/_channel-list.css +276 -6
- package/src/styles/_media-lightbox.css +263 -0
- package/src/styles/_message-bubble.css +170 -13
- package/src/styles/_message-input.css +24 -0
- package/src/styles/_message-list.css +76 -6
- package/src/styles/_message-quick-reactions.css +5 -0
- package/src/styles/_message-reactions.css +7 -0
- package/src/styles/_topic-modal.css +154 -0
- package/src/styles/index.css +2 -0
- package/src/types.ts +203 -3
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Panel } from '../Panel';
|
|
3
|
+
import { useChatClient } from '../../hooks/useChatClient';
|
|
3
4
|
import type { ChannelSettingsPanelProps } from '../../types';
|
|
5
|
+
import { isGroupChannel } from '../../channelTypeUtils';
|
|
6
|
+
import { CHANNEL_ROLES } from '../../channelRoleUtils';
|
|
4
7
|
|
|
5
8
|
export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.memo(({
|
|
6
9
|
isOpen,
|
|
@@ -16,9 +19,18 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
|
|
|
16
19
|
{ label: '15m', value: 900000 },
|
|
17
20
|
{ label: '1h', value: 3600000 },
|
|
18
21
|
],
|
|
22
|
+
workspaceTopicsTitle = 'Workspace Topics',
|
|
23
|
+
topicsFeatureName = 'Topics',
|
|
24
|
+
topicsFeatureDescription = 'Enable sub-channels and discussions',
|
|
19
25
|
}) => {
|
|
20
26
|
// Config state
|
|
27
|
+
const { client } = useChatClient();
|
|
28
|
+
const currentUserId = client?.userID;
|
|
29
|
+
const currentUserRole = currentUserId ? channel?.state?.members?.[currentUserId]?.channel_role : undefined;
|
|
30
|
+
const isOwner = currentUserRole === CHANNEL_ROLES.OWNER;
|
|
31
|
+
|
|
21
32
|
const [slowMode, setSlowMode] = useState<number>(0);
|
|
33
|
+
const [topicsEnabled, setTopicsEnabled] = useState<boolean>(false);
|
|
22
34
|
const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
|
|
23
35
|
'send-message': true,
|
|
24
36
|
'send-links': true,
|
|
@@ -43,6 +55,7 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
|
|
|
43
55
|
console.log('---syncData---', dataToSync);
|
|
44
56
|
setSlowMode((dataToSync?.member_message_cooldown as number) || 0);
|
|
45
57
|
setKeywords((dataToSync?.filter_words as string[]) || []);
|
|
58
|
+
setTopicsEnabled(dataToSync?.topics_enabled === true);
|
|
46
59
|
|
|
47
60
|
const caps = dataToSync?.member_capabilities as string[] || [];
|
|
48
61
|
setCapabilities({
|
|
@@ -86,6 +99,7 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
|
|
|
86
99
|
|
|
87
100
|
// Compute dirty state
|
|
88
101
|
const isSlowModeChanged = slowMode !== ((channel?.data?.member_message_cooldown as number) || 0);
|
|
102
|
+
const isTopicsChanged = topicsEnabled !== (channel?.data?.topics_enabled === true);
|
|
89
103
|
|
|
90
104
|
const currentKeywordsSorted = [...keywords].sort().join(',');
|
|
91
105
|
const originalKeywordsSorted = [...((channel?.data?.filter_words as string[]) || [])].sort().join(',');
|
|
@@ -104,7 +118,7 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
|
|
|
104
118
|
};
|
|
105
119
|
const isCapabilitiesChanged = Object.keys(capabilities).some(k => capabilities[k] !== initialCapabilities[k]);
|
|
106
120
|
|
|
107
|
-
const isDirty = isSlowModeChanged || isKeywordsChanged || isCapabilitiesChanged;
|
|
121
|
+
const isDirty = isSlowModeChanged || isKeywordsChanged || isCapabilitiesChanged || isTopicsChanged;
|
|
108
122
|
|
|
109
123
|
const handleAddNewKeyword = () => {
|
|
110
124
|
if (newKeyword.trim()) {
|
|
@@ -176,6 +190,15 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
|
|
|
176
190
|
// Use _update instead of update to safely construct root-level payloads
|
|
177
191
|
await (channel as any)._update(payload);
|
|
178
192
|
}
|
|
193
|
+
|
|
194
|
+
if (isTopicsChanged) {
|
|
195
|
+
if (topicsEnabled) {
|
|
196
|
+
await channel.enableTopics();
|
|
197
|
+
} else {
|
|
198
|
+
await channel.disableTopics();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
179
202
|
onClose();
|
|
180
203
|
} catch (err: any) {
|
|
181
204
|
setError(err?.message || 'Failed to update settings');
|
|
@@ -422,6 +445,72 @@ export const ChannelSettingsPanel: React.FC<ChannelSettingsPanelProps> = React.m
|
|
|
422
445
|
</div>
|
|
423
446
|
</section>
|
|
424
447
|
|
|
448
|
+
{/* Section 3: Features */}
|
|
449
|
+
{isGroupChannel(channel) && (
|
|
450
|
+
<section
|
|
451
|
+
className="ermis-settings-panel__section"
|
|
452
|
+
style={{
|
|
453
|
+
background: 'var(--ermis-bg-primary)',
|
|
454
|
+
padding: '16px',
|
|
455
|
+
borderRadius: '12px',
|
|
456
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
|
457
|
+
border: '1px solid var(--ermis-border-color)'
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '16px', gap: '8px' }}>
|
|
461
|
+
<div style={{ background: 'rgba(168, 85, 247, 0.1)', padding: '4px', borderRadius: '8px', color: '#a855f7' }}>
|
|
462
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
463
|
+
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
|
|
464
|
+
<polyline points="2 17 12 22 22 17"></polyline>
|
|
465
|
+
<polyline points="2 12 12 17 22 12"></polyline>
|
|
466
|
+
</svg>
|
|
467
|
+
</div>
|
|
468
|
+
<h4 style={{ fontSize: '14px', color: 'var(--ermis-text-primary)', fontWeight: 600, margin: 0 }}>
|
|
469
|
+
{workspaceTopicsTitle}
|
|
470
|
+
</h4>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<div className="ermis-settings-panel__toggles" style={{ display: 'flex', flexDirection: 'column' }}>
|
|
474
|
+
<div style={{ background: 'var(--ermis-bg-secondary)', borderRadius: '8px', overflow: 'hidden', border: '1px solid var(--ermis-border-color)' }}>
|
|
475
|
+
<div
|
|
476
|
+
style={{
|
|
477
|
+
display: 'flex',
|
|
478
|
+
alignItems: 'center',
|
|
479
|
+
justifyContent: 'space-between',
|
|
480
|
+
padding: '10px 14px',
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
484
|
+
<span style={{ fontSize: '14px', fontWeight: 500, color: 'var(--ermis-text-primary)' }}>
|
|
485
|
+
{topicsFeatureName}
|
|
486
|
+
</span>
|
|
487
|
+
<span style={{ fontSize: '12px', color: 'var(--ermis-text-secondary)', marginTop: '2px' }}>
|
|
488
|
+
{topicsFeatureDescription}
|
|
489
|
+
</span>
|
|
490
|
+
{!isOwner && (
|
|
491
|
+
<span style={{ fontSize: '11px', color: 'var(--ermis-color-danger)', marginTop: '4px', fontWeight: 500 }}>
|
|
492
|
+
Only channel owner can change this.
|
|
493
|
+
</span>
|
|
494
|
+
)}
|
|
495
|
+
</div>
|
|
496
|
+
<button
|
|
497
|
+
type="button"
|
|
498
|
+
role="switch"
|
|
499
|
+
aria-checked={topicsEnabled}
|
|
500
|
+
className={`ermis-channel-info__edit-toggle ${topicsEnabled ? 'ermis-channel-info__edit-toggle--on' : ''}`}
|
|
501
|
+
onClick={() => isOwner && setTopicsEnabled(!topicsEnabled)}
|
|
502
|
+
disabled={isSaving || !isOwner}
|
|
503
|
+
style={{ transform: 'scale(0.85)', transformOrigin: 'right center', cursor: (!isOwner || isSaving) ? 'not-allowed' : 'pointer' }}
|
|
504
|
+
>
|
|
505
|
+
<span className="ermis-channel-info__edit-toggle-thumb" />
|
|
506
|
+
</button>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</section>
|
|
511
|
+
)}
|
|
512
|
+
|
|
513
|
+
|
|
425
514
|
</div>
|
|
426
515
|
|
|
427
516
|
{/* Footer Area */}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
import { Modal } from '../Modal';
|
|
3
3
|
import type { EditChannelModalProps, EditChannelData } from '../../types';
|
|
4
|
+
import { isGroupChannel } from '../../channelTypeUtils';
|
|
4
5
|
|
|
5
6
|
const DEFAULT_MAX_IMAGE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
6
7
|
|
|
@@ -28,7 +29,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
|
|
|
28
29
|
const originalImage = (channel.data?.image as string) || '';
|
|
29
30
|
const originalDescription = (channel.data?.description as string) || '';
|
|
30
31
|
const originalPublic = Boolean(channel.data?.public);
|
|
31
|
-
const
|
|
32
|
+
const isTeamOrMeetingChannel = isGroupChannel(channel);
|
|
32
33
|
|
|
33
34
|
// Form state
|
|
34
35
|
const [name, setName] = useState(originalName);
|
|
@@ -92,7 +93,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
|
|
|
92
93
|
payload.description = description.trim();
|
|
93
94
|
hasChanges = true;
|
|
94
95
|
}
|
|
95
|
-
if (
|
|
96
|
+
if (isTeamOrMeetingChannel && isPublic !== originalPublic) {
|
|
96
97
|
payload.public = isPublic;
|
|
97
98
|
hasChanges = true;
|
|
98
99
|
}
|
|
@@ -102,7 +103,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
return hasChanges ? payload : null;
|
|
105
|
-
}, [name, description, isPublic, selectedFile, originalName, originalDescription, originalPublic,
|
|
106
|
+
}, [name, description, isPublic, selectedFile, originalName, originalDescription, originalPublic, isTeamOrMeetingChannel]);
|
|
106
107
|
|
|
107
108
|
const handleSave = useCallback(async () => {
|
|
108
109
|
const payload = buildPayload();
|
|
@@ -237,7 +238,7 @@ export const EditChannelModal: React.FC<EditChannelModalProps> = React.memo(({
|
|
|
237
238
|
</div>
|
|
238
239
|
|
|
239
240
|
{/* Public toggle — only for team channels */}
|
|
240
|
-
{
|
|
241
|
+
{isTeamOrMeetingChannel && (
|
|
241
242
|
<div className="ermis-channel-info__edit-field ermis-channel-info__edit-field--toggle">
|
|
242
243
|
<label className="ermis-channel-info__edit-label">{publicLabel}</label>
|
|
243
244
|
<button
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Dropdown } from '../Dropdown';
|
|
3
3
|
import type { ChannelInfoMemberItemProps } from '../../types';
|
|
4
|
+
import { CHANNEL_ROLES } from '../../channelRoleUtils';
|
|
4
5
|
|
|
5
6
|
export const MemberListItem = React.memo(({
|
|
6
7
|
member, AvatarComponent,
|
|
@@ -14,7 +15,7 @@ export const MemberListItem = React.memo(({
|
|
|
14
15
|
const isOpen = anchorRect !== null;
|
|
15
16
|
|
|
16
17
|
if (!member) return null;
|
|
17
|
-
const role = member.channel_role ||
|
|
18
|
+
const role = member.channel_role || CHANNEL_ROLES.MEMBER;
|
|
18
19
|
const hasActions = canRemove || canBan || canUnban || canPromote || canDemote;
|
|
19
20
|
|
|
20
21
|
return (
|