mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 22:40:29 +03:00
thread view - WIP
This commit is contained in:
parent
b5fd41f862
commit
f35aa384e5
7 changed files with 137 additions and 11 deletions
|
|
@ -13,6 +13,8 @@ import { useKeyDown } from '../../hooks/useKeyDown';
|
||||||
import { markAsRead } from '../../utils/notifications';
|
import { markAsRead } from '../../utils/notifications';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useRoomMembers } from '../../hooks/useRoomMembers';
|
import { useRoomMembers } from '../../hooks/useRoomMembers';
|
||||||
|
import { ThreadView } from './thread-view';
|
||||||
|
import { useActiveThread } from '../../state/hooks/roomToActiveThread';
|
||||||
|
|
||||||
export function Room() {
|
export function Room() {
|
||||||
const { eventId } = useParams();
|
const { eventId } = useParams();
|
||||||
|
|
@ -25,6 +27,8 @@ export function Room() {
|
||||||
const powerLevels = usePowerLevels(room);
|
const powerLevels = usePowerLevels(room);
|
||||||
const members = useRoomMembers(mx, room.roomId);
|
const members = useRoomMembers(mx, room.roomId);
|
||||||
|
|
||||||
|
const threadId = useActiveThread(room.roomId);
|
||||||
|
|
||||||
useKeyDown(
|
useKeyDown(
|
||||||
window,
|
window,
|
||||||
useCallback(
|
useCallback(
|
||||||
|
|
@ -41,11 +45,19 @@ export function Room() {
|
||||||
<PowerLevelsContextProvider value={powerLevels}>
|
<PowerLevelsContextProvider value={powerLevels}>
|
||||||
<Box grow="Yes">
|
<Box grow="Yes">
|
||||||
<RoomView room={room} eventId={eventId} />
|
<RoomView room={room} eventId={eventId} />
|
||||||
{screenSize === ScreenSize.Desktop && isDrawer && (
|
{threadId ? (
|
||||||
<>
|
<>
|
||||||
<Line variant="Background" direction="Vertical" size="300" />
|
<Line variant="Surface" direction="Vertical" size="300" />
|
||||||
<MembersDrawer key={room.roomId} room={room} members={members} />
|
<ThreadView threadId={threadId} />
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
screenSize === ScreenSize.Desktop &&
|
||||||
|
isDrawer && (
|
||||||
|
<>
|
||||||
|
<Line variant="Background" direction="Vertical" size="300" />
|
||||||
|
<MembersDrawer key={room.roomId} room={room} members={members} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</PowerLevelsContextProvider>
|
</PowerLevelsContextProvider>
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ import {
|
||||||
getTimelinesEventsCount,
|
getTimelinesEventsCount,
|
||||||
timelineToEventsCount,
|
timelineToEventsCount,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
import { useThreadSelector } from '../../state/hooks/roomToActiveThread';
|
||||||
|
|
||||||
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
||||||
({ position, className, ...props }, ref) => (
|
({ position, className, ...props }, ref) => (
|
||||||
|
|
@ -429,6 +430,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const spoilerClickHandler = useSpoilerClickHandler();
|
const spoilerClickHandler = useSpoilerClickHandler();
|
||||||
const openUserRoomProfile = useOpenUserRoomProfile();
|
const openUserRoomProfile = useOpenUserRoomProfile();
|
||||||
const space = useSpaceOptionally();
|
const space = useSpaceOptionally();
|
||||||
|
const handleThreadClick = useThreadSelector(room.roomId);
|
||||||
|
|
||||||
const imagePackRooms: Room[] = useImagePackRooms(room.roomId, roomToParents);
|
const imagePackRooms: Room[] = useImagePackRooms(room.roomId, roomToParents);
|
||||||
|
|
||||||
|
|
@ -961,6 +963,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
},
|
},
|
||||||
[editor]
|
[editor]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const renderMatrixEvent = useMatrixEventRenderer<
|
const renderMatrixEvent = useMatrixEventRenderer<
|
||||||
|
|
@ -1060,10 +1063,12 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<ThreadSelectorContainer>
|
<ThreadSelectorContainer>
|
||||||
<ThreadSelector
|
<ThreadSelector
|
||||||
room={room}
|
room={room}
|
||||||
|
threadId={mEventId}
|
||||||
threadDetail={threadDetail}
|
threadDetail={threadDetail}
|
||||||
hour24Clock={hour24Clock}
|
hour24Clock={hour24Clock}
|
||||||
dateFormatString={dateFormatString}
|
dateFormatString={dateFormatString}
|
||||||
outlined={messageLayout === MessageLayout.Bubble}
|
outlined={messageLayout === MessageLayout.Bubble}
|
||||||
|
onClick={handleThreadClick}
|
||||||
/>
|
/>
|
||||||
</ThreadSelectorContainer>
|
</ThreadSelectorContainer>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
79
src/app/features/room/thread-view/ThreadView.tsx
Normal file
79
src/app/features/room/thread-view/ThreadView.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/app/features/room/thread-view/index.tsx
Normal file
1
src/app/features/room/thread-view/index.tsx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './ThreadView';
|
||||||
21
src/app/features/room/thread-view/styles.css.ts
Normal file
21
src/app/features/room/thread-view/styles.css.ts
Normal 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,
|
||||||
|
});
|
||||||
|
|
@ -77,7 +77,7 @@ import * as css from './ThreadsMenu.css';
|
||||||
type ThreadMessageProps = {
|
type ThreadMessageProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
event: MatrixEvent;
|
event: MatrixEvent;
|
||||||
renderContent: RenderMatrixEvent<[MatrixEvent, string, GetContentCallback]>;
|
renderContent: RenderMatrixEvent<[string, MatrixEvent, string, GetContentCallback]>;
|
||||||
onOpen: (roomId: string, eventId: string) => void;
|
onOpen: (roomId: string, eventId: string) => void;
|
||||||
getMemberPowerTag: GetMemberPowerTag;
|
getMemberPowerTag: GetMemberPowerTag;
|
||||||
accessibleTagColors: Map<string, string>;
|
accessibleTagColors: Map<string, string>;
|
||||||
|
|
@ -134,6 +134,10 @@ function ThreadMessage({
|
||||||
|
|
||||||
const usernameColor = legacyUsernameColor ? colorMXID(sender) : tagColor;
|
const usernameColor = legacyUsernameColor ? colorMXID(sender) : tagColor;
|
||||||
|
|
||||||
|
const mEventId = event.getId();
|
||||||
|
|
||||||
|
if (!mEventId) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModernLayout
|
<ModernLayout
|
||||||
before={
|
before={
|
||||||
|
|
@ -179,7 +183,7 @@ function ThreadMessage({
|
||||||
legacyUsernameColor={legacyUsernameColor}
|
legacyUsernameColor={legacyUsernameColor}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{renderContent(event.getType(), false, event, displayName, getContent)}
|
{renderContent(event.getType(), false, mEventId, event, displayName, getContent)}
|
||||||
</ModernLayout>
|
</ModernLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -238,9 +242,11 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
|
||||||
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication]
|
[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()) {
|
if (event.isRedacted()) {
|
||||||
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
|
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +271,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
|
||||||
<ThreadSelectorContainer>
|
<ThreadSelectorContainer>
|
||||||
<ThreadSelector
|
<ThreadSelector
|
||||||
room={room}
|
room={room}
|
||||||
|
threadId={eventId}
|
||||||
threadDetail={threadDetail}
|
threadDetail={threadDetail}
|
||||||
outlined
|
outlined
|
||||||
hour24Clock={hour24Clock}
|
hour24Clock={hour24Clock}
|
||||||
|
|
@ -275,8 +282,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[MessageEvent.RoomMessageEncrypted]: (mEvent, displayName) => {
|
[MessageEvent.RoomMessageEncrypted]: (eventId, mEvent, displayName) => {
|
||||||
const eventId = mEvent.getId()!;
|
|
||||||
const evtTimeline = room.getTimelineForEvent(eventId);
|
const evtTimeline = room.getTimelineForEvent(eventId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -333,7 +339,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
|
||||||
</EncryptedContent>
|
</EncryptedContent>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[MessageEvent.Sticker]: (event, displayName, getContent) => {
|
[MessageEvent.Sticker]: (eventId, event, displayName, getContent) => {
|
||||||
if (event.isRedacted()) {
|
if (event.isRedacted()) {
|
||||||
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
|
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
|
||||||
}
|
}
|
||||||
|
|
@ -357,6 +363,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
|
||||||
<ThreadSelectorContainer>
|
<ThreadSelectorContainer>
|
||||||
<ThreadSelector
|
<ThreadSelector
|
||||||
room={room}
|
room={room}
|
||||||
|
threadId={eventId}
|
||||||
threadDetail={threadDetail}
|
threadDetail={threadDetail}
|
||||||
outlined
|
outlined
|
||||||
hour24Clock={hour24Clock}
|
hour24Clock={hour24Clock}
|
||||||
|
|
@ -369,7 +376,7 @@ export function ThreadsTimeline({ timelines, requestClose }: ThreadsTimelineProp
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(event) => {
|
(eventId, event) => {
|
||||||
if (event.isRedacted()) {
|
if (event.isRedacted()) {
|
||||||
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
|
return <RedactedContent reason={event.getUnsigned().redacted_because?.content.reason} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,7 @@ export const isNotificationEvent = (mEvent: MatrixEvent) => {
|
||||||
|
|
||||||
if (mEvent.isRedacted()) return false;
|
if (mEvent.isRedacted()) return false;
|
||||||
if (mEvent.getRelation()?.rel_type === 'm.replace') return false;
|
if (mEvent.getRelation()?.rel_type === 'm.replace') return false;
|
||||||
|
if (mEvent.getRelation()?.rel_type === 'm.thread') return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue