mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-13 02:30:29 +03:00
Some checks are pending
Deploy to Netlify (dev) / Deploy to Netlify (push) Waiting to run
* WIP - support room version 12 * add room creators hook * revert changes from powerlevels * improve use room creators hook * add hook to get dm users * add options to add creators in create room/space * add member item component in member drawer * remove unused import * extract member drawer header component * get room creators as set only if room version support them * add room permissions hook * support room v12 creators power * make predecessor event id optional * add info about founders in permissions * allow to create infinite powers to room creators * allow everyone with permission to create infinite power * handle additional creators in room upgrade * add option to follow space tombstone
138 lines
4.3 KiB
TypeScript
138 lines
4.3 KiB
TypeScript
import { Box, Icon, Icons, Text, as, color, toRem } from 'folds';
|
|
import { EventTimelineSet, Room } from 'matrix-js-sdk';
|
|
import React, { MouseEventHandler, ReactNode, useCallback, useMemo } from 'react';
|
|
import classNames from 'classnames';
|
|
import { getMemberDisplayName, trimReplyFromBody } from '../../utils/room';
|
|
import { getMxIdLocalPart } from '../../utils/matrix';
|
|
import { LinePlaceholder } from './placeholder';
|
|
import { randomNumberBetween } from '../../utils/common';
|
|
import * as css from './Reply.css';
|
|
import { MessageBadEncryptedContent, MessageDeletedContent, MessageFailedContent } from './content';
|
|
import { scaleSystemEmoji } from '../../plugins/react-custom-html-parser';
|
|
import { useRoomEvent } from '../../hooks/useRoomEvent';
|
|
import colorMXID from '../../../util/colorMXID';
|
|
import { GetMemberPowerTag } from '../../hooks/useMemberPowerTag';
|
|
|
|
type ReplyLayoutProps = {
|
|
userColor?: string;
|
|
username?: ReactNode;
|
|
};
|
|
export const ReplyLayout = as<'div', ReplyLayoutProps>(
|
|
({ username, userColor, className, children, ...props }, ref) => (
|
|
<Box
|
|
className={classNames(css.Reply, className)}
|
|
alignItems="Center"
|
|
gap="100"
|
|
{...props}
|
|
ref={ref}
|
|
>
|
|
<Box style={{ color: userColor, maxWidth: toRem(200) }} alignItems="Center" shrink="No">
|
|
<Icon size="100" src={Icons.ReplyArrow} />
|
|
{username}
|
|
</Box>
|
|
<Box grow="Yes" className={css.ReplyContent}>
|
|
{children}
|
|
</Box>
|
|
</Box>
|
|
)
|
|
);
|
|
|
|
export const ThreadIndicator = as<'div'>(({ ...props }, ref) => (
|
|
<Box
|
|
shrink="No"
|
|
className={css.ThreadIndicator}
|
|
alignItems="Center"
|
|
gap="100"
|
|
{...props}
|
|
ref={ref}
|
|
>
|
|
<Icon size="50" src={Icons.Thread} />
|
|
<Text size="L400">Thread</Text>
|
|
</Box>
|
|
));
|
|
|
|
type ReplyProps = {
|
|
room: Room;
|
|
timelineSet?: EventTimelineSet | undefined;
|
|
replyEventId: string;
|
|
threadRootId?: string | undefined;
|
|
onClick?: MouseEventHandler | undefined;
|
|
getMemberPowerTag?: GetMemberPowerTag;
|
|
accessibleTagColors?: Map<string, string>;
|
|
legacyUsernameColor?: boolean;
|
|
};
|
|
|
|
export const Reply = as<'div', ReplyProps>(
|
|
(
|
|
{
|
|
room,
|
|
timelineSet,
|
|
replyEventId,
|
|
threadRootId,
|
|
onClick,
|
|
getMemberPowerTag,
|
|
accessibleTagColors,
|
|
legacyUsernameColor,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []);
|
|
const getFromLocalTimeline = useCallback(
|
|
() => timelineSet?.findEventById(replyEventId),
|
|
[timelineSet, replyEventId]
|
|
);
|
|
const replyEvent = useRoomEvent(room, replyEventId, getFromLocalTimeline);
|
|
|
|
const { body } = replyEvent?.getContent() ?? {};
|
|
const sender = replyEvent?.getSender();
|
|
const powerTag = sender ? getMemberPowerTag?.(sender) : undefined;
|
|
const tagColor = powerTag?.color ? accessibleTagColors?.get(powerTag.color) : undefined;
|
|
|
|
const usernameColor = legacyUsernameColor ? colorMXID(sender ?? replyEventId) : tagColor;
|
|
|
|
const fallbackBody = replyEvent?.isRedacted() ? (
|
|
<MessageDeletedContent />
|
|
) : (
|
|
<MessageFailedContent />
|
|
);
|
|
|
|
const badEncryption = replyEvent?.getContent().msgtype === 'm.bad.encrypted';
|
|
const bodyJSX = body ? scaleSystemEmoji(trimReplyFromBody(body)) : fallbackBody;
|
|
|
|
return (
|
|
<Box direction="Row" gap="200" alignItems="Center" {...props} ref={ref}>
|
|
{threadRootId && (
|
|
<ThreadIndicator as="button" data-event-id={threadRootId} onClick={onClick} />
|
|
)}
|
|
<ReplyLayout
|
|
as="button"
|
|
userColor={usernameColor}
|
|
username={
|
|
sender && (
|
|
<Text size="T300" truncate>
|
|
<b>{getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)}</b>
|
|
</Text>
|
|
)
|
|
}
|
|
data-event-id={replyEventId}
|
|
onClick={onClick}
|
|
>
|
|
{replyEvent !== undefined ? (
|
|
<Text size="T300" truncate>
|
|
{badEncryption ? <MessageBadEncryptedContent /> : bodyJSX}
|
|
</Text>
|
|
) : (
|
|
<LinePlaceholder
|
|
style={{
|
|
backgroundColor: color.SurfaceVariant.ContainerActive,
|
|
width: toRem(placeholderWidth),
|
|
maxWidth: '100%',
|
|
}}
|
|
/>
|
|
)}
|
|
</ReplyLayout>
|
|
</Box>
|
|
);
|
|
}
|
|
);
|