chore: prettier format all files, brotli, Sentry release tagging, CI gates
Prettier: auto-formatted 103 files to fix baseline. Prettier check in CI is now a hard gate (removed continue-on-error). Brotli: installed libnginx-mod-http-brotli-filter/static. Enabled in nginx with brotli_static on for pre-compressed assets and comp_level 6. Sentry releases: deploy script now exports VITE_APP_VERSION=<git-short-sha> before building so each Sentry release maps to an exact commit. CI also passes github.sha as VITE_APP_VERSION. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -637,7 +637,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
// and either there are no unread messages or the latest message is from the current user.
|
||||
// If either condition is met, trigger the markAsRead function to send a read receipt.
|
||||
const _roomId = mEvt.getRoomId();
|
||||
if (_roomId) requestAnimationFrame(() => markAsRead(mx, _roomId, hideActivity));
|
||||
if (_roomId) requestAnimationFrame(() => markAsRead(mx, _roomId, hideActivity));
|
||||
}
|
||||
|
||||
if (!document.hasFocus() && !unreadInfo) {
|
||||
@@ -673,7 +673,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
) => {
|
||||
const evtTimeline = getEventTimeline(room, evtId);
|
||||
const absoluteIndex =
|
||||
evtTimeline && getEventIdAbsoluteIndex(timelineRef.current.linkedTimelines, evtTimeline, evtId);
|
||||
evtTimeline &&
|
||||
getEventIdAbsoluteIndex(timelineRef.current.linkedTimelines, evtTimeline, evtId);
|
||||
|
||||
if (typeof absoluteIndex === 'number') {
|
||||
const scrolled = scrollToItem(absoluteIndex, {
|
||||
@@ -1242,7 +1243,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
mEvent.getType() === 'm.poll.start' ||
|
||||
mEvent.getType() === 'org.matrix.msc3381.poll.start'
|
||||
)
|
||||
return <PollContent content={mEvent.getContent()} roomId={room.roomId} eventId={mEvent.getId() ?? undefined} />;
|
||||
return (
|
||||
<PollContent
|
||||
content={mEvent.getContent()}
|
||||
roomId={room.roomId}
|
||||
eventId={mEvent.getId() ?? undefined}
|
||||
/>
|
||||
);
|
||||
if (mEvent.getType() === MessageEvent.RoomMessageEncrypted)
|
||||
return (
|
||||
<Text>
|
||||
@@ -1371,7 +1378,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
{mEvent.isRedacted() ? (
|
||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
||||
) : (
|
||||
<PollContent content={mEvent.getContent()} roomId={room.roomId} eventId={mEvent.getId() ?? undefined} />
|
||||
<PollContent
|
||||
content={mEvent.getContent()}
|
||||
roomId={room.roomId}
|
||||
eventId={mEvent.getId() ?? undefined}
|
||||
/>
|
||||
)}
|
||||
</Message>
|
||||
);
|
||||
@@ -1424,7 +1435,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
{mEvent.isRedacted() ? (
|
||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
||||
) : (
|
||||
<PollContent content={mEvent.getContent()} roomId={room.roomId} eventId={mEvent.getId() ?? undefined} />
|
||||
<PollContent
|
||||
content={mEvent.getContent()}
|
||||
roomId={room.roomId}
|
||||
eventId={mEvent.getId() ?? undefined}
|
||||
/>
|
||||
)}
|
||||
</Message>
|
||||
);
|
||||
@@ -1608,12 +1623,38 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||
const timeJSX = (
|
||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact={messageLayout === MessageLayout.Compact}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Lock}
|
||||
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{' enabled end-to-end encryption'}</Text></Box>}
|
||||
<Event
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
time={timeJSX}
|
||||
iconSrc={Icons.Lock}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
<b>{senderName}</b>
|
||||
{' enabled end-to-end encryption'}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
);
|
||||
@@ -1623,14 +1664,45 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||
const joinRule = mEvent.getContent<{ join_rule?: string }>().join_rule ?? 'unknown';
|
||||
const ruleLabel: Record<string, string> = { public: 'public', invite: 'invite-only', knock: 'knock', restricted: 'restricted' };
|
||||
const ruleLabel: Record<string, string> = {
|
||||
public: 'public',
|
||||
invite: 'invite-only',
|
||||
knock: 'knock',
|
||||
restricted: 'restricted',
|
||||
};
|
||||
const timeJSX = (
|
||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact={messageLayout === MessageLayout.Compact}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Settings}
|
||||
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{` set room join rule to ${ruleLabel[joinRule] ?? joinRule}`}</Text></Box>}
|
||||
<Event
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
time={timeJSX}
|
||||
iconSrc={Icons.Settings}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
<b>{senderName}</b>
|
||||
{` set room join rule to ${ruleLabel[joinRule] ?? joinRule}`}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
);
|
||||
@@ -1641,12 +1713,38 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||
const access = mEvent.getContent<{ guest_access?: string }>().guest_access ?? 'unknown';
|
||||
const timeJSX = (
|
||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact={messageLayout === MessageLayout.Compact}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Settings}
|
||||
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{access === 'can_join' ? ' allowed guest access' : ' disabled guest access'}</Text></Box>}
|
||||
<Event
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
time={timeJSX}
|
||||
iconSrc={Icons.Settings}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
<b>{senderName}</b>
|
||||
{access === 'can_join' ? ' allowed guest access' : ' disabled guest access'}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
);
|
||||
@@ -1657,12 +1755,38 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||
const alias = mEvent.getContent<{ alias?: string }>().alias;
|
||||
const timeJSX = (
|
||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} hour24Clock={hour24Clock} dateFormatString={dateFormatString} />
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact={messageLayout === MessageLayout.Compact}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Event key={mEvent.getId()} data-message-item={item} data-message-id={mEventId} room={room} mEvent={mEvent} highlight={highlighted} messageSpacing={messageSpacing} canDelete={canRedact || mEvent.getSender() === mx.getUserId()} hideReadReceipts={hideActivity} showDeveloperTools={showDeveloperTools}>
|
||||
<EventContent messageLayout={messageLayout} time={timeJSX} iconSrc={Icons.Hash}
|
||||
content={<Box grow="Yes" direction="Column"><Text size="T300" priority="300"><b>{senderName}</b>{alias ? ` set room address to ${alias}` : ' removed room address'}</Text></Box>}
|
||||
<Event
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
highlight={highlighted}
|
||||
messageSpacing={messageSpacing}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
messageLayout={messageLayout}
|
||||
time={timeJSX}
|
||||
iconSrc={Icons.Hash}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
<b>{senderName}</b>
|
||||
{alias ? ` set room address to ${alias}` : ' removed room address'}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
);
|
||||
@@ -1831,9 +1955,15 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >>> 1;
|
||||
const [base, len] = timelineSegments[mid];
|
||||
if (item < base) { hi = mid - 1; }
|
||||
else if (item >= base + len) { lo = mid + 1; }
|
||||
else { eventTimeline = timelineSegments[mid][2]; baseIndex = base; break; }
|
||||
if (item < base) {
|
||||
hi = mid - 1;
|
||||
} else if (item >= base + len) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
eventTimeline = timelineSegments[mid][2];
|
||||
baseIndex = base;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!eventTimeline) return null;
|
||||
@@ -1935,131 +2065,131 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
|
||||
return (
|
||||
<ReadPositionsContext.Provider value={readPositions}>
|
||||
<Box grow="Yes" style={{ position: 'relative' }}>
|
||||
{unreadInfo?.readUptoEventId && !unreadInfo?.inLiveTimeline && (
|
||||
<TimelineFloat position="Top">
|
||||
<Chip
|
||||
variant="Primary"
|
||||
radii="Pill"
|
||||
outlined
|
||||
before={<Icon size="50" src={Icons.MessageUnread} />}
|
||||
onClick={handleJumpToUnread}
|
||||
>
|
||||
<Text size="L400">Jump to Unread</Text>
|
||||
</Chip>
|
||||
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
outlined
|
||||
before={<Icon size="50" src={Icons.CheckTwice} />}
|
||||
onClick={handleMarkAsRead}
|
||||
>
|
||||
<Text size="L400">Mark as Read</Text>
|
||||
</Chip>
|
||||
</TimelineFloat>
|
||||
)}
|
||||
<Scroll ref={scrollRef} visibility="Hover">
|
||||
<Box
|
||||
direction="Column"
|
||||
justifyContent="End"
|
||||
style={{ minHeight: '100%', padding: `${config.space.S600} 0` }}
|
||||
>
|
||||
{!canPaginateBack && rangeAtStart && getItems().length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
padding: `${config.space.S700} ${config.space.S400} ${config.space.S600} ${
|
||||
messageLayout === MessageLayout.Compact ? config.space.S400 : toRem(64)
|
||||
}`,
|
||||
}}
|
||||
<Box grow="Yes" style={{ position: 'relative' }}>
|
||||
{unreadInfo?.readUptoEventId && !unreadInfo?.inLiveTimeline && (
|
||||
<TimelineFloat position="Top">
|
||||
<Chip
|
||||
variant="Primary"
|
||||
radii="Pill"
|
||||
outlined
|
||||
before={<Icon size="50" src={Icons.MessageUnread} />}
|
||||
onClick={handleJumpToUnread}
|
||||
>
|
||||
<RoomIntro room={room} />
|
||||
</div>
|
||||
)}
|
||||
{(canPaginateBack || !rangeAtStart) &&
|
||||
(messageLayout === MessageLayout.Compact ? (
|
||||
<>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase ref={observeBackAnchor}>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase ref={observeBackAnchor}>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
))}
|
||||
<Text size="L400">Jump to Unread</Text>
|
||||
</Chip>
|
||||
|
||||
{getItems().map(eventRenderer)}
|
||||
|
||||
{(!liveTimelineLinked || !rangeAtEnd) &&
|
||||
(messageLayout === MessageLayout.Compact ? (
|
||||
<>
|
||||
<MessageBase ref={observeFrontAnchor}>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MessageBase ref={observeFrontAnchor}>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
))}
|
||||
<span ref={atBottomAnchorRef} />
|
||||
</Box>
|
||||
</Scroll>
|
||||
{!atBottom && (
|
||||
<TimelineFloat position="Bottom">
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
outlined
|
||||
before={<Icon size="50" src={Icons.ArrowBottom} />}
|
||||
onClick={handleJumpToLatest}
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
outlined
|
||||
before={<Icon size="50" src={Icons.CheckTwice} />}
|
||||
onClick={handleMarkAsRead}
|
||||
>
|
||||
<Text size="L400">Mark as Read</Text>
|
||||
</Chip>
|
||||
</TimelineFloat>
|
||||
)}
|
||||
<Scroll ref={scrollRef} visibility="Hover">
|
||||
<Box
|
||||
direction="Column"
|
||||
justifyContent="End"
|
||||
style={{ minHeight: '100%', padding: `${config.space.S600} 0` }}
|
||||
>
|
||||
<Text size="L400">Jump to Latest</Text>
|
||||
</Chip>
|
||||
</TimelineFloat>
|
||||
)}
|
||||
</Box>
|
||||
{!canPaginateBack && rangeAtStart && getItems().length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
padding: `${config.space.S700} ${config.space.S400} ${config.space.S600} ${
|
||||
messageLayout === MessageLayout.Compact ? config.space.S400 : toRem(64)
|
||||
}`,
|
||||
}}
|
||||
>
|
||||
<RoomIntro room={room} />
|
||||
</div>
|
||||
)}
|
||||
{(canPaginateBack || !rangeAtStart) &&
|
||||
(messageLayout === MessageLayout.Compact ? (
|
||||
<>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase ref={observeBackAnchor}>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase ref={observeBackAnchor}>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
))}
|
||||
|
||||
{getItems().map(eventRenderer)}
|
||||
|
||||
{(!liveTimelineLinked || !rangeAtEnd) &&
|
||||
(messageLayout === MessageLayout.Compact ? (
|
||||
<>
|
||||
<MessageBase ref={observeFrontAnchor}>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<CompactPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MessageBase ref={observeFrontAnchor}>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
<MessageBase>
|
||||
<DefaultPlaceholder key={getItems().length} />
|
||||
</MessageBase>
|
||||
</>
|
||||
))}
|
||||
<span ref={atBottomAnchorRef} />
|
||||
</Box>
|
||||
</Scroll>
|
||||
{!atBottom && (
|
||||
<TimelineFloat position="Bottom">
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
outlined
|
||||
before={<Icon size="50" src={Icons.ArrowBottom} />}
|
||||
onClick={handleJumpToLatest}
|
||||
>
|
||||
<Text size="L400">Jump to Latest</Text>
|
||||
</Chip>
|
||||
</TimelineFloat>
|
||||
)}
|
||||
</Box>
|
||||
</ReadPositionsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user