@linktr.ee/messaging-react 3.38.2 → 4.0.0-rc-1786383199
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-2Ms7lBDO.js → Card-B3XsV8Td.js} +2 -2
- package/dist/{Card-2Ms7lBDO.js.map → Card-B3XsV8Td.js.map} +1 -1
- package/dist/{Card-KwVE3M5j.cjs → Card-DkQdjw4V.cjs} +2 -2
- package/dist/{Card-KwVE3M5j.cjs.map → Card-DkQdjw4V.cjs.map} +1 -1
- package/dist/index-B1u9TBSD.cjs +3 -0
- package/dist/index-B1u9TBSD.cjs.map +1 -0
- package/dist/{index-i03XGFsr.js → index-NfAAanC-.js} +372 -371
- package/dist/index-NfAAanC-.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +18 -13
- package/dist/index.js +1 -1
- package/dist/testing.d.ts +5 -2
- package/package.json +2 -2
- package/src/components/{ChannelHeaderRedesign.tsx → ChannelHeader.tsx} +4 -2
- package/src/components/{ChannelView.broadcast-name.integration.test.tsx → ChannelView.broadcast-resolution.integration.test.tsx} +11 -9
- package/src/components/ChannelView.tsx +15 -15
- package/src/components/CustomMessage/BroadcastContext.test.ts +52 -0
- package/src/components/CustomMessage/BroadcastContext.ts +101 -0
- package/src/components/CustomMessage/CustomMessage.stories.tsx +29 -21
- package/src/components/CustomMessage/CustomMessage.test.tsx +49 -20
- package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +37 -17
- package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +17 -24
- package/src/components/CustomMessage/index.tsx +18 -13
- package/src/components/MessagingShell/index.tsx +2 -2
- package/src/index.ts +2 -1
- package/src/stream-custom-data.ts +5 -2
- package/src/types.ts +13 -9
- package/dist/index-DzOGH7lG.cjs +0 -3
- package/dist/index-DzOGH7lG.cjs.map +0 -1
- package/dist/index-i03XGFsr.js.map +0 -1
- package/src/components/CustomMessage/BroadcastNameContext.test.ts +0 -43
- package/src/components/CustomMessage/BroadcastNameContext.ts +0 -95
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-B1u9TBSD.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageBubble=e.MessageBubble;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.RichCard=e.RichCard;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.optimizeMessagingAttachmentUrl=e.optimizeMessagingAttachmentUrl;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessaging=e.useMessaging;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -85,12 +85,9 @@ export declare interface AvatarProps {
|
|
|
85
85
|
dmAgentEnabled?: boolean;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export declare type BroadcastClickHandler = (broadcast:
|
|
89
|
-
id: string;
|
|
90
|
-
name?: string;
|
|
91
|
-
}) => void;
|
|
88
|
+
export declare type BroadcastClickHandler = (broadcast: ResolvedBroadcast) => void;
|
|
92
89
|
|
|
93
|
-
export declare type
|
|
90
|
+
export declare type BroadcastResolver = (outboundId: string) => ResolvedBroadcast | undefined;
|
|
94
91
|
|
|
95
92
|
/**
|
|
96
93
|
* Build the meta line shown under the title in compact document / file
|
|
@@ -215,7 +212,7 @@ export declare const ChannelView: default_2.NamedExoticComponent<ChannelViewProp
|
|
|
215
212
|
* Props that MessagingShell passes through to ChannelView.
|
|
216
213
|
* ChannelViewProps is the source of truth for these props.
|
|
217
214
|
*/
|
|
218
|
-
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'viewerLanguage' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'onBroadcastClick' | '
|
|
215
|
+
declare type ChannelViewPassthroughProps = Pick<ChannelViewProps, 'renderMessageInputActions' | 'renderConversationFooter' | 'CustomChannelEmptyState' | 'onBlockParticipantClick' | 'onReportParticipantClick' | 'dmAgentEnabled' | 'onMessageSent' | 'viewerLanguage' | 'renderChannelBanner' | 'customChannelActions' | 'renderChannelActions' | 'onParticipantNameClick' | 'renderMessage' | 'onMessageLinkClick' | 'onBroadcastClick' | 'resolveBroadcast' | 'showChannelInfo' | 'composerInput'>;
|
|
219
216
|
|
|
220
217
|
/**
|
|
221
218
|
* ChannelView component props
|
|
@@ -400,12 +397,12 @@ export declare interface ChannelViewProps {
|
|
|
400
397
|
*/
|
|
401
398
|
onBroadcastClick?: BroadcastClickHandler;
|
|
402
399
|
/**
|
|
403
|
-
* Resolves
|
|
404
|
-
* provide a new resolver reference when a returned
|
|
405
|
-
* ChannelView re-renders and publishes the new value to memoized
|
|
406
|
-
*
|
|
400
|
+
* Resolves an outbound record id to the current broadcast id and name. The
|
|
401
|
+
* host must provide a new resolver reference when a returned broadcast
|
|
402
|
+
* changes so ChannelView re-renders and publishes the new value to memoized
|
|
403
|
+
* rows. Missing records stay unresolved and render no label.
|
|
407
404
|
*/
|
|
408
|
-
|
|
405
|
+
resolveBroadcast?: BroadcastResolver;
|
|
409
406
|
/**
|
|
410
407
|
* Passed to Stream `Channel` as `SendButton`. Required for hosts that replace
|
|
411
408
|
* the send control: `Channel` merges this into `ComponentContext` and an
|
|
@@ -1409,8 +1406,11 @@ export declare interface MessageMetadata {
|
|
|
1409
1406
|
* `custom_type` — it identifies the send context, not the message kind. (MES-1266)
|
|
1410
1407
|
*/
|
|
1411
1408
|
sent_from?: 'stack_detail';
|
|
1412
|
-
|
|
1413
|
-
|
|
1409
|
+
/**
|
|
1410
|
+
* Opaque Outbound Record id on automation broadcast messages. Hosts resolve
|
|
1411
|
+
* this server-side to the current automation id and name.
|
|
1412
|
+
*/
|
|
1413
|
+
outbound_id?: string;
|
|
1414
1414
|
}
|
|
1415
1415
|
|
|
1416
1416
|
declare type MessageWithI18n = Pick<LocalMessage, 'text'> & {
|
|
@@ -1635,6 +1635,11 @@ export declare interface ReceivedBubbleProps extends MessageBubbleBaseProps {
|
|
|
1635
1635
|
*/
|
|
1636
1636
|
export declare function resolveConversationParticipant(members: ChannelMembers | undefined, currentUserId: string | undefined, channelType: string | undefined): ChannelMember | undefined;
|
|
1637
1637
|
|
|
1638
|
+
export declare type ResolvedBroadcast = {
|
|
1639
|
+
id: string;
|
|
1640
|
+
name?: string;
|
|
1641
|
+
};
|
|
1642
|
+
|
|
1638
1643
|
export declare function resolveLinkAttachment(message: LocalMessage): Attachment | undefined;
|
|
1639
1644
|
|
|
1640
1645
|
export declare function resolveMediaFromMessage(message: LocalMessage): MediaMessageResolved | null;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, b as t, C as i, c as n, d as o, e as m, F as r, f as l, L as g, h as c, M, i as h, j as L, k as u, l as d, R as p, m as C, n as v, p as A, q as k, s as F, t as b, u as P, v as f, w as x, o as y, x as z, y as S, z as q, B, D, E, G as R } from "./index-
|
|
1
|
+
import { a as e, b as t, C as i, c as n, d as o, e as m, F as r, f as l, L as g, h as c, M, i as h, j as L, k as u, l as d, R as p, m as C, n as v, p as A, q as k, s as F, t as b, u as P, v as f, w as x, o as y, x as z, y as S, z as q, B, D, E, G as R } from "./index-NfAAanC-.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
package/dist/testing.d.ts
CHANGED
|
@@ -56,8 +56,11 @@ export declare interface MessageMetadata {
|
|
|
56
56
|
* `custom_type` — it identifies the send context, not the message kind. (MES-1266)
|
|
57
57
|
*/
|
|
58
58
|
sent_from?: 'stack_detail';
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Opaque Outbound Record id on automation broadcast messages. Hosts resolve
|
|
61
|
+
* this server-side to the current automation id and name.
|
|
62
|
+
*/
|
|
63
|
+
outbound_id?: string;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc-1786383199",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"chromatic:local": "yarn storybook:build && chromatic"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@linktr.ee/messaging-core": "
|
|
53
|
+
"@linktr.ee/messaging-core": "2.4.0-rc-1786383199",
|
|
54
54
|
"@linktr.ee/messaging-taxonomy": "^0.9.0",
|
|
55
55
|
"@phosphor-icons/react": "^2.1.10"
|
|
56
56
|
},
|
|
@@ -28,7 +28,7 @@ const isCustomerPaidTag = (value: unknown) =>
|
|
|
28
28
|
.replace(/[\s-]+/g, '_')
|
|
29
29
|
.toUpperCase() === CUSTOMER_PAID_TAG
|
|
30
30
|
|
|
31
|
-
interface
|
|
31
|
+
interface ChannelHeaderProps {
|
|
32
32
|
className?: string
|
|
33
33
|
onBack?: () => void
|
|
34
34
|
showBackButton: boolean
|
|
@@ -47,7 +47,7 @@ interface ChannelHeaderRedesignProps {
|
|
|
47
47
|
onParticipantNameClick?: () => void
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
const ChannelHeader: React.FC<ChannelHeaderProps> = ({
|
|
51
51
|
className,
|
|
52
52
|
onBack,
|
|
53
53
|
showBackButton,
|
|
@@ -241,3 +241,5 @@ export const ChannelHeaderRedesign: React.FC<ChannelHeaderRedesignProps> = ({
|
|
|
241
241
|
</div>
|
|
242
242
|
)
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
export default ChannelHeader
|
|
@@ -14,8 +14,7 @@ const message = {
|
|
|
14
14
|
status: 'received',
|
|
15
15
|
user: { id: 'linker-1', name: 'Linker' },
|
|
16
16
|
metadata: {
|
|
17
|
-
|
|
18
|
-
broadcast_name: 'July 9th Class attendees',
|
|
17
|
+
outbound_id: 'outbound-1',
|
|
19
18
|
},
|
|
20
19
|
attachments: [],
|
|
21
20
|
latest_reactions: [],
|
|
@@ -54,11 +53,14 @@ const createChannel = () => {
|
|
|
54
53
|
return { channel, client }
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
describe('ChannelView broadcast
|
|
58
|
-
it('updates an existing Stream message row when
|
|
56
|
+
describe('ChannelView broadcast resolution', () => {
|
|
57
|
+
it('updates an existing Stream message row when its outbound lookup resolves', async () => {
|
|
59
58
|
const { channel, client } = createChannel()
|
|
60
59
|
const firstResolver = () => undefined
|
|
61
|
-
const secondResolver = () =>
|
|
60
|
+
const secondResolver = () => ({
|
|
61
|
+
id: 'broadcast-1',
|
|
62
|
+
name: 'July 9th Class absentee',
|
|
63
|
+
})
|
|
62
64
|
const mounts = vi.fn()
|
|
63
65
|
const unmounts = vi.fn()
|
|
64
66
|
|
|
@@ -80,7 +82,7 @@ describe('ChannelView broadcast-name resolution', () => {
|
|
|
80
82
|
<Channel channel={channel as StreamChannel}>
|
|
81
83
|
<ChannelView
|
|
82
84
|
channel={channel as StreamChannel}
|
|
83
|
-
|
|
85
|
+
resolveBroadcast={firstResolver}
|
|
84
86
|
renderMessage={renderMessage}
|
|
85
87
|
/>
|
|
86
88
|
</Channel>
|
|
@@ -89,8 +91,8 @@ describe('ChannelView broadcast-name resolution', () => {
|
|
|
89
91
|
})
|
|
90
92
|
|
|
91
93
|
expect(
|
|
92
|
-
screen.
|
|
93
|
-
).
|
|
94
|
+
screen.queryByTestId('sent-message-broadcast-status')
|
|
95
|
+
).not.toBeInTheDocument()
|
|
94
96
|
expect(mounts).toHaveBeenCalledOnce()
|
|
95
97
|
|
|
96
98
|
act(() => {
|
|
@@ -99,7 +101,7 @@ describe('ChannelView broadcast-name resolution', () => {
|
|
|
99
101
|
<Channel channel={channel as StreamChannel}>
|
|
100
102
|
<ChannelView
|
|
101
103
|
channel={channel as StreamChannel}
|
|
102
|
-
|
|
104
|
+
resolveBroadcast={secondResolver}
|
|
103
105
|
renderMessage={renderMessage}
|
|
104
106
|
/>
|
|
105
107
|
</Channel>
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import type { ChannelViewProps } from '../types'
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import ChannelHeader from './ChannelHeader'
|
|
17
17
|
import { CustomDateSeparator } from './CustomDateSeparator'
|
|
18
18
|
import { CustomMessage } from './CustomMessage'
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from './CustomMessage/
|
|
20
|
+
BroadcastContext,
|
|
21
|
+
useBroadcastsByOutboundId,
|
|
22
|
+
} from './CustomMessage/BroadcastContext'
|
|
23
23
|
import { CustomMessageActions } from './CustomMessage/CustomMessageActions'
|
|
24
24
|
import { CustomMessageInput } from './CustomMessageInput'
|
|
25
25
|
import { CustomSystemMessage } from './CustomSystemMessage'
|
|
@@ -60,7 +60,7 @@ const ChannelViewInner: React.FC<{
|
|
|
60
60
|
showChannelInfo?: boolean
|
|
61
61
|
onParticipantNameClick?: () => void
|
|
62
62
|
onBroadcastClick?: ChannelViewProps['onBroadcastClick']
|
|
63
|
-
|
|
63
|
+
resolveBroadcast?: ChannelViewProps['resolveBroadcast']
|
|
64
64
|
composerInput?: ChannelViewProps['composerInput']
|
|
65
65
|
}> = ({
|
|
66
66
|
onBack,
|
|
@@ -87,7 +87,7 @@ const ChannelViewInner: React.FC<{
|
|
|
87
87
|
showChannelInfo = true,
|
|
88
88
|
onParticipantNameClick,
|
|
89
89
|
onBroadcastClick,
|
|
90
|
-
|
|
90
|
+
resolveBroadcast,
|
|
91
91
|
composerInput,
|
|
92
92
|
}) => {
|
|
93
93
|
const { channel, messages } = useChannelStateContext()
|
|
@@ -103,11 +103,11 @@ const ChannelViewInner: React.FC<{
|
|
|
103
103
|
const messageBroadcastClick = onBroadcastClick
|
|
104
104
|
? stableOnBroadcastClick
|
|
105
105
|
: undefined
|
|
106
|
-
// Resolve outside memoized Stream rows so
|
|
107
|
-
// remounting the rows.
|
|
108
|
-
const
|
|
106
|
+
// Resolve outside memoized Stream rows so current campaign details publish
|
|
107
|
+
// without remounting the rows.
|
|
108
|
+
const broadcastsByOutboundId = useBroadcastsByOutboundId(
|
|
109
109
|
messages,
|
|
110
|
-
|
|
110
|
+
resolveBroadcast
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
const hasVisibleMessages = channel.state.messages.some(
|
|
@@ -119,7 +119,7 @@ const ChannelViewInner: React.FC<{
|
|
|
119
119
|
</React.Fragment>
|
|
120
120
|
) : null
|
|
121
121
|
const channelHeader = (
|
|
122
|
-
<
|
|
122
|
+
<ChannelHeader
|
|
123
123
|
className="-mx-[var(--str-chat\_\_spacing-2)] px-[var(--str-chat\_\_spacing-2)] md:-mx-[min(var(--str-chat\_\_spacing-10),4%)] md:px-[min(var(--str-chat\_\_spacing-10),4%)]"
|
|
124
124
|
onBack={onBack}
|
|
125
125
|
showBackButton={showBackButton}
|
|
@@ -162,7 +162,7 @@ const ChannelViewInner: React.FC<{
|
|
|
162
162
|
)
|
|
163
163
|
|
|
164
164
|
return (
|
|
165
|
-
<
|
|
165
|
+
<BroadcastContext.Provider value={broadcastsByOutboundId}>
|
|
166
166
|
<WithComponents
|
|
167
167
|
overrides={{
|
|
168
168
|
Message: MessageOverride,
|
|
@@ -274,7 +274,7 @@ const ChannelViewInner: React.FC<{
|
|
|
274
274
|
)}
|
|
275
275
|
</Window>
|
|
276
276
|
</WithComponents>
|
|
277
|
-
</
|
|
277
|
+
</BroadcastContext.Provider>
|
|
278
278
|
)
|
|
279
279
|
}
|
|
280
280
|
|
|
@@ -316,7 +316,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
316
316
|
showChannelInfo = true,
|
|
317
317
|
onParticipantNameClick,
|
|
318
318
|
onBroadcastClick,
|
|
319
|
-
|
|
319
|
+
resolveBroadcast,
|
|
320
320
|
composerInput,
|
|
321
321
|
}) => {
|
|
322
322
|
// Custom send message handler that:
|
|
@@ -430,7 +430,7 @@ export const ChannelView = React.memo<ChannelViewProps>(
|
|
|
430
430
|
showChannelInfo={showChannelInfo}
|
|
431
431
|
onParticipantNameClick={onParticipantNameClick}
|
|
432
432
|
onBroadcastClick={onBroadcastClick}
|
|
433
|
-
|
|
433
|
+
resolveBroadcast={resolveBroadcast}
|
|
434
434
|
composerInput={composerInput}
|
|
435
435
|
/>
|
|
436
436
|
</Channel>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { buildBroadcastsByOutboundId } from './BroadcastContext'
|
|
4
|
+
|
|
5
|
+
describe('buildBroadcastsByOutboundId', () => {
|
|
6
|
+
it('returns undefined when no resolver is provided', () => {
|
|
7
|
+
expect(
|
|
8
|
+
buildBroadcastsByOutboundId(
|
|
9
|
+
[{ metadata: { outbound_id: 'outbound-1' } }],
|
|
10
|
+
undefined
|
|
11
|
+
)
|
|
12
|
+
).toBeUndefined()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('resolves each unique outbound id once', () => {
|
|
16
|
+
const resolveBroadcast = vi.fn((outboundId: string) => ({
|
|
17
|
+
id: `broadcast-for-${outboundId}`,
|
|
18
|
+
name: `name-for-${outboundId}`,
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
expect(
|
|
22
|
+
buildBroadcastsByOutboundId(
|
|
23
|
+
[
|
|
24
|
+
{ metadata: { outbound_id: 'outbound-1' } },
|
|
25
|
+
{ metadata: { outbound_id: 'outbound-1' } },
|
|
26
|
+
{ metadata: { outbound_id: 'outbound-2' } },
|
|
27
|
+
{ metadata: {} },
|
|
28
|
+
],
|
|
29
|
+
resolveBroadcast
|
|
30
|
+
)
|
|
31
|
+
).toEqual({
|
|
32
|
+
'outbound-1': {
|
|
33
|
+
id: 'broadcast-for-outbound-1',
|
|
34
|
+
name: 'name-for-outbound-1',
|
|
35
|
+
},
|
|
36
|
+
'outbound-2': {
|
|
37
|
+
id: 'broadcast-for-outbound-2',
|
|
38
|
+
name: 'name-for-outbound-2',
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
expect(resolveBroadcast).toHaveBeenCalledTimes(2)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('omits outbound records the resolver cannot resolve', () => {
|
|
45
|
+
expect(
|
|
46
|
+
buildBroadcastsByOutboundId(
|
|
47
|
+
[{ metadata: { outbound_id: 'outbound-1' } }],
|
|
48
|
+
() => undefined
|
|
49
|
+
)
|
|
50
|
+
).toBeUndefined()
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createContext, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { BroadcastResolver, ResolvedBroadcast } from '../../types'
|
|
4
|
+
|
|
5
|
+
/** Current broadcasts keyed by the outbound record id stored on each message. */
|
|
6
|
+
export type BroadcastsByOutboundId = Readonly<
|
|
7
|
+
Record<string, ResolvedBroadcast>
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
export const BroadcastContext = createContext<
|
|
11
|
+
BroadcastsByOutboundId | undefined
|
|
12
|
+
>(undefined)
|
|
13
|
+
|
|
14
|
+
const outboundIdFromMessage = (message: {
|
|
15
|
+
metadata?: { outbound_id?: unknown }
|
|
16
|
+
}): string | undefined => {
|
|
17
|
+
const outboundId = message.metadata?.outbound_id
|
|
18
|
+
return typeof outboundId === 'string' ? outboundId : undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve broadcasts for the messages currently in view.
|
|
23
|
+
*
|
|
24
|
+
* Built outside memoized Stream rows so a new resolver reference can publish
|
|
25
|
+
* current automation details without remounting the Stream message rows.
|
|
26
|
+
*/
|
|
27
|
+
export const buildBroadcastsByOutboundId = (
|
|
28
|
+
messages: readonly { metadata?: { outbound_id?: unknown } }[] | undefined,
|
|
29
|
+
resolveBroadcast: BroadcastResolver | undefined
|
|
30
|
+
): BroadcastsByOutboundId | undefined => {
|
|
31
|
+
if (!resolveBroadcast || !messages?.length) {
|
|
32
|
+
return undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const seen = new Set<string>()
|
|
36
|
+
let broadcasts: Record<string, ResolvedBroadcast> | undefined
|
|
37
|
+
|
|
38
|
+
for (const message of messages) {
|
|
39
|
+
const outboundId = outboundIdFromMessage(message)
|
|
40
|
+
if (!outboundId || seen.has(outboundId)) {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
seen.add(outboundId)
|
|
44
|
+
|
|
45
|
+
const resolved = resolveBroadcast(outboundId)
|
|
46
|
+
if (!resolved) {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
broadcasts ??= {}
|
|
51
|
+
broadcasts[outboundId] = resolved
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return broadcasts
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const areBroadcastsEqual = (
|
|
58
|
+
previous: BroadcastsByOutboundId | undefined,
|
|
59
|
+
next: BroadcastsByOutboundId | undefined
|
|
60
|
+
): boolean => {
|
|
61
|
+
if (previous === next) {
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
if (!previous || !next) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const previousOutboundIds = Object.keys(previous)
|
|
69
|
+
const nextOutboundIds = Object.keys(next)
|
|
70
|
+
if (previousOutboundIds.length !== nextOutboundIds.length) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return previousOutboundIds.every(
|
|
75
|
+
(outboundId) =>
|
|
76
|
+
previous[outboundId].id === next[outboundId]?.id &&
|
|
77
|
+
previous[outboundId].name === next[outboundId]?.name
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Re-invokes the resolver on every render, but only returns a new object when
|
|
83
|
+
* the resolved id/name pairs actually change — so context consumers update for
|
|
84
|
+
* late-arriving broadcasts without thrashing on unrelated renders.
|
|
85
|
+
*/
|
|
86
|
+
export const useBroadcastsByOutboundId = (
|
|
87
|
+
messages: readonly { metadata?: { outbound_id?: unknown } }[] | undefined,
|
|
88
|
+
resolveBroadcast: BroadcastResolver | undefined
|
|
89
|
+
): BroadcastsByOutboundId | undefined => {
|
|
90
|
+
const broadcasts = buildBroadcastsByOutboundId(messages, resolveBroadcast)
|
|
91
|
+
const [publishedBroadcasts, setPublishedBroadcasts] = useState<
|
|
92
|
+
BroadcastsByOutboundId | undefined
|
|
93
|
+
>(broadcasts)
|
|
94
|
+
|
|
95
|
+
if (!areBroadcastsEqual(publishedBroadcasts, broadcasts)) {
|
|
96
|
+
setPublishedBroadcasts(broadcasts)
|
|
97
|
+
return broadcasts
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return publishedBroadcasts
|
|
101
|
+
}
|
|
@@ -23,13 +23,13 @@ import {
|
|
|
23
23
|
storyUsers,
|
|
24
24
|
} from '../../stories/decorators/storyUser'
|
|
25
25
|
import { createMockStreamChatClient } from '../../testing/createMockStreamChatClient'
|
|
26
|
-
import type {
|
|
26
|
+
import type { BroadcastResolver } from '../../types'
|
|
27
27
|
import CustomTypingIndicator from '../CustomTypingIndicator'
|
|
28
28
|
|
|
29
29
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from './
|
|
30
|
+
BroadcastContext,
|
|
31
|
+
buildBroadcastsByOutboundId,
|
|
32
|
+
} from './BroadcastContext'
|
|
33
33
|
import { CustomMessageActions } from './CustomMessageActions'
|
|
34
34
|
|
|
35
35
|
import { CustomMessage } from './index'
|
|
@@ -112,7 +112,7 @@ const createMockChannel = async (
|
|
|
112
112
|
interface TemplateProps {
|
|
113
113
|
currentUser: StoryUser
|
|
114
114
|
typingUser?: StoryUser
|
|
115
|
-
|
|
115
|
+
resolveBroadcast?: BroadcastResolver
|
|
116
116
|
messages: Array<{
|
|
117
117
|
id: string
|
|
118
118
|
text: string
|
|
@@ -134,8 +134,7 @@ interface TemplateProps {
|
|
|
134
134
|
attachment_thumbnail?: string
|
|
135
135
|
attachment_detail?: string
|
|
136
136
|
attachment_content_type?: 'text' | 'media'
|
|
137
|
-
|
|
138
|
-
broadcast_name?: string
|
|
137
|
+
outbound_id?: string
|
|
139
138
|
}
|
|
140
139
|
}>
|
|
141
140
|
}
|
|
@@ -144,8 +143,8 @@ const TemplateInner: React.FC<{
|
|
|
144
143
|
currentUser: StoryUser
|
|
145
144
|
typingUser?: StoryUser
|
|
146
145
|
messages: TemplateProps['messages']
|
|
147
|
-
|
|
148
|
-
}> = ({ currentUser, typingUser, messages,
|
|
146
|
+
resolveBroadcast?: BroadcastResolver
|
|
147
|
+
}> = ({ currentUser, typingUser, messages, resolveBroadcast }) => {
|
|
149
148
|
const [client] = React.useState(() => createMockStreamChatClient(currentUser))
|
|
150
149
|
|
|
151
150
|
const [channel, setChannel] = React.useState<ChannelType | null>(null)
|
|
@@ -171,9 +170,9 @@ const TemplateInner: React.FC<{
|
|
|
171
170
|
return () => clearTimeout(timer)
|
|
172
171
|
}, [channel, client, typingUser])
|
|
173
172
|
|
|
174
|
-
const
|
|
175
|
-
() =>
|
|
176
|
-
[messages,
|
|
173
|
+
const broadcastsByOutboundId = React.useMemo(
|
|
174
|
+
() => buildBroadcastsByOutboundId(messages, resolveBroadcast),
|
|
175
|
+
[messages, resolveBroadcast]
|
|
177
176
|
)
|
|
178
177
|
|
|
179
178
|
const MessageComponent = React.useMemo(() => {
|
|
@@ -187,7 +186,7 @@ const TemplateInner: React.FC<{
|
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
return (
|
|
190
|
-
<
|
|
189
|
+
<BroadcastContext.Provider value={broadcastsByOutboundId}>
|
|
191
190
|
<Chat client={client}>
|
|
192
191
|
<div className="h-screen w-full bg-white">
|
|
193
192
|
<Channel
|
|
@@ -201,7 +200,7 @@ const TemplateInner: React.FC<{
|
|
|
201
200
|
</Channel>
|
|
202
201
|
</div>
|
|
203
202
|
</Chat>
|
|
204
|
-
</
|
|
203
|
+
</BroadcastContext.Provider>
|
|
205
204
|
)
|
|
206
205
|
}
|
|
207
206
|
|
|
@@ -209,14 +208,14 @@ const Template: StoryFn<TemplateProps> = ({
|
|
|
209
208
|
currentUser = storyUsers.creator,
|
|
210
209
|
typingUser,
|
|
211
210
|
messages,
|
|
212
|
-
|
|
211
|
+
resolveBroadcast,
|
|
213
212
|
}) => (
|
|
214
213
|
<TemplateInner
|
|
215
214
|
key={currentUser.id}
|
|
216
215
|
currentUser={currentUser}
|
|
217
216
|
typingUser={typingUser}
|
|
218
217
|
messages={messages}
|
|
219
|
-
|
|
218
|
+
resolveBroadcast={resolveBroadcast}
|
|
220
219
|
/>
|
|
221
220
|
)
|
|
222
221
|
|
|
@@ -303,11 +302,14 @@ BroadcastStatus.args = {
|
|
|
303
302
|
user: storyUsers.creator,
|
|
304
303
|
metadata: {
|
|
305
304
|
custom_type: 'MESSAGE_OUTBOUND',
|
|
306
|
-
|
|
307
|
-
broadcast_name: 'July 9th Class attendees',
|
|
305
|
+
outbound_id: 'outbound-1',
|
|
308
306
|
},
|
|
309
307
|
},
|
|
310
308
|
],
|
|
309
|
+
resolveBroadcast: () => ({
|
|
310
|
+
id: 'broadcast-1',
|
|
311
|
+
name: 'July 9th Class attendees',
|
|
312
|
+
}),
|
|
311
313
|
}
|
|
312
314
|
|
|
313
315
|
export const BroadcastStatusNonLatest: StoryFn<TemplateProps> = Template.bind(
|
|
@@ -321,8 +323,7 @@ BroadcastStatusNonLatest.args = {
|
|
|
321
323
|
user: storyUsers.creator,
|
|
322
324
|
metadata: {
|
|
323
325
|
custom_type: 'MESSAGE_OUTBOUND',
|
|
324
|
-
|
|
325
|
-
broadcast_name: 'July 9th Class attendees',
|
|
326
|
+
outbound_id: 'outbound-1',
|
|
326
327
|
},
|
|
327
328
|
},
|
|
328
329
|
{
|
|
@@ -331,12 +332,19 @@ BroadcastStatusNonLatest.args = {
|
|
|
331
332
|
user: storyUsers.creator,
|
|
332
333
|
},
|
|
333
334
|
],
|
|
335
|
+
resolveBroadcast: () => ({
|
|
336
|
+
id: 'broadcast-1',
|
|
337
|
+
name: 'July 9th Class attendees',
|
|
338
|
+
}),
|
|
334
339
|
}
|
|
335
340
|
|
|
336
341
|
export const BroadcastStatusResolved: StoryFn<TemplateProps> = Template.bind({})
|
|
337
342
|
BroadcastStatusResolved.args = {
|
|
338
343
|
...BroadcastStatus.args,
|
|
339
|
-
|
|
344
|
+
resolveBroadcast: () => ({
|
|
345
|
+
id: 'broadcast-1',
|
|
346
|
+
name: 'July 9th Class absentee',
|
|
347
|
+
}),
|
|
340
348
|
}
|
|
341
349
|
|
|
342
350
|
export const WithTipTag: StoryFn<TemplateProps> = Template.bind({})
|