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,
|
Event,
|
||||||
CallMessage,
|
CallMessage,
|
||||||
CallAggregate,
|
CallAggregate,
|
||||||
|
SyslineMessage,
|
||||||
EncryptedContent,
|
EncryptedContent,
|
||||||
useMessageInteractionHandlers,
|
useMessageInteractionHandlers,
|
||||||
} from './message';
|
} from './message';
|
||||||
|
|
@ -1849,44 +1850,23 @@ export function RoomTimeline({
|
||||||
|
|
||||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
const parsed = parseMemberEvent(mEvent);
|
const parsed = parseMemberEvent(mEvent);
|
||||||
const iconSrc =
|
|
||||||
parsed.icon === Icons.ArrowGoRightPlus ? Icons.ArrowGoRight : parsed.icon;
|
|
||||||
|
|
||||||
const timeJSX = (
|
|
||||||
<Time
|
|
||||||
ts={mEvent.getTs()}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Event
|
<SyslineMessage
|
||||||
key={mEvent.getId()}
|
key={mEvent.getId()}
|
||||||
data-message-item={item}
|
data-message-item={item}
|
||||||
data-message-id={mEventId}
|
data-message-id={mEventId}
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
|
body={parsed.body}
|
||||||
highlight={highlighted}
|
highlight={highlighted}
|
||||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||||
hideReadReceipts={hideActivity}
|
hideReadReceipts={hideActivity}
|
||||||
showDeveloperTools={showDeveloperTools}
|
showDeveloperTools={showDeveloperTools}
|
||||||
>
|
streamRailStart={streamRailStart}
|
||||||
<EventContent
|
streamRailEnd={streamRailEnd}
|
||||||
time={timeJSX}
|
layout={messageLayout}
|
||||||
railStart={streamRailStart}
|
channelHeaderInBubble={channelHeaderInBubble}
|
||||||
railEnd={streamRailEnd}
|
/>
|
||||||
layout={messageLayout}
|
|
||||||
channelHeaderInBubble={channelHeaderInBubble}
|
|
||||||
iconSrc={iconSrc}
|
|
||||||
content={
|
|
||||||
<Box grow="Yes" direction="Column">
|
|
||||||
<Text size="T300" priority="300">
|
|
||||||
{parsed.body}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Event>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[StateEvent.RoomName]: (
|
[StateEvent.RoomName]: (
|
||||||
|
|
@ -1900,44 +1880,30 @@ export function RoomTimeline({
|
||||||
) => {
|
) => {
|
||||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
const senderId = mEvent.getSender() ?? '';
|
const senderId = mEvent.getSender() ?? '';
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName =
|
||||||
|
getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
||||||
const timeJSX = (
|
|
||||||
<Time
|
|
||||||
ts={mEvent.getTs()}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Event
|
<SyslineMessage
|
||||||
key={mEvent.getId()}
|
key={mEvent.getId()}
|
||||||
data-message-item={item}
|
data-message-item={item}
|
||||||
data-message-id={mEventId}
|
data-message-id={mEventId}
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
|
body={
|
||||||
|
<>
|
||||||
|
<b>{senderName}</b>
|
||||||
|
{t('Organisms.RoomCommon.changed_room_name')}
|
||||||
|
</>
|
||||||
|
}
|
||||||
highlight={highlighted}
|
highlight={highlighted}
|
||||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||||
hideReadReceipts={hideActivity}
|
hideReadReceipts={hideActivity}
|
||||||
showDeveloperTools={showDeveloperTools}
|
showDeveloperTools={showDeveloperTools}
|
||||||
>
|
streamRailStart={streamRailStart}
|
||||||
<EventContent
|
streamRailEnd={streamRailEnd}
|
||||||
time={timeJSX}
|
layout={messageLayout}
|
||||||
railStart={streamRailStart}
|
channelHeaderInBubble={channelHeaderInBubble}
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[StateEvent.RoomTopic]: (
|
[StateEvent.RoomTopic]: (
|
||||||
|
|
@ -1951,44 +1917,30 @@ export function RoomTimeline({
|
||||||
) => {
|
) => {
|
||||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
const senderId = mEvent.getSender() ?? '';
|
const senderId = mEvent.getSender() ?? '';
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName =
|
||||||
|
getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
||||||
const timeJSX = (
|
|
||||||
<Time
|
|
||||||
ts={mEvent.getTs()}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Event
|
<SyslineMessage
|
||||||
key={mEvent.getId()}
|
key={mEvent.getId()}
|
||||||
data-message-item={item}
|
data-message-item={item}
|
||||||
data-message-id={mEventId}
|
data-message-id={mEventId}
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
|
body={
|
||||||
|
<>
|
||||||
|
<b>{senderName}</b>
|
||||||
|
{' changed room topic'}
|
||||||
|
</>
|
||||||
|
}
|
||||||
highlight={highlighted}
|
highlight={highlighted}
|
||||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||||
hideReadReceipts={hideActivity}
|
hideReadReceipts={hideActivity}
|
||||||
showDeveloperTools={showDeveloperTools}
|
showDeveloperTools={showDeveloperTools}
|
||||||
>
|
streamRailStart={streamRailStart}
|
||||||
<EventContent
|
streamRailEnd={streamRailEnd}
|
||||||
time={timeJSX}
|
layout={messageLayout}
|
||||||
railStart={streamRailStart}
|
channelHeaderInBubble={channelHeaderInBubble}
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[StateEvent.RoomAvatar]: (
|
[StateEvent.RoomAvatar]: (
|
||||||
|
|
@ -2002,44 +1954,30 @@ export function RoomTimeline({
|
||||||
) => {
|
) => {
|
||||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
const senderId = mEvent.getSender() ?? '';
|
const senderId = mEvent.getSender() ?? '';
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName =
|
||||||
|
getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
||||||
const timeJSX = (
|
|
||||||
<Time
|
|
||||||
ts={mEvent.getTs()}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Event
|
<SyslineMessage
|
||||||
key={mEvent.getId()}
|
key={mEvent.getId()}
|
||||||
data-message-item={item}
|
data-message-item={item}
|
||||||
data-message-id={mEventId}
|
data-message-id={mEventId}
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
|
body={
|
||||||
|
<>
|
||||||
|
<b>{senderName}</b>
|
||||||
|
{' changed room avatar'}
|
||||||
|
</>
|
||||||
|
}
|
||||||
highlight={highlighted}
|
highlight={highlighted}
|
||||||
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
|
||||||
hideReadReceipts={hideActivity}
|
hideReadReceipts={hideActivity}
|
||||||
showDeveloperTools={showDeveloperTools}
|
showDeveloperTools={showDeveloperTools}
|
||||||
>
|
streamRailStart={streamRailStart}
|
||||||
<EventContent
|
streamRailEnd={streamRailEnd}
|
||||||
time={timeJSX}
|
layout={messageLayout}
|
||||||
railStart={streamRailStart}
|
channelHeaderInBubble={channelHeaderInBubble}
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[StateEvent.GroupCallMemberPrefix]: (
|
[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 './Reactions';
|
||||||
export * from './Message';
|
export * from './Message';
|
||||||
export * from './CallMessage';
|
export * from './CallMessage';
|
||||||
|
export * from './SyslineMessage';
|
||||||
export * from './EncryptedContent';
|
export * from './EncryptedContent';
|
||||||
export * from './useMessageInteractionHandlers';
|
export * from './useMessageInteractionHandlers';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue