@linktr.ee/messaging-react 1.26.1 → 1.27.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/Creator-B6M8dB0U.js +87 -0
- package/dist/Creator-B6M8dB0U.js.map +1 -0
- package/dist/MediaPlayer-DsjlYGGH.js +539 -0
- package/dist/MediaPlayer-DsjlYGGH.js.map +1 -0
- package/dist/Preview-DqAv16NS.js +87 -0
- package/dist/Preview-DqAv16NS.js.map +1 -0
- package/dist/Visitor-CpmFZRGO.js +175 -0
- package/dist/Visitor-CpmFZRGO.js.map +1 -0
- package/dist/dash.all.min-Duv4lvGS.js +18858 -0
- package/dist/dash.all.min-Duv4lvGS.js.map +1 -0
- package/dist/hls-Bogc7CBn.js +21710 -0
- package/dist/hls-Bogc7CBn.js.map +1 -0
- package/dist/index-Da-xN4Yq.js +16142 -0
- package/dist/index-Da-xN4Yq.js.map +1 -0
- package/dist/index-Dj9rqWcU.js +69 -0
- package/dist/index-Dj9rqWcU.js.map +1 -0
- package/dist/index.d.ts +73 -10
- package/dist/index.js +979 -934
- package/dist/index.js.map +1 -1
- package/dist/mixin-B6jYfIcp.js +808 -0
- package/dist/mixin-B6jYfIcp.js.map +1 -0
- package/dist/react-BxlQMOfz.js +419 -0
- package/dist/react-BxlQMOfz.js.map +1 -0
- package/dist/react-COAP-MIW.js +377 -0
- package/dist/react-COAP-MIW.js.map +1 -0
- package/dist/react-Cn4WlMcl.js +3108 -0
- package/dist/react-Cn4WlMcl.js.map +1 -0
- package/dist/react-CwTJArKY.js +459 -0
- package/dist/react-CwTJArKY.js.map +1 -0
- package/dist/react-DkfS_atT.js +373 -0
- package/dist/react-DkfS_atT.js.map +1 -0
- package/dist/react-Pea5fum1.js +286 -0
- package/dist/react-Pea5fum1.js.map +1 -0
- package/dist/react-RiBbsUDd.js +534 -0
- package/dist/react-RiBbsUDd.js.map +1 -0
- package/dist/react-dS1WBxxz.js +238 -0
- package/dist/react-dS1WBxxz.js.map +1 -0
- package/package.json +2 -1
- package/src/components/ChannelView.tsx +12 -2
- package/src/components/CustomMessage/CustomMessage.stories.tsx +173 -41
- package/src/components/CustomMessage/MessageTag.tsx +5 -0
- package/src/components/CustomMessage/index.tsx +43 -4
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +249 -0
- package/src/components/LockedAttachment/components/Creator.tsx +171 -0
- package/src/components/LockedAttachment/components/MediaPlayer.tsx +299 -0
- package/src/components/LockedAttachment/components/Visitor.tsx +293 -0
- package/src/components/LockedAttachment/index.tsx +39 -0
- package/src/components/LockedAttachment/types.ts +18 -0
- package/src/components/LockedAttachment/utils/icons.ts +52 -0
- package/src/components/LockedAttachment/utils/mimeType.test.ts +97 -0
- package/src/components/LockedAttachment/utils/mimeType.ts +35 -0
- package/src/components/ParticipantPicker/index.tsx +8 -1
- package/src/hooks/useParticipants.ts +3 -2
- package/src/index.ts +4 -0
- package/src/stories/decorators/storyUser.tsx +37 -0
- package/src/stream-custom-data.ts +9 -3
- package/src/types.ts +21 -1
- package/src/utils/isDevBuild.ts +10 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getDocumentIconType, getSourceType } from './mimeType'
|
|
2
|
+
|
|
3
|
+
describe('getSourceType', () => {
|
|
4
|
+
it('returns video for video/* types', () => {
|
|
5
|
+
expect(getSourceType('video/mp4')).toBe('video')
|
|
6
|
+
expect(getSourceType('video/webm')).toBe('video')
|
|
7
|
+
expect(getSourceType('video/quicktime')).toBe('video')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('returns audio for audio/* types', () => {
|
|
11
|
+
expect(getSourceType('audio/mpeg')).toBe('audio')
|
|
12
|
+
expect(getSourceType('audio/mp4')).toBe('audio')
|
|
13
|
+
expect(getSourceType('audio/ogg')).toBe('audio')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns image for image/* types', () => {
|
|
17
|
+
expect(getSourceType('image/jpeg')).toBe('image')
|
|
18
|
+
expect(getSourceType('image/png')).toBe('image')
|
|
19
|
+
expect(getSourceType('image/webp')).toBe('image')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('returns document for application/* types', () => {
|
|
23
|
+
expect(getSourceType('application/pdf')).toBe('document')
|
|
24
|
+
expect(getSourceType('application/msword')).toBe('document')
|
|
25
|
+
expect(getSourceType('application/zip')).toBe('document')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns document for text/* types', () => {
|
|
29
|
+
expect(getSourceType('text/plain')).toBe('document')
|
|
30
|
+
expect(getSourceType('text/csv')).toBe('document')
|
|
31
|
+
expect(getSourceType('text/markdown')).toBe('document')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('getDocumentIconType', () => {
|
|
36
|
+
it('returns pdf for application/pdf', () => {
|
|
37
|
+
expect(getDocumentIconType('application/pdf')).toBe('pdf')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('returns doc for Word types', () => {
|
|
41
|
+
expect(getDocumentIconType('application/msword')).toBe('doc')
|
|
42
|
+
expect(getDocumentIconType('application/vnd.openxmlformats-officedocument.wordprocessingml.document')).toBe('doc')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('returns xls for Excel types', () => {
|
|
46
|
+
expect(getDocumentIconType('application/vnd.ms-excel')).toBe('xls')
|
|
47
|
+
expect(getDocumentIconType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')).toBe('xls')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('returns csv for text/csv', () => {
|
|
51
|
+
expect(getDocumentIconType('text/csv')).toBe('csv')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('returns ppt for PowerPoint types', () => {
|
|
55
|
+
expect(getDocumentIconType('application/vnd.ms-powerpoint')).toBe('ppt')
|
|
56
|
+
expect(getDocumentIconType('application/vnd.openxmlformats-officedocument.presentationml.presentation')).toBe('ppt')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('returns zip for archive types', () => {
|
|
60
|
+
expect(getDocumentIconType('application/zip')).toBe('zip')
|
|
61
|
+
expect(getDocumentIconType('application/x-zip-compressed')).toBe('zip')
|
|
62
|
+
expect(getDocumentIconType('application/gzip')).toBe('zip')
|
|
63
|
+
expect(getDocumentIconType('application/x-gzip')).toBe('zip')
|
|
64
|
+
expect(getDocumentIconType('application/x-rar-compressed')).toBe('zip')
|
|
65
|
+
expect(getDocumentIconType('application/x-7z-compressed')).toBe('zip')
|
|
66
|
+
expect(getDocumentIconType('application/x-tar')).toBe('zip')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('returns text for plain text and rtf', () => {
|
|
70
|
+
expect(getDocumentIconType('text/plain')).toBe('text')
|
|
71
|
+
expect(getDocumentIconType('text/rtf')).toBe('text')
|
|
72
|
+
expect(getDocumentIconType('application/rtf')).toBe('text')
|
|
73
|
+
expect(getDocumentIconType('application/x-rtf')).toBe('text')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('returns markdown for markdown types', () => {
|
|
77
|
+
expect(getDocumentIconType('text/markdown')).toBe('markdown')
|
|
78
|
+
expect(getDocumentIconType('text/x-markdown')).toBe('markdown')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('returns xls for macro-enabled Excel', () => {
|
|
82
|
+
expect(getDocumentIconType('application/vnd.ms-excel.sheet.macroEnabled.12')).toBe('xls')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('returns ppt for macro-enabled PowerPoint', () => {
|
|
86
|
+
expect(getDocumentIconType('application/vnd.ms-powerpoint.presentation.macroEnabled.12')).toBe('ppt')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('returns generic for unknown types', () => {
|
|
90
|
+
expect(getDocumentIconType('application/octet-stream')).toBe('generic')
|
|
91
|
+
expect(getDocumentIconType('application/json')).toBe('generic')
|
|
92
|
+
expect(getDocumentIconType('text/html')).toBe('generic')
|
|
93
|
+
expect(getDocumentIconType('application/vnd.rar')).toBe('generic')
|
|
94
|
+
expect(getDocumentIconType('application/vnd.oasis.opendocument.text')).toBe('generic')
|
|
95
|
+
expect(getDocumentIconType('application/vnd.oasis.opendocument.spreadsheet')).toBe('generic')
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type AttachmentSourceType = 'image' | 'audio' | 'video' | 'document'
|
|
2
|
+
|
|
3
|
+
export type DocumentIconType =
|
|
4
|
+
| 'pdf'
|
|
5
|
+
| 'doc'
|
|
6
|
+
| 'xls'
|
|
7
|
+
| 'csv'
|
|
8
|
+
| 'ppt'
|
|
9
|
+
| 'zip'
|
|
10
|
+
| 'text'
|
|
11
|
+
| 'markdown'
|
|
12
|
+
| 'generic'
|
|
13
|
+
|
|
14
|
+
const DOCUMENT_ICON_PATTERNS: Array<[RegExp, DocumentIconType]> = [
|
|
15
|
+
[/pdf/, 'pdf'],
|
|
16
|
+
[/wordprocessingml|msword|\.doc/, 'doc'],
|
|
17
|
+
[/spreadsheetml|ms-excel|\.xls/, 'xls'],
|
|
18
|
+
[/csv/, 'csv'],
|
|
19
|
+
[/presentationml|ms-powerpoint|\.ppt/, 'ppt'],
|
|
20
|
+
[/zip|x-rar|x-7z|x-tar|x-gzip/, 'zip'],
|
|
21
|
+
[/plain|rtf/, 'text'],
|
|
22
|
+
[/markdown/, 'markdown'],
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
export function getSourceType(mimeType: string): AttachmentSourceType {
|
|
26
|
+
if (mimeType.startsWith('video/')) return 'video'
|
|
27
|
+
if (mimeType.startsWith('audio/')) return 'audio'
|
|
28
|
+
if (mimeType.startsWith('image/')) return 'image'
|
|
29
|
+
return 'document'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getDocumentIconType(mimeType: string): DocumentIconType {
|
|
33
|
+
const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) => pattern.test(mimeType))
|
|
34
|
+
return match ? match[1] : 'generic'
|
|
35
|
+
}
|
|
@@ -32,6 +32,12 @@ export const ParticipantPicker: React.FC<ParticipantPickerProps> = ({
|
|
|
32
32
|
// Track if we've already loaded participants to prevent repeated loading
|
|
33
33
|
const loadedRef = useRef(false)
|
|
34
34
|
|
|
35
|
+
// New source instance should be allowed to load again
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
loadedRef.current = false
|
|
38
|
+
}, [participantSource])
|
|
39
|
+
|
|
40
|
+
/* eslint-disable react-hooks/exhaustive-deps -- syncs with participantSource + debug; inner async uses participantSource.loadParticipants */
|
|
35
41
|
// Load participants initially - wait for participantSource to finish loading first
|
|
36
42
|
useEffect(() => {
|
|
37
43
|
// Wait for the participantSource to finish loading before we try to load participants
|
|
@@ -78,7 +84,8 @@ export const ParticipantPicker: React.FC<ParticipantPickerProps> = ({
|
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
loadInitialParticipants()
|
|
81
|
-
}, [participantSource
|
|
87
|
+
}, [participantSource, debug])
|
|
88
|
+
/* eslint-enable react-hooks/exhaustive-deps */
|
|
82
89
|
|
|
83
90
|
// Filter participants by search query and existing participants
|
|
84
91
|
const availableParticipants = participants
|
|
@@ -74,10 +74,11 @@ export const useParticipants = (
|
|
|
74
74
|
loadParticipants(true);
|
|
75
75
|
}, [loadParticipants]);
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
/* eslint-disable react-hooks/exhaustive-deps -- initial load only; `loadParticipants` changes whenever `loading` flips */
|
|
78
78
|
useEffect(() => {
|
|
79
79
|
loadParticipants(true);
|
|
80
|
-
}, [participantSource.loadParticipants]);
|
|
80
|
+
}, [participantSource.loadParticipants]);
|
|
81
|
+
/* eslint-enable react-hooks/exhaustive-deps */
|
|
81
82
|
|
|
82
83
|
return {
|
|
83
84
|
participants,
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { MessagingShell } from './components/MessagingShell'
|
|
|
6
6
|
export { ChannelList } from './components/ChannelList'
|
|
7
7
|
export { ChannelView } from './components/ChannelView'
|
|
8
8
|
export { default as ActionButton } from './components/ActionButton'
|
|
9
|
+
export { default as LockedAttachment } from './components/LockedAttachment'
|
|
9
10
|
export { ParticipantPicker } from './components/ParticipantPicker'
|
|
10
11
|
export { Avatar } from './components/Avatar'
|
|
11
12
|
export { FaqList } from './components/FaqList'
|
|
@@ -34,10 +35,13 @@ export type {
|
|
|
34
35
|
MessagingCapabilities,
|
|
35
36
|
ParticipantSource,
|
|
36
37
|
Participant,
|
|
38
|
+
LockedAttachmentSource,
|
|
37
39
|
} from './types'
|
|
38
40
|
export type { MessageMetadata } from './stream-custom-data'
|
|
39
41
|
export type { AvatarProps } from './components/Avatar'
|
|
40
42
|
export type { ActionButtonProps } from './components/ActionButton'
|
|
43
|
+
export type { LockedAttachmentProps } from './components/LockedAttachment'
|
|
44
|
+
export type { AttachmentSourceType } from './components/LockedAttachment/utils/mimeType'
|
|
41
45
|
export type { Faq, FaqListProps } from './components/FaqList'
|
|
42
46
|
export type { FaqListItemProps } from './components/FaqList/FaqListItem'
|
|
43
47
|
export type { VoteSelection } from './hooks/useMessageVote'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ArgTypes } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
export const storyUsers = {
|
|
4
|
+
creator: {
|
|
5
|
+
id: 'creator-user',
|
|
6
|
+
name: 'Creator',
|
|
7
|
+
image: 'https://i.pravatar.cc/150?img=1',
|
|
8
|
+
},
|
|
9
|
+
visitor: {
|
|
10
|
+
id: 'visitor-user',
|
|
11
|
+
name: 'Visitor',
|
|
12
|
+
image: 'https://i.pravatar.cc/150?img=5',
|
|
13
|
+
},
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
export type StoryRole = keyof typeof storyUsers
|
|
17
|
+
|
|
18
|
+
export type StoryUser = typeof storyUsers[keyof typeof storyUsers]
|
|
19
|
+
|
|
20
|
+
const DEFAULT_ROLE: StoryRole = 'creator'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Storybook control: pick viewer identity; `args.currentUser` is the full user object.
|
|
24
|
+
* Use with `key={currentUser.id}` on the story root so Stream client remounts when it changes.
|
|
25
|
+
*/
|
|
26
|
+
export const currentUserArgType: ArgTypes<{ currentUser?: StoryUser }> = {
|
|
27
|
+
currentUser: {
|
|
28
|
+
name: 'participant',
|
|
29
|
+
control: { type: 'inline-radio' as const },
|
|
30
|
+
options: ['creator', 'visitor'] as StoryRole[],
|
|
31
|
+
mapping: {
|
|
32
|
+
creator: storyUsers.creator,
|
|
33
|
+
visitor: storyUsers.visitor,
|
|
34
|
+
},
|
|
35
|
+
table: { defaultValue: { summary: DEFAULT_ROLE } },
|
|
36
|
+
},
|
|
37
|
+
} satisfies ArgTypes<{ currentUser?: StoryUser }>
|
|
@@ -24,6 +24,7 @@ export type MessageCustomType =
|
|
|
24
24
|
| 'MESSAGE_TIP'
|
|
25
25
|
| 'MESSAGE_PAID'
|
|
26
26
|
| 'MESSAGE_CHATBOT'
|
|
27
|
+
| 'MESSAGE_ATTACHMENT'
|
|
27
28
|
| AgeSafetySystemType
|
|
28
29
|
| DmAgentSystemType
|
|
29
30
|
|
|
@@ -31,12 +32,17 @@ export type MessageCustomType =
|
|
|
31
32
|
* Message metadata for paid messaging and chatbot flows.
|
|
32
33
|
* Used to identify message types and payment status.
|
|
33
34
|
*/
|
|
35
|
+
export type PaymentStatus = 'pending' | 'paid' | 'failed' | 'refunded'
|
|
36
|
+
|
|
34
37
|
export interface MessageMetadata {
|
|
35
38
|
custom_type?: MessageCustomType
|
|
36
|
-
amount_text?: string
|
|
37
|
-
payment_status?: string
|
|
38
|
-
payment_intent_id?: string
|
|
39
39
|
listing_id?: string
|
|
40
|
+
amount_text?: string
|
|
41
|
+
payment_status?: PaymentStatus
|
|
42
|
+
attachment_title?: string
|
|
43
|
+
attachment_mime_type?: string
|
|
44
|
+
attachment_thumbnail?: string
|
|
45
|
+
attachment_detail?: string
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
declare module 'stream-chat' {
|
package/src/types.ts
CHANGED
|
@@ -117,6 +117,11 @@ export interface ChannelListProps {
|
|
|
117
117
|
) => React.ReactNode
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export interface LockedAttachmentSource {
|
|
121
|
+
source: string
|
|
122
|
+
poster?: string
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
/**
|
|
121
126
|
* ChannelView component props
|
|
122
127
|
*/
|
|
@@ -163,7 +168,7 @@ export interface ChannelViewProps {
|
|
|
163
168
|
* @example
|
|
164
169
|
* messageMetadata={{ custom_type: 'MESSAGE_PAID', listing_id: '...' }}
|
|
165
170
|
*/
|
|
166
|
-
messageMetadata?:
|
|
171
|
+
messageMetadata?: Partial<MessageMetadata>
|
|
167
172
|
|
|
168
173
|
/**
|
|
169
174
|
* Callback fired after a message is successfully sent.
|
|
@@ -231,6 +236,19 @@ export interface ChannelViewProps {
|
|
|
231
236
|
messageNode: React.ReactElement,
|
|
232
237
|
message: LocalMessage
|
|
233
238
|
) => React.ReactNode
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Called when the visitor clicks Unlock on a locked attachment message.
|
|
242
|
+
* Receives the message and channel. Show checkout, confirm payment, fetch
|
|
243
|
+
* the unlocked URL. `attachment_source` must NOT be stored on the Stream message metadata.
|
|
244
|
+
* The card shows a loading state for the full duration of the promise.
|
|
245
|
+
*/
|
|
246
|
+
onAttachmentUnlock?: (message: LocalMessage, channel: Channel) => Promise<LockedAttachmentSource>
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Called when the visitor clicks Download on an unlocked attachment message.
|
|
250
|
+
*/
|
|
251
|
+
onAttachmentDownload?: (message: LocalMessage, channel: Channel) => void
|
|
234
252
|
}
|
|
235
253
|
|
|
236
254
|
/**
|
|
@@ -254,6 +272,8 @@ export type ChannelViewPassthroughProps = Pick<
|
|
|
254
272
|
| 'customProfileContent'
|
|
255
273
|
| 'customChannelActions'
|
|
256
274
|
| 'renderMessage'
|
|
275
|
+
| 'onAttachmentUnlock'
|
|
276
|
+
| 'onAttachmentDownload'
|
|
257
277
|
>
|
|
258
278
|
|
|
259
279
|
/**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True in Vite dev builds (`import.meta.env.DEV`). Uses a type assertion so
|
|
3
|
+
* `tsc` does not rely on ambient `ImportMeta` merging (vite/client).
|
|
4
|
+
*/
|
|
5
|
+
export function isDevBuild(): boolean {
|
|
6
|
+
return (
|
|
7
|
+
typeof import.meta !== 'undefined' &&
|
|
8
|
+
(import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV === true
|
|
9
|
+
)
|
|
10
|
+
}
|