108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
import React, { ReactNode } from 'react';
|
|
import { Box, Icons, Text } from 'folds';
|
|
import { MatrixEvent, Room } from 'matrix-js-sdk';
|
|
import {
|
|
BubbleSysline,
|
|
ChannelLayout,
|
|
ChannelMessageAvatar,
|
|
Time,
|
|
Username,
|
|
UsernameBold,
|
|
} from '../../../components/message';
|
|
import { Event } from './Message';
|
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
|
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 row adds no extra
|
|
// header, 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,
|
|
// Vestigial Stream-rail props — accepted but unused by the bubble/channel
|
|
// layouts (pending the stream-rail cleanup).
|
|
streamRailStart: _streamRailStart,
|
|
streamRailEnd: _streamRailEnd,
|
|
layout = 'stream',
|
|
channelHeaderInBubble,
|
|
...rest
|
|
}: SyslineMessageProps) {
|
|
const mx = useMatrixClient();
|
|
|
|
const senderId = mEvent.getSender() ?? '';
|
|
const isOwnMessage = !!mx.getUserId() && senderId === mx.getUserId();
|
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;
|
|
|
|
return (
|
|
<Event
|
|
key={mEvent.getId()}
|
|
room={room}
|
|
mEvent={mEvent}
|
|
highlight={highlight}
|
|
canDelete={canDelete}
|
|
hideReadReceipts={hideReadReceipts}
|
|
showDeveloperTools={showDeveloperTools}
|
|
bubble={layout !== 'channel'}
|
|
{...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>
|
|
{/* Own events show the user's own nick too (not a «me» label). */}
|
|
<UsernameBold>{senderName}</UsernameBold>
|
|
</Text>
|
|
</Username>
|
|
<Time ts={mEvent.getTs()} compact size="T200" priority="300" />
|
|
</>
|
|
}
|
|
>
|
|
<Box style={{ minWidth: 0 }}>
|
|
<Text as="span" size="T300" priority="300">
|
|
{body}
|
|
</Text>
|
|
</Box>
|
|
</ChannelLayout>
|
|
) : (
|
|
// System notices (room name / topic / avatar changes) render as a
|
|
// muted, centred one-liner in the bubble DM — no rail, no bubble,
|
|
// matching the AI-bot chat's understated tone.
|
|
<BubbleSysline
|
|
iconSrc={Icons.Info}
|
|
content={body}
|
|
time={<Time ts={mEvent.getTs()} compact />}
|
|
/>
|
|
)}
|
|
</Event>
|
|
);
|
|
}
|