@linktr.ee/messaging-react 4.4.6-rc-1788235358 → 4.4.6
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.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +175 -178
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/CustomMessage/StreamAttachmentMessage.stories.tsx +14 -0
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +27 -0
- package/src/components/CustomMessage/StreamAttachmentMessage.tsx +9 -1
- package/src/components/MessageAttachment/MessageAttachment.behavior.test.tsx +4 -8
- package/src/components/MessageAttachment/Video/index.tsx +6 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "4.4.6
|
|
3
|
+
"version": "4.4.6",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"chromatic:local": "yarn storybook:build && chromatic"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@linktr.ee/messaging-core": "2.6.0
|
|
54
|
-
"@linktr.ee/messaging-taxonomy": "0.11.0
|
|
53
|
+
"@linktr.ee/messaging-core": "^2.6.0",
|
|
54
|
+
"@linktr.ee/messaging-taxonomy": "^0.11.0",
|
|
55
55
|
"@phosphor-icons/react": "^2.1.10"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
@@ -109,6 +109,20 @@ export const LinkClassic = Render([
|
|
|
109
109
|
linkAttachment({ image_url: undefined, thumb_url: undefined }),
|
|
110
110
|
])
|
|
111
111
|
|
|
112
|
+
/** Two link shares stacked in one message. */
|
|
113
|
+
export const MultipleLinks = Render([
|
|
114
|
+
linkAttachment({
|
|
115
|
+
og_scrape_url: 'https://linktr.ee/marketplace',
|
|
116
|
+
title: 'Linktree Marketplace',
|
|
117
|
+
image_url: HERO,
|
|
118
|
+
}),
|
|
119
|
+
linkAttachment({
|
|
120
|
+
og_scrape_url: 'https://linktr.ee/creator',
|
|
121
|
+
title: 'Creator profile',
|
|
122
|
+
image_url: PHOTO('multiple-links'),
|
|
123
|
+
}),
|
|
124
|
+
])
|
|
125
|
+
|
|
112
126
|
/**
|
|
113
127
|
* Link share with an emoji-only caption alongside the URL. The emoji is
|
|
114
128
|
* real content, so the caption is preserved as a separate bubble beneath
|
|
@@ -95,6 +95,33 @@ describe('linkCaptionDuplicatesAttachmentUrl', () => {
|
|
|
95
95
|
})
|
|
96
96
|
|
|
97
97
|
describe('StreamAttachmentMessage', () => {
|
|
98
|
+
it('spaces multiple link cards inside the attachment rows wrapper', () => {
|
|
99
|
+
renderWithProviders(
|
|
100
|
+
<StreamAttachmentMessage
|
|
101
|
+
groupPosition="single"
|
|
102
|
+
isMyMessage={true}
|
|
103
|
+
message={message([
|
|
104
|
+
{
|
|
105
|
+
type: 'link',
|
|
106
|
+
og_scrape_url: 'https://linktr.ee/first',
|
|
107
|
+
title: 'First Link',
|
|
108
|
+
image_url: 'https://cdn.example.com/first.jpg',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'link',
|
|
112
|
+
og_scrape_url: 'https://linktr.ee/second',
|
|
113
|
+
title: 'Second Link',
|
|
114
|
+
image_url: 'https://cdn.example.com/second.jpg',
|
|
115
|
+
},
|
|
116
|
+
])}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const rows = screen.getByTestId('message-attachment-rows')
|
|
121
|
+
expect(rows).toHaveClass('flex', 'flex-col', 'gap-[4px]')
|
|
122
|
+
expect(within(rows).getAllByTestId('link-card')).toHaveLength(2)
|
|
123
|
+
})
|
|
124
|
+
|
|
98
125
|
it('renders a sent link card and suppresses a URL-only caption', () => {
|
|
99
126
|
renderWithProviders(
|
|
100
127
|
<StreamAttachmentMessage
|
|
@@ -424,7 +424,15 @@ const StreamAttachmentMessage: React.FC<StreamAttachmentMessageProps> = ({
|
|
|
424
424
|
padding: 0,
|
|
425
425
|
}}
|
|
426
426
|
>
|
|
427
|
-
|
|
427
|
+
<div
|
|
428
|
+
data-testid="message-attachment-rows"
|
|
429
|
+
className={classNames(
|
|
430
|
+
'flex flex-col gap-[4px]',
|
|
431
|
+
isMyMessage ? 'items-end' : 'items-start'
|
|
432
|
+
)}
|
|
433
|
+
>
|
|
434
|
+
{rows}
|
|
435
|
+
</div>
|
|
428
436
|
{!caption && <MessageErrorIcon />}
|
|
429
437
|
</div>
|
|
430
438
|
{caption ? (
|
|
@@ -105,7 +105,7 @@ describe('MessageAttachment behavior', () => {
|
|
|
105
105
|
)
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
-
it('renders video
|
|
108
|
+
it('renders video poster and fallback, and wires viewer attributes', () => {
|
|
109
109
|
const onDismiss = vi.fn()
|
|
110
110
|
const { rerender } = renderWithProviders(
|
|
111
111
|
<MessageAttachment.Video.Composer
|
|
@@ -122,15 +122,11 @@ describe('MessageAttachment behavior', () => {
|
|
|
122
122
|
fireEvent.click(screen.getByRole('button', { name: 'Dismiss attachment' }))
|
|
123
123
|
expect(onDismiss).toHaveBeenCalledOnce()
|
|
124
124
|
rerender(
|
|
125
|
-
<MessageAttachment.Video.Sent
|
|
125
|
+
<MessageAttachment.Video.Sent
|
|
126
|
+
items={[{ src: 'https://cdn.example.com/video.mp4' }]}
|
|
127
|
+
/>
|
|
126
128
|
)
|
|
127
|
-
expect(screen.getByLabelText('Video 1 thumbnail').tagName).toBe('VIDEO')
|
|
128
129
|
expect(screen.getByLabelText('Play video 1 of 1')).toBeInTheDocument()
|
|
129
|
-
|
|
130
|
-
rerender(
|
|
131
|
-
<MessageAttachment.Video.Received src="https://cdn.example.com/received-video.mp4" />
|
|
132
|
-
)
|
|
133
|
-
expect(screen.getByLabelText('Video 1 thumbnail').tagName).toBe('VIDEO')
|
|
134
130
|
})
|
|
135
131
|
|
|
136
132
|
it('supports PDF titles, download actions, dismiss, and viewer index', () => {
|
|
@@ -42,8 +42,9 @@ export interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps {
|
|
|
42
42
|
items?: VideoItem[]
|
|
43
43
|
/**
|
|
44
44
|
* `<video preload>` hint forwarded into the viewer. Defaults to
|
|
45
|
-
* `'none'` — poster
|
|
46
|
-
*
|
|
45
|
+
* `'none'` — the poster `<img>` carries the visual weight on the
|
|
46
|
+
* bubble surface, and we shouldn't fetch any video bytes until the
|
|
47
|
+
* user actually opens the viewer. Per-item overrides live on
|
|
47
48
|
* `VideoItem.preload`. The opened `VideoViewer` always preloads
|
|
48
49
|
* metadata for the active item (so duration / first-frame appear
|
|
49
50
|
* immediately) regardless of this value.
|
|
@@ -69,8 +70,9 @@ export interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps {
|
|
|
69
70
|
* recorded it, and only otherwise measured off the decoded poster. Measuring
|
|
70
71
|
* the poster alone was not enough: an uploaded clip may have no poster, and a
|
|
71
72
|
* portrait video then sat in whatever `FALLBACK_ASPECT` guessed. Reading the
|
|
72
|
-
* dimensions Stream already stores costs nothing at render time
|
|
73
|
-
*
|
|
73
|
+
* dimensions Stream already stores costs nothing at render time and needs no
|
|
74
|
+
* `loadedmetadata` probe, which would fetch video bytes for every card in the
|
|
75
|
+
* thread against the deliberate `preload: 'none'`.
|
|
74
76
|
*
|
|
75
77
|
* `FALLBACK_ASPECT` is **square** on purpose. It covers only the case where we
|
|
76
78
|
* know nothing — no recorded dimensions, no decoded poster — and a square box
|
|
@@ -99,10 +101,6 @@ const PlayBadge: React.FC = () => (
|
|
|
99
101
|
</div>
|
|
100
102
|
)
|
|
101
103
|
|
|
102
|
-
// Safari needs a time fragment to paint a first frame from a remote MP4.
|
|
103
|
-
const withFirstFrameHint = (src: string): string =>
|
|
104
|
-
src.includes('#') ? src : `${src}#t=0.001`
|
|
105
|
-
|
|
106
104
|
// The tile shell owns the cluster's geometry: `MediaStackGrid` gives
|
|
107
105
|
// each tile the 18px radius on the cluster's OUTER corners only and
|
|
108
106
|
// clips with `overflow-hidden`, so a poster must carry no radius of
|
|
@@ -139,15 +137,6 @@ const PosterTile: React.FC<{
|
|
|
139
137
|
onLoad={onLoad}
|
|
140
138
|
className="absolute inset-0 size-full object-cover"
|
|
141
139
|
/>
|
|
142
|
-
) : item.src ? (
|
|
143
|
-
<video
|
|
144
|
-
src={withFirstFrameHint(item.src)}
|
|
145
|
-
muted
|
|
146
|
-
playsInline
|
|
147
|
-
preload="metadata"
|
|
148
|
-
aria-label={`Video ${index + 1} thumbnail`}
|
|
149
|
-
className="absolute inset-0 size-full object-cover"
|
|
150
|
-
/>
|
|
151
140
|
) : (
|
|
152
141
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
153
142
|
<VideoCameraIcon
|