@linktr.ee/messaging-react 3.4.2-rc-1782480798 → 3.4.3-rc-1782553322
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-VO-i6CuV.cjs → Card-1oglowd2.cjs} +2 -2
- package/dist/{Card-VO-i6CuV.cjs.map → Card-1oglowd2.cjs.map} +1 -1
- package/dist/{Card-BYu6OBuX.js → Card-BmVutVEt.js} +2 -2
- package/dist/{Card-BYu6OBuX.js.map → Card-BmVutVEt.js.map} +1 -1
- package/dist/{Card-BKi2g6x-.js → Card-CKn-iPjf.js} +2 -2
- package/dist/{Card-BKi2g6x-.js.map → Card-CKn-iPjf.js.map} +1 -1
- package/dist/{Card-BMmrCMqJ.cjs → Card-DEEK3wsC.cjs} +2 -2
- package/dist/{Card-BMmrCMqJ.cjs.map → Card-DEEK3wsC.cjs.map} +1 -1
- package/dist/{Card-DvE5BCPf.js → Card-DoBWURRU.js} +3 -3
- package/dist/{Card-DvE5BCPf.js.map → Card-DoBWURRU.js.map} +1 -1
- package/dist/{Card-DtIjtWy6.cjs → Card-DwaOIthl.cjs} +2 -2
- package/dist/{Card-DtIjtWy6.cjs.map → Card-DwaOIthl.cjs.map} +1 -1
- package/dist/{LockedThumbnail-Cl8uwCxM.cjs → LockedThumbnail-B59cJFsF.cjs} +2 -2
- package/dist/{LockedThumbnail-Cl8uwCxM.cjs.map → LockedThumbnail-B59cJFsF.cjs.map} +1 -1
- package/dist/{LockedThumbnail-DTkd_kEt.js → LockedThumbnail-DI-aReA7.js} +2 -2
- package/dist/{LockedThumbnail-DTkd_kEt.js.map → LockedThumbnail-DI-aReA7.js.map} +1 -1
- package/dist/index-12HxQ_GM.cjs +2 -0
- package/dist/index-12HxQ_GM.cjs.map +1 -0
- package/dist/{index-IzhF38yj.js → index-Dv13-2iu.js} +3200 -3445
- package/dist/index-Dv13-2iu.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/components/ChannelView.stories.tsx +7 -5
- package/src/components/ChannelView.test.tsx +10 -0
- package/src/components/ChannelView.tsx +95 -59
- package/src/components/CustomMessage/index.tsx +0 -42
- package/dist/index-CSSe9eu7.cjs +0 -2
- package/dist/index-CSSe9eu7.cjs.map +0 -1
- package/dist/index-IzhF38yj.js.map +0 -1
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +0 -109
- package/src/components/CustomMessage/StreamAttachmentMessage.tsx +0 -408
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { renderWithProviders, screen } from '../../test/utils'
|
|
5
|
-
|
|
6
|
-
import StreamAttachmentMessage, {
|
|
7
|
-
linkCaptionDuplicatesAttachmentUrl,
|
|
8
|
-
} from './StreamAttachmentMessage'
|
|
9
|
-
|
|
10
|
-
describe('linkCaptionDuplicatesAttachmentUrl', () => {
|
|
11
|
-
it('suppresses captions that are just the link plus punctuation', () => {
|
|
12
|
-
expect(
|
|
13
|
-
linkCaptionDuplicatesAttachmentUrl(
|
|
14
|
-
'https://tr.ee/abc !',
|
|
15
|
-
'https://tr.ee/abc'
|
|
16
|
-
)
|
|
17
|
-
).toBe(true)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('keeps captions that add emoji after the link', () => {
|
|
21
|
-
expect(
|
|
22
|
-
linkCaptionDuplicatesAttachmentUrl(
|
|
23
|
-
'https://tr.ee/abc 😎',
|
|
24
|
-
'https://tr.ee/abc'
|
|
25
|
-
)
|
|
26
|
-
).toBe(false)
|
|
27
|
-
})
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
describe('StreamAttachmentMessage', () => {
|
|
31
|
-
it('renders a sent link card and suppresses a URL-only caption', () => {
|
|
32
|
-
renderWithProviders(
|
|
33
|
-
<StreamAttachmentMessage
|
|
34
|
-
groupPosition="single"
|
|
35
|
-
isMyMessage={true}
|
|
36
|
-
message={{
|
|
37
|
-
attachments: [
|
|
38
|
-
{
|
|
39
|
-
type: 'link',
|
|
40
|
-
og_scrape_url: 'https://linktr.ee/someone',
|
|
41
|
-
title: 'My Linktree',
|
|
42
|
-
text: 'Check out my links',
|
|
43
|
-
image_url: 'https://cdn.example.com/thumb.jpg',
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
text: 'https://linktr.ee/someone !',
|
|
47
|
-
}}
|
|
48
|
-
/>
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
expect(
|
|
52
|
-
screen.getByRole('link', { name: /my linktree/i })
|
|
53
|
-
).toHaveAttribute('href', 'https://linktr.ee/someone')
|
|
54
|
-
expect(screen.queryByText('https://linktr.ee/someone !')).toBeNull()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('keeps an emoji-only caption instead of dropping it', () => {
|
|
58
|
-
renderWithProviders(
|
|
59
|
-
<StreamAttachmentMessage
|
|
60
|
-
groupPosition="single"
|
|
61
|
-
isMyMessage={false}
|
|
62
|
-
message={{
|
|
63
|
-
attachments: [
|
|
64
|
-
{
|
|
65
|
-
type: 'link',
|
|
66
|
-
og_scrape_url: 'https://linktr.ee/someone',
|
|
67
|
-
title: 'My Linktree',
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
text: 'https://linktr.ee/someone 😎',
|
|
71
|
-
}}
|
|
72
|
-
/>
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
expect(screen.getByText('https://linktr.ee/someone 😎')).toBeInTheDocument()
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('renders mixed link and media attachments in order', () => {
|
|
79
|
-
const { container } = renderWithProviders(
|
|
80
|
-
<StreamAttachmentMessage
|
|
81
|
-
groupPosition="single"
|
|
82
|
-
isMyMessage={true}
|
|
83
|
-
message={{
|
|
84
|
-
attachments: [
|
|
85
|
-
{
|
|
86
|
-
type: 'link',
|
|
87
|
-
og_scrape_url: 'https://linktr.ee/someone',
|
|
88
|
-
title: 'My Linktree',
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
type: 'image',
|
|
92
|
-
image_url: 'https://cdn.example.com/photo.jpg',
|
|
93
|
-
title: 'Vacation',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
text: 'caption',
|
|
97
|
-
}}
|
|
98
|
-
/>
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
const link = screen.getByRole('link', { name: /my linktree/i })
|
|
102
|
-
const imageBubble = screen.getByTestId('image-attachment')
|
|
103
|
-
|
|
104
|
-
expect(link.compareDocumentPosition(imageBubble)).toBe(
|
|
105
|
-
Node.DOCUMENT_POSITION_FOLLOWING
|
|
106
|
-
)
|
|
107
|
-
expect(container).toHaveTextContent('caption')
|
|
108
|
-
})
|
|
109
|
-
})
|
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { Attachment } from 'stream-chat'
|
|
3
|
-
|
|
4
|
-
import LinkAttachment from '../LinkAttachment'
|
|
5
|
-
import MessageAttachment from '../MessageAttachment'
|
|
6
|
-
import type { BubbleGroupPosition } from '../MessageAttachment/types'
|
|
7
|
-
|
|
8
|
-
type StreamMediaKind = 'image' | 'video' | 'audio' | 'pdf' | 'file'
|
|
9
|
-
|
|
10
|
-
export type AttachmentSegment =
|
|
11
|
-
| { type: 'link'; attachments: Attachment[] }
|
|
12
|
-
| { type: 'media'; kind: StreamMediaKind; attachments: Attachment[] }
|
|
13
|
-
|
|
14
|
-
type StreamThreadMessage = {
|
|
15
|
-
attachments?: Attachment[]
|
|
16
|
-
text?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const SENT_CAPTION_CLASS =
|
|
20
|
-
'mt-[2px] max-w-[280px] whitespace-pre-wrap break-words rounded-[18px] bg-[#121110] px-4 py-2 text-white'
|
|
21
|
-
const RECEIVED_CAPTION_CLASS =
|
|
22
|
-
'mt-[2px] max-w-[280px] whitespace-pre-wrap break-words rounded-[18px] bg-[#e9eaed] px-4 py-2 text-[#080707]'
|
|
23
|
-
|
|
24
|
-
function trimToUndefined(value?: string | null): string | undefined {
|
|
25
|
-
const trimmed = value?.trim()
|
|
26
|
-
return trimmed ? trimmed : undefined
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function normalizeLinkUrl(url: string): string {
|
|
30
|
-
return url
|
|
31
|
-
.trim()
|
|
32
|
-
.replace(/^https?:\/\//i, '')
|
|
33
|
-
.replace(/\/+$/, '')
|
|
34
|
-
.toLowerCase()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function linkCaptionDuplicatesAttachmentUrl(
|
|
38
|
-
caption: string | undefined,
|
|
39
|
-
attachmentUrl?: string
|
|
40
|
-
): boolean {
|
|
41
|
-
if (!caption?.trim() || !attachmentUrl?.trim()) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const normalizedCaption = normalizeLinkUrl(caption)
|
|
46
|
-
const normalizedUrl = normalizeLinkUrl(attachmentUrl)
|
|
47
|
-
|
|
48
|
-
if (normalizedCaption === normalizedUrl) {
|
|
49
|
-
return true
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!normalizedCaption.includes(normalizedUrl)) {
|
|
53
|
-
return false
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const remainder = normalizedCaption.split(normalizedUrl).join('')
|
|
57
|
-
return !/[^\p{P}\p{Z}\p{C}]/u.test(remainder)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function linkCardPropsFromStreamAttachment(a: Attachment) {
|
|
61
|
-
const url = trimToUndefined(a.og_scrape_url) ?? trimToUndefined(a.title_link)
|
|
62
|
-
const thumbnailUrl =
|
|
63
|
-
trimToUndefined((a as { image_url?: string }).image_url) ??
|
|
64
|
-
trimToUndefined((a as { thumb_url?: string }).thumb_url)
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
title: trimToUndefined((a as { title?: string }).title),
|
|
68
|
-
description: trimToUndefined((a as { text?: string }).text),
|
|
69
|
-
url,
|
|
70
|
-
thumbnailUrl,
|
|
71
|
-
layout: thumbnailUrl ? 'featured' : 'classic',
|
|
72
|
-
mimeType: trimToUndefined(a.mime_type),
|
|
73
|
-
} as const
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function getAttachmentMediaKind(
|
|
77
|
-
attachment: Attachment
|
|
78
|
-
): StreamMediaKind | null {
|
|
79
|
-
if (isLinkAttachment(attachment)) return null
|
|
80
|
-
|
|
81
|
-
if (attachment.type === 'image') {
|
|
82
|
-
const imageUrl = (attachment as { image_url?: string }).image_url
|
|
83
|
-
if (imageUrl || attachment.asset_url) return 'image'
|
|
84
|
-
return null
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (attachment.type === 'video' && attachment.asset_url) return 'video'
|
|
88
|
-
if (attachment.type === 'audio' && attachment.asset_url) return 'audio'
|
|
89
|
-
|
|
90
|
-
if (attachment.type === 'file' && attachment.asset_url) {
|
|
91
|
-
if (attachment.mime_type === 'application/pdf') return 'pdf'
|
|
92
|
-
if (attachment.mime_type?.startsWith('audio/')) return 'audio'
|
|
93
|
-
return 'file'
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return null
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function isLinkAttachment(a: Attachment): boolean {
|
|
100
|
-
const ogScrapeUrl = trimToUndefined(a.og_scrape_url)
|
|
101
|
-
return a.type === 'link' || (!!ogScrapeUrl && !a.asset_url)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function buildOrderedAttachmentSegments(
|
|
105
|
-
attachments: Attachment[] | undefined
|
|
106
|
-
): AttachmentSegment[] {
|
|
107
|
-
if (!attachments?.length) return []
|
|
108
|
-
|
|
109
|
-
const segments: AttachmentSegment[] = []
|
|
110
|
-
|
|
111
|
-
const pushOrMergeMedia = (kind: StreamMediaKind, attachment: Attachment) => {
|
|
112
|
-
const last = segments[segments.length - 1]
|
|
113
|
-
if (last?.type === 'media' && last.kind === kind) {
|
|
114
|
-
last.attachments.push(attachment)
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
segments.push({ type: 'media', kind, attachments: [attachment] })
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const pushOrMergeLink = (attachment: Attachment) => {
|
|
122
|
-
const last = segments[segments.length - 1]
|
|
123
|
-
if (last?.type === 'link') {
|
|
124
|
-
last.attachments.push(attachment)
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
segments.push({ type: 'link', attachments: [attachment] })
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
for (const attachment of attachments) {
|
|
132
|
-
if (isLinkAttachment(attachment)) {
|
|
133
|
-
pushOrMergeLink(attachment)
|
|
134
|
-
continue
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const kind = getAttachmentMediaKind(attachment)
|
|
138
|
-
if (!kind) continue
|
|
139
|
-
|
|
140
|
-
pushOrMergeMedia(kind, attachment)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return segments
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function countAttachmentSegmentBubbles(
|
|
147
|
-
segments: AttachmentSegment[]
|
|
148
|
-
): number {
|
|
149
|
-
let count = 0
|
|
150
|
-
|
|
151
|
-
for (const segment of segments) {
|
|
152
|
-
count += segment.type === 'link' ? segment.attachments.length : 1
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return count
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function countMediaSegmentBubbles(segments: AttachmentSegment[]): number {
|
|
159
|
-
return segments.filter((segment) => segment.type === 'media').length
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function bubblePositionInRun(
|
|
163
|
-
index: number,
|
|
164
|
-
total: number,
|
|
165
|
-
streamPosition: BubbleGroupPosition
|
|
166
|
-
): BubbleGroupPosition {
|
|
167
|
-
if (total <= 1) return streamPosition
|
|
168
|
-
if (index === 0) return 'first'
|
|
169
|
-
if (index === total - 1) return 'end'
|
|
170
|
-
return 'middle'
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function imageSrcFromAttachment(attachment: Attachment): string {
|
|
174
|
-
return (
|
|
175
|
-
trimToUndefined((attachment as { image_url?: string }).image_url) ??
|
|
176
|
-
trimToUndefined(attachment.asset_url) ??
|
|
177
|
-
''
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function messageAttachmentNamespaceForKind(kind: StreamMediaKind) {
|
|
182
|
-
switch (kind) {
|
|
183
|
-
case 'image':
|
|
184
|
-
return MessageAttachment.Image
|
|
185
|
-
case 'video':
|
|
186
|
-
return MessageAttachment.Video
|
|
187
|
-
case 'audio':
|
|
188
|
-
return MessageAttachment.Audio
|
|
189
|
-
case 'pdf':
|
|
190
|
-
return MessageAttachment.Pdf
|
|
191
|
-
default:
|
|
192
|
-
return MessageAttachment.File
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function buildMediaClusterRenderProps(
|
|
197
|
-
kind: StreamMediaKind,
|
|
198
|
-
attachments: Attachment[]
|
|
199
|
-
): Record<string, unknown> {
|
|
200
|
-
if (attachments.length > 1) {
|
|
201
|
-
switch (kind) {
|
|
202
|
-
case 'image':
|
|
203
|
-
return {
|
|
204
|
-
items: attachments.map((attachment) => ({
|
|
205
|
-
src: imageSrcFromAttachment(attachment),
|
|
206
|
-
alt: trimToUndefined((attachment as { title?: string }).title),
|
|
207
|
-
})),
|
|
208
|
-
}
|
|
209
|
-
case 'video':
|
|
210
|
-
return {
|
|
211
|
-
items: attachments.map((attachment) => ({
|
|
212
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
213
|
-
poster: trimToUndefined(
|
|
214
|
-
(attachment as { thumb_url?: string }).thumb_url
|
|
215
|
-
),
|
|
216
|
-
mimeType: trimToUndefined(attachment.mime_type),
|
|
217
|
-
})),
|
|
218
|
-
}
|
|
219
|
-
case 'audio':
|
|
220
|
-
return {
|
|
221
|
-
items: attachments.map((attachment) => ({
|
|
222
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
223
|
-
mimeType:
|
|
224
|
-
trimToUndefined(attachment.mime_type) ?? 'audio/mpeg',
|
|
225
|
-
filename: trimToUndefined(
|
|
226
|
-
(attachment as { title?: string }).title
|
|
227
|
-
),
|
|
228
|
-
})),
|
|
229
|
-
}
|
|
230
|
-
case 'pdf':
|
|
231
|
-
return {
|
|
232
|
-
items: attachments.map((attachment) => ({
|
|
233
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
234
|
-
filename: trimToUndefined(
|
|
235
|
-
(attachment as { title?: string }).title
|
|
236
|
-
),
|
|
237
|
-
fileSize: (attachment as { file_size?: number }).file_size,
|
|
238
|
-
})),
|
|
239
|
-
}
|
|
240
|
-
case 'file':
|
|
241
|
-
return {
|
|
242
|
-
items: attachments.map((attachment) => ({
|
|
243
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
244
|
-
filename: trimToUndefined(
|
|
245
|
-
(attachment as { title?: string }).title
|
|
246
|
-
),
|
|
247
|
-
fileSize: (attachment as { file_size?: number }).file_size,
|
|
248
|
-
mimeType:
|
|
249
|
-
trimToUndefined(attachment.mime_type) ??
|
|
250
|
-
'application/octet-stream',
|
|
251
|
-
})),
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const attachment = attachments[0]
|
|
257
|
-
if (!attachment) return {}
|
|
258
|
-
|
|
259
|
-
switch (kind) {
|
|
260
|
-
case 'image':
|
|
261
|
-
return {
|
|
262
|
-
src: imageSrcFromAttachment(attachment),
|
|
263
|
-
alt: trimToUndefined((attachment as { title?: string }).title),
|
|
264
|
-
}
|
|
265
|
-
case 'video':
|
|
266
|
-
return {
|
|
267
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
268
|
-
poster: trimToUndefined((attachment as { thumb_url?: string }).thumb_url),
|
|
269
|
-
mimeType: trimToUndefined(attachment.mime_type),
|
|
270
|
-
}
|
|
271
|
-
case 'audio':
|
|
272
|
-
return {
|
|
273
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
274
|
-
mimeType: trimToUndefined(attachment.mime_type) ?? 'audio/mpeg',
|
|
275
|
-
filename: trimToUndefined((attachment as { title?: string }).title),
|
|
276
|
-
}
|
|
277
|
-
case 'pdf':
|
|
278
|
-
return {
|
|
279
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
280
|
-
filename: trimToUndefined((attachment as { title?: string }).title),
|
|
281
|
-
fileSize: (attachment as { file_size?: number }).file_size,
|
|
282
|
-
}
|
|
283
|
-
case 'file':
|
|
284
|
-
return {
|
|
285
|
-
src: trimToUndefined(attachment.asset_url) ?? '',
|
|
286
|
-
filename: trimToUndefined((attachment as { title?: string }).title),
|
|
287
|
-
fileSize: (attachment as { file_size?: number }).file_size,
|
|
288
|
-
mimeType:
|
|
289
|
-
trimToUndefined(attachment.mime_type) ?? 'application/octet-stream',
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function MediaClusterBubble({
|
|
295
|
-
groupPosition,
|
|
296
|
-
isMyMessage,
|
|
297
|
-
segment,
|
|
298
|
-
text,
|
|
299
|
-
}: {
|
|
300
|
-
groupPosition: BubbleGroupPosition
|
|
301
|
-
isMyMessage: boolean
|
|
302
|
-
segment: Extract<AttachmentSegment, { type: 'media' }>
|
|
303
|
-
text?: string
|
|
304
|
-
}) {
|
|
305
|
-
const TypeNamespace = messageAttachmentNamespaceForKind(segment.kind)
|
|
306
|
-
const props = {
|
|
307
|
-
...buildMediaClusterRenderProps(segment.kind, segment.attachments),
|
|
308
|
-
groupPosition,
|
|
309
|
-
...(text ? { text } : {}),
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (isMyMessage) {
|
|
313
|
-
return <TypeNamespace.Sent {...props} />
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return <TypeNamespace.Received {...props} />
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export interface StreamAttachmentMessageProps {
|
|
320
|
-
groupPosition: BubbleGroupPosition
|
|
321
|
-
isMyMessage: boolean
|
|
322
|
-
message: StreamThreadMessage
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const StreamAttachmentMessage: React.FC<StreamAttachmentMessageProps> = ({
|
|
326
|
-
groupPosition,
|
|
327
|
-
isMyMessage,
|
|
328
|
-
message,
|
|
329
|
-
}) => {
|
|
330
|
-
const segments = buildOrderedAttachmentSegments(message.attachments)
|
|
331
|
-
if (segments.length === 0) return null
|
|
332
|
-
|
|
333
|
-
const totalBubbles = countAttachmentSegmentBubbles(segments)
|
|
334
|
-
const totalMediaBubbles = countMediaSegmentBubbles(segments)
|
|
335
|
-
const rawCaption = message.text?.trim()
|
|
336
|
-
const linkSegment = segments.find((segment) => segment.type === 'link')
|
|
337
|
-
const primaryLinkUrl =
|
|
338
|
-
linkSegment?.type === 'link'
|
|
339
|
-
? linkCardPropsFromStreamAttachment(linkSegment.attachments[0] ?? {}).url
|
|
340
|
-
: undefined
|
|
341
|
-
const caption =
|
|
342
|
-
rawCaption &&
|
|
343
|
-
linkCaptionDuplicatesAttachmentUrl(rawCaption, primaryLinkUrl)
|
|
344
|
-
? undefined
|
|
345
|
-
: rawCaption
|
|
346
|
-
|
|
347
|
-
let bubbleIndex = 0
|
|
348
|
-
let mediaBubbleIndex = 0
|
|
349
|
-
const rows: React.ReactNode[] = []
|
|
350
|
-
|
|
351
|
-
const pushLinkRow = (attachment: Attachment, key: string) => {
|
|
352
|
-
const isLastBubble = bubbleIndex === totalBubbles - 1
|
|
353
|
-
bubbleIndex += 1
|
|
354
|
-
|
|
355
|
-
const Card = isMyMessage ? LinkAttachment.Sent : LinkAttachment.Received
|
|
356
|
-
|
|
357
|
-
rows.push(
|
|
358
|
-
<React.Fragment key={key}>
|
|
359
|
-
<Card {...linkCardPropsFromStreamAttachment(attachment)} />
|
|
360
|
-
{isLastBubble && caption ? (
|
|
361
|
-
<div className={isMyMessage ? SENT_CAPTION_CLASS : RECEIVED_CAPTION_CLASS}>
|
|
362
|
-
{caption}
|
|
363
|
-
</div>
|
|
364
|
-
) : null}
|
|
365
|
-
</React.Fragment>
|
|
366
|
-
)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const pushMediaCluster = (
|
|
370
|
-
segment: Extract<AttachmentSegment, { type: 'media' }>,
|
|
371
|
-
key: string
|
|
372
|
-
) => {
|
|
373
|
-
const isLastBubble = bubbleIndex === totalBubbles - 1
|
|
374
|
-
const position = bubblePositionInRun(
|
|
375
|
-
mediaBubbleIndex,
|
|
376
|
-
totalMediaBubbles,
|
|
377
|
-
groupPosition
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
bubbleIndex += 1
|
|
381
|
-
mediaBubbleIndex += 1
|
|
382
|
-
|
|
383
|
-
rows.push(
|
|
384
|
-
<MediaClusterBubble
|
|
385
|
-
key={key}
|
|
386
|
-
groupPosition={position}
|
|
387
|
-
isMyMessage={isMyMessage}
|
|
388
|
-
segment={segment}
|
|
389
|
-
text={isLastBubble ? caption : undefined}
|
|
390
|
-
/>
|
|
391
|
-
)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
segments.forEach((segment, segmentIndex) => {
|
|
395
|
-
if (segment.type === 'link') {
|
|
396
|
-
segment.attachments.forEach((attachment, attachmentIndex) => {
|
|
397
|
-
pushLinkRow(attachment, `link-${segmentIndex}-${attachmentIndex}`)
|
|
398
|
-
})
|
|
399
|
-
return
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
pushMediaCluster(segment, `media-${segmentIndex}`)
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
return <>{rows}</>
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
export default StreamAttachmentMessage
|