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:
@@ -41,7 +41,12 @@ export function CallChatView() {
|
||||
}
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<IconButton ref={triggerRef} variant="Surface" onClick={handleClose} aria-label="Close call chat">
|
||||
<IconButton
|
||||
ref={triggerRef}
|
||||
variant="Surface"
|
||||
onClick={handleClose}
|
||||
aria-label="Close call chat"
|
||||
>
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
} from 'folds';
|
||||
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
const GifPicker = React.lazy(() => import('../../components/GifPicker').then((m) => ({ default: m.GifPicker })));
|
||||
const GifPicker = React.lazy(() =>
|
||||
import('../../components/GifPicker').then((m) => ({ default: m.GifPicker }))
|
||||
);
|
||||
import { useClientConfig } from '../../hooks/useClientConfig';
|
||||
import {
|
||||
CustomEditor,
|
||||
@@ -57,7 +59,9 @@ import {
|
||||
getMentions,
|
||||
} from '../../components/editor';
|
||||
import { EmojiBoardTab } from '../../components/emoji-board/types';
|
||||
const EmojiBoard = React.lazy(() => import('../../components/emoji-board').then((m) => ({ default: m.EmojiBoard })));
|
||||
const EmojiBoard = React.lazy(() =>
|
||||
import('../../components/emoji-board').then((m) => ({ default: m.EmojiBoard }))
|
||||
);
|
||||
import { UseStateProvider } from '../../components/UseStateProvider';
|
||||
import {
|
||||
TUploadContent,
|
||||
@@ -143,7 +147,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const alive = useAlive();
|
||||
const alive = useAlive();
|
||||
const [msgDraft, setMsgDraft] = useAtom(roomIdToMsgDraftAtomFamily(roomId));
|
||||
const [replyDraft, setReplyDraft] = useAtom(roomIdToReplyDraftAtomFamily(roomId));
|
||||
const replyUserID = replyDraft?.userId;
|
||||
@@ -467,8 +471,15 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
async (gifUrl: string, w: number, h: number) => {
|
||||
try {
|
||||
// Only fetch from trusted Giphy CDN domains
|
||||
const allowed = ['media.giphy.com', 'i.giphy.com', 'media0.giphy.com',
|
||||
'media1.giphy.com', 'media2.giphy.com', 'media3.giphy.com', 'media4.giphy.com'];
|
||||
const allowed = [
|
||||
'media.giphy.com',
|
||||
'i.giphy.com',
|
||||
'media0.giphy.com',
|
||||
'media1.giphy.com',
|
||||
'media2.giphy.com',
|
||||
'media3.giphy.com',
|
||||
'media4.giphy.com',
|
||||
];
|
||||
const { hostname } = new URL(gifUrl);
|
||||
if (!allowed.includes(hostname)) return;
|
||||
|
||||
@@ -695,24 +706,26 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
: emojiBtnRef.current?.getBoundingClientRect() ?? undefined
|
||||
}
|
||||
content={
|
||||
<React.Suspense fallback={null}><EmojiBoard
|
||||
tab={emojiBoardTab}
|
||||
onTabChange={setEmojiBoardTab}
|
||||
imagePackRooms={imagePackRooms}
|
||||
returnFocusOnDeactivate={false}
|
||||
onEmojiSelect={handleEmoticonSelect}
|
||||
onCustomEmojiSelect={handleEmoticonSelect}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
requestClose={() => {
|
||||
setEmojiBoardTab((t) => {
|
||||
if (t) {
|
||||
if (!mobileOrTablet()) ReactEditor.focus(editor);
|
||||
return undefined;
|
||||
}
|
||||
return t;
|
||||
});
|
||||
}}
|
||||
/></React.Suspense>
|
||||
<React.Suspense fallback={null}>
|
||||
<EmojiBoard
|
||||
tab={emojiBoardTab}
|
||||
onTabChange={setEmojiBoardTab}
|
||||
imagePackRooms={imagePackRooms}
|
||||
returnFocusOnDeactivate={false}
|
||||
onEmojiSelect={handleEmoticonSelect}
|
||||
onCustomEmojiSelect={handleEmoticonSelect}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
requestClose={() => {
|
||||
setEmojiBoardTab((t) => {
|
||||
if (t) {
|
||||
if (!mobileOrTablet()) ReactEditor.focus(editor);
|
||||
return undefined;
|
||||
}
|
||||
return t;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</React.Suspense>
|
||||
}
|
||||
>
|
||||
{!hideStickerBtn && (
|
||||
@@ -765,11 +778,13 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
: undefined
|
||||
}
|
||||
content={
|
||||
<React.Suspense fallback={null}><GifPicker
|
||||
apiKey={gifApiKey}
|
||||
onSelect={handleGifSelect}
|
||||
requestClose={() => setGifOpen(false)}
|
||||
/></React.Suspense>
|
||||
<React.Suspense fallback={null}>
|
||||
<GifPicker
|
||||
apiKey={gifApiKey}
|
||||
onSelect={handleGifSelect}
|
||||
requestClose={() => setGifOpen(false)}
|
||||
/>
|
||||
</React.Suspense>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
@@ -798,7 +813,15 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
</UseStateProvider>
|
||||
)}
|
||||
{gifError && (
|
||||
<Text size="T100" style={{ color: 'var(--tc-danger-normal)', padding: '2px 6px', alignSelf: 'center', whiteSpace: 'nowrap' }}>
|
||||
<Text
|
||||
size="T100"
|
||||
style={{
|
||||
color: 'var(--tc-danger-normal)',
|
||||
padding: '2px 6px',
|
||||
alignSelf: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{gifError}
|
||||
</Text>
|
||||
)}
|
||||
@@ -811,14 +834,28 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
title="Share location"
|
||||
>
|
||||
{locating ? (
|
||||
<Text size="T200" style={{ fontWeight: 800, fontSize: '10px', letterSpacing: '0.04em', lineHeight: 1 }}>
|
||||
<Text
|
||||
size="T200"
|
||||
style={{
|
||||
fontWeight: 800,
|
||||
fontSize: '10px',
|
||||
letterSpacing: '0.04em',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
...
|
||||
</Text>
|
||||
) : (
|
||||
<Icon src={Icons.Pin} size="100" />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton onClick={submit} variant="SurfaceVariant" size="300" radii="300" aria-label="Send message">
|
||||
<IconButton
|
||||
onClick={submit}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
aria-label="Send message"
|
||||
>
|
||||
<Icon src={Icons.Send} />
|
||||
</IconButton>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,8 +56,6 @@ const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export function RoomView({ eventId }: { eventId?: string }) {
|
||||
const roomInputRef = useRef<HTMLDivElement>(null);
|
||||
const roomViewRef = useRef<HTMLDivElement>(null);
|
||||
@@ -98,8 +96,9 @@ export function RoomView({ eventId }: { eventId?: string }) {
|
||||
)
|
||||
);
|
||||
|
||||
const chatBgStyle = useMemo(
|
||||
() => getChatBg(lotusTerminal && chatBackground === 'none' ? 'tactical' : chatBackground, isDark),
|
||||
const chatBgStyle = useMemo(
|
||||
() =>
|
||||
getChatBg(lotusTerminal && chatBackground === 'none' ? 'tactical' : chatBackground, isDark),
|
||||
[chatBackground, lotusTerminal, isDark]
|
||||
);
|
||||
|
||||
|
||||
@@ -533,7 +533,12 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
|
||||
}
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<IconButton fill="None" ref={triggerRef} onClick={handleSearchClick} aria-label="Search">
|
||||
<IconButton
|
||||
fill="None"
|
||||
ref={triggerRef}
|
||||
onClick={handleSearchClick}
|
||||
aria-label="Search"
|
||||
>
|
||||
<Icon size="400" src={Icons.Search} />
|
||||
</IconButton>
|
||||
)}
|
||||
@@ -596,11 +601,13 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
|
||||
</FocusTrap>
|
||||
}
|
||||
/>
|
||||
{!room.isCallRoom() && livekitSupported && rtcSupported && hasCallPermission &&
|
||||
(direct || (room.getJoinRule() === 'invite' &&
|
||||
getStateEvents(room, StateEvent.SpaceParent).length === 0)) && (
|
||||
<CallButton />
|
||||
)}
|
||||
{!room.isCallRoom() &&
|
||||
livekitSupported &&
|
||||
rtcSupported &&
|
||||
hasCallPermission &&
|
||||
(direct ||
|
||||
(room.getJoinRule() === 'invite' &&
|
||||
getStateEvents(room, StateEvent.SpaceParent).length === 0)) && <CallButton />}
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<TooltipProvider
|
||||
position="Bottom"
|
||||
@@ -616,7 +623,12 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
|
||||
}
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<IconButton fill="None" ref={triggerRef} onClick={handleMemberToggle} aria-label="Toggle member list">
|
||||
<IconButton
|
||||
fill="None"
|
||||
ref={triggerRef}
|
||||
onClick={handleMemberToggle}
|
||||
aria-label="Toggle member list"
|
||||
>
|
||||
<Icon size="400" src={Icons.User} />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
@@ -117,7 +117,13 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>(
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<IconButton title="Drop Typing Status" aria-label="Drop typing status" size="300" radii="Pill" onClick={handleDropAll}>
|
||||
<IconButton
|
||||
title="Drop Typing Status"
|
||||
aria-label="Drop typing status"
|
||||
size="300"
|
||||
radii="Pill"
|
||||
onClick={handleDropAll}
|
||||
>
|
||||
<Icon size="50" src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
@@ -57,7 +57,12 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
||||
>
|
||||
<Modal
|
||||
size="400"
|
||||
style={{ maxHeight: '440px', borderRadius: config.radii.R500, display: 'flex', flexDirection: 'column' }}
|
||||
style={{
|
||||
maxHeight: '440px',
|
||||
borderRadius: config.radii.R500,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
direction="Column"
|
||||
@@ -89,11 +94,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
|
||||
) : (
|
||||
<Box grow="Yes" style={{ minHeight: 0 }}>
|
||||
<Scroll size="300" hideTrack visibility="Hover">
|
||||
<Box
|
||||
direction="Column"
|
||||
gap="100"
|
||||
style={{ padding: config.space.S200 }}
|
||||
>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S200 }}>
|
||||
{filtered.slice(0, 60).map((room) => (
|
||||
<MenuItem
|
||||
key={room.roomId}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user