@linktr.ee/messaging-react 1.27.0 → 1.28.0-rc-1776225927
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/Creator-D38dWn2X.js +318 -0
- package/dist/Creator-D38dWn2X.js.map +1 -0
- package/dist/MediaPlayer-DE9MC6k6.js +599 -0
- package/dist/MediaPlayer-DE9MC6k6.js.map +1 -0
- package/dist/Visitor-BG-9-3HU.js +199 -0
- package/dist/Visitor-BG-9-3HU.js.map +1 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/CustomMessage/CustomMessage.stories.tsx +1 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +143 -49
- package/src/components/LockedAttachment/components/Creator.tsx +410 -112
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +112 -52
- package/src/components/LockedAttachment/components/Visitor.tsx +206 -143
- package/src/components/LockedAttachment/types.ts +2 -3
- package/src/components/LockedAttachment/utils/icons.ts +2 -1
- package/src/components/LockedAttachment/utils/mimeType.test.ts +29 -7
- package/src/components/LockedAttachment/utils/mimeType.ts +3 -1
- package/src/types.ts +0 -1
- package/dist/Creator-B6M8dB0U.js +0 -87
- package/dist/Creator-B6M8dB0U.js.map +0 -1
- package/dist/MediaPlayer-DsjlYGGH.js +0 -539
- package/dist/MediaPlayer-DsjlYGGH.js.map +0 -1
- package/dist/Visitor-CpmFZRGO.js +0 -175
- package/dist/Visitor-CpmFZRGO.js.map +0 -1
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CheckCircleIcon,
|
|
3
3
|
DownloadSimpleIcon,
|
|
4
|
+
LockOpenIcon,
|
|
4
5
|
LockSimpleIcon,
|
|
5
|
-
LockSimpleOpenIcon,
|
|
6
6
|
} from '@phosphor-icons/react'
|
|
7
7
|
import React, { useEffect, useState } from 'react'
|
|
8
8
|
|
|
9
9
|
import { isDevBuild } from '../../../utils/isDevBuild'
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
LockedAttachmentBaseProps,
|
|
12
|
+
LockedAttachmentSource,
|
|
13
|
+
PaymentStatus,
|
|
14
|
+
} from '../types'
|
|
11
15
|
import { renderTypeIcon } from '../utils/icons'
|
|
12
16
|
import { getSourceType } from '../utils/mimeType'
|
|
13
17
|
|
|
@@ -16,8 +20,8 @@ import MediaPlayer from './MediaPlayer'
|
|
|
16
20
|
export interface VisitorCardProps extends LockedAttachmentBaseProps {
|
|
17
21
|
title?: string
|
|
18
22
|
/**
|
|
19
|
-
* Called when the visitor clicks Unlock. Return the resolved source
|
|
20
|
-
* The component manages loading state and sets source
|
|
23
|
+
* Called when the visitor clicks Unlock. Return the resolved source URL.
|
|
24
|
+
* The component manages loading state and sets source internally on resolution.
|
|
21
25
|
* Omit to hide the Unlock button.
|
|
22
26
|
*/
|
|
23
27
|
onUnlock?: () => Promise<LockedAttachmentSource>
|
|
@@ -25,75 +29,164 @@ export interface VisitorCardProps extends LockedAttachmentBaseProps {
|
|
|
25
29
|
onDownload?: () => void
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
29
|
-
|
|
32
|
+
const getLockIcon = (paymentStatus?: PaymentStatus): React.ElementType =>
|
|
33
|
+
paymentStatus === 'paid' ? LockOpenIcon : LockSimpleIcon
|
|
34
|
+
|
|
35
|
+
// ─── Shared primitives ────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
interface LockOverlayProps {
|
|
38
|
+
icon: React.ElementType
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const LockOverlay: React.FC<LockOverlayProps> = (props) => {
|
|
42
|
+
const { icon: Icon } = props
|
|
43
|
+
return (
|
|
44
|
+
<div className="absolute inset-0 bg-black/30">
|
|
45
|
+
<div className="absolute left-3 top-3 flex size-8 items-center justify-center rounded-full bg-black/60">
|
|
46
|
+
<Icon className="size-4 text-white" weight="fill" />
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface LockedPreviewProps {
|
|
53
|
+
thumbnail?: string
|
|
54
|
+
mimeType: string
|
|
55
|
+
LockIcon: React.ElementType
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const LockedPreview: React.FC<LockedPreviewProps> = (props) => {
|
|
59
|
+
const { thumbnail, mimeType, LockIcon } = props
|
|
60
|
+
return (
|
|
61
|
+
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
62
|
+
{thumbnail ? (
|
|
63
|
+
<img
|
|
64
|
+
src={thumbnail}
|
|
65
|
+
alt=""
|
|
66
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
67
|
+
/>
|
|
68
|
+
) : (
|
|
69
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
70
|
+
{renderTypeIcon(mimeType, {
|
|
71
|
+
className: 'size-12 text-black/20',
|
|
72
|
+
weight: 'regular',
|
|
73
|
+
})}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
<LockOverlay icon={LockIcon} />
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Per-type preview components ─────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
interface ImagePreviewProps {
|
|
84
|
+
source?: string
|
|
85
|
+
thumbnail?: string
|
|
86
|
+
mimeType: string
|
|
87
|
+
title?: string
|
|
88
|
+
paymentStatus?: PaymentStatus
|
|
89
|
+
isLocked: boolean
|
|
30
90
|
}
|
|
31
91
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
92
|
+
const ImagePreview: React.FC<ImagePreviewProps> = (props) => {
|
|
93
|
+
const { source, thumbnail, mimeType, title, paymentStatus, isLocked } = props
|
|
94
|
+
const [sourceReady, setSourceReady] = useState(false)
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
setSourceReady(false)
|
|
98
|
+
}, [source])
|
|
99
|
+
|
|
100
|
+
if (isLocked) {
|
|
37
101
|
return (
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
102
|
+
<LockedPreview
|
|
103
|
+
thumbnail={thumbnail}
|
|
104
|
+
mimeType={mimeType}
|
|
105
|
+
LockIcon={getLockIcon(paymentStatus)}
|
|
42
106
|
/>
|
|
43
107
|
)
|
|
44
108
|
}
|
|
45
109
|
|
|
46
110
|
return (
|
|
47
|
-
<div className="
|
|
48
|
-
|
|
111
|
+
<div className="relative overflow-hidden bg-black/5">
|
|
112
|
+
<img
|
|
113
|
+
src={source}
|
|
114
|
+
alt={title}
|
|
115
|
+
className={`block w-full transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}
|
|
116
|
+
onLoad={() => setSourceReady(true)}
|
|
117
|
+
/>
|
|
49
118
|
</div>
|
|
50
119
|
)
|
|
51
120
|
}
|
|
52
121
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
122
|
+
interface DocumentPreviewProps {
|
|
123
|
+
thumbnail?: string
|
|
124
|
+
mimeType: string
|
|
125
|
+
paymentStatus?: PaymentStatus
|
|
126
|
+
isLocked: boolean
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const DocumentPreview: React.FC<DocumentPreviewProps> = (props) => {
|
|
130
|
+
const { thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
131
|
+
return (
|
|
132
|
+
<div className="relative aspect-video overflow-hidden bg-black/5">
|
|
133
|
+
{thumbnail ? (
|
|
134
|
+
<img
|
|
135
|
+
src={thumbnail}
|
|
136
|
+
alt=""
|
|
137
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
138
|
+
/>
|
|
139
|
+
) : (
|
|
140
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
141
|
+
{renderTypeIcon(mimeType, {
|
|
142
|
+
className: 'size-12 text-black/20',
|
|
143
|
+
weight: 'regular',
|
|
144
|
+
})}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
{isLocked && <LockOverlay icon={getLockIcon(paymentStatus)} />}
|
|
57
148
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
149
|
+
)
|
|
150
|
+
}
|
|
60
151
|
|
|
61
|
-
interface
|
|
152
|
+
interface MediaPreviewProps {
|
|
153
|
+
source?: string
|
|
62
154
|
thumbnail?: string
|
|
63
155
|
mimeType: string
|
|
64
|
-
|
|
156
|
+
paymentStatus?: PaymentStatus
|
|
157
|
+
isLocked: boolean
|
|
65
158
|
}
|
|
66
159
|
|
|
67
|
-
const
|
|
68
|
-
thumbnail,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
160
|
+
const MediaPreview: React.FC<MediaPreviewProps> = (props) => {
|
|
161
|
+
const { source, thumbnail, mimeType, paymentStatus, isLocked } = props
|
|
162
|
+
if (isLocked) {
|
|
163
|
+
return (
|
|
164
|
+
<LockedPreview
|
|
165
|
+
thumbnail={thumbnail}
|
|
166
|
+
mimeType={mimeType}
|
|
167
|
+
LockIcon={getLockIcon(paymentStatus)}
|
|
168
|
+
/>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
return <MediaPlayer source={source!} mimeType={mimeType} poster={thumbnail} />
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ─── Actions ─────────────────────────────────────────────────────────────────
|
|
77
175
|
|
|
78
176
|
interface CardActionsProps {
|
|
79
177
|
isLocked: boolean
|
|
80
178
|
loading: boolean
|
|
81
179
|
paymentStatus?: PaymentStatus
|
|
82
180
|
source?: string
|
|
83
|
-
LockIcon: React.ElementType
|
|
84
181
|
onUnlock?: () => void
|
|
85
182
|
onDownload?: () => void
|
|
86
183
|
}
|
|
87
184
|
|
|
88
|
-
const CardActions: React.FC<CardActionsProps> = ({
|
|
89
|
-
isLocked,
|
|
90
|
-
|
|
91
|
-
paymentStatus
|
|
92
|
-
|
|
93
|
-
LockIcon,
|
|
94
|
-
onUnlock,
|
|
95
|
-
onDownload,
|
|
96
|
-
}) => {
|
|
185
|
+
const CardActions: React.FC<CardActionsProps> = (props) => {
|
|
186
|
+
const { isLocked, loading, paymentStatus, source, onUnlock, onDownload } =
|
|
187
|
+
props
|
|
188
|
+
const LockIcon = getLockIcon(paymentStatus)
|
|
189
|
+
|
|
97
190
|
if (isLocked && onUnlock) {
|
|
98
191
|
return (
|
|
99
192
|
<button
|
|
@@ -110,13 +203,18 @@ const CardActions: React.FC<CardActionsProps> = ({
|
|
|
110
203
|
</span>
|
|
111
204
|
) : (
|
|
112
205
|
<>
|
|
113
|
-
|
|
206
|
+
{paymentStatus === 'paid' ? (
|
|
207
|
+
<LockOpenIcon className="size-4" weight="fill" />
|
|
208
|
+
) : (
|
|
209
|
+
<LockIcon className="size-4" weight="fill" />
|
|
210
|
+
)}
|
|
114
211
|
{paymentStatus === 'paid' ? 'Open' : 'Unlock'}
|
|
115
212
|
</>
|
|
116
213
|
)}
|
|
117
214
|
</button>
|
|
118
215
|
)
|
|
119
216
|
}
|
|
217
|
+
|
|
120
218
|
if (!isLocked && onDownload && source) {
|
|
121
219
|
return (
|
|
122
220
|
<a
|
|
@@ -131,75 +229,32 @@ const CardActions: React.FC<CardActionsProps> = ({
|
|
|
131
229
|
</a>
|
|
132
230
|
)
|
|
133
231
|
}
|
|
134
|
-
return null
|
|
135
|
-
}
|
|
136
232
|
|
|
137
|
-
|
|
138
|
-
mimeType: string
|
|
139
|
-
detail?: string
|
|
140
|
-
paymentStatus?: PaymentStatus
|
|
141
|
-
amountText?: string
|
|
233
|
+
return null
|
|
142
234
|
}
|
|
143
235
|
|
|
144
|
-
|
|
145
|
-
mimeType,
|
|
146
|
-
detail,
|
|
147
|
-
paymentStatus,
|
|
148
|
-
amountText,
|
|
149
|
-
}) => {
|
|
150
|
-
return (
|
|
151
|
-
<div className="flex items-center gap-1">
|
|
152
|
-
{renderTypeIcon(mimeType, { className: 'size-5 shrink-0 text-black/55', weight: 'regular' })}
|
|
153
|
-
{detail && (
|
|
154
|
-
<span className="text-xs font-medium text-black/55">{detail}</span>
|
|
155
|
-
)}
|
|
156
|
-
{paymentStatus === 'paid' ? (
|
|
157
|
-
<>
|
|
158
|
-
<span className="text-xs font-medium text-black/55">•</span>
|
|
159
|
-
<span className="text-xs font-medium text-[#008236]">Purchased</span>
|
|
160
|
-
<CheckCircleIcon className="size-4 text-[#008236]" weight="bold" />
|
|
161
|
-
</>
|
|
162
|
-
) : (
|
|
163
|
-
amountText && (
|
|
164
|
-
<>
|
|
165
|
-
<span className="text-xs font-medium text-black/55">•</span>
|
|
166
|
-
<span className="text-xs font-medium text-black/55">
|
|
167
|
-
{amountText}
|
|
168
|
-
</span>
|
|
169
|
-
</>
|
|
170
|
-
)
|
|
171
|
-
)}
|
|
172
|
-
</div>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
236
|
+
// ─── Card shell ───────────────────────────────────────────────────────────────
|
|
175
237
|
|
|
176
|
-
const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
238
|
+
const VisitorCard: React.FC<VisitorCardProps> = (props) => {
|
|
239
|
+
const {
|
|
240
|
+
title,
|
|
241
|
+
amountText,
|
|
242
|
+
thumbnail,
|
|
243
|
+
source: sourceProp,
|
|
244
|
+
mimeType = 'application/octet-stream',
|
|
245
|
+
detail,
|
|
246
|
+
onUnlock,
|
|
247
|
+
onDownload,
|
|
248
|
+
paymentStatus,
|
|
249
|
+
} = props
|
|
188
250
|
const [source, setSource] = useState(sourceProp)
|
|
189
|
-
const [poster, setPoster] = useState(posterProp)
|
|
190
251
|
const [loading, setLoading] = useState(false)
|
|
191
|
-
const [sourceReady, setSourceReady] = useState(false)
|
|
192
252
|
|
|
193
253
|
useEffect(() => {
|
|
194
254
|
if (sourceProp !== undefined) setSource(sourceProp)
|
|
195
255
|
}, [sourceProp])
|
|
196
256
|
|
|
197
|
-
useEffect(() => {
|
|
198
|
-
if (posterProp !== undefined) setPoster(posterProp)
|
|
199
|
-
}, [posterProp])
|
|
200
|
-
|
|
201
257
|
const isLocked = source === undefined
|
|
202
|
-
const LockIcon = getLockIcon(paymentStatus)
|
|
203
258
|
const sourceType = getSourceType(mimeType)
|
|
204
259
|
|
|
205
260
|
const handleUnlock = async () => {
|
|
@@ -208,12 +263,8 @@ const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
|
208
263
|
try {
|
|
209
264
|
const result = await onUnlock()
|
|
210
265
|
setSource(result.source)
|
|
211
|
-
if (result.poster) setPoster(result.poster)
|
|
212
266
|
} catch (err) {
|
|
213
|
-
|
|
214
|
-
if (isDevBuild()) {
|
|
215
|
-
console.debug('[LockedAttachment] onUnlock failed', err)
|
|
216
|
-
}
|
|
267
|
+
if (isDevBuild()) console.debug('[LockedAttachment] onUnlock failed', err)
|
|
217
268
|
} finally {
|
|
218
269
|
setLoading(false)
|
|
219
270
|
}
|
|
@@ -221,67 +272,79 @@ const VisitorCard: React.FC<VisitorCardProps> = ({
|
|
|
221
272
|
|
|
222
273
|
let mediaPreview: React.ReactNode
|
|
223
274
|
if (sourceType === 'image') {
|
|
224
|
-
mediaPreview =
|
|
225
|
-
<
|
|
275
|
+
mediaPreview = (
|
|
276
|
+
<ImagePreview
|
|
277
|
+
source={source}
|
|
226
278
|
thumbnail={thumbnail}
|
|
227
279
|
mimeType={mimeType}
|
|
228
|
-
|
|
280
|
+
title={title}
|
|
281
|
+
paymentStatus={paymentStatus}
|
|
282
|
+
isLocked={isLocked}
|
|
229
283
|
/>
|
|
230
|
-
) : (
|
|
231
|
-
<div className="relative overflow-hidden bg-black/5">
|
|
232
|
-
<img
|
|
233
|
-
src={source}
|
|
234
|
-
alt={title}
|
|
235
|
-
className={`block w-full transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}
|
|
236
|
-
onLoad={() => setSourceReady(true)}
|
|
237
|
-
/>
|
|
238
|
-
</div>
|
|
239
284
|
)
|
|
240
285
|
} else if (sourceType === 'document') {
|
|
241
286
|
mediaPreview = (
|
|
242
|
-
<
|
|
243
|
-
<ThumbnailOrIcon
|
|
244
|
-
src={isLocked ? thumbnail : poster}
|
|
245
|
-
mimeType={mimeType}
|
|
246
|
-
/>
|
|
247
|
-
{isLocked && <LockOverlay icon={LockIcon} />}
|
|
248
|
-
</div>
|
|
249
|
-
)
|
|
250
|
-
} else {
|
|
251
|
-
mediaPreview = isLocked ? (
|
|
252
|
-
<LockedPreview
|
|
287
|
+
<DocumentPreview
|
|
253
288
|
thumbnail={thumbnail}
|
|
254
289
|
mimeType={mimeType}
|
|
255
|
-
|
|
290
|
+
paymentStatus={paymentStatus}
|
|
291
|
+
isLocked={isLocked}
|
|
256
292
|
/>
|
|
257
|
-
)
|
|
258
|
-
|
|
293
|
+
)
|
|
294
|
+
} else {
|
|
295
|
+
mediaPreview = (
|
|
296
|
+
<MediaPreview
|
|
259
297
|
source={source}
|
|
298
|
+
thumbnail={thumbnail}
|
|
260
299
|
mimeType={mimeType}
|
|
261
|
-
|
|
300
|
+
paymentStatus={paymentStatus}
|
|
301
|
+
isLocked={isLocked}
|
|
262
302
|
/>
|
|
263
303
|
)
|
|
264
304
|
}
|
|
265
305
|
|
|
266
306
|
return (
|
|
267
|
-
<div className="w-[280px] overflow-hidden rounded-3xl bg-white shadow-
|
|
307
|
+
<div className="w-[280px] select-none overflow-hidden rounded-3xl bg-white shadow-card">
|
|
268
308
|
{mediaPreview}
|
|
269
309
|
<div className="px-4 pb-3 pt-3">
|
|
270
310
|
<p className="mb-1.5 truncate text-base font-medium text-black">
|
|
271
311
|
{title}
|
|
272
312
|
</p>
|
|
273
|
-
<
|
|
274
|
-
mimeType
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
313
|
+
<div className="flex items-center gap-1">
|
|
314
|
+
{renderTypeIcon(mimeType, {
|
|
315
|
+
className: 'size-5 shrink-0 text-black/55',
|
|
316
|
+
weight: 'regular',
|
|
317
|
+
})}
|
|
318
|
+
{detail && (
|
|
319
|
+
<span className="text-xs font-medium text-black/55">{detail}</span>
|
|
320
|
+
)}
|
|
321
|
+
{paymentStatus === 'paid' ? (
|
|
322
|
+
<>
|
|
323
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
324
|
+
<span className="text-xs font-medium text-[#008236]">
|
|
325
|
+
Purchased
|
|
326
|
+
</span>
|
|
327
|
+
<CheckCircleIcon
|
|
328
|
+
className="size-4 text-[#008236]"
|
|
329
|
+
weight="bold"
|
|
330
|
+
/>
|
|
331
|
+
</>
|
|
332
|
+
) : (
|
|
333
|
+
amountText && (
|
|
334
|
+
<>
|
|
335
|
+
<span className="text-xs font-medium text-black/55">•</span>
|
|
336
|
+
<span className="text-xs font-medium text-black/55">
|
|
337
|
+
{amountText}
|
|
338
|
+
</span>
|
|
339
|
+
</>
|
|
340
|
+
)
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
279
343
|
<CardActions
|
|
280
344
|
isLocked={isLocked}
|
|
281
345
|
loading={loading}
|
|
282
346
|
paymentStatus={paymentStatus}
|
|
283
347
|
source={source}
|
|
284
|
-
LockIcon={LockIcon}
|
|
285
348
|
onUnlock={onUnlock ? handleUnlock : undefined}
|
|
286
349
|
onDownload={onDownload}
|
|
287
350
|
/>
|
|
@@ -2,11 +2,10 @@ import type { PaymentStatus } from '../../stream-custom-data'
|
|
|
2
2
|
|
|
3
3
|
/** Shared fields for creator and visitor locked-attachment cards (internal). */
|
|
4
4
|
export interface LockedAttachmentBaseProps {
|
|
5
|
+
title?: string
|
|
5
6
|
mimeType?: string
|
|
6
|
-
/**
|
|
7
|
+
/** Preview image. Video/image: pass blurred version. Audio/document: pass unblurred version. */
|
|
7
8
|
thumbnail?: string
|
|
8
|
-
/** Clean poster image passed to the media player. Falls back to thumbnail. */
|
|
9
|
-
poster?: string
|
|
10
9
|
/** Unlocked media URL. Undefined while locked or pending unlock. */
|
|
11
10
|
source?: string
|
|
12
11
|
detail?: string
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ImageIcon,
|
|
12
12
|
SpeakerHighIcon,
|
|
13
13
|
VideoCameraIcon,
|
|
14
|
+
IconProps,
|
|
14
15
|
} from '@phosphor-icons/react'
|
|
15
16
|
import React from 'react'
|
|
16
17
|
|
|
@@ -46,7 +47,7 @@ export function getTypeIcon(mimeType: string): React.ElementType {
|
|
|
46
47
|
/** Use instead of `<TypeIcon />` where TypeIcon = getTypeIcon(mime) to satisfy react-hooks/static-components. */
|
|
47
48
|
export function renderTypeIcon(
|
|
48
49
|
mimeType: string,
|
|
49
|
-
props:
|
|
50
|
+
props: IconProps
|
|
50
51
|
): React.ReactElement {
|
|
51
52
|
return React.createElement(getTypeIcon(mimeType), props)
|
|
52
53
|
}
|
|
@@ -39,12 +39,20 @@ describe('getDocumentIconType', () => {
|
|
|
39
39
|
|
|
40
40
|
it('returns doc for Word types', () => {
|
|
41
41
|
expect(getDocumentIconType('application/msword')).toBe('doc')
|
|
42
|
-
expect(
|
|
42
|
+
expect(
|
|
43
|
+
getDocumentIconType(
|
|
44
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
45
|
+
)
|
|
46
|
+
).toBe('doc')
|
|
43
47
|
})
|
|
44
48
|
|
|
45
49
|
it('returns xls for Excel types', () => {
|
|
46
50
|
expect(getDocumentIconType('application/vnd.ms-excel')).toBe('xls')
|
|
47
|
-
expect(
|
|
51
|
+
expect(
|
|
52
|
+
getDocumentIconType(
|
|
53
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
54
|
+
)
|
|
55
|
+
).toBe('xls')
|
|
48
56
|
})
|
|
49
57
|
|
|
50
58
|
it('returns csv for text/csv', () => {
|
|
@@ -53,7 +61,11 @@ describe('getDocumentIconType', () => {
|
|
|
53
61
|
|
|
54
62
|
it('returns ppt for PowerPoint types', () => {
|
|
55
63
|
expect(getDocumentIconType('application/vnd.ms-powerpoint')).toBe('ppt')
|
|
56
|
-
expect(
|
|
64
|
+
expect(
|
|
65
|
+
getDocumentIconType(
|
|
66
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
67
|
+
)
|
|
68
|
+
).toBe('ppt')
|
|
57
69
|
})
|
|
58
70
|
|
|
59
71
|
it('returns zip for archive types', () => {
|
|
@@ -79,11 +91,17 @@ describe('getDocumentIconType', () => {
|
|
|
79
91
|
})
|
|
80
92
|
|
|
81
93
|
it('returns xls for macro-enabled Excel', () => {
|
|
82
|
-
expect(
|
|
94
|
+
expect(
|
|
95
|
+
getDocumentIconType('application/vnd.ms-excel.sheet.macroEnabled.12')
|
|
96
|
+
).toBe('xls')
|
|
83
97
|
})
|
|
84
98
|
|
|
85
99
|
it('returns ppt for macro-enabled PowerPoint', () => {
|
|
86
|
-
expect(
|
|
100
|
+
expect(
|
|
101
|
+
getDocumentIconType(
|
|
102
|
+
'application/vnd.ms-powerpoint.presentation.macroEnabled.12'
|
|
103
|
+
)
|
|
104
|
+
).toBe('ppt')
|
|
87
105
|
})
|
|
88
106
|
|
|
89
107
|
it('returns generic for unknown types', () => {
|
|
@@ -91,7 +109,11 @@ describe('getDocumentIconType', () => {
|
|
|
91
109
|
expect(getDocumentIconType('application/json')).toBe('generic')
|
|
92
110
|
expect(getDocumentIconType('text/html')).toBe('generic')
|
|
93
111
|
expect(getDocumentIconType('application/vnd.rar')).toBe('generic')
|
|
94
|
-
expect(getDocumentIconType('application/vnd.oasis.opendocument.text')).toBe(
|
|
95
|
-
|
|
112
|
+
expect(getDocumentIconType('application/vnd.oasis.opendocument.text')).toBe(
|
|
113
|
+
'generic'
|
|
114
|
+
)
|
|
115
|
+
expect(
|
|
116
|
+
getDocumentIconType('application/vnd.oasis.opendocument.spreadsheet')
|
|
117
|
+
).toBe('generic')
|
|
96
118
|
})
|
|
97
119
|
})
|
|
@@ -30,6 +30,8 @@ export function getSourceType(mimeType: string): AttachmentSourceType {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function getDocumentIconType(mimeType: string): DocumentIconType {
|
|
33
|
-
const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>
|
|
33
|
+
const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>
|
|
34
|
+
pattern.test(mimeType)
|
|
35
|
+
)
|
|
34
36
|
return match ? match[1] : 'generic'
|
|
35
37
|
}
|