@linktr.ee/messaging-react 3.3.4 → 3.3.6-rc-1780987607
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-DLmUSU4A.cjs → Card-BlviN8Fb.cjs} +2 -2
- package/dist/{Card-DLmUSU4A.cjs.map → Card-BlviN8Fb.cjs.map} +1 -1
- package/dist/{Card-DmPpcrSU.js → Card-C4ncqjxJ.js} +2 -2
- package/dist/{Card-DmPpcrSU.js.map → Card-C4ncqjxJ.js.map} +1 -1
- package/dist/{Card-0BgubwgM.cjs → Card-Cn7Zxc6U.cjs} +2 -2
- package/dist/{Card-0BgubwgM.cjs.map → Card-Cn7Zxc6U.cjs.map} +1 -1
- package/dist/{Card-DchJqvYq.js → Card-DE5bfj0l.js} +2 -2
- package/dist/{Card-DchJqvYq.js.map → Card-DE5bfj0l.js.map} +1 -1
- package/dist/{Card-B7AF5uOB.js → Card-IjOI7UXs.js} +3 -3
- package/dist/{Card-B7AF5uOB.js.map → Card-IjOI7UXs.js.map} +1 -1
- package/dist/{Card-CvBbAoUo.cjs → Card-KgQxeR-B.cjs} +2 -2
- package/dist/{Card-CvBbAoUo.cjs.map → Card-KgQxeR-B.cjs.map} +1 -1
- package/dist/{LockedThumbnail-BQjA4HaB.js → LockedThumbnail-4-54cyJG.js} +2 -2
- package/dist/{LockedThumbnail-BQjA4HaB.js.map → LockedThumbnail-4-54cyJG.js.map} +1 -1
- package/dist/{LockedThumbnail-D9fSb4N-.cjs → LockedThumbnail-DL5NZzWJ.cjs} +2 -2
- package/dist/{LockedThumbnail-D9fSb4N-.cjs.map → LockedThumbnail-DL5NZzWJ.cjs.map} +1 -1
- package/dist/{index-BcHUpyyw.js → index-C2wfgpUU.js} +855 -823
- package/dist/index-C2wfgpUU.js.map +1 -0
- package/dist/index-nanry0Io.cjs +2 -0
- package/dist/index-nanry0Io.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -2
- package/src/components/ActionButton/ActionButton.test.tsx +0 -25
- package/src/components/AttachmentCard/AttachmentCard.stories.tsx +226 -0
- package/src/components/Avatar/Avatar.stories.tsx +20 -0
- package/src/components/ChannelActionsMenu/ChannelActionsMenu.test.tsx +33 -8
- package/src/components/ChannelList/ChannelList.stories.tsx +5 -0
- package/src/components/ChannelList/CustomChannelPreview.stories.tsx +77 -47
- package/src/components/ChannelView.stories.tsx +8 -7
- package/src/components/ChannelView.test.tsx +12 -1
- package/src/components/ChannelView.tsx +34 -17
- package/src/components/CloseButton/CloseButton.stories.tsx +31 -0
- package/src/components/CustomDateSeparator/CustomDateSeparator.stories.tsx +33 -0
- package/src/components/CustomLinkPreviewList/CustomLinkPreviewCard.stories.tsx +63 -0
- package/src/components/CustomLinkPreviewList/CustomLinkPreviewCard.tsx +57 -0
- package/src/components/CustomLinkPreviewList/index.tsx +2 -54
- package/src/components/CustomMessage/CustomMessage.stories.tsx +3 -18
- package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +13 -0
- package/src/components/CustomMessage/MessageTag.stories.tsx +22 -2
- package/src/components/CustomMessage/MessageVoteButtons.stories.tsx +9 -0
- package/src/components/CustomMessageInput/index.tsx +14 -4
- package/src/components/CustomSystemMessage/CustomSystemMessage.stories.tsx +54 -0
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +7 -0
- package/src/components/LinkAttachment/LinkAttachment.stories.tsx +11 -1
- package/src/components/LockedAttachment/LockedAttachment.stories.tsx +4 -0
- package/src/components/MediaMessage/MediaMessage.stories.tsx +4 -2
- package/src/components/MediaMessage/MediaMessage.test.tsx +0 -38
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +25 -84
- package/src/components/SearchInput/SearchInput.test.tsx +0 -8
- package/src/hooks/useChannelModerationActions.ts +32 -14
- package/src/stories/decorators/storyTime.ts +31 -0
- package/src/utils/formatRelativeTime.test.ts +1 -32
- package/dist/index-BcHUpyyw.js.map +0 -1
- package/dist/index-DTZNltUC.cjs +0 -2
- package/dist/index-DTZNltUC.cjs.map +0 -1
|
@@ -813,55 +813,20 @@ describe('MessageAttachment lazy-loading defaults', () => {
|
|
|
813
813
|
})
|
|
814
814
|
|
|
815
815
|
describe('bubbleGroupPositionFromStream', () => {
|
|
816
|
-
it(
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
firstOfGroup: true,
|
|
831
|
-
endOfGroup: true,
|
|
832
|
-
})
|
|
833
|
-
).toBe('single')
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
it('returns "first" when firstOfGroup is the only flag set', () => {
|
|
837
|
-
expect(
|
|
838
|
-
bubbleGroupPositionFromStream({
|
|
839
|
-
groupedByUser: true,
|
|
840
|
-
firstOfGroup: true,
|
|
841
|
-
endOfGroup: false,
|
|
842
|
-
})
|
|
843
|
-
).toBe('first')
|
|
844
|
-
})
|
|
845
|
-
|
|
846
|
-
it('returns "end" when endOfGroup is the only flag set', () => {
|
|
847
|
-
expect(
|
|
848
|
-
bubbleGroupPositionFromStream({
|
|
849
|
-
groupedByUser: true,
|
|
850
|
-
firstOfGroup: false,
|
|
851
|
-
endOfGroup: true,
|
|
852
|
-
})
|
|
853
|
-
).toBe('end')
|
|
854
|
-
})
|
|
855
|
-
|
|
856
|
-
it('returns "middle" when grouped but neither first nor end', () => {
|
|
857
|
-
expect(
|
|
858
|
-
bubbleGroupPositionFromStream({
|
|
859
|
-
groupedByUser: true,
|
|
860
|
-
firstOfGroup: false,
|
|
861
|
-
endOfGroup: false,
|
|
862
|
-
})
|
|
863
|
-
).toBe('middle')
|
|
864
|
-
})
|
|
816
|
+
it.each([
|
|
817
|
+
{ groupedByUser: false, firstOfGroup: true, endOfGroup: true, expected: 'single' },
|
|
818
|
+
{ groupedByUser: true, firstOfGroup: true, endOfGroup: true, expected: 'single' },
|
|
819
|
+
{ groupedByUser: true, firstOfGroup: true, endOfGroup: false, expected: 'first' },
|
|
820
|
+
{ groupedByUser: true, firstOfGroup: false, endOfGroup: true, expected: 'end' },
|
|
821
|
+
{ groupedByUser: true, firstOfGroup: false, endOfGroup: false, expected: 'middle' },
|
|
822
|
+
])(
|
|
823
|
+
'maps groupedByUser=$groupedByUser firstOfGroup=$firstOfGroup endOfGroup=$endOfGroup → $expected',
|
|
824
|
+
({ groupedByUser, firstOfGroup, endOfGroup, expected }) => {
|
|
825
|
+
expect(
|
|
826
|
+
bubbleGroupPositionFromStream({ groupedByUser, firstOfGroup, endOfGroup })
|
|
827
|
+
).toBe(expected)
|
|
828
|
+
}
|
|
829
|
+
)
|
|
865
830
|
})
|
|
866
831
|
|
|
867
832
|
describe('MessageAttachment.Bubble grouping', () => {
|
|
@@ -884,41 +849,17 @@ describe('MessageAttachment.Bubble grouping', () => {
|
|
|
884
849
|
mimeType: 'application/pdf',
|
|
885
850
|
}
|
|
886
851
|
|
|
887
|
-
it('
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
<MessageAttachment.File.Sent {...FILE_PROPS} groupPosition="first" />
|
|
899
|
-
)
|
|
900
|
-
expect(
|
|
901
|
-
screen.getByTestId('file-attachment').getAttribute('data-group-position')
|
|
902
|
-
).toBe('first')
|
|
903
|
-
})
|
|
904
|
-
|
|
905
|
-
it('serializes groupPosition="middle" for an interior bubble in a run', () => {
|
|
906
|
-
renderWithProviders(
|
|
907
|
-
<MessageAttachment.File.Sent {...FILE_PROPS} groupPosition="middle" />
|
|
908
|
-
)
|
|
909
|
-
expect(
|
|
910
|
-
screen.getByTestId('file-attachment').getAttribute('data-group-position')
|
|
911
|
-
).toBe('middle')
|
|
912
|
-
})
|
|
913
|
-
|
|
914
|
-
it('serializes groupPosition="end" for the last bubble in a run', () => {
|
|
915
|
-
renderWithProviders(
|
|
916
|
-
<MessageAttachment.File.Sent {...FILE_PROPS} groupPosition="end" />
|
|
917
|
-
)
|
|
918
|
-
expect(
|
|
919
|
-
screen.getByTestId('file-attachment').getAttribute('data-group-position')
|
|
920
|
-
).toBe('end')
|
|
921
|
-
})
|
|
852
|
+
it.each(['single', 'first', 'middle', 'end'] as const)(
|
|
853
|
+
'serializes groupPosition="%s" on the bubble',
|
|
854
|
+
(groupPosition) => {
|
|
855
|
+
renderWithProviders(
|
|
856
|
+
<MessageAttachment.File.Sent {...FILE_PROPS} groupPosition={groupPosition} />
|
|
857
|
+
)
|
|
858
|
+
expect(
|
|
859
|
+
screen.getByTestId('file-attachment').getAttribute('data-group-position')
|
|
860
|
+
).toBe(groupPosition)
|
|
861
|
+
}
|
|
862
|
+
)
|
|
922
863
|
|
|
923
864
|
it('serializes groupPosition on receiver-side bubbles too', () => {
|
|
924
865
|
renderWithProviders(
|
|
@@ -17,14 +17,6 @@ describe('SearchInput', () => {
|
|
|
17
17
|
expect(screen.getByPlaceholderText('Search messages...')).toBeInTheDocument();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
it('renders with search icon', () => {
|
|
21
|
-
renderWithProviders(
|
|
22
|
-
<SearchInput searchQuery="" setSearchQuery={vi.fn()} placeholder="Search" />
|
|
23
|
-
);
|
|
24
|
-
const searchIcon = document.querySelector('svg');
|
|
25
|
-
expect(searchIcon).toBeInTheDocument();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
20
|
it('displays the current value', () => {
|
|
29
21
|
renderWithProviders(
|
|
30
22
|
<SearchInput searchQuery="test query" setSearchQuery={vi.fn()} placeholder="Search" />
|
|
@@ -75,33 +75,46 @@ export const useChannelModerationActions = ({
|
|
|
75
75
|
logLabel = 'useChannelModerationActions',
|
|
76
76
|
}: UseChannelModerationActionsParams): ChannelModerationActions => {
|
|
77
77
|
const { service, debug } = useMessagingContext()
|
|
78
|
+
const participantId = participant?.user?.id
|
|
79
|
+
const willLookup = Boolean(
|
|
80
|
+
enabled && showBlockParticipant && service && participantId
|
|
81
|
+
)
|
|
82
|
+
|
|
78
83
|
const [isParticipantBlocked, setIsParticipantBlocked] = useState(false)
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
// Tracks the participant + service the most recent lookup has completed
|
|
85
|
+
// for. Keying on both so a service swap (e.g. MessagingProvider rebuilding
|
|
86
|
+
// its StreamChatService when config/apiKey/debug changes) re-triggers the
|
|
87
|
+
// loading state for the same participant — otherwise the menu would
|
|
88
|
+
// briefly show actionable Block/Unblock against the new service with the
|
|
89
|
+
// old service's blocked result. Computing the flag at render time — rather
|
|
90
|
+
// than from a useState updated inside useEffect — also closes the brief
|
|
91
|
+
// window where the menu would render the regular (enabled) Block button
|
|
92
|
+
// before the effect flipped the disabled placeholder on.
|
|
93
|
+
const [resolvedFor, setResolvedFor] = useState<{
|
|
94
|
+
participantId: string
|
|
95
|
+
service: unknown
|
|
96
|
+
} | null>(null)
|
|
81
97
|
const [isLeaving, setIsLeaving] = useState(false)
|
|
82
98
|
const [isUpdatingBlockStatus, setIsUpdatingBlockStatus] = useState(false)
|
|
83
99
|
|
|
100
|
+
const isCheckingBlockedStatus =
|
|
101
|
+
willLookup &&
|
|
102
|
+
(resolvedFor?.participantId !== participantId ||
|
|
103
|
+
resolvedFor?.service !== service)
|
|
104
|
+
|
|
84
105
|
// Resolve whether the participant is blocked whenever the participant or
|
|
85
106
|
// surface visibility changes.
|
|
86
107
|
useEffect(() => {
|
|
87
108
|
// When the lookup is skipped (Block action hidden, surface disabled, or no
|
|
88
109
|
// participant), clear any stale blocked state so a previous participant's
|
|
89
110
|
// value can't leak into the next conversation.
|
|
90
|
-
if (
|
|
91
|
-
!enabled ||
|
|
92
|
-
!showBlockParticipant ||
|
|
93
|
-
!service ||
|
|
94
|
-
!participant?.user?.id
|
|
95
|
-
) {
|
|
111
|
+
if (!willLookup || !service || !participantId) {
|
|
96
112
|
setIsParticipantBlocked(false)
|
|
97
|
-
|
|
113
|
+
setResolvedFor(null)
|
|
98
114
|
return
|
|
99
115
|
}
|
|
100
116
|
|
|
101
117
|
let cancelled = false
|
|
102
|
-
const participantId = participant.user.id
|
|
103
|
-
|
|
104
|
-
setIsCheckingBlockedStatus(true)
|
|
105
118
|
|
|
106
119
|
void (async () => {
|
|
107
120
|
try {
|
|
@@ -117,7 +130,12 @@ export const useChannelModerationActions = ({
|
|
|
117
130
|
console.error(`[${logLabel}] Failed to check blocked status:`, error)
|
|
118
131
|
}
|
|
119
132
|
} finally {
|
|
120
|
-
|
|
133
|
+
// Mark the lookup as resolved regardless of success/failure so a
|
|
134
|
+
// rejected `getBlockedUsers()` doesn't leave the menu stuck in the
|
|
135
|
+
// disabled-spinner state. On failure the blocked flag stays at its
|
|
136
|
+
// default (false), matching the prior behavior — the user can attempt
|
|
137
|
+
// to block/unblock and the server rejects if the state is wrong.
|
|
138
|
+
if (!cancelled) setResolvedFor({ participantId, service })
|
|
121
139
|
}
|
|
122
140
|
})()
|
|
123
141
|
|
|
@@ -125,7 +143,7 @@ export const useChannelModerationActions = ({
|
|
|
125
143
|
return () => {
|
|
126
144
|
cancelled = true
|
|
127
145
|
}
|
|
128
|
-
}, [
|
|
146
|
+
}, [willLookup, service, participantId, logLabel])
|
|
129
147
|
|
|
130
148
|
const handleLeaveConversation = async () => {
|
|
131
149
|
if (isLeaving) return
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic clock for Storybook stories.
|
|
3
|
+
*
|
|
4
|
+
* Stories that render relative timestamps ("3m ago", "yesterday") must use these
|
|
5
|
+
* helpers instead of `new Date()` / `Date.now()`, or visual diffs in Chromatic
|
|
6
|
+
* will drift on every run.
|
|
7
|
+
*
|
|
8
|
+
* FROZEN_NOW is the reference instant. Helpers below produce timestamps relative
|
|
9
|
+
* to it so the rendered output is identical regardless of when the build runs.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const FROZEN_NOW = new Date('2026-01-15T12:00:00.000Z')
|
|
13
|
+
|
|
14
|
+
const MS_PER_SEC = 1000
|
|
15
|
+
const MS_PER_MIN = 60 * MS_PER_SEC
|
|
16
|
+
const MS_PER_HOUR = 60 * MS_PER_MIN
|
|
17
|
+
const MS_PER_DAY = 24 * MS_PER_HOUR
|
|
18
|
+
|
|
19
|
+
export const now = (): Date => new Date(FROZEN_NOW)
|
|
20
|
+
|
|
21
|
+
export const secondsAgo = (n: number): Date =>
|
|
22
|
+
new Date(FROZEN_NOW.getTime() - n * MS_PER_SEC)
|
|
23
|
+
|
|
24
|
+
export const minutesAgo = (n: number): Date =>
|
|
25
|
+
new Date(FROZEN_NOW.getTime() - n * MS_PER_MIN)
|
|
26
|
+
|
|
27
|
+
export const hoursAgo = (n: number): Date =>
|
|
28
|
+
new Date(FROZEN_NOW.getTime() - n * MS_PER_HOUR)
|
|
29
|
+
|
|
30
|
+
export const daysAgo = (n: number): Date =>
|
|
31
|
+
new Date(FROZEN_NOW.getTime() - n * MS_PER_DAY)
|
|
@@ -39,12 +39,6 @@ describe('formatRelativeTime', () => {
|
|
|
39
39
|
const date = new Date('2024-01-15T11:59:30Z') // 30 seconds ago
|
|
40
40
|
expect(formatRelativeTime(date)).toBe('Just now')
|
|
41
41
|
})
|
|
42
|
-
|
|
43
|
-
it('should return "Just now" for messages 0 seconds ago', () => {
|
|
44
|
-
mockDate('2024-01-15T12:00:00Z')
|
|
45
|
-
const date = new Date('2024-01-15T12:00:00Z')
|
|
46
|
-
expect(formatRelativeTime(date)).toBe('Just now')
|
|
47
|
-
})
|
|
48
42
|
})
|
|
49
43
|
|
|
50
44
|
describe('Today', () => {
|
|
@@ -55,13 +49,6 @@ describe('formatRelativeTime', () => {
|
|
|
55
49
|
// Should be "9:30 AM" format
|
|
56
50
|
expect(result).toMatch(/^\d{1,2}:\d{2} (AM|PM)$/i)
|
|
57
51
|
})
|
|
58
|
-
|
|
59
|
-
it('should return time in 12-hour format for messages from late last night (same day)', () => {
|
|
60
|
-
mockDate('2024-01-15T23:59:00Z')
|
|
61
|
-
const date = new Date('2024-01-15T00:01:00Z')
|
|
62
|
-
const result = formatRelativeTime(date)
|
|
63
|
-
expect(result).toMatch(/^\d{1,2}:\d{2} (AM|PM)$/i)
|
|
64
|
-
})
|
|
65
52
|
})
|
|
66
53
|
|
|
67
54
|
describe('Yesterday', () => {
|
|
@@ -92,13 +79,7 @@ describe('formatRelativeTime', () => {
|
|
|
92
79
|
expect(formatRelativeTime(date)).toBe('2d')
|
|
93
80
|
})
|
|
94
81
|
|
|
95
|
-
it('should return "
|
|
96
|
-
mockDate('2024-01-15T12:00:00Z')
|
|
97
|
-
const date = new Date('2024-01-12T12:00:00Z')
|
|
98
|
-
expect(formatRelativeTime(date)).toBe('3d')
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('should return "6d" for messages 6 days ago', () => {
|
|
82
|
+
it('should return "6d" for messages 6 days ago (boundary before weeks)', () => {
|
|
102
83
|
mockDate('2024-01-15T12:00:00Z')
|
|
103
84
|
const date = new Date('2024-01-09T12:00:00Z')
|
|
104
85
|
expect(formatRelativeTime(date)).toBe('6d')
|
|
@@ -111,18 +92,6 @@ describe('formatRelativeTime', () => {
|
|
|
111
92
|
const date = new Date('2024-01-08T12:00:00Z')
|
|
112
93
|
expect(formatRelativeTime(date)).toBe('1w')
|
|
113
94
|
})
|
|
114
|
-
|
|
115
|
-
it('should return "2w" for messages 2 weeks ago', () => {
|
|
116
|
-
mockDate('2024-01-15T12:00:00Z')
|
|
117
|
-
const date = new Date('2024-01-01T12:00:00Z')
|
|
118
|
-
expect(formatRelativeTime(date)).toBe('2w')
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('should return "3w" for messages 3 weeks ago', () => {
|
|
122
|
-
mockDate('2024-01-29T12:00:00Z')
|
|
123
|
-
const date = new Date('2024-01-08T12:00:00Z')
|
|
124
|
-
expect(formatRelativeTime(date)).toBe('3w')
|
|
125
|
-
})
|
|
126
95
|
})
|
|
127
96
|
|
|
128
97
|
describe('Date format', () => {
|