@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.
Files changed (62) hide show
  1. package/dist/index.cjs +3802 -1772
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +836 -25
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.mts +304 -1
  6. package/dist/index.d.ts +304 -1
  7. package/dist/index.mjs +3755 -1761
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +2 -2
  10. package/src/channelRoleUtils.ts +73 -0
  11. package/src/channelTypeUtils.ts +46 -0
  12. package/src/components/Avatar.tsx +57 -31
  13. package/src/components/BannedOverlay.tsx +40 -0
  14. package/src/components/ChannelActions.tsx +233 -0
  15. package/src/components/ChannelHeader.tsx +126 -5
  16. package/src/components/ChannelInfo/ChannelInfo.tsx +128 -24
  17. package/src/components/ChannelInfo/ChannelInfoTabs.tsx +67 -28
  18. package/src/components/ChannelInfo/ChannelSettingsPanel.tsx +90 -1
  19. package/src/components/ChannelInfo/EditChannelModal.tsx +5 -4
  20. package/src/components/ChannelInfo/MemberListItem.tsx +2 -1
  21. package/src/components/ChannelList.tsx +514 -47
  22. package/src/components/ClosedTopicOverlay.tsx +38 -0
  23. package/src/components/CreateChannelModal.tsx +53 -16
  24. package/src/components/EditPreview.tsx +2 -1
  25. package/src/components/ForwardMessageModal.tsx +2 -1
  26. package/src/components/MediaLightbox.tsx +314 -0
  27. package/src/components/MessageInput.tsx +21 -3
  28. package/src/components/MessageItem.tsx +10 -12
  29. package/src/components/MessageQuickReactions.tsx +3 -2
  30. package/src/components/MessageReactions.tsx +8 -3
  31. package/src/components/MessageRenderers.tsx +174 -54
  32. package/src/components/PendingOverlay.tsx +51 -0
  33. package/src/components/PinnedMessages.tsx +2 -1
  34. package/src/components/ReplyPreview.tsx +2 -1
  35. package/src/components/SkippedOverlay.tsx +36 -0
  36. package/src/components/TopicModal.tsx +189 -0
  37. package/src/components/UserPicker.tsx +1 -1
  38. package/src/components/VirtualMessageList.tsx +162 -47
  39. package/src/hooks/useBannedState.ts +27 -3
  40. package/src/hooks/useBlockedState.ts +3 -2
  41. package/src/hooks/useChannelCapabilities.ts +10 -8
  42. package/src/hooks/useChannelData.ts +1 -1
  43. package/src/hooks/useChannelListUpdates.ts +28 -5
  44. package/src/hooks/useChannelMessages.ts +2 -3
  45. package/src/hooks/useChannelRowUpdates.ts +9 -2
  46. package/src/hooks/useMessageActions.ts +23 -9
  47. package/src/hooks/useOnlineStatus.ts +71 -0
  48. package/src/hooks/useOnlineUsers.ts +115 -0
  49. package/src/hooks/usePendingState.ts +8 -3
  50. package/src/index.ts +67 -10
  51. package/src/messageTypeUtils.ts +64 -0
  52. package/src/styles/_channel-info.css +21 -0
  53. package/src/styles/_channel-list.css +276 -6
  54. package/src/styles/_media-lightbox.css +263 -0
  55. package/src/styles/_message-bubble.css +170 -13
  56. package/src/styles/_message-input.css +24 -0
  57. package/src/styles/_message-list.css +76 -6
  58. package/src/styles/_message-quick-reactions.css +5 -0
  59. package/src/styles/_message-reactions.css +7 -0
  60. package/src/styles/_topic-modal.css +154 -0
  61. package/src/styles/index.css +2 -0
  62. 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 isTeamChannel = channel.type === 'team';
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 (isTeamChannel && isPublic !== originalPublic) {
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, isTeamChannel]);
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
- {isTeamChannel && (
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 || 'member';
18
+ const role = member.channel_role || CHANNEL_ROLES.MEMBER;
18
19
  const hasActions = canRemove || canBan || canUnban || canPromote || canDemote;
19
20
 
20
21
  return (