@linktr.ee/messaging-react 1.40.1 → 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/{Card-QrKCxqCI.js → Card-BHknCeHw.js} +2 -2
- package/dist/{Card-QrKCxqCI.js.map → Card-BHknCeHw.js.map} +1 -1
- package/dist/{Card-CmnkZNNQ.js → Card-DT7_ms2p.js} +2 -2
- package/dist/{Card-CmnkZNNQ.js.map → Card-DT7_ms2p.js.map} +1 -1
- package/dist/assets/index.css +1 -1
- package/dist/index-Brz9orsI.js +3053 -0
- package/dist/index-Brz9orsI.js.map +1 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +15 -12
- package/package.json +2 -2
- package/src/components/ChannelInfoDialog/ChannelInfoDialog.test.tsx +2 -14
- package/src/components/ChannelInfoDialog/index.tsx +4 -8
- package/src/components/ChannelList/ChannelListContext.tsx +2 -0
- package/src/components/ChannelList/CustomChannelPreview.tsx +14 -3
- package/src/components/ChannelList/index.tsx +9 -1
- package/src/components/ChannelView.tsx +10 -2
- package/src/components/CustomMessage/CustomMessage.stories.tsx +28 -0
- package/src/components/CustomMessage/index.tsx +34 -13
- package/src/components/MediaMessage/index.tsx +5 -3
- package/src/components/MessagingShell/index.tsx +4 -4
- package/src/index.ts +14 -2
- package/src/stories/mocks.tsx +2 -9
- package/src/styles.css +1 -4
- package/src/types.ts +11 -1
- package/src/utils/getMessageDisplayText.test.ts +44 -0
- package/src/utils/getMessageDisplayText.ts +27 -0
- package/dist/index-8ZuHtwFb.js +0 -2990
- package/dist/index-8ZuHtwFb.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,11 @@ export declare interface ChannelListProps {
|
|
|
101
101
|
sort?: ChannelSort;
|
|
102
102
|
customEmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>;
|
|
103
103
|
renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
|
|
104
|
+
/**
|
|
105
|
+
* Language code used to pick translated message text from Stream Chat i18n.
|
|
106
|
+
* Falls back to message.text when no matching translation exists.
|
|
107
|
+
*/
|
|
108
|
+
viewerLanguage?: string;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
/**
|
|
@@ -112,7 +117,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
|
|
|
112
117
|
* Props that MessagingShell passes through to ChannelView.
|
|
113
118
|
* ChannelViewProps is the source of truth for these props.
|
|
114
119
|
*/
|
|
115
|
-
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'sendButton'>;
|
|
120
|
+
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onDeleteConversationClick' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'messageMetadata' | 'onMessageSent' | 'showStarButton' | 'chatbotVotingEnabled' | 'viewerLanguage' | 'renderChannelBanner' | 'customProfileContent' | 'customChannelActions' | 'renderMessage' | 'sendButton'>;
|
|
116
121
|
|
|
117
122
|
/**
|
|
118
123
|
* ChannelView component props
|
|
@@ -185,6 +190,11 @@ export declare interface ChannelViewProps {
|
|
|
185
190
|
* Defaults to false.
|
|
186
191
|
*/
|
|
187
192
|
chatbotVotingEnabled?: boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Language code used to pick translated message text from Stream Chat i18n.
|
|
195
|
+
* Falls back to message.text when no matching translation exists.
|
|
196
|
+
*/
|
|
197
|
+
viewerLanguage?: string;
|
|
188
198
|
/**
|
|
189
199
|
* Custom render function for a banner/card component that renders
|
|
190
200
|
* between the channel header and message list.
|
|
@@ -283,6 +293,13 @@ export declare interface FaqListProps {
|
|
|
283
293
|
*/
|
|
284
294
|
export declare const formatRelativeTime: (date: Date) => string;
|
|
285
295
|
|
|
296
|
+
export declare function getMessageDisplayText({ message, viewerLanguage, }: {
|
|
297
|
+
message?: MessageWithI18n | null;
|
|
298
|
+
viewerLanguage?: string;
|
|
299
|
+
}): string | undefined;
|
|
300
|
+
|
|
301
|
+
export declare function isLinkAttachment(a: Attachment): boolean;
|
|
302
|
+
|
|
286
303
|
export declare const LockedAttachment: {
|
|
287
304
|
Creator: (props: CreatorCardProps) => JSX_2.Element;
|
|
288
305
|
Visitor: (props: VisitorCardProps) => JSX_2.Element;
|
|
@@ -356,6 +373,10 @@ declare interface MessageVoteButtonsProps {
|
|
|
356
373
|
onVoteDown: () => void;
|
|
357
374
|
}
|
|
358
375
|
|
|
376
|
+
declare type MessageWithI18n = Pick<LocalMessage, 'text'> & {
|
|
377
|
+
i18n?: Record<string, string> | null;
|
|
378
|
+
};
|
|
379
|
+
|
|
359
380
|
/**
|
|
360
381
|
* Messaging capabilities configuration
|
|
361
382
|
*/
|
|
@@ -460,13 +481,14 @@ export declare interface MessagingShellProps extends ChannelViewPassthroughProps
|
|
|
460
481
|
renderMessagePreview?: (message: LocalMessage | undefined, defaultPreview?: string) => React.ReactNode;
|
|
461
482
|
}
|
|
462
483
|
|
|
484
|
+
export declare function normalizeLanguageCode(language?: string): string | undefined;
|
|
485
|
+
|
|
463
486
|
/**
|
|
464
487
|
* Generic participant interface for different host environments
|
|
465
488
|
*/
|
|
466
489
|
export declare interface Participant {
|
|
467
490
|
id: string;
|
|
468
491
|
name: string;
|
|
469
|
-
email?: string;
|
|
470
492
|
image?: string;
|
|
471
493
|
username?: string;
|
|
472
494
|
phone?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
import { b as
|
|
1
|
+
import { b as e, c as t, C as i, d as n, e as o, f as g, F as m, g as r, L as M, M as l, h as u, i as h, j as L, k as d, l as C, m as c, n as v, r as A, o as k, u as p, p as F, q as f } from "./index-Brz9orsI.js";
|
|
2
2
|
export {
|
|
3
|
-
|
|
3
|
+
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
|
5
5
|
i as ChannelEmptyState,
|
|
6
6
|
n as ChannelList,
|
|
7
7
|
o as ChannelView,
|
|
8
8
|
g as CustomMessageProvider,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
m as FaqList,
|
|
10
|
+
r as FaqListItem,
|
|
11
|
+
M as LockedAttachment,
|
|
12
12
|
l as MediaMessage,
|
|
13
13
|
u as MessageVoteButtons,
|
|
14
14
|
h as MessagingProvider,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
C as
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
A as
|
|
21
|
-
|
|
15
|
+
L as MessagingShell,
|
|
16
|
+
d as formatRelativeTime,
|
|
17
|
+
C as getMessageDisplayText,
|
|
18
|
+
c as isLinkAttachment,
|
|
19
|
+
v as normalizeLanguageCode,
|
|
20
|
+
A as resolveLinkAttachment,
|
|
21
|
+
k as resolveMediaFromMessage,
|
|
22
|
+
p as useCustomMessage,
|
|
23
|
+
F as useMessageVote,
|
|
24
|
+
f as useMessaging
|
|
22
25
|
};
|
|
23
26
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@linktr.ee/component-library": "11.8.6",
|
|
37
|
-
"@linktr.ee/messaging-core": "^
|
|
37
|
+
"@linktr.ee/messaging-core": "^2.0.0",
|
|
38
38
|
"@phosphor-icons/react": "^2.1.10"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -65,14 +65,13 @@ const createChannel = () =>
|
|
|
65
65
|
}) as unknown as Channel
|
|
66
66
|
|
|
67
67
|
const createParticipant = (
|
|
68
|
-
overrides: Partial<{ name: string;
|
|
68
|
+
overrides: Partial<{ name: string; username: string }> = {}
|
|
69
69
|
) =>
|
|
70
70
|
({
|
|
71
71
|
user: {
|
|
72
72
|
id: 'linker-1',
|
|
73
73
|
name: overrides.name ?? 'Linker',
|
|
74
74
|
image: undefined,
|
|
75
|
-
email: overrides.email,
|
|
76
75
|
username: overrides.username,
|
|
77
76
|
},
|
|
78
77
|
role: 'member',
|
|
@@ -99,18 +98,7 @@ describe('ChannelInfoDialog', () => {
|
|
|
99
98
|
expect(nameEl).toBeInTheDocument()
|
|
100
99
|
})
|
|
101
100
|
|
|
102
|
-
it('renders participant
|
|
103
|
-
renderWithProviders(
|
|
104
|
-
<ChannelInfoDialog
|
|
105
|
-
{...defaultProps()}
|
|
106
|
-
participant={createParticipant({ email: 'linker@example.com' })}
|
|
107
|
-
/>
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
expect(screen.getByText('linker@example.com')).toBeInTheDocument()
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('renders participant username as secondary info when no email', () => {
|
|
101
|
+
it('renders participant username as secondary info', () => {
|
|
114
102
|
renderWithProviders(
|
|
115
103
|
<ChannelInfoDialog
|
|
116
104
|
{...defaultProps()}
|
|
@@ -12,9 +12,8 @@ import ActionButton from '../ActionButton'
|
|
|
12
12
|
import { Avatar } from '../Avatar'
|
|
13
13
|
import { CloseButton } from '../CloseButton'
|
|
14
14
|
|
|
15
|
-
// Custom user type with
|
|
15
|
+
// Custom user type with username
|
|
16
16
|
type CustomUser = {
|
|
17
|
-
email?: string
|
|
18
17
|
username?: string
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -180,13 +179,10 @@ export const ChannelInfoDialog: React.FC<ChannelInfoDialogProps> = ({
|
|
|
180
179
|
const participantName =
|
|
181
180
|
participant.user?.name || participant.user?.id || 'Unknown member'
|
|
182
181
|
const participantImage = participant.user?.image
|
|
183
|
-
const participantEmail = (participant.user as CustomUser)?.email
|
|
184
182
|
const participantUsername = (participant.user as CustomUser)?.username
|
|
185
|
-
const participantSecondary =
|
|
186
|
-
?
|
|
187
|
-
:
|
|
188
|
-
? `linktr.ee/${participantUsername}`
|
|
189
|
-
: undefined
|
|
183
|
+
const participantSecondary = participantUsername
|
|
184
|
+
? `linktr.ee/${participantUsername}`
|
|
185
|
+
: undefined
|
|
190
186
|
const participantId = participant.user?.id || 'unknown'
|
|
191
187
|
|
|
192
188
|
return (
|
|
@@ -9,6 +9,7 @@ type ChannelListContextValue = {
|
|
|
9
9
|
message: LocalMessage | undefined,
|
|
10
10
|
defaultPreview?: string
|
|
11
11
|
) => React.ReactNode
|
|
12
|
+
viewerLanguage?: string
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const ChannelListContext = React.createContext<ChannelListContextValue>({
|
|
@@ -16,6 +17,7 @@ const ChannelListContext = React.createContext<ChannelListContextValue>({
|
|
|
16
17
|
onChannelSelect: () => {},
|
|
17
18
|
debug: false,
|
|
18
19
|
renderMessagePreview: undefined,
|
|
20
|
+
viewerLanguage: undefined,
|
|
19
21
|
})
|
|
20
22
|
|
|
21
23
|
export const ChannelListProvider = ChannelListContext.Provider
|
|
@@ -4,6 +4,7 @@ import { ChannelPreviewUIComponentProps } from 'stream-chat-react'
|
|
|
4
4
|
|
|
5
5
|
import { useChannelStar } from '../../hooks/useChannelStar'
|
|
6
6
|
import { formatRelativeTime } from '../../utils/formatRelativeTime'
|
|
7
|
+
import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
|
|
7
8
|
import { Avatar } from '../Avatar'
|
|
8
9
|
import { isChatbotMessage } from '../CustomMessage/MessageTag'
|
|
9
10
|
|
|
@@ -14,8 +15,13 @@ import { useChannelListContext } from './ChannelListContext'
|
|
|
14
15
|
*/
|
|
15
16
|
const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
16
17
|
({ channel, unread }) => {
|
|
17
|
-
const {
|
|
18
|
-
|
|
18
|
+
const {
|
|
19
|
+
selectedChannel,
|
|
20
|
+
onChannelSelect,
|
|
21
|
+
debug,
|
|
22
|
+
renderMessagePreview,
|
|
23
|
+
viewerLanguage,
|
|
24
|
+
} = useChannelListContext()
|
|
19
25
|
|
|
20
26
|
const isSelected = selectedChannel?.id === channel?.id
|
|
21
27
|
|
|
@@ -53,7 +59,12 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
|
|
|
53
59
|
})()
|
|
54
60
|
|
|
55
61
|
const getLastMessageText = () => {
|
|
56
|
-
|
|
62
|
+
const displayText = getMessageDisplayText({
|
|
63
|
+
message: lastMessage,
|
|
64
|
+
viewerLanguage,
|
|
65
|
+
})
|
|
66
|
+
if (displayText) return displayText
|
|
67
|
+
|
|
57
68
|
const isTip = lastMessage?.metadata?.custom_type === 'MESSAGE_TIP'
|
|
58
69
|
if (isTip) return '💵 Sent a tip'
|
|
59
70
|
|
|
@@ -28,6 +28,7 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
28
28
|
className,
|
|
29
29
|
customEmptyStateIndicator,
|
|
30
30
|
renderMessagePreview,
|
|
31
|
+
viewerLanguage,
|
|
31
32
|
}) => {
|
|
32
33
|
// Track renders
|
|
33
34
|
const renderCountRef = React.useRef(0)
|
|
@@ -64,8 +65,15 @@ export const ChannelList = React.memo<ChannelListProps>(
|
|
|
64
65
|
onChannelSelect,
|
|
65
66
|
debug,
|
|
66
67
|
renderMessagePreview,
|
|
68
|
+
viewerLanguage,
|
|
67
69
|
}),
|
|
68
|
-
[
|
|
70
|
+
[
|
|
71
|
+
selectedChannel,
|
|
72
|
+
onChannelSelect,
|
|
73
|
+
debug,
|
|
74
|
+
renderMessagePreview,
|
|
75
|
+
viewerLanguage,
|
|
76
|
+
]
|
|
69
77
|
)
|
|
70
78
|
|
|
71
79
|
return (
|
|
@@ -257,6 +257,7 @@ const ChannelViewInner: React.FC<{
|
|
|
257
257
|
message: NonNullable<MessageUIComponentProps['message']>
|
|
258
258
|
) => React.ReactNode
|
|
259
259
|
dmAgentEnabled?: boolean
|
|
260
|
+
viewerLanguage?: string
|
|
260
261
|
}> = ({
|
|
261
262
|
onBack,
|
|
262
263
|
showBackButton,
|
|
@@ -275,6 +276,7 @@ const ChannelViewInner: React.FC<{
|
|
|
275
276
|
customChannelActions,
|
|
276
277
|
renderMessage,
|
|
277
278
|
dmAgentEnabled = false,
|
|
279
|
+
viewerLanguage,
|
|
278
280
|
}) => {
|
|
279
281
|
const { channel } = useChannelStateContext()
|
|
280
282
|
const infoDialogRef = useRef<HTMLDialogElement>(null)
|
|
@@ -339,7 +341,11 @@ const ChannelViewInner: React.FC<{
|
|
|
339
341
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
340
342
|
const { message } = useMessageContext('ChannelView')
|
|
341
343
|
const messageNode = (
|
|
342
|
-
<CustomMessage
|
|
344
|
+
<CustomMessage
|
|
345
|
+
{...props}
|
|
346
|
+
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
347
|
+
viewerLanguage={viewerLanguage}
|
|
348
|
+
/>
|
|
343
349
|
)
|
|
344
350
|
|
|
345
351
|
if (!renderMessage || !message) {
|
|
@@ -348,7 +354,7 @@ const ChannelViewInner: React.FC<{
|
|
|
348
354
|
|
|
349
355
|
return renderMessage(messageNode, message)
|
|
350
356
|
},
|
|
351
|
-
[chatbotVotingEnabled, renderMessage]
|
|
357
|
+
[chatbotVotingEnabled, renderMessage, viewerLanguage]
|
|
352
358
|
)
|
|
353
359
|
|
|
354
360
|
return (
|
|
@@ -436,6 +442,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
436
442
|
customChannelActions,
|
|
437
443
|
renderMessage,
|
|
438
444
|
sendButton,
|
|
445
|
+
viewerLanguage,
|
|
439
446
|
}) => {
|
|
440
447
|
// Custom send message handler that:
|
|
441
448
|
// 1. Applies messageMetadata if provided
|
|
@@ -517,6 +524,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
517
524
|
customProfileContent={customProfileContent}
|
|
518
525
|
customChannelActions={customChannelActions}
|
|
519
526
|
renderMessage={renderMessage}
|
|
527
|
+
viewerLanguage={viewerLanguage}
|
|
520
528
|
/>
|
|
521
529
|
</Channel>
|
|
522
530
|
</div>
|
|
@@ -386,6 +386,34 @@ ChatbotVariants.args = {
|
|
|
386
386
|
],
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
export const ChatbotLinkPreviews: StoryFn<TemplateProps> = Template.bind({})
|
|
390
|
+
ChatbotLinkPreviews.args = {
|
|
391
|
+
messages: [
|
|
392
|
+
{
|
|
393
|
+
id: 'msg-1',
|
|
394
|
+
text: 'Check the FAQ pages: pizzeriamozza.com/faq or osteriamozza.com/faq.',
|
|
395
|
+
user: storyUsers.creator,
|
|
396
|
+
metadata: { custom_type: 'MESSAGE_CHATBOT' },
|
|
397
|
+
attachments: [
|
|
398
|
+
{
|
|
399
|
+
type: 'link',
|
|
400
|
+
og_scrape_url: 'https://www.pizzeriamozza.com/faq',
|
|
401
|
+
title: 'Pizzeria Mozza | Pizza Restaurant in CA',
|
|
402
|
+
text: 'FAQ | Pizzeria Mozza',
|
|
403
|
+
image_url: 'https://picsum.photos/seed/mozza1/560/315',
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
type: 'link',
|
|
407
|
+
og_scrape_url: 'https://www.osteriamozza.com/faq',
|
|
408
|
+
title: 'Osteria Mozza | Italian Restaurant in LA',
|
|
409
|
+
text: 'FAQ | Osteria Mozza',
|
|
410
|
+
image_url: 'https://picsum.photos/seed/mozza2/560/315',
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
}
|
|
416
|
+
|
|
389
417
|
export const WithTypingIndicatorComparison: StoryFn<TemplateProps> =
|
|
390
418
|
Template.bind({})
|
|
391
419
|
WithTypingIndicatorComparison.args = {
|
|
@@ -30,8 +30,10 @@ import {
|
|
|
30
30
|
} from 'stream-chat-react'
|
|
31
31
|
|
|
32
32
|
import { useMessageVote } from '../../hooks/useMessageVote'
|
|
33
|
+
import { getMessageDisplayText } from '../../utils/getMessageDisplayText'
|
|
33
34
|
import { Avatar } from '../Avatar'
|
|
34
35
|
import LockedAttachment from '../LockedAttachment'
|
|
36
|
+
import { isLinkAttachment } from '../MediaMessage'
|
|
35
37
|
|
|
36
38
|
import { useCustomMessage } from './context'
|
|
37
39
|
import {
|
|
@@ -44,10 +46,12 @@ import { MessageVoteButtons } from './MessageVoteButtons'
|
|
|
44
46
|
|
|
45
47
|
type CustomMessageUIComponentProps = MessageUIComponentProps & {
|
|
46
48
|
chatbotVotingEnabled?: boolean
|
|
49
|
+
viewerLanguage?: string
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
type CustomMessageWithContextProps = MessageContextValue & {
|
|
50
53
|
chatbotVotingEnabled?: boolean
|
|
54
|
+
viewerLanguage?: string
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
@@ -67,11 +71,13 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
67
71
|
message,
|
|
68
72
|
renderText,
|
|
69
73
|
threadList,
|
|
74
|
+
viewerLanguage,
|
|
70
75
|
} = props
|
|
71
76
|
|
|
72
77
|
const { client } = useChatContext('CustomMessage')
|
|
73
78
|
const { channel } = useChannelStateContext('CustomMessage')
|
|
74
|
-
const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } =
|
|
79
|
+
const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } =
|
|
80
|
+
useCustomMessage('LockedAttachment')
|
|
75
81
|
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)
|
|
76
82
|
const reminder = useMessageReminder(message.id)
|
|
77
83
|
const { selected: voteState, voteUp, voteDown } = useMessageVote(message)
|
|
@@ -95,15 +101,23 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
95
101
|
() => isMessageAIGenerated?.(message),
|
|
96
102
|
[isMessageAIGenerated, message]
|
|
97
103
|
)
|
|
98
|
-
const finalAttachments = useMemo(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const finalAttachments = useMemo(() => {
|
|
105
|
+
const attachments = message.attachments ?? []
|
|
106
|
+
const raw = message.shared_location
|
|
107
|
+
? [message.shared_location, ...attachments]
|
|
108
|
+
: attachments
|
|
109
|
+
|
|
110
|
+
if (!isChatbotMessage(message)) return raw
|
|
111
|
+
|
|
112
|
+
const filtered = raw.filter((a) => !('type' in a) || !isLinkAttachment(a))
|
|
113
|
+
return filtered.length === raw.length ? raw : filtered
|
|
114
|
+
}, [message])
|
|
115
|
+
const displayMessage = useMemo(() => {
|
|
116
|
+
const displayText = getMessageDisplayText({ message, viewerLanguage })
|
|
117
|
+
return displayText === message.text
|
|
118
|
+
? message
|
|
119
|
+
: { ...message, text: displayText }
|
|
120
|
+
}, [message, viewerLanguage])
|
|
107
121
|
|
|
108
122
|
if (isDateSeparatorMessage(message)) {
|
|
109
123
|
return null
|
|
@@ -244,7 +258,10 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
244
258
|
)}
|
|
245
259
|
{message.text && (
|
|
246
260
|
<div className="str-chat__message-bubble">
|
|
247
|
-
<MessageText
|
|
261
|
+
<MessageText
|
|
262
|
+
message={displayMessage}
|
|
263
|
+
renderText={renderText}
|
|
264
|
+
/>
|
|
248
265
|
</div>
|
|
249
266
|
)}
|
|
250
267
|
</div>
|
|
@@ -270,11 +287,14 @@ const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
|
270
287
|
) : null}
|
|
271
288
|
{isAIGenerated ? (
|
|
272
289
|
<StreamedMessageText
|
|
273
|
-
message={
|
|
290
|
+
message={displayMessage}
|
|
274
291
|
renderText={renderText}
|
|
275
292
|
/>
|
|
276
293
|
) : (
|
|
277
|
-
<MessageText
|
|
294
|
+
<MessageText
|
|
295
|
+
message={displayMessage}
|
|
296
|
+
renderText={renderText}
|
|
297
|
+
/>
|
|
278
298
|
)}
|
|
279
299
|
<MessageErrorIcon />
|
|
280
300
|
</div>
|
|
@@ -314,6 +334,7 @@ const MemoizedCustomMessage = React.memo(
|
|
|
314
334
|
CustomMessageWithContext,
|
|
315
335
|
(prev, next) => {
|
|
316
336
|
if (prev.chatbotVotingEnabled !== next.chatbotVotingEnabled) return false
|
|
337
|
+
if (prev.viewerLanguage !== next.viewerLanguage) return false
|
|
317
338
|
return areMessageUIPropsEqual(prev, next)
|
|
318
339
|
}
|
|
319
340
|
) as typeof CustomMessageWithContext
|
|
@@ -95,12 +95,14 @@ const LinkCard: React.FC<{
|
|
|
95
95
|
return <div className="block">{body}</div>
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
export function isLinkAttachment(a: Attachment): boolean {
|
|
99
|
+
return a.type === 'link' || (!!a.og_scrape_url && !a.asset_url)
|
|
100
|
+
}
|
|
101
|
+
|
|
98
102
|
export function resolveLinkAttachment(
|
|
99
103
|
message: LocalMessage
|
|
100
104
|
): Attachment | undefined {
|
|
101
|
-
return message.attachments?.find(
|
|
102
|
-
(a) => a.type === 'link' || (!!a.og_scrape_url && !a.asset_url)
|
|
103
|
-
)
|
|
105
|
+
return message.attachments?.find(isLinkAttachment)
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
async function triggerDownload(url: string, filename?: string): Promise<void> {
|
|
@@ -35,6 +35,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
35
35
|
onMessageSent,
|
|
36
36
|
showStarButton = false,
|
|
37
37
|
chatbotVotingEnabled = false,
|
|
38
|
+
viewerLanguage,
|
|
38
39
|
renderMessagePreview,
|
|
39
40
|
renderChannelBanner,
|
|
40
41
|
customProfileContent,
|
|
@@ -60,9 +61,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
60
61
|
string | null
|
|
61
62
|
>(null)
|
|
62
63
|
|
|
63
|
-
const {
|
|
64
|
-
showDeleteConversation = true,
|
|
65
|
-
} = capabilities
|
|
64
|
+
const { showDeleteConversation = true } = capabilities
|
|
66
65
|
|
|
67
66
|
// Create default filters and merge with provided filters
|
|
68
67
|
const channelFilters = React.useMemo(() => {
|
|
@@ -194,7 +193,6 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
194
193
|
const channel = await service.startChannelWithParticipant({
|
|
195
194
|
id: initialParticipantData.id,
|
|
196
195
|
name: initialParticipantData.name,
|
|
197
|
-
email: initialParticipantData.email,
|
|
198
196
|
phone: initialParticipantData.phone,
|
|
199
197
|
})
|
|
200
198
|
|
|
@@ -375,6 +373,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
375
373
|
channelRenderFilterFn={channelRenderFilterFn}
|
|
376
374
|
customEmptyStateIndicator={channelListCustomEmptyStateIndicator}
|
|
377
375
|
renderMessagePreview={renderMessagePreview}
|
|
376
|
+
viewerLanguage={viewerLanguage}
|
|
378
377
|
/>
|
|
379
378
|
</div>
|
|
380
379
|
|
|
@@ -418,6 +417,7 @@ export const MessagingShell: React.FC<MessagingShellProps> = ({
|
|
|
418
417
|
onMessageSent={onMessageSent}
|
|
419
418
|
showStarButton={showStarButton}
|
|
420
419
|
chatbotVotingEnabled={chatbotVotingEnabled}
|
|
420
|
+
viewerLanguage={viewerLanguage}
|
|
421
421
|
customProfileContent={customProfileContent}
|
|
422
422
|
customChannelActions={customChannelActions}
|
|
423
423
|
renderMessage={renderMessage}
|
package/src/index.ts
CHANGED
|
@@ -14,10 +14,14 @@ export { ChannelEmptyState } from './components/MessagingShell/ChannelEmptyState
|
|
|
14
14
|
export { MessageVoteButtons } from './components/CustomMessage/MessageVoteButtons'
|
|
15
15
|
export {
|
|
16
16
|
MediaMessage,
|
|
17
|
+
isLinkAttachment,
|
|
17
18
|
resolveLinkAttachment,
|
|
18
19
|
resolveMediaFromMessage,
|
|
19
20
|
} from './components/MediaMessage'
|
|
20
|
-
export type {
|
|
21
|
+
export type {
|
|
22
|
+
MediaMessageProps,
|
|
23
|
+
MediaMessageResolved,
|
|
24
|
+
} from './components/MediaMessage'
|
|
21
25
|
|
|
22
26
|
// Providers
|
|
23
27
|
export { MessagingProvider } from './providers/MessagingProvider'
|
|
@@ -30,6 +34,10 @@ export { useCustomMessage } from './components/CustomMessage/context'
|
|
|
30
34
|
|
|
31
35
|
// Utils
|
|
32
36
|
export { formatRelativeTime } from './utils/formatRelativeTime'
|
|
37
|
+
export {
|
|
38
|
+
getMessageDisplayText,
|
|
39
|
+
normalizeLanguageCode,
|
|
40
|
+
} from './utils/getMessageDisplayText'
|
|
33
41
|
|
|
34
42
|
// Types
|
|
35
43
|
export type {
|
|
@@ -44,7 +52,11 @@ export type {
|
|
|
44
52
|
export type { MessageMetadata } from './stream-custom-data'
|
|
45
53
|
export type { AvatarProps } from './components/Avatar'
|
|
46
54
|
export type { ActionButtonProps } from './components/ActionButton'
|
|
47
|
-
export type {
|
|
55
|
+
export type {
|
|
56
|
+
CreatorCardProps,
|
|
57
|
+
VisitorCardProps,
|
|
58
|
+
LockedAttachmentContextValue,
|
|
59
|
+
} from './components/LockedAttachment'
|
|
48
60
|
export type { CustomMessageRegistry } from './components/CustomMessage/context'
|
|
49
61
|
export type { AttachmentSourceType } from './components/AttachmentCard/utils/mimeType'
|
|
50
62
|
export type { Faq, FaqListProps } from './components/FaqList'
|
package/src/stories/mocks.tsx
CHANGED
|
@@ -110,31 +110,26 @@ export const mockParticipants = [
|
|
|
110
110
|
{
|
|
111
111
|
id: 'participant-1',
|
|
112
112
|
name: 'Alice Johnson',
|
|
113
|
-
email: 'alice@example.com',
|
|
114
113
|
image: 'https://i.pravatar.cc/150?img=2',
|
|
115
114
|
},
|
|
116
115
|
{
|
|
117
116
|
id: 'participant-2',
|
|
118
117
|
name: 'Bob Smith',
|
|
119
|
-
email: 'bob@example.com',
|
|
120
118
|
image: 'https://i.pravatar.cc/150?img=3',
|
|
121
119
|
},
|
|
122
120
|
{
|
|
123
121
|
id: 'participant-3',
|
|
124
122
|
name: 'Carol Williams',
|
|
125
|
-
email: 'carol@example.com',
|
|
126
123
|
image: 'https://i.pravatar.cc/150?img=4',
|
|
127
124
|
},
|
|
128
125
|
{
|
|
129
126
|
id: 'participant-4',
|
|
130
127
|
name: 'David Brown',
|
|
131
|
-
email: 'david@example.com',
|
|
132
128
|
image: 'https://i.pravatar.cc/150?img=5',
|
|
133
129
|
},
|
|
134
130
|
{
|
|
135
131
|
id: 'participant-5',
|
|
136
132
|
name: 'Emma Davis',
|
|
137
|
-
email: 'emma@example.com',
|
|
138
133
|
image: 'https://i.pravatar.cc/150?img=6',
|
|
139
134
|
},
|
|
140
135
|
]
|
|
@@ -143,10 +138,8 @@ export const mockParticipants = [
|
|
|
143
138
|
export const mockParticipantSource = {
|
|
144
139
|
loadParticipants: async (options?: { search?: string; limit?: number }) => {
|
|
145
140
|
const searchTerm = options?.search || ''
|
|
146
|
-
const filtered = mockParticipants.filter(
|
|
147
|
-
(
|
|
148
|
-
p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
149
|
-
p.email.toLowerCase().includes(searchTerm.toLowerCase())
|
|
141
|
+
const filtered = mockParticipants.filter((p) =>
|
|
142
|
+
p.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
150
143
|
)
|
|
151
144
|
return {
|
|
152
145
|
participants: filtered,
|
package/src/styles.css
CHANGED
|
@@ -137,10 +137,6 @@
|
|
|
137
137
|
.str-chat__attachment-list .str-chat__message-attachment--card {
|
|
138
138
|
color: white;
|
|
139
139
|
}
|
|
140
|
-
|
|
141
|
-
a {
|
|
142
|
-
color: color-mix(in srgb, white 60%, #005fff);
|
|
143
|
-
}
|
|
144
140
|
}
|
|
145
141
|
|
|
146
142
|
.str-chat__unread-messages-separator,
|
|
@@ -369,6 +365,7 @@
|
|
|
369
365
|
}
|
|
370
366
|
|
|
371
367
|
.str-chat__message-text-inner a {
|
|
368
|
+
color: inherit;
|
|
372
369
|
text-decoration: underline;
|
|
373
370
|
text-underline-offset: 0.15em;
|
|
374
371
|
word-break: break-word;
|
package/src/types.ts
CHANGED
|
@@ -25,7 +25,6 @@ export type { LockedAttachmentSource } from './components/LockedAttachment'
|
|
|
25
25
|
export interface Participant {
|
|
26
26
|
id: string
|
|
27
27
|
name: string
|
|
28
|
-
email?: string
|
|
29
28
|
image?: string
|
|
30
29
|
username?: string
|
|
31
30
|
phone?: string
|
|
@@ -95,6 +94,11 @@ export interface ChannelListProps {
|
|
|
95
94
|
message: LocalMessage | undefined,
|
|
96
95
|
defaultPreview?: string
|
|
97
96
|
) => React.ReactNode
|
|
97
|
+
/**
|
|
98
|
+
* Language code used to pick translated message text from Stream Chat i18n.
|
|
99
|
+
* Falls back to message.text when no matching translation exists.
|
|
100
|
+
*/
|
|
101
|
+
viewerLanguage?: string
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
/**
|
|
@@ -173,6 +177,11 @@ export interface ChannelViewProps {
|
|
|
173
177
|
* Defaults to false.
|
|
174
178
|
*/
|
|
175
179
|
chatbotVotingEnabled?: boolean
|
|
180
|
+
/**
|
|
181
|
+
* Language code used to pick translated message text from Stream Chat i18n.
|
|
182
|
+
* Falls back to message.text when no matching translation exists.
|
|
183
|
+
*/
|
|
184
|
+
viewerLanguage?: string
|
|
176
185
|
|
|
177
186
|
/**
|
|
178
187
|
* Custom render function for a banner/card component that renders
|
|
@@ -244,6 +253,7 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
244
253
|
| 'onMessageSent'
|
|
245
254
|
| 'showStarButton'
|
|
246
255
|
| 'chatbotVotingEnabled'
|
|
256
|
+
| 'viewerLanguage'
|
|
247
257
|
| 'renderChannelBanner'
|
|
248
258
|
| 'customProfileContent'
|
|
249
259
|
| 'customChannelActions'
|