thread view - WIP

This commit is contained in:
Ajay Bura 2025-11-03 15:29:51 +05:30
parent b5fd41f862
commit f35aa384e5
7 changed files with 137 additions and 11 deletions

View file

@ -13,6 +13,8 @@ import { useKeyDown } from '../../hooks/useKeyDown';
import { markAsRead } from '../../utils/notifications';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomMembers } from '../../hooks/useRoomMembers';
import { ThreadView } from './thread-view';
import { useActiveThread } from '../../state/hooks/roomToActiveThread';
export function Room() {
const { eventId } = useParams();
@ -25,6 +27,8 @@ export function Room() {
const powerLevels = usePowerLevels(room);
const members = useRoomMembers(mx, room.roomId);
const threadId = useActiveThread(room.roomId);
useKeyDown(
window,
useCallback(
@ -41,11 +45,19 @@ export function Room() {
<PowerLevelsContextProvider value={powerLevels}>
<Box grow="Yes">
<RoomView room={room} eventId={eventId} />
{screenSize === ScreenSize.Desktop && isDrawer && (
{threadId ? (
<>
<Line variant="Surface" direction="Vertical" size="300" />
<ThreadView threadId={threadId} />
</>
) : (
screenSize === ScreenSize.Desktop &&
isDrawer && (
<>
<Line variant="Background" direction="Vertical" size="300" />
<MembersDrawer key={room.roomId} room={room} members={members} />
</>
)
)}
</Box>
</PowerLevelsContextProvider>

View file

@ -138,6 +138,7 @@ import {
getTimelinesEventsCount,
timelineToEventsCount,
} from './utils';
import { useThreadSelector } from '../../state/hooks/roomToActiveThread';
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
({ position, className, ...props }, ref) => (
@ -429,6 +430,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const spoilerClickHandler = useSpoilerClickHandler();
const openUserRoomProfile = useOpenUserRoomProfile();
const space = useSpaceOptionally();
const handleThreadClick = useThreadSelector(room.roomId);
const imagePackRooms: Room[] = useImagePackRooms(room.roomId, roomToParents);
@ -961,6 +963,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
},
[editor]
);
const { t } = useTranslation();
const renderMatrixEvent = useMatrixEventRenderer<
@ -1060,10 +1063,12 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
<ThreadSelectorContainer>
<ThreadSelector
room={room}
threadId={mEventId}
threadDetail={threadDetail}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
outlined={messageLayout === MessageLayout.Bubble}
onClick={handleThreadClick}
/>
</ThreadSelectorContainer>
)}

View file

@ -0,0 +1,79 @@
import React from 'react';
import { Box, Icon, IconButton, Icons, Scroll, Text, Tooltip, TooltipProvider } from 'folds';
import classNames from 'classnames';
import FocusTrap from 'focus-trap-react';
import { Page, PageHeader } from '../../../components/page';
import * as css from './styles.css';
import { useRoom } from '../../../hooks/useRoom';
import { useThreadClose } from '../../../state/hooks/roomToActiveThread';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
type ThreadViewProps = {
threadId: string;
};
export function ThreadView({ threadId }: ThreadViewProps) {
const room = useRoom();
const screenSize = useScreenSizeContext();
const floating = screenSize !== ScreenSize.Desktop;
const closeThread = useThreadClose(room.roomId);
const thread = room.getThread(threadId);
const events = thread?.events ?? [];
return (
<FocusTrap
paused={!floating}
focusTrapOptions={{
initialFocus: false,
clickOutsideDeactivates: true,
onDeactivate: floating ? closeThread : undefined,
}}
>
<Page
className={classNames(css.ThreadView, {
[css.ThreadViewFloating]: floating,
})}
>
<PageHeader>
<Box grow="Yes" alignItems="Center" gap="200">
<Box grow="Yes">
<Text size="H5" truncate>
Thread
</Text>
</Box>
<Box shrink="No" alignItems="Center">
<TooltipProvider
position="Bottom"
align="End"
offset={4}
tooltip={
<Tooltip>
<Text>Close</Text>
</Tooltip>
}
>
{(triggerRef) => (
<IconButton ref={triggerRef} variant="Surface" onClick={closeThread}>
<Icon src={Icons.Cross} />
</IconButton>
)}
</TooltipProvider>
</Box>
</Box>
</PageHeader>
<Box grow="Yes" direction="Column">
<Scroll visibility="Hover" hideTrack>
<div>
{events.map((mEvent) => (
<p style={{ padding: `8px 16px` }} key={mEvent.getId()}>
{mEvent.sender?.name}: {mEvent.getContent().body}
</p>
))}
</div>
</Scroll>
</Box>
</Page>
</FocusTrap>
);
}

View file

@ -0,0 +1 @@
export * from './ThreadView';

View file

@ -0,0 +1,21 @@
import { style } from '@vanilla-extract/css';
import { config, toRem } from 'folds';
export const ThreadView = style({
width: toRem(456),
flexShrink: 0,
flexGrow: 0,
});
export const ThreadViewFloating = style({
position: 'absolute',
right: 0,
top: 0,
bottom: 0,
zIndex: 1,
maxWidth: toRem(456),
flexShrink: 1,
width: '100vw',
boxShadow: config.shadow.E400,
});

View file

@ -77,7 +77,7 @@ import * as css from './ThreadsMenu.css';
type ThreadMessageProps = {
room: Room;
event: MatrixEvent;
renderContent: RenderMatrixEvent<[MatrixEvent, string, GetContentCallback]>;
renderContent: RenderMatrixEvent<[string, MatrixEvent, string, GetContentCallback]>;
onOpen: (roomId: string, eventId: string) => void;
getMemberPowerTag: GetMemberPowerTag;
accessibleTagColors: Map<string, string>;
@ -134,6 +134,10 @@ function ThreadMessage({
const usernameColor = legacyUsernameColor ? colorMXID(sender) : tagColor;
const mEventId = event.getId();
if (!mEventId) return null;
return (
<ModernLayout
before={
@ -179,7 +183,7 @@ function ThreadMessage({
legacyUsernameColor={legacyUsernameColor}
/>
)}
{renderContent(event.getType(), false, event, displayName, getContent)}
{renderContent(event.getType(), false, mEventId, event, displayName, getContent)}
</ModernLayout>
);
}
@ -238,9 +242,11 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication]
);
const renderMatrixEvent = useMatrixEventRenderer<[MatrixEvent, string, GetContentCallback]>(
const renderMatrixEvent = useMatrixEventRenderer<
[string, MatrixEvent, string, GetContentCallback]
>(
{
[MessageEvent.RoomMessage]: (event, displayName, getContent) => {
[MessageEvent.RoomMessage]: (eventId, event, displayName, getContent) => {
if (event.isRedacted()) {
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
}
@ -265,6 +271,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
<ThreadSelectorContainer>
<ThreadSelector
room={room}
threadId={eventId}
threadDetail={threadDetail}
outlined
hour24Clock={hour24Clock}
@ -275,8 +282,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
</>
);
},
[MessageEvent.RoomMessageEncrypted]: (mEvent, displayName) => {
const eventId = mEvent.getId()!;
[MessageEvent.RoomMessageEncrypted]: (eventId, mEvent, displayName) => {
const evtTimeline = room.getTimelineForEvent(eventId);
return (
@ -333,7 +339,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
</EncryptedContent>
);
},
[MessageEvent.Sticker]: (event, displayName, getContent) => {
[MessageEvent.Sticker]: (eventId, event, displayName, getContent) => {
if (event.isRedacted()) {
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
}
@ -357,6 +363,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
<ThreadSelectorContainer>
<ThreadSelector
room={room}
threadId={eventId}
threadDetail={threadDetail}
outlined
hour24Clock={hour24Clock}
@ -369,7 +376,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
},
},
undefined,
(event) => {
(eventId, event) => {
if (event.isRedacted()) {
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
}

View file

@ -203,6 +203,7 @@ export const isNotificationEvent = (mEvent: MatrixEvent) => {
if (mEvent.isRedacted()) return false;
if (mEvent.getRelation()?.rel_type === 'm.replace') return false;
if (mEvent.getRelation()?.rel_type === 'm.thread') return false;
return true;
};