diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx
index 090fc4c7..aaa79cc5 100644
--- a/src/app/features/room/RoomTimeline.tsx
+++ b/src/app/features/room/RoomTimeline.tsx
@@ -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 = (
-
- );
-
return (
-
-
-
- {parsed.body}
-
-
- }
- />
-
+ 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 = (
-
- );
-
+ const senderName =
+ getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
return (
-
+ {senderName}
+ {t('Organisms.RoomCommon.changed_room_name')}
+ >
+ }
highlight={highlighted}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
showDeveloperTools={showDeveloperTools}
- >
-
-
- {senderName}
- {t('Organisms.RoomCommon.changed_room_name')}
-
-
- }
- />
-
+ 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 = (
-
- );
-
+ const senderName =
+ getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
return (
-
+ {senderName}
+ {' changed room topic'}
+ >
+ }
highlight={highlighted}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
showDeveloperTools={showDeveloperTools}
- >
-
-
- {senderName}
- {' changed room topic'}
-
-
- }
- />
-
+ 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 = (
-
- );
-
+ const senderName =
+ getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
return (
-
+ {senderName}
+ {' changed room avatar'}
+ >
+ }
highlight={highlighted}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
showDeveloperTools={showDeveloperTools}
- >
-
-
- {senderName}
- {' changed room avatar'}
-
-
- }
- />
-
+ streamRailStart={streamRailStart}
+ streamRailEnd={streamRailEnd}
+ layout={messageLayout}
+ channelHeaderInBubble={channelHeaderInBubble}
+ />
);
},
[StateEvent.GroupCallMemberPrefix]: (
diff --git a/src/app/features/room/message/SyslineMessage.tsx b/src/app/features/room/message/SyslineMessage.tsx
new file mode 100644
index 00000000..d60620a7
--- /dev/null
+++ b/src/app/features/room/message/SyslineMessage.tsx
@@ -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 = (
+
+
+ {body}
+
+
+ );
+
+ return (
+
+ {layout === 'channel' ? (
+
+ }
+ header={
+ <>
+
+
+
+ {isOwnMessage ? t('Direct.message_me_label') : senderName}
+
+
+
+
+ >
+ }
+ >
+ {bubbleBody}
+
+ ) : (
+ }
+ dotColor={dot.color}
+ dotOpacity={dot.opacity}
+ isOwn={isOwnMessage}
+ compact={isMobile}
+ railStart={streamRailStart}
+ railEnd={streamRailEnd}
+ >
+ {bubbleBody}
+
+ )}
+
+ );
+}
diff --git a/src/app/features/room/message/index.ts b/src/app/features/room/message/index.ts
index cf912c14..a2c1c042 100644
--- a/src/app/features/room/message/index.ts
+++ b/src/app/features/room/message/index.ts
@@ -1,5 +1,6 @@
export * from './Reactions';
export * from './Message';
export * from './CallMessage';
+export * from './SyslineMessage';
export * from './EncryptedContent';
export * from './useMessageInteractionHandlers';