feat(syslines): render membership and room-state events as sender-anchored chat bubbles via StreamLayout instead of thin rail syslines
This commit is contained in:
parent
a893e86d92
commit
8400ef54ee
3 changed files with 175 additions and 112 deletions
|
|
@ -90,6 +90,7 @@ import {
|
|||
Event,
|
||||
CallMessage,
|
||||
CallAggregate,
|
||||
SyslineMessage,
|
||||
EncryptedContent,
|
||||
useMessageInteractionHandlers,
|
||||
} from './message';
|
||||
|
|
@ -1849,44 +1850,23 @@ export function RoomTimeline({
|
|||
|
||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||
const parsed = parseMemberEvent(mEvent);
|
||||
const iconSrc =
|
||||
parsed.icon === Icons.ArrowGoRightPlus ? Icons.ArrowGoRight : parsed.icon;
|
||||
|
||||
const timeJSX = (
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Event
|
||||
<SyslineMessage
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
body={parsed.body}
|
||||
highlight={highlighted}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
time={timeJSX}
|
||||
railStart={streamRailStart}
|
||||
railEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
iconSrc={iconSrc}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
{parsed.body}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
streamRailStart={streamRailStart}
|
||||
streamRailEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[StateEvent.RoomName]: (
|
||||
|
|
@ -1900,44 +1880,30 @@ export function RoomTimeline({
|
|||
) => {
|
||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||
|
||||
const timeJSX = (
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact
|
||||
/>
|
||||
);
|
||||
|
||||
const senderName =
|
||||
getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
||||
return (
|
||||
<Event
|
||||
<SyslineMessage
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
body={
|
||||
<>
|
||||
<b>{senderName}</b>
|
||||
{t('Organisms.RoomCommon.changed_room_name')}
|
||||
</>
|
||||
}
|
||||
highlight={highlighted}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
time={timeJSX}
|
||||
railStart={streamRailStart}
|
||||
railEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
iconSrc={Icons.Hash}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
<b>{senderName}</b>
|
||||
{t('Organisms.RoomCommon.changed_room_name')}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
streamRailStart={streamRailStart}
|
||||
streamRailEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[StateEvent.RoomTopic]: (
|
||||
|
|
@ -1951,44 +1917,30 @@ export function RoomTimeline({
|
|||
) => {
|
||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||
|
||||
const timeJSX = (
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact
|
||||
/>
|
||||
);
|
||||
|
||||
const senderName =
|
||||
getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
||||
return (
|
||||
<Event
|
||||
<SyslineMessage
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
body={
|
||||
<>
|
||||
<b>{senderName}</b>
|
||||
{' changed room topic'}
|
||||
</>
|
||||
}
|
||||
highlight={highlighted}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
time={timeJSX}
|
||||
railStart={streamRailStart}
|
||||
railEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
iconSrc={Icons.Hash}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
<b>{senderName}</b>
|
||||
{' changed room topic'}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
streamRailStart={streamRailStart}
|
||||
streamRailEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[StateEvent.RoomAvatar]: (
|
||||
|
|
@ -2002,44 +1954,30 @@ export function RoomTimeline({
|
|||
) => {
|
||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||
|
||||
const timeJSX = (
|
||||
<Time
|
||||
ts={mEvent.getTs()}
|
||||
compact
|
||||
/>
|
||||
);
|
||||
|
||||
const senderName =
|
||||
getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
||||
return (
|
||||
<Event
|
||||
<SyslineMessage
|
||||
key={mEvent.getId()}
|
||||
data-message-item={item}
|
||||
data-message-id={mEventId}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
body={
|
||||
<>
|
||||
<b>{senderName}</b>
|
||||
{' changed room avatar'}
|
||||
</>
|
||||
}
|
||||
highlight={highlighted}
|
||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
>
|
||||
<EventContent
|
||||
time={timeJSX}
|
||||
railStart={streamRailStart}
|
||||
railEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
iconSrc={Icons.Hash}
|
||||
content={
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Text size="T300" priority="300">
|
||||
<b>{senderName}</b>
|
||||
{' changed room avatar'}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Event>
|
||||
streamRailStart={streamRailStart}
|
||||
streamRailEnd={streamRailEnd}
|
||||
layout={messageLayout}
|
||||
channelHeaderInBubble={channelHeaderInBubble}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[StateEvent.GroupCallMemberPrefix]: (
|
||||
|
|
|
|||
124
src/app/features/room/message/SyslineMessage.tsx
Normal file
124
src/app/features/room/message/SyslineMessage.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { Box, Text } from 'folds';
|
||||
import { MatrixEvent, Room } from 'matrix-js-sdk';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ChannelLayout,
|
||||
ChannelMessageAvatar,
|
||||
StreamLayout,
|
||||
Time,
|
||||
Username,
|
||||
UsernameBold,
|
||||
} from '../../../components/message';
|
||||
import { Event } from './Message';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { useDotColor } from '../../../hooks/useDotColor';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||
import { getMemberDisplayName } from '../../../utils/room';
|
||||
import { getMxIdLocalPart } from '../../../utils/matrix';
|
||||
|
||||
export type SyslineMessageProps = {
|
||||
room: Room;
|
||||
mEvent: MatrixEvent;
|
||||
// Pre-rendered body — already includes any sender names / interpolation
|
||||
// the per-type renderer wants to show. The sysline bubble adds no extra
|
||||
// header or icon, so the body is the only visible content.
|
||||
body: ReactNode;
|
||||
highlight: boolean;
|
||||
canDelete?: boolean;
|
||||
hideReadReceipts?: boolean;
|
||||
showDeveloperTools?: boolean;
|
||||
streamRailStart?: boolean;
|
||||
streamRailEnd?: boolean;
|
||||
layout?: 'stream' | 'channel';
|
||||
channelHeaderInBubble?: boolean;
|
||||
// Spread onto Event wrapper (data-message-item / data-message-id) so the
|
||||
// virtual paginator can locate the row.
|
||||
[dataAttr: `data-${string}`]: string | number | undefined;
|
||||
};
|
||||
|
||||
export function SyslineMessage({
|
||||
room,
|
||||
mEvent,
|
||||
body,
|
||||
highlight,
|
||||
canDelete,
|
||||
hideReadReceipts,
|
||||
showDeveloperTools,
|
||||
streamRailStart,
|
||||
streamRailEnd,
|
||||
layout = 'stream',
|
||||
channelHeaderInBubble,
|
||||
...rest
|
||||
}: SyslineMessageProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const isMobile = useScreenSizeContext() === ScreenSize.Mobile;
|
||||
const dot = useDotColor(room, mEvent, true, hideReadReceipts);
|
||||
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const isOwnMessage = !!mx.getUserId() && senderId === mx.getUserId();
|
||||
const senderName =
|
||||
getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
||||
|
||||
const bubbleBody = (
|
||||
<Box style={{ minWidth: 0 }}>
|
||||
<Text as="span" size="T300" priority="300">
|
||||
{body}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Event
|
||||
key={mEvent.getId()}
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
highlight={highlight}
|
||||
canDelete={canDelete}
|
||||
hideReadReceipts={hideReadReceipts}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
{...rest}
|
||||
>
|
||||
{layout === 'channel' ? (
|
||||
<ChannelLayout
|
||||
isOwn={isOwnMessage}
|
||||
headerInBubble={channelHeaderInBubble}
|
||||
avatar={
|
||||
<ChannelMessageAvatar
|
||||
room={room}
|
||||
senderId={senderId}
|
||||
senderDisplayName={senderName}
|
||||
/>
|
||||
}
|
||||
header={
|
||||
<>
|
||||
<Username as="span">
|
||||
<Text as="span" size={channelHeaderInBubble ? 'T200' : 'T400'} truncate>
|
||||
<UsernameBold>
|
||||
{isOwnMessage ? t('Direct.message_me_label') : senderName}
|
||||
</UsernameBold>
|
||||
</Text>
|
||||
</Username>
|
||||
<Time ts={mEvent.getTs()} compact size="T200" priority="300" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
{bubbleBody}
|
||||
</ChannelLayout>
|
||||
) : (
|
||||
<StreamLayout
|
||||
time={<Time ts={mEvent.getTs()} compact />}
|
||||
dotColor={dot.color}
|
||||
dotOpacity={dot.opacity}
|
||||
isOwn={isOwnMessage}
|
||||
compact={isMobile}
|
||||
railStart={streamRailStart}
|
||||
railEnd={streamRailEnd}
|
||||
>
|
||||
{bubbleBody}
|
||||
</StreamLayout>
|
||||
)}
|
||||
</Event>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
export * from './Reactions';
|
||||
export * from './Message';
|
||||
export * from './CallMessage';
|
||||
export * from './SyslineMessage';
|
||||
export * from './EncryptedContent';
|
||||
export * from './useMessageInteractionHandlers';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue