@planningcenter/chat-react-native 3.35.0-rc.2 → 3.35.0-rc.4
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/README.md +1 -1
- package/build/components/conversation/jump_to_bottom_button.d.ts +2 -1
- package/build/components/conversation/jump_to_bottom_button.d.ts.map +1 -1
- package/build/components/conversation/jump_to_bottom_button.js +39 -7
- package/build/components/conversation/jump_to_bottom_button.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.d.ts +1 -2
- package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/components/conversation/unread_divider.d.ts +6 -0
- package/build/components/conversation/unread_divider.d.ts.map +1 -0
- package/build/components/conversation/unread_divider.js +59 -0
- package/build/components/conversation/unread_divider.js.map +1 -0
- package/build/contexts/conversation_context.d.ts +2 -0
- package/build/contexts/conversation_context.d.ts.map +1 -1
- package/build/contexts/conversation_context.js +13 -5
- package/build/contexts/conversation_context.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +2 -0
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +9 -5
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +4 -4
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_conversations_actions.d.ts +5 -0
- package/build/hooks/use_conversations_actions.d.ts.map +1 -1
- package/build/hooks/use_conversations_actions.js +12 -0
- package/build/hooks/use_conversations_actions.js.map +1 -1
- package/build/hooks/use_flat_list_viewability.d.ts +20 -0
- package/build/hooks/use_flat_list_viewability.d.ts.map +1 -0
- package/build/hooks/use_flat_list_viewability.js +30 -0
- package/build/hooks/use_flat_list_viewability.js.map +1 -0
- package/build/hooks/use_jump_to_bottom_action.d.ts +9 -0
- package/build/hooks/use_jump_to_bottom_action.d.ts.map +1 -0
- package/build/hooks/use_jump_to_bottom_action.js +62 -0
- package/build/hooks/use_jump_to_bottom_action.js.map +1 -0
- package/build/hooks/use_jump_to_unread_anchor.d.ts +20 -0
- package/build/hooks/use_jump_to_unread_anchor.d.ts.map +1 -0
- package/build/hooks/use_jump_to_unread_anchor.js +53 -0
- package/build/hooks/use_jump_to_unread_anchor.js.map +1 -0
- package/build/hooks/use_jump_to_unread_gates.d.ts +5 -0
- package/build/hooks/use_jump_to_unread_gates.d.ts.map +1 -0
- package/build/hooks/use_jump_to_unread_gates.js +10 -0
- package/build/hooks/use_jump_to_unread_gates.js.map +1 -0
- package/build/hooks/use_mark_latest_message_read.d.ts +1 -1
- package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
- package/build/hooks/use_mark_latest_message_read.js +17 -1
- package/build/hooks/use_mark_latest_message_read.js.map +1 -1
- package/build/hooks/use_scroll_tracking.d.ts +13 -0
- package/build/hooks/use_scroll_tracking.d.ts.map +1 -0
- package/build/hooks/use_scroll_tracking.js +45 -0
- package/build/hooks/use_scroll_tracking.js.map +1 -0
- package/build/hooks/use_track_highest_seen_message.d.ts +4 -0
- package/build/hooks/use_track_highest_seen_message.d.ts.map +1 -0
- package/build/hooks/use_track_highest_seen_message.js +35 -0
- package/build/hooks/use_track_highest_seen_message.js.map +1 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/screens/conversation_screen.d.ts +0 -19
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +87 -139
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/utils/cache/messages_cache.d.ts +1 -0
- package/build/utils/cache/messages_cache.d.ts.map +1 -1
- package/build/utils/cache/messages_cache.js +4 -0
- package/build/utils/cache/messages_cache.js.map +1 -1
- package/build/utils/group_messages.d.ts +28 -0
- package/build/utils/group_messages.d.ts.map +1 -0
- package/build/utils/group_messages.js +142 -0
- package/build/utils/group_messages.js.map +1 -0
- package/build/utils/highest_seen_tracker.d.ts +12 -0
- package/build/utils/highest_seen_tracker.d.ts.map +1 -0
- package/build/utils/highest_seen_tracker.js +37 -0
- package/build/utils/highest_seen_tracker.js.map +1 -0
- package/build/utils/message_viewability.d.ts +24 -0
- package/build/utils/message_viewability.d.ts.map +1 -0
- package/build/utils/message_viewability.js +29 -0
- package/build/utils/message_viewability.js.map +1 -0
- package/build/utils/unread_divider_helpers.d.ts +18 -0
- package/build/utils/unread_divider_helpers.d.ts.map +1 -0
- package/build/utils/unread_divider_helpers.js +13 -0
- package/build/utils/unread_divider_helpers.js.map +1 -0
- package/package.json +10 -4
- package/src/__tests__/contexts/session_context.tsx +1 -1
- package/src/__tests__/hooks/use_async_storage.test.tsx +1 -1
- package/src/__tests__/hooks/use_attachment_uploader.test.tsx +1 -1
- package/src/__tests__/hooks/use_chat_configuration.test.tsx +1 -1
- package/src/__tests__/hooks/use_conversation_messages.test.tsx +1 -1
- package/src/__tests__/hooks/use_mark_latest_message_read.test.tsx +154 -0
- package/src/__tests__/utils/cache/messages_cache.test.ts +54 -0
- package/src/components/conversation/jump_to_bottom_button.tsx +57 -8
- package/src/components/conversation/reply_shadow_message.tsx +4 -2
- package/src/components/conversation/unread_divider.tsx +90 -0
- package/src/contexts/conversation_context.tsx +15 -13
- package/src/hooks/use_conversation_messages.ts +19 -3
- package/src/hooks/use_conversation_messages_jolt_events.ts +4 -3
- package/src/hooks/use_conversations_actions.ts +15 -0
- package/src/hooks/use_flat_list_viewability.ts +50 -0
- package/src/hooks/use_jump_to_bottom_action.ts +75 -0
- package/src/hooks/use_jump_to_unread_anchor.ts +68 -0
- package/src/hooks/use_jump_to_unread_gates.ts +10 -0
- package/src/hooks/use_mark_latest_message_read.ts +16 -2
- package/src/hooks/use_scroll_tracking.ts +64 -0
- package/src/hooks/use_track_highest_seen_message.ts +43 -0
- package/src/screens/conversation_screen.tsx +173 -197
- package/src/utils/__tests__/group_messages.test.ts +214 -0
- package/src/utils/__tests__/highest_seen_tracker.test.ts +82 -0
- package/src/utils/__tests__/message_viewability.test.ts +168 -0
- package/src/utils/__tests__/unread_divider_helpers.test.ts +85 -0
- package/src/utils/cache/messages_cache.ts +5 -0
- package/src/utils/group_messages.ts +217 -0
- package/src/utils/highest_seen_tracker.ts +42 -0
- package/src/utils/message_viewability.ts +49 -0
- package/src/utils/unread_divider_helpers.ts +25 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { MessageResource } from '../types/resources/message'
|
|
2
|
+
import dayjs from './dayjs'
|
|
3
|
+
import { isSystemMessage } from './system_messages'
|
|
4
|
+
|
|
5
|
+
const FIVE_MINUTES_MS = 5 * 60 * 1000
|
|
6
|
+
|
|
7
|
+
export const UNREAD_DIVIDER_KEY = 'unread-divider'
|
|
8
|
+
|
|
9
|
+
export type DateSeparator = { type: 'DateSeparator'; id: string; date: string }
|
|
10
|
+
|
|
11
|
+
export type UnreadDividerItem = { type: 'UnreadDivider'; id: typeof UNREAD_DIVIDER_KEY }
|
|
12
|
+
|
|
13
|
+
export type ReplyShadowMessage = {
|
|
14
|
+
type: 'ReplyShadowMessage'
|
|
15
|
+
id: string
|
|
16
|
+
messageId: string
|
|
17
|
+
isReplyShadowMessage: boolean
|
|
18
|
+
nextRendersAuthor: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type EnrichedMessage =
|
|
22
|
+
| MessageResource
|
|
23
|
+
| DateSeparator
|
|
24
|
+
| UnreadDividerItem
|
|
25
|
+
| ReplyShadowMessage
|
|
26
|
+
|
|
27
|
+
interface GroupMessagesProps {
|
|
28
|
+
ms: MessageResource[]
|
|
29
|
+
inReplyScreen?: boolean
|
|
30
|
+
jumpToUnreadActive?: boolean
|
|
31
|
+
initialMessageId?: string | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function groupMessages({
|
|
35
|
+
ms,
|
|
36
|
+
inReplyScreen,
|
|
37
|
+
jumpToUnreadActive,
|
|
38
|
+
initialMessageId,
|
|
39
|
+
}: GroupMessagesProps): EnrichedMessage[] {
|
|
40
|
+
const items: EnrichedMessage[] = []
|
|
41
|
+
let myLatestSeen = false
|
|
42
|
+
let nextNeighborEnriched: MessageResource | undefined
|
|
43
|
+
|
|
44
|
+
ms.forEach((message, i) => {
|
|
45
|
+
const { prev } = neighborsOf(ms, i)
|
|
46
|
+
const next = nextNeighborEnriched
|
|
47
|
+
|
|
48
|
+
if (isSystemMessage(message)) {
|
|
49
|
+
const enriched = enrichSystemMessage(message, next)
|
|
50
|
+
items.push(enriched)
|
|
51
|
+
if (crossesUnreadBoundary(message, prev, jumpToUnreadActive, initialMessageId)) {
|
|
52
|
+
items.push(unreadDivider())
|
|
53
|
+
}
|
|
54
|
+
if (datesDifferBetween(message, prev)) items.push(dateSeparator(message))
|
|
55
|
+
nextNeighborEnriched = enriched
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isMyLatest = !myLatestSeen && message.mine
|
|
60
|
+
if (isMyLatest) myLatestSeen = true
|
|
61
|
+
|
|
62
|
+
const enriched = enrichRegularMessage(message, prev, next, isMyLatest, !!inReplyScreen)
|
|
63
|
+
items.push(enriched)
|
|
64
|
+
|
|
65
|
+
if (crossesUnreadBoundary(message, prev, jumpToUnreadActive, initialMessageId)) {
|
|
66
|
+
items.push(unreadDivider())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const shadow = replyShadowFor(enriched, prev)
|
|
70
|
+
if (shadow) items.push(shadow)
|
|
71
|
+
|
|
72
|
+
if (!prev || datesDifferBetween(message, prev)) {
|
|
73
|
+
items.push(dateSeparator(message))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
nextNeighborEnriched = enriched
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return items
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function crossesUnreadBoundary(
|
|
83
|
+
message: MessageResource,
|
|
84
|
+
prev: MessageResource | undefined,
|
|
85
|
+
jumpToUnreadActive: boolean | undefined,
|
|
86
|
+
initialMessageId: string | null | undefined
|
|
87
|
+
): boolean {
|
|
88
|
+
if (!jumpToUnreadActive) return false
|
|
89
|
+
if (!initialMessageId) return false
|
|
90
|
+
if (!prev) return false
|
|
91
|
+
return (
|
|
92
|
+
prev.id.localeCompare(initialMessageId) <= 0 && message.id.localeCompare(initialMessageId) > 0
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function unreadDivider(): UnreadDividerItem {
|
|
97
|
+
return { type: 'UnreadDivider', id: UNREAD_DIVIDER_KEY }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function neighborsOf<T>(arr: T[], i: number): { prev: T | undefined; next: T | undefined } {
|
|
101
|
+
return { prev: arr[i + 1], next: arr[i - 1] }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function enrichSystemMessage(
|
|
105
|
+
message: MessageResource,
|
|
106
|
+
next: MessageResource | undefined
|
|
107
|
+
): MessageResource {
|
|
108
|
+
return {
|
|
109
|
+
...message,
|
|
110
|
+
myLatestInConversation: false,
|
|
111
|
+
lastInGroup: true,
|
|
112
|
+
renderAuthor: false,
|
|
113
|
+
nextRendersAuthor: next?.renderAuthor,
|
|
114
|
+
isReplyShadowMessage: false,
|
|
115
|
+
nextIsReplyShadowMessage: false,
|
|
116
|
+
threadPosition: null,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function enrichRegularMessage(
|
|
121
|
+
message: MessageResource,
|
|
122
|
+
prev: MessageResource | undefined,
|
|
123
|
+
next: MessageResource | undefined,
|
|
124
|
+
isMyLatest: boolean,
|
|
125
|
+
inReplyScreen: boolean
|
|
126
|
+
): MessageResource {
|
|
127
|
+
const inThread = message.replyRootId !== null
|
|
128
|
+
const showThreadDetails = !inReplyScreen && inThread
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
...message,
|
|
132
|
+
myLatestInConversation: isMyLatest,
|
|
133
|
+
lastInGroup: !next || startsNewGroup(next, message),
|
|
134
|
+
renderAuthor: !message.mine && (!prev || startsNewGroup(message, prev)),
|
|
135
|
+
nextRendersAuthor: next?.renderAuthor,
|
|
136
|
+
isReplyShadowMessage: false,
|
|
137
|
+
nextIsReplyShadowMessage: nextIntroducesReplyShadow(next, message),
|
|
138
|
+
threadPosition: showThreadDetails ? threadPositionFor(message, next) : null,
|
|
139
|
+
prevIsMyReply: showThreadDetails ? prev?.mine : undefined,
|
|
140
|
+
nextIsMyReply: showThreadDetails ? next?.mine : undefined,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function startsNewGroup(a: MessageResource, b: MessageResource): boolean {
|
|
145
|
+
return (
|
|
146
|
+
a.author?.id !== b.author?.id ||
|
|
147
|
+
differsByMoreThan5Min(a, b) ||
|
|
148
|
+
a.replyRootId !== b.replyRootId ||
|
|
149
|
+
datesDifferBetween(a, b)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function differsByMoreThan5Min(a: MessageResource, b: MessageResource): boolean {
|
|
154
|
+
return Math.abs(toMillis(a.createdAt) - toMillis(b.createdAt)) > FIVE_MINUTES_MS
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function toMillis(iso: string): number {
|
|
158
|
+
return new Date(iso).getTime()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function datesDifferBetween(a: MessageResource, b: MessageResource | undefined): boolean {
|
|
162
|
+
if (!b) return false
|
|
163
|
+
return dateKey(a) !== dateKey(b)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function dateKey(message: MessageResource): string {
|
|
167
|
+
return dayjs(message.createdAt).format('YYYY-MM-DD')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function dateSeparator(message: MessageResource): DateSeparator {
|
|
171
|
+
return { type: 'DateSeparator', id: `day-divider-${message.id}`, date: dateKey(message) }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function threadPositionFor(
|
|
175
|
+
message: MessageResource,
|
|
176
|
+
next: MessageResource | undefined
|
|
177
|
+
): 'first' | 'center' | 'last' | null {
|
|
178
|
+
const isThreadRoot = message.replyRootId === message.id
|
|
179
|
+
const isLast =
|
|
180
|
+
!next || next.replyRootId !== message.replyRootId || datesDifferBetween(next, message)
|
|
181
|
+
|
|
182
|
+
if (isThreadRoot && isLast) return null
|
|
183
|
+
if (isThreadRoot) return 'first'
|
|
184
|
+
if (isLast) return 'last'
|
|
185
|
+
return 'center'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function nextIntroducesReplyShadow(
|
|
189
|
+
next: MessageResource | undefined,
|
|
190
|
+
current: MessageResource
|
|
191
|
+
): boolean {
|
|
192
|
+
if (!next) return false
|
|
193
|
+
const nextInThread = next.replyRootId !== null
|
|
194
|
+
const nextIsThreadRoot = next.replyRootId === next.id
|
|
195
|
+
const differentThread = next.replyRootId !== current.replyRootId
|
|
196
|
+
return nextInThread && !nextIsThreadRoot && (differentThread || datesDifferBetween(next, current))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function replyShadowFor(
|
|
200
|
+
message: MessageResource,
|
|
201
|
+
prev: MessageResource | undefined
|
|
202
|
+
): ReplyShadowMessage | undefined {
|
|
203
|
+
if (!message.replyRootId) return undefined
|
|
204
|
+
if (message.replyRootId === message.id) return undefined
|
|
205
|
+
|
|
206
|
+
const enteringNewThread =
|
|
207
|
+
!prev || prev.replyRootId !== message.replyRootId || datesDifferBetween(prev, message)
|
|
208
|
+
if (!enteringNewThread) return undefined
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
type: 'ReplyShadowMessage',
|
|
212
|
+
id: `${message.id}-${message.replyRootId}`,
|
|
213
|
+
messageId: message.replyRootId,
|
|
214
|
+
isReplyShadowMessage: true,
|
|
215
|
+
nextRendersAuthor: message.renderAuthor ?? false,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const FLUSH_DELAY_MS = 2000
|
|
2
|
+
|
|
3
|
+
type SendFn = (args: { conversationId: number; sortKey: string }) => void
|
|
4
|
+
|
|
5
|
+
export function makeHighestSeenTracker(
|
|
6
|
+
conversationId: number,
|
|
7
|
+
send: SendFn,
|
|
8
|
+
flushDelayMs: number = FLUSH_DELAY_MS
|
|
9
|
+
) {
|
|
10
|
+
let highest: string | null = null
|
|
11
|
+
let lastSent: string | null = null
|
|
12
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
13
|
+
|
|
14
|
+
const fire = () => {
|
|
15
|
+
timer = null
|
|
16
|
+
if (!highest || highest === lastSent) return
|
|
17
|
+
lastSent = highest
|
|
18
|
+
send({ conversationId, sortKey: highest })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
onSeen(sortKey: string) {
|
|
23
|
+
if (highest && sortKey.localeCompare(highest) <= 0) return
|
|
24
|
+
highest = sortKey
|
|
25
|
+
if (timer) clearTimeout(timer)
|
|
26
|
+
timer = setTimeout(fire, flushDelayMs)
|
|
27
|
+
},
|
|
28
|
+
flushNow() {
|
|
29
|
+
if (timer) {
|
|
30
|
+
clearTimeout(timer)
|
|
31
|
+
timer = null
|
|
32
|
+
}
|
|
33
|
+
fire()
|
|
34
|
+
},
|
|
35
|
+
cancel() {
|
|
36
|
+
if (timer) {
|
|
37
|
+
clearTimeout(timer)
|
|
38
|
+
timer = null
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { dividerExitedTowardNewer } from './unread_divider_helpers'
|
|
2
|
+
|
|
3
|
+
export interface ViewableEntry<Item> {
|
|
4
|
+
key: string
|
|
5
|
+
isViewable: boolean
|
|
6
|
+
item: Item
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ViewabilityEvent<Item> {
|
|
10
|
+
viewableItems: ViewableEntry<Item>[]
|
|
11
|
+
changed: ViewableEntry<Item>[]
|
|
12
|
+
userHasScrolled: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ViewabilityObserver<Item> = (event: ViewabilityEvent<Item>) => void
|
|
16
|
+
|
|
17
|
+
export function reportViewableMessages<Item extends { id?: string; type?: string }>(
|
|
18
|
+
onMessageSeen: (id: string) => void
|
|
19
|
+
): ViewabilityObserver<Item> {
|
|
20
|
+
return ({ viewableItems, userHasScrolled }) => {
|
|
21
|
+
if (!userHasScrolled) return
|
|
22
|
+
for (const entry of viewableItems) {
|
|
23
|
+
if (entry.item?.type !== 'Message') continue
|
|
24
|
+
const id = entry.item?.id
|
|
25
|
+
if (typeof id === 'string') onMessageSeen(id)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function detectDividerExitTowardNewer<Item extends { id?: string; type?: string }>({
|
|
31
|
+
dividerKey,
|
|
32
|
+
initialMessageId,
|
|
33
|
+
onExited,
|
|
34
|
+
}: {
|
|
35
|
+
dividerKey: string
|
|
36
|
+
initialMessageId: string | null
|
|
37
|
+
onExited: () => void
|
|
38
|
+
}): ViewabilityObserver<Item> {
|
|
39
|
+
return ({ viewableItems, changed, userHasScrolled }) => {
|
|
40
|
+
if (!userHasScrolled || !initialMessageId) return
|
|
41
|
+
const exited = dividerExitedTowardNewer({
|
|
42
|
+
changed,
|
|
43
|
+
viewableItems,
|
|
44
|
+
dividerKey,
|
|
45
|
+
initialMessageId,
|
|
46
|
+
})
|
|
47
|
+
if (exited) onExited()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type ViewableChangeEntry = { key: string; isViewable: boolean }
|
|
2
|
+
type ViewableItem = { item: { id?: string; type?: string } }
|
|
3
|
+
|
|
4
|
+
export function dividerExitedTowardNewer({
|
|
5
|
+
changed,
|
|
6
|
+
viewableItems,
|
|
7
|
+
dividerKey,
|
|
8
|
+
initialMessageId,
|
|
9
|
+
}: {
|
|
10
|
+
changed: ViewableChangeEntry[]
|
|
11
|
+
viewableItems: ViewableItem[]
|
|
12
|
+
dividerKey: string
|
|
13
|
+
initialMessageId: string
|
|
14
|
+
}): boolean {
|
|
15
|
+
const dividerExited = changed.some(c => c.key === dividerKey && !c.isViewable)
|
|
16
|
+
if (!dividerExited) return false
|
|
17
|
+
|
|
18
|
+
const visibleMessageIds = viewableItems
|
|
19
|
+
.filter(v => v.item?.type === 'Message')
|
|
20
|
+
.map(v => v.item?.id)
|
|
21
|
+
.filter((id): id is string => !!id)
|
|
22
|
+
if (visibleMessageIds.length === 0) return false
|
|
23
|
+
|
|
24
|
+
return visibleMessageIds.every(id => id.localeCompare(initialMessageId) > 0)
|
|
25
|
+
}
|