@linktr.ee/messaging-react 1.23.0-rc-1772427007 → 1.24.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.d.ts +19 -1
- package/dist/index.js +976 -1068
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelList/ChannelListContext.tsx +23 -0
- package/src/components/ChannelList/CustomChannelPreview.tsx +6 -19
- package/src/components/ChannelList/index.tsx +23 -22
- package/src/components/ChannelView.stories.tsx +53 -1
- package/src/components/ChannelView.tsx +23 -3
- package/src/components/MessagingShell/index.tsx +68 -102
- package/src/types.ts +22 -0
- package/src/components/MessagingShell/queryChannelsManager.test.ts +0 -244
- package/src/components/MessagingShell/queryChannelsManager.ts +0 -174
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { Channel, LocalMessage } from 'stream-chat'
|
|
3
|
+
|
|
4
|
+
type ChannelListContextValue = {
|
|
5
|
+
selectedChannel?: Channel | null
|
|
6
|
+
onChannelSelect: (channel: Channel) => void
|
|
7
|
+
debug: boolean
|
|
8
|
+
renderMessagePreview?: (
|
|
9
|
+
message: LocalMessage | undefined,
|
|
10
|
+
defaultPreview?: string
|
|
11
|
+
) => React.ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ChannelListContext = React.createContext<ChannelListContextValue>({
|
|
15
|
+
selectedChannel: undefined,
|
|
16
|
+
onChannelSelect: () => {},
|
|
17
|
+
debug: false,
|
|
18
|
+
renderMessagePreview: undefined,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const ChannelListProvider = ChannelListContext.Provider
|
|
22
|
+
|
|
23
|
+
export const useChannelListContext = () => React.useContext(ChannelListContext)
|
|
@@ -1,34 +1,21 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import { Channel, LocalMessage } from 'stream-chat'
|
|
4
3
|
import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
5
4
|
|
|
6
5
|
import { formatRelativeTime } from '../../utils/formatRelativeTime'
|
|
7
6
|
import { Avatar } from '../Avatar'
|
|
8
7
|
import { isChatbotMessage } from '../CustomMessage/MessageTag'
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
selectedChannel?: Channel | null
|
|
12
|
-
onChannelSelect: (channel: Channel) => void
|
|
13
|
-
debug?: boolean
|
|
14
|
-
renderMessagePreview?: (
|
|
15
|
-
message: LocalMessage | undefined,
|
|
16
|
-
defaultPreview?: string
|
|
17
|
-
) => React.ReactNode
|
|
18
|
-
}
|
|
9
|
+
import { useChannelListContext } from './ChannelListContext'
|
|
19
10
|
|
|
20
11
|
/**
|
|
21
12
|
* Custom channel preview that handles selection
|
|
22
13
|
*/
|
|
23
|
-
const CustomChannelPreview = React.memo<
|
|
24
|
-
({
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
debug = false,
|
|
29
|
-
unread,
|
|
30
|
-
renderMessagePreview,
|
|
31
|
-
}) => {
|
|
14
|
+
const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
15
|
+
({ channel, unread }) => {
|
|
16
|
+
const { selectedChannel, onChannelSelect, debug, renderMessagePreview } =
|
|
17
|
+
useChannelListContext()
|
|
18
|
+
|
|
32
19
|
const isSelected = selectedChannel?.id === channel?.id
|
|
33
20
|
|
|
34
21
|
const handleClick = () => {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import type { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
4
3
|
import { ChannelList as StreamChannelList } from 'stream-chat-react'
|
|
5
4
|
|
|
6
5
|
import { useMessagingContext } from '../../providers/MessagingProvider'
|
|
7
6
|
import type { ChannelListProps } from '../../types'
|
|
8
7
|
|
|
8
|
+
import { ChannelListProvider } from './ChannelListContext'
|
|
9
9
|
import CustomChannelPreview from './CustomChannelPreview'
|
|
10
10
|
|
|
11
|
+
const DEFAULT_SORT = { last_message_at: -1 } as const
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Channel list component with customizable header and actions
|
|
13
15
|
*/
|
|
@@ -16,6 +18,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
16
18
|
onChannelSelect,
|
|
17
19
|
selectedChannel,
|
|
18
20
|
filters,
|
|
21
|
+
sort = DEFAULT_SORT,
|
|
19
22
|
className,
|
|
20
23
|
customEmptyStateIndicator,
|
|
21
24
|
renderMessagePreview,
|
|
@@ -35,19 +38,15 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
35
38
|
})
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/>
|
|
48
|
-
)
|
|
49
|
-
return Preview
|
|
50
|
-
}, [selectedChannel, onChannelSelect, debug, renderMessagePreview])
|
|
41
|
+
const contextValue = React.useMemo(
|
|
42
|
+
() => ({
|
|
43
|
+
selectedChannel,
|
|
44
|
+
onChannelSelect,
|
|
45
|
+
debug,
|
|
46
|
+
renderMessagePreview,
|
|
47
|
+
}),
|
|
48
|
+
[selectedChannel, onChannelSelect, debug, renderMessagePreview]
|
|
49
|
+
)
|
|
51
50
|
|
|
52
51
|
return (
|
|
53
52
|
<div
|
|
@@ -58,14 +57,16 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
58
57
|
>
|
|
59
58
|
{/* Channel List */}
|
|
60
59
|
<div className="flex-1 overflow-hidden min-w-0">
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
<ChannelListProvider value={contextValue}>
|
|
61
|
+
<StreamChannelList
|
|
62
|
+
key={`${JSON.stringify(filters)}:${JSON.stringify(sort)}`}
|
|
63
|
+
filters={filters}
|
|
64
|
+
sort={sort}
|
|
65
|
+
options={{ limit: 30 }}
|
|
66
|
+
Preview={CustomChannelPreview}
|
|
67
|
+
EmptyStateIndicator={customEmptyStateIndicator}
|
|
68
|
+
/>
|
|
69
|
+
</ChannelListProvider>
|
|
69
70
|
</div>
|
|
70
71
|
</div>
|
|
71
72
|
)
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
2
|
import React, { useEffect } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Channel as ChannelType,
|
|
5
|
+
ChannelMemberResponse,
|
|
6
|
+
Event,
|
|
7
|
+
QueryChannelAPIResponse,
|
|
8
|
+
StreamChat,
|
|
9
|
+
} from 'stream-chat'
|
|
4
10
|
import { Chat } from 'stream-chat-react'
|
|
5
11
|
|
|
6
12
|
import { mockParticipants } from '../stories/mocks'
|
|
@@ -248,6 +254,52 @@ WithMessageActions.parameters = {
|
|
|
248
254
|
},
|
|
249
255
|
}
|
|
250
256
|
|
|
257
|
+
export const WithMessageDecoration: StoryFn<TemplateProps> = Template.bind({})
|
|
258
|
+
WithMessageDecoration.args = {
|
|
259
|
+
showBackButton: false,
|
|
260
|
+
renderMessage: (messageNode, message) => {
|
|
261
|
+
const isPriorityMessage = message.id === 'msg-3'
|
|
262
|
+
|
|
263
|
+
if (!isPriorityMessage) {
|
|
264
|
+
return messageNode
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<section className="bg-[#F6F2FF] px-4 py-3 my-4 [&_.str-chat\_\_message-bubble]:bg-white">
|
|
269
|
+
<header className="mb-3 flex items-center justify-between gap-3">
|
|
270
|
+
<span className="text-sm font-semibold text-[#6D28D9]">
|
|
271
|
+
✧ Marked as a priority by AI
|
|
272
|
+
</span>
|
|
273
|
+
<span className="flex items-center gap-3">
|
|
274
|
+
<button
|
|
275
|
+
className="text-sm text-[#5B5662] underline underline-offset-2 hover:text-[#2E2A34]"
|
|
276
|
+
type="button"
|
|
277
|
+
>
|
|
278
|
+
Not a priority
|
|
279
|
+
</button>
|
|
280
|
+
<button
|
|
281
|
+
aria-label="Dismiss priority banner"
|
|
282
|
+
className="text-base text-[#5B5662] hover:text-[#2E2A34]"
|
|
283
|
+
type="button"
|
|
284
|
+
>
|
|
285
|
+
×
|
|
286
|
+
</button>
|
|
287
|
+
</span>
|
|
288
|
+
</header>
|
|
289
|
+
{messageNode}
|
|
290
|
+
</section>
|
|
291
|
+
)
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
WithMessageDecoration.parameters = {
|
|
295
|
+
docs: {
|
|
296
|
+
description: {
|
|
297
|
+
story:
|
|
298
|
+
'Decorates one message with a priority treatment using ChannelView renderMessage(messageNode, message).',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
|
|
251
303
|
const WithStarButtonTemplate: StoryFn<ComponentProps> = (args) => {
|
|
252
304
|
const [client] = React.useState(() => {
|
|
253
305
|
const client = new StreamChat('mock-api-key', {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Channel,
|
|
19
19
|
Window,
|
|
20
20
|
MessageList,
|
|
21
|
+
useMessageContext,
|
|
21
22
|
useChannelStateContext,
|
|
22
23
|
WithComponents,
|
|
23
24
|
MessageUIComponentProps,
|
|
@@ -534,6 +535,10 @@ const ChannelViewInner: React.FC<{
|
|
|
534
535
|
chatbotVotingEnabled?: boolean
|
|
535
536
|
renderChannelBanner?: () => React.ReactNode
|
|
536
537
|
customChannelActions?: React.ReactNode
|
|
538
|
+
renderMessage?: (
|
|
539
|
+
messageNode: React.ReactElement,
|
|
540
|
+
message: NonNullable<MessageUIComponentProps['message']>
|
|
541
|
+
) => React.ReactNode
|
|
537
542
|
}> = ({
|
|
538
543
|
onBack,
|
|
539
544
|
showBackButton,
|
|
@@ -548,6 +553,7 @@ const ChannelViewInner: React.FC<{
|
|
|
548
553
|
chatbotVotingEnabled = false,
|
|
549
554
|
renderChannelBanner,
|
|
550
555
|
customChannelActions,
|
|
556
|
+
renderMessage,
|
|
551
557
|
}) => {
|
|
552
558
|
const { channel } = useChannelStateContext()
|
|
553
559
|
const infoDialogRef = useRef<HTMLDialogElement>(null)
|
|
@@ -593,9 +599,21 @@ const ChannelViewInner: React.FC<{
|
|
|
593
599
|
<>
|
|
594
600
|
<WithComponents
|
|
595
601
|
overrides={{
|
|
596
|
-
Message: (props: MessageUIComponentProps) =>
|
|
597
|
-
|
|
598
|
-
|
|
602
|
+
Message: (props: MessageUIComponentProps) => {
|
|
603
|
+
const { message } = useMessageContext('ChannelView')
|
|
604
|
+
const messageNode = (
|
|
605
|
+
<CustomMessage
|
|
606
|
+
{...props}
|
|
607
|
+
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
608
|
+
/>
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
if (!renderMessage || !message) {
|
|
612
|
+
return messageNode
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return renderMessage(messageNode, message)
|
|
616
|
+
},
|
|
599
617
|
}}
|
|
600
618
|
>
|
|
601
619
|
<Window>
|
|
@@ -672,6 +690,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
672
690
|
chatbotVotingEnabled = false,
|
|
673
691
|
renderChannelBanner,
|
|
674
692
|
customChannelActions,
|
|
693
|
+
renderMessage,
|
|
675
694
|
}) => {
|
|
676
695
|
// Custom send message handler that:
|
|
677
696
|
// 1. Applies messageMetadata if provided
|
|
@@ -747,6 +766,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
747
766
|
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
748
767
|
renderChannelBanner={renderChannelBanner}
|
|
749
768
|
customChannelActions={customChannelActions}
|
|
769
|
+
renderMessage={renderMessage}
|
|
750
770
|
/>
|
|
751
771
|
</Channel>
|
|
752
772
|
</div>
|
|
@@ -11,7 +11,6 @@ import { ParticipantPicker } from '../ParticipantPicker'
|
|
|
11
11
|
import { EmptyState } from './EmptyState'
|
|
12
12
|
import { ErrorState } from './ErrorState'
|
|
13
13
|
import { LoadingState } from './LoadingState'
|
|
14
|
-
import { createQueryChannelsManager } from './queryChannelsManager'
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Main messaging interface component that combines channel list and channel view
|
|
@@ -39,6 +38,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
39
38
|
renderMessagePreview,
|
|
40
39
|
renderChannelBanner,
|
|
41
40
|
customChannelActions,
|
|
41
|
+
renderMessage,
|
|
42
42
|
}) => {
|
|
43
43
|
const {
|
|
44
44
|
service,
|
|
@@ -95,96 +95,64 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
95
95
|
|
|
96
96
|
// Track if we've already synced channels to prevent repeated API calls
|
|
97
97
|
const syncedRef = useRef<string | null>(null)
|
|
98
|
-
const syncRequestNonceRef = useRef(0)
|
|
99
|
-
const onChannelSelectRef = useRef(onChannelSelect)
|
|
100
|
-
const debugEnabledRef = useRef(debug)
|
|
101
|
-
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
onChannelSelectRef.current = onChannelSelect
|
|
104
|
-
}, [onChannelSelect])
|
|
105
|
-
|
|
106
|
-
const queryChannelsManager = React.useMemo(() => {
|
|
107
|
-
if (!client) return null
|
|
108
|
-
|
|
109
|
-
return createQueryChannelsManager({
|
|
110
|
-
client,
|
|
111
|
-
onRetry: ({ requestKey, attempt, delayMs, error }) => {
|
|
112
|
-
if (!debugEnabledRef.current) return
|
|
113
|
-
|
|
114
|
-
console.warn('[MessagingShell] queryChannels rate limited; retrying', {
|
|
115
|
-
requestKey,
|
|
116
|
-
attempt,
|
|
117
|
-
delayMs,
|
|
118
|
-
error,
|
|
119
|
-
})
|
|
120
|
-
},
|
|
121
|
-
})
|
|
122
|
-
}, [client])
|
|
123
98
|
|
|
124
99
|
// Function to sync channels (extracted for reuse)
|
|
125
|
-
const syncChannels = useCallback(
|
|
126
|
-
|
|
127
|
-
if (!client || !isConnected || !queryChannelsManager) return
|
|
128
|
-
|
|
129
|
-
const userId = client.userID
|
|
130
|
-
if (!userId) return
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
if (debug) {
|
|
134
|
-
console.log('[MessagingShell] Syncing channels for user:', userId)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const requestKey = forceFresh
|
|
138
|
-
? `sync:${userId}:refresh:${syncRequestNonceRef.current++}`
|
|
139
|
-
: `sync:${userId}`
|
|
100
|
+
const syncChannels = useCallback(async () => {
|
|
101
|
+
if (!client || !isConnected) return
|
|
140
102
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
filters: {
|
|
144
|
-
type: 'messaging',
|
|
145
|
-
members: { $in: [userId] },
|
|
146
|
-
},
|
|
147
|
-
options: { limit: 100 },
|
|
148
|
-
})
|
|
103
|
+
const userId = client.userID
|
|
104
|
+
if (!userId) return
|
|
149
105
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const memberId = member.user?.id
|
|
155
|
-
if (memberId && memberId !== userId) {
|
|
156
|
-
memberIds.add(memberId)
|
|
157
|
-
}
|
|
158
|
-
})
|
|
159
|
-
})
|
|
106
|
+
try {
|
|
107
|
+
if (debug) {
|
|
108
|
+
console.log('[MessagingShell] Syncing channels for user:', userId)
|
|
109
|
+
}
|
|
160
110
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
111
|
+
const channels = await client.queryChannels(
|
|
112
|
+
{
|
|
113
|
+
type: 'messaging',
|
|
114
|
+
members: { $in: [userId] },
|
|
115
|
+
},
|
|
116
|
+
{},
|
|
117
|
+
{ limit: 100 }
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const memberIds = new Set<string>()
|
|
121
|
+
channels.forEach((channel: Channel) => {
|
|
122
|
+
const members = channel.state.members
|
|
123
|
+
Object.values(members).forEach((member) => {
|
|
124
|
+
const memberId = member.user?.id
|
|
125
|
+
if (memberId && memberId !== userId) {
|
|
126
|
+
memberIds.add(memberId)
|
|
168
127
|
}
|
|
169
|
-
return memberIds
|
|
170
128
|
})
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Only update if the set contents have changed to prevent re-renders
|
|
132
|
+
setExistingParticipantIds((prev) => {
|
|
133
|
+
if (
|
|
134
|
+
prev.size === memberIds.size &&
|
|
135
|
+
[...prev].every((id) => memberIds.has(id))
|
|
136
|
+
) {
|
|
137
|
+
return prev
|
|
180
138
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
139
|
+
return memberIds
|
|
140
|
+
})
|
|
141
|
+
setHasChannels(channels.length > 0)
|
|
142
|
+
setChannelsLoaded(true)
|
|
143
|
+
syncedRef.current = userId // Mark as synced for this user
|
|
144
|
+
|
|
145
|
+
if (debug) {
|
|
146
|
+
console.log('[MessagingShell] Channels synced successfully:', {
|
|
147
|
+
channelCount: channels.length,
|
|
148
|
+
memberCount: memberIds.size,
|
|
149
|
+
})
|
|
184
150
|
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('[MessagingShell] Failed to sync channels:', error)
|
|
153
|
+
// Don't mark as synced on error, allow retry
|
|
154
|
+
}
|
|
155
|
+
}, [client, isConnected, debug])
|
|
188
156
|
|
|
189
157
|
// Sync existing channels to track which participants we can already message
|
|
190
158
|
useEffect(() => {
|
|
@@ -196,19 +164,12 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
196
164
|
// Prevent repeated sync for the same user
|
|
197
165
|
if (syncedRef.current === userId) return
|
|
198
166
|
|
|
199
|
-
|
|
167
|
+
syncChannels()
|
|
200
168
|
}, [client, isConnected, syncChannels])
|
|
201
169
|
|
|
202
170
|
// Load initial channel for direct conversation mode
|
|
203
171
|
useEffect(() => {
|
|
204
|
-
if (
|
|
205
|
-
!initialParticipantFilter ||
|
|
206
|
-
!client ||
|
|
207
|
-
!isConnected ||
|
|
208
|
-
!queryChannelsManager
|
|
209
|
-
) {
|
|
210
|
-
return
|
|
211
|
-
}
|
|
172
|
+
if (!initialParticipantFilter || !client || !isConnected) return
|
|
212
173
|
|
|
213
174
|
const loadInitialChannel = async () => {
|
|
214
175
|
const userId = client.userID
|
|
@@ -222,14 +183,14 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
222
183
|
)
|
|
223
184
|
}
|
|
224
185
|
|
|
225
|
-
const channels = await
|
|
226
|
-
|
|
227
|
-
filters: {
|
|
186
|
+
const channels = await client.queryChannels(
|
|
187
|
+
{
|
|
228
188
|
type: 'messaging',
|
|
229
189
|
members: { $eq: [userId, initialParticipantFilter] },
|
|
230
190
|
},
|
|
231
|
-
|
|
232
|
-
|
|
191
|
+
{},
|
|
192
|
+
{ limit: 1 }
|
|
193
|
+
)
|
|
233
194
|
|
|
234
195
|
if (channels.length > 0) {
|
|
235
196
|
setSelectedChannel(channels[0])
|
|
@@ -237,7 +198,9 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
237
198
|
setDirectConversationError(null)
|
|
238
199
|
|
|
239
200
|
// Notify parent component of channel selection
|
|
240
|
-
|
|
201
|
+
if (onChannelSelect) {
|
|
202
|
+
onChannelSelect(channels[0])
|
|
203
|
+
}
|
|
241
204
|
|
|
242
205
|
if (debug) {
|
|
243
206
|
console.log(
|
|
@@ -269,7 +232,9 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
269
232
|
setDirectConversationError(null)
|
|
270
233
|
|
|
271
234
|
// Notify parent component of channel selection
|
|
272
|
-
|
|
235
|
+
if (onChannelSelect) {
|
|
236
|
+
onChannelSelect(channel)
|
|
237
|
+
}
|
|
273
238
|
|
|
274
239
|
if (debug) {
|
|
275
240
|
console.log(
|
|
@@ -307,7 +272,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
307
272
|
}
|
|
308
273
|
}
|
|
309
274
|
|
|
310
|
-
|
|
275
|
+
loadInitialChannel()
|
|
311
276
|
}, [
|
|
312
277
|
initialParticipantFilter,
|
|
313
278
|
initialParticipantData,
|
|
@@ -315,7 +280,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
315
280
|
isConnected,
|
|
316
281
|
service,
|
|
317
282
|
debug,
|
|
318
|
-
|
|
283
|
+
onChannelSelect,
|
|
319
284
|
])
|
|
320
285
|
|
|
321
286
|
const handleChannelSelect = useCallback(
|
|
@@ -396,7 +361,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
396
361
|
|
|
397
362
|
// Force re-sync to update the existing participants list
|
|
398
363
|
syncedRef.current = null
|
|
399
|
-
await syncChannels(
|
|
364
|
+
await syncChannels()
|
|
400
365
|
},
|
|
401
366
|
[syncChannels, debug]
|
|
402
367
|
)
|
|
@@ -411,7 +376,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
411
376
|
|
|
412
377
|
// Force re-sync to update the existing participants list
|
|
413
378
|
syncedRef.current = null
|
|
414
|
-
await syncChannels(
|
|
379
|
+
await syncChannels()
|
|
415
380
|
},
|
|
416
381
|
[syncChannels, debug]
|
|
417
382
|
)
|
|
@@ -533,6 +498,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
533
498
|
showStarButton={showStarButton}
|
|
534
499
|
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
535
500
|
customChannelActions={customChannelActions}
|
|
501
|
+
renderMessage={renderMessage}
|
|
536
502
|
/>
|
|
537
503
|
</div>
|
|
538
504
|
) : initialParticipantFilter ? (
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
import type {
|
|
6
6
|
Channel,
|
|
7
7
|
ChannelFilters,
|
|
8
|
+
ChannelSort,
|
|
8
9
|
LocalMessage,
|
|
9
10
|
SendMessageAPIResponse,
|
|
10
11
|
} from 'stream-chat'
|
|
@@ -67,6 +68,11 @@ export interface ChannelListProps {
|
|
|
67
68
|
participantLabel?: string
|
|
68
69
|
className?: string
|
|
69
70
|
filters: ChannelFilters
|
|
71
|
+
/**
|
|
72
|
+
* Sort order for the channel list query.
|
|
73
|
+
* Defaults to `{ last_message_at: -1 }` (most recent first).
|
|
74
|
+
*/
|
|
75
|
+
sort?: ChannelSort
|
|
70
76
|
customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>
|
|
71
77
|
renderMessagePreview?: (
|
|
72
78
|
message: LocalMessage | undefined,
|
|
@@ -162,6 +168,21 @@ export interface ChannelViewProps {
|
|
|
162
168
|
* Use the exported ActionButton for consistent styling.
|
|
163
169
|
*/
|
|
164
170
|
customChannelActions?: React.ReactNode
|
|
171
|
+
/**
|
|
172
|
+
* Custom render function for decorating each message in the message list.
|
|
173
|
+
* Receives the default message node and the message object.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* renderMessage={(messageNode, message) => (
|
|
177
|
+
* <MessageDecorator isStarred={Boolean(message.pinned)}>
|
|
178
|
+
* {messageNode}
|
|
179
|
+
* </MessageDecorator>
|
|
180
|
+
* )}
|
|
181
|
+
*/
|
|
182
|
+
renderMessage?: (
|
|
183
|
+
messageNode: React.ReactElement,
|
|
184
|
+
message: LocalMessage
|
|
185
|
+
) => React.ReactNode
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
/**
|
|
@@ -182,6 +203,7 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
182
203
|
| 'chatbotVotingEnabled'
|
|
183
204
|
| 'renderChannelBanner'
|
|
184
205
|
| 'customChannelActions'
|
|
206
|
+
| 'renderMessage'
|
|
185
207
|
>
|
|
186
208
|
|
|
187
209
|
/**
|