@linktr.ee/messaging-react 1.13.0 → 1.14.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/assets/index.css +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +931 -721
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelView.tsx +36 -23
- package/src/components/CustomDateSeparator/index.tsx +5 -0
- package/src/components/CustomLinkPreviewList/index.tsx +1 -1
- package/src/components/CustomMessage/CustomMessage.stories.tsx +208 -0
- package/src/components/CustomMessage/MessageTag.stories.tsx +119 -0
- package/src/components/CustomMessage/MessageTag.tsx +84 -0
- package/src/components/CustomMessage/index.tsx +271 -0
- package/src/components/CustomSystemMessage/index.tsx +1 -0
- package/src/stream-custom-data.ts +11 -0
- package/src/styles.css +45 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
import React, { useMemo, useState } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
Attachment as DefaultAttachment,
|
|
5
|
+
Avatar as DefaultAvatar,
|
|
6
|
+
EditMessageModal as DefaultEditMessageModal,
|
|
7
|
+
MessageBounceModal,
|
|
8
|
+
MessageBouncePrompt as DefaultMessageBouncePrompt,
|
|
9
|
+
MessageBlocked as DefaultMessageBlocked,
|
|
10
|
+
MessageDeleted as DefaultMessageDeleted,
|
|
11
|
+
MessageEditedTimestamp,
|
|
12
|
+
MessageErrorIcon,
|
|
13
|
+
MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator,
|
|
14
|
+
MessageOptions as DefaultMessageOptions,
|
|
15
|
+
MessageRepliesCountButton as DefaultMessageRepliesCountButton,
|
|
16
|
+
MessageStatus as DefaultMessageStatus,
|
|
17
|
+
MessageText,
|
|
18
|
+
MessageTimestamp as DefaultMessageTimestamp,
|
|
19
|
+
Poll,
|
|
20
|
+
ReactionsList as DefaultReactionList,
|
|
21
|
+
ReminderNotification as DefaultReminderNotification,
|
|
22
|
+
StreamedMessageText as DefaultStreamedMessageText,
|
|
23
|
+
areMessageUIPropsEqual,
|
|
24
|
+
isDateSeparatorMessage,
|
|
25
|
+
isMessageBlocked,
|
|
26
|
+
isMessageBounced,
|
|
27
|
+
isMessageEdited,
|
|
28
|
+
messageHasAttachments,
|
|
29
|
+
messageHasReactions,
|
|
30
|
+
useComponentContext,
|
|
31
|
+
useChatContext,
|
|
32
|
+
useMessageContext,
|
|
33
|
+
useMessageReminder,
|
|
34
|
+
useTranslationContext,
|
|
35
|
+
type MessageContextValue,
|
|
36
|
+
type MessageUIComponentProps,
|
|
37
|
+
} from 'stream-chat-react'
|
|
38
|
+
|
|
39
|
+
import { MessageTag, isTipOnlyMessage } from './MessageTag'
|
|
40
|
+
|
|
41
|
+
type CustomMessageWithContextProps = MessageContextValue
|
|
42
|
+
|
|
43
|
+
const CustomMessageWithContext = (props: CustomMessageWithContextProps) => {
|
|
44
|
+
const {
|
|
45
|
+
additionalMessageInputProps,
|
|
46
|
+
editing,
|
|
47
|
+
endOfGroup,
|
|
48
|
+
firstOfGroup,
|
|
49
|
+
groupedByUser,
|
|
50
|
+
handleAction,
|
|
51
|
+
handleOpenThread,
|
|
52
|
+
handleRetry,
|
|
53
|
+
highlighted,
|
|
54
|
+
isMessageAIGenerated,
|
|
55
|
+
isMyMessage,
|
|
56
|
+
message,
|
|
57
|
+
onUserClick,
|
|
58
|
+
onUserHover,
|
|
59
|
+
renderText,
|
|
60
|
+
threadList,
|
|
61
|
+
} = props
|
|
62
|
+
|
|
63
|
+
const { client } = useChatContext('CustomMessage')
|
|
64
|
+
const { t } = useTranslationContext('CustomMessage')
|
|
65
|
+
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)
|
|
66
|
+
const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false)
|
|
67
|
+
const reminder = useMessageReminder(message.id)
|
|
68
|
+
|
|
69
|
+
const {
|
|
70
|
+
Attachment = DefaultAttachment,
|
|
71
|
+
Avatar = DefaultAvatar,
|
|
72
|
+
EditMessageModal = DefaultEditMessageModal,
|
|
73
|
+
MessageOptions = DefaultMessageOptions,
|
|
74
|
+
MessageActions = MessageOptions,
|
|
75
|
+
MessageBlocked = DefaultMessageBlocked,
|
|
76
|
+
MessageBouncePrompt = DefaultMessageBouncePrompt,
|
|
77
|
+
MessageDeleted = DefaultMessageDeleted,
|
|
78
|
+
MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator,
|
|
79
|
+
MessageRepliesCountButton = DefaultMessageRepliesCountButton,
|
|
80
|
+
MessageStatus = DefaultMessageStatus,
|
|
81
|
+
MessageTimestamp = DefaultMessageTimestamp,
|
|
82
|
+
ReactionsList = DefaultReactionList,
|
|
83
|
+
ReminderNotification = DefaultReminderNotification,
|
|
84
|
+
StreamedMessageText = DefaultStreamedMessageText,
|
|
85
|
+
PinIndicator,
|
|
86
|
+
} = useComponentContext('CustomMessage')
|
|
87
|
+
|
|
88
|
+
const hasAttachment = messageHasAttachments(message)
|
|
89
|
+
const hasReactions = messageHasReactions(message)
|
|
90
|
+
const isAIGenerated = useMemo(
|
|
91
|
+
() => isMessageAIGenerated?.(message),
|
|
92
|
+
[isMessageAIGenerated, message]
|
|
93
|
+
)
|
|
94
|
+
const finalAttachments = useMemo(
|
|
95
|
+
() =>
|
|
96
|
+
!message.shared_location && !message.attachments
|
|
97
|
+
? []
|
|
98
|
+
: !message.shared_location
|
|
99
|
+
? message.attachments
|
|
100
|
+
: [message.shared_location, ...(message.attachments ?? [])],
|
|
101
|
+
[message]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if (isDateSeparatorMessage(message)) {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (message.deleted_at || message.type === 'deleted') {
|
|
109
|
+
return <MessageDeleted message={message} />
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isMessageBlocked(message)) {
|
|
113
|
+
return <MessageBlocked />
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const showMetadata = !groupedByUser || endOfGroup
|
|
117
|
+
const showReplyCountButton = !threadList && !!message.reply_count
|
|
118
|
+
const showIsReplyInChannel =
|
|
119
|
+
!threadList && message.show_in_channel && message.parent_id
|
|
120
|
+
const allowRetry =
|
|
121
|
+
message.status === 'failed' && message.error?.status !== 403
|
|
122
|
+
const isBounced = isMessageBounced(message)
|
|
123
|
+
const isEdited = isMessageEdited(message) && !isAIGenerated
|
|
124
|
+
|
|
125
|
+
let handleClick: (() => void) | undefined = undefined
|
|
126
|
+
|
|
127
|
+
if (allowRetry) {
|
|
128
|
+
handleClick = () => handleRetry(message)
|
|
129
|
+
} else if (isBounced) {
|
|
130
|
+
handleClick = () => setIsBounceDialogOpen(true)
|
|
131
|
+
} else if (isEdited) {
|
|
132
|
+
handleClick = () => setEditedTimestampOpen((prev) => !prev)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const rootClassName = classNames(
|
|
136
|
+
'str-chat__message str-chat__message-simple',
|
|
137
|
+
`str-chat__message--${message.type}`,
|
|
138
|
+
`str-chat__message--${message.status}`,
|
|
139
|
+
isMyMessage()
|
|
140
|
+
? 'str-chat__message--me str-chat__message-simple--me'
|
|
141
|
+
: 'str-chat__message--other',
|
|
142
|
+
message.text ? 'str-chat__message--has-text' : 'has-no-text',
|
|
143
|
+
{
|
|
144
|
+
'str-chat__message--has-attachment': hasAttachment,
|
|
145
|
+
'str-chat__message--highlighted': highlighted,
|
|
146
|
+
'str-chat__message--pinned pinned-message': message.pinned,
|
|
147
|
+
'str-chat__message--with-reactions': hasReactions,
|
|
148
|
+
'str-chat__message-send-can-be-retried':
|
|
149
|
+
message?.status === 'failed' && message?.error?.status !== 403,
|
|
150
|
+
'str-chat__message-with-thread-link':
|
|
151
|
+
showReplyCountButton || showIsReplyInChannel,
|
|
152
|
+
'str-chat__virtual-message__wrapper--end': endOfGroup,
|
|
153
|
+
'str-chat__virtual-message__wrapper--first': firstOfGroup,
|
|
154
|
+
'str-chat__virtual-message__wrapper--group': groupedByUser,
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
const poll = message.poll_id && client.polls.fromState(message.poll_id)
|
|
159
|
+
const isTipOnly = isTipOnlyMessage(message)
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<>
|
|
163
|
+
{editing && (
|
|
164
|
+
<EditMessageModal
|
|
165
|
+
additionalMessageInputProps={additionalMessageInputProps}
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
{isBounceDialogOpen && (
|
|
169
|
+
<MessageBounceModal
|
|
170
|
+
MessageBouncePrompt={MessageBouncePrompt}
|
|
171
|
+
onClose={() => setIsBounceDialogOpen(false)}
|
|
172
|
+
open={isBounceDialogOpen}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
<div className={rootClassName} key={message.id}>
|
|
176
|
+
{PinIndicator && <PinIndicator />}
|
|
177
|
+
{!!reminder && <ReminderNotification reminder={reminder} />}
|
|
178
|
+
{message.user && (
|
|
179
|
+
<Avatar
|
|
180
|
+
image={message.user.image}
|
|
181
|
+
name={message.user.name || message.user.id}
|
|
182
|
+
onClick={onUserClick}
|
|
183
|
+
onMouseOver={onUserHover}
|
|
184
|
+
user={message.user}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
|
188
|
+
<div
|
|
189
|
+
className={classNames('str-chat__message-inner', {
|
|
190
|
+
'str-chat__simple-message--error-failed': allowRetry || isBounced,
|
|
191
|
+
})}
|
|
192
|
+
data-testid="message-inner"
|
|
193
|
+
onClick={handleClick}
|
|
194
|
+
onKeyDown={handleClick}
|
|
195
|
+
role={handleClick ? 'button' : undefined}
|
|
196
|
+
tabIndex={handleClick ? 0 : undefined}
|
|
197
|
+
>
|
|
198
|
+
<MessageActions />
|
|
199
|
+
<div className="str-chat__message-reactions-host">
|
|
200
|
+
{hasReactions && <ReactionsList reverse />}
|
|
201
|
+
</div>
|
|
202
|
+
{isTipOnly ? (
|
|
203
|
+
/* Tip-only messages render as a standalone bubble */
|
|
204
|
+
<MessageTag message={message} standalone />
|
|
205
|
+
) : (
|
|
206
|
+
<div className="str-chat__message-bubble-wrapper">
|
|
207
|
+
<div className="str-chat__message-bubble">
|
|
208
|
+
{poll && <Poll poll={poll} />}
|
|
209
|
+
{finalAttachments?.length && !message.quoted_message ? (
|
|
210
|
+
<Attachment
|
|
211
|
+
actionHandler={handleAction}
|
|
212
|
+
attachments={finalAttachments}
|
|
213
|
+
/>
|
|
214
|
+
) : null}
|
|
215
|
+
{isAIGenerated ? (
|
|
216
|
+
<StreamedMessageText
|
|
217
|
+
message={message}
|
|
218
|
+
renderText={renderText}
|
|
219
|
+
/>
|
|
220
|
+
) : (
|
|
221
|
+
<MessageText message={message} renderText={renderText} />
|
|
222
|
+
)}
|
|
223
|
+
<MessageErrorIcon />
|
|
224
|
+
</div>
|
|
225
|
+
{/* Tag positioned outside and below the bubble */}
|
|
226
|
+
<MessageTag message={message} />
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
{showReplyCountButton && (
|
|
231
|
+
<MessageRepliesCountButton
|
|
232
|
+
onClick={handleOpenThread}
|
|
233
|
+
reply_count={message.reply_count}
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
{showIsReplyInChannel && (
|
|
237
|
+
<MessageIsThreadReplyInChannelButtonIndicator />
|
|
238
|
+
)}
|
|
239
|
+
{showMetadata && (
|
|
240
|
+
<div className="str-chat__message-metadata">
|
|
241
|
+
<MessageStatus />
|
|
242
|
+
{!isMyMessage() && !!message.user && (
|
|
243
|
+
<span className="str-chat__message-simple-name">
|
|
244
|
+
{message.user.name || message.user.id}
|
|
245
|
+
</span>
|
|
246
|
+
)}
|
|
247
|
+
<MessageTimestamp customClass="str-chat__message-simple-timestamp" />
|
|
248
|
+
{isEdited && (
|
|
249
|
+
<span className="str-chat__mesage-simple-edited">
|
|
250
|
+
{t('Edited')}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
{isEdited && (
|
|
254
|
+
<MessageEditedTimestamp calendar open={isEditedTimestampOpen} />
|
|
255
|
+
)}
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
</>
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const MemoizedCustomMessage = React.memo(
|
|
264
|
+
CustomMessageWithContext,
|
|
265
|
+
areMessageUIPropsEqual
|
|
266
|
+
) as typeof CustomMessageWithContext
|
|
267
|
+
|
|
268
|
+
export const CustomMessage = (props: MessageUIComponentProps) => {
|
|
269
|
+
const messageContext = useMessageContext('CustomMessage')
|
|
270
|
+
return <MemoizedCustomMessage {...messageContext} {...props} />
|
|
271
|
+
}
|
|
@@ -21,6 +21,17 @@ declare module 'stream-chat' {
|
|
|
21
21
|
* Used by CustomSystemMessage component.
|
|
22
22
|
*/
|
|
23
23
|
hide_date?: boolean
|
|
24
|
+
/**
|
|
25
|
+
* Message metadata from backend.
|
|
26
|
+
* Contains type and payment information.
|
|
27
|
+
*/
|
|
28
|
+
metadata?: {
|
|
29
|
+
custom_type?: 'MESSAGE_TIP' | 'MESSAGE_PAID' | 'MESSAGE_CHATBOT'
|
|
30
|
+
amount_text?: string
|
|
31
|
+
payment_status?: string
|
|
32
|
+
payment_intent_id?: string
|
|
33
|
+
listing_id?: string
|
|
34
|
+
}
|
|
24
35
|
}
|
|
25
36
|
}
|
|
26
37
|
|
package/src/styles.css
CHANGED
|
@@ -102,3 +102,48 @@
|
|
|
102
102
|
.str-chat__list {
|
|
103
103
|
background: transparent;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/* Wrapper to ensure tag stays below bubble */
|
|
107
|
+
.str-chat__message-bubble-wrapper {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Custom message tag styles */
|
|
113
|
+
.message-tag {
|
|
114
|
+
display: inline-flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 4px;
|
|
117
|
+
margin-top: 4px;
|
|
118
|
+
padding: 0;
|
|
119
|
+
font-size: 12px;
|
|
120
|
+
font-weight: 500;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.message-tag__icon {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.message-tag--tip {
|
|
129
|
+
background-color: transparent;
|
|
130
|
+
color: #016630;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.message-tag--chatbot {
|
|
134
|
+
background-color: transparent;
|
|
135
|
+
color: #7f22fe;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Standalone tip message (tip without text) */
|
|
139
|
+
.message-tip-standalone {
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: 6px;
|
|
143
|
+
padding: 10px 14px;
|
|
144
|
+
border-radius: 18px;
|
|
145
|
+
font-size: 14px;
|
|
146
|
+
font-weight: 500;
|
|
147
|
+
color: #016e1a;
|
|
148
|
+
background-color: #dbf0e0;
|
|
149
|
+
}
|