soapbox/app/soapbox/components/account.tsx

260 wiersze
7.6 KiB
TypeScript
Czysty Zwykły widok Historia

2022-03-21 18:09:01 +00:00
import * as React from 'react';
import { Link, useHistory } from 'react-router-dom';
2022-03-21 18:09:01 +00:00
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
import VerificationBadge from 'soapbox/components/verification_badge';
2022-05-03 13:27:15 +00:00
import ActionButton from 'soapbox/features/ui/components/action-button';
import { useAppSelector, useOnScreen } from 'soapbox/hooks';
import { getAcct } from 'soapbox/utils/accounts';
2022-03-21 18:09:01 +00:00
import { displayFqn } from 'soapbox/utils/state';
import RelativeTimestamp from './relative_timestamp';
2022-07-01 20:07:01 +00:00
import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui';
2022-03-21 18:09:01 +00:00
import type { Account as AccountEntity } from 'soapbox/types/entities';
interface IInstanceFavicon {
account: AccountEntity,
}
const InstanceFavicon: React.FC<IInstanceFavicon> = ({ account }) => {
const history = useHistory();
const handleClick: React.MouseEventHandler = (e) => {
e.stopPropagation();
history.push(`/timeline/${account.domain}`);
};
return (
<button className='w-4 h-4 flex-none focus:ring-primary-500 focus:ring-2 focus:ring-offset-2' onClick={handleClick}>
<img src={account.favicon} alt='' title={account.domain} className='w-full max-h-full' />
</button>
);
};
2022-03-21 18:09:01 +00:00
interface IProfilePopper {
condition: boolean,
wrapper: (children: any) => React.ReactElement<any, any>
}
const ProfilePopper: React.FC<IProfilePopper> = ({ condition, wrapper, children }): any =>
condition ? wrapper(children) : children;
interface IAccount {
account: AccountEntity,
action?: React.ReactElement,
actionAlignment?: 'center' | 'top',
actionIcon?: string,
actionTitle?: string,
/** Override other actions for specificity like mute/unmute. */
actionType?: 'muting' | 'blocking',
2022-03-21 18:09:01 +00:00
avatarSize?: number,
hidden?: boolean,
hideActions?: boolean,
id?: string,
2022-03-21 18:09:01 +00:00
onActionClick?: (account: any) => void,
showProfileHoverCard?: boolean,
timestamp?: string | Date,
2022-03-21 18:09:01 +00:00
timestampUrl?: string,
futureTimestamp?: boolean,
2022-07-01 20:07:01 +00:00
withAccountNote?: boolean,
withDate?: boolean,
2022-07-01 20:07:01 +00:00
withLinkToProfile?: boolean,
2022-03-21 18:09:01 +00:00
withRelationship?: boolean,
showEdit?: boolean,
emoji?: string,
2022-03-21 18:09:01 +00:00
}
const Account = ({
account,
actionType,
2022-03-21 18:09:01 +00:00
action,
actionIcon,
actionTitle,
actionAlignment = 'center',
avatarSize = 42,
hidden = false,
hideActions = false,
onActionClick,
showProfileHoverCard = true,
timestamp,
timestampUrl,
futureTimestamp = false,
2022-07-01 20:07:01 +00:00
withAccountNote = false,
withDate = false,
2022-07-01 20:07:01 +00:00
withLinkToProfile = true,
2022-03-21 18:09:01 +00:00
withRelationship = true,
showEdit = false,
emoji,
2022-03-21 18:09:01 +00:00
}: IAccount) => {
const overflowRef = React.useRef<HTMLDivElement>(null);
const actionRef = React.useRef<HTMLDivElement>(null);
// @ts-ignore
const isOnScreen = useOnScreen(overflowRef);
2022-03-21 18:09:01 +00:00
const [style, setStyle] = React.useState<React.CSSProperties>({ visibility: 'hidden' });
const me = useAppSelector((state) => state.me);
const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null);
const handleAction = () => {
// @ts-ignore
2022-03-21 18:09:01 +00:00
onActionClick(account);
};
const renderAction = () => {
if (action) {
return action;
}
if (hideActions) {
return null;
}
if (onActionClick && actionIcon) {
return (
<IconButton
src={actionIcon}
title={actionTitle}
onClick={handleAction}
className='bg-transparent text-gray-400 hover:text-gray-600'
iconClassName='w-4 h-4'
/>
);
}
if (account.id !== me) {
return <ActionButton account={account} actionType={actionType} />;
2022-03-21 18:09:01 +00:00
}
return null;
};
React.useEffect(() => {
2022-03-24 16:16:41 +00:00
const style: React.CSSProperties = {};
const actionWidth = actionRef.current?.clientWidth || 0;
2022-03-21 18:09:01 +00:00
2022-03-24 16:16:41 +00:00
if (overflowRef.current) {
style.maxWidth = overflowRef.current.clientWidth - 30 - avatarSize - actionWidth;
} else {
style.visibility = 'hidden';
2022-03-21 18:09:01 +00:00
}
2022-03-24 16:16:41 +00:00
setStyle(style);
}, [isOnScreen, overflowRef, actionRef]);
2022-03-21 18:09:01 +00:00
if (!account) {
return null;
}
if (hidden) {
return (
<>
{account.display_name}
{account.username}
2022-03-21 18:09:01 +00:00
</>
);
}
if (withDate) timestamp = account.created_at;
2022-07-01 20:07:01 +00:00
const LinkEl: any = withLinkToProfile ? Link : 'div';
2022-03-21 18:09:01 +00:00
return (
2022-04-18 16:15:15 +00:00
<div data-testid='account' className='flex-shrink-0 group block w-full' ref={overflowRef}>
2022-03-21 18:09:01 +00:00
<HStack alignItems={actionAlignment} justifyContent='between'>
2022-07-01 20:07:01 +00:00
<HStack alignItems={withAccountNote ? 'top' : 'center'} space={3}>
2022-03-21 18:09:01 +00:00
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper className='relative' accountId={account.id} inline>{children}</HoverRefWrapper>}
2022-03-21 18:09:01 +00:00
>
<LinkEl
to={`/@${account.acct}`}
title={account.acct}
2022-03-22 12:42:26 +00:00
onClick={(event: React.MouseEvent) => event.stopPropagation()}
2022-03-21 18:09:01 +00:00
>
<Avatar src={account.avatar} size={avatarSize} />
{emoji && (
<Emoji
className='w-5 h-5 absolute -bottom-1.5 -right-1.5'
emoji={emoji}
/>
)}
2022-03-21 18:09:01 +00:00
</LinkEl>
</ProfilePopper>
<div className='flex-grow'>
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper accountId={account.id} inline>{children}</HoverRefWrapper>}
2022-03-21 18:09:01 +00:00
>
<LinkEl
to={`/@${account.acct}`}
title={account.acct}
2022-03-22 12:42:26 +00:00
onClick={(event: React.MouseEvent) => event.stopPropagation()}
2022-03-21 18:09:01 +00:00
>
<div className='flex items-center space-x-1 flex-grow' style={style}>
<Text
size='sm'
weight='semibold'
truncate
dangerouslySetInnerHTML={{ __html: account.display_name_html }}
2022-03-21 18:09:01 +00:00
/>
{account.verified && <VerificationBadge />}
2022-03-21 18:09:01 +00:00
</div>
</LinkEl>
</ProfilePopper>
2022-07-01 20:07:01 +00:00
<Stack space={withAccountNote ? 1 : 0}>
<HStack alignItems='center' space={1} style={style}>
<Text theme='muted' size='sm' truncate>@{username}</Text>
2022-03-21 18:09:01 +00:00
2022-07-01 20:07:01 +00:00
{account.favicon && (
<InstanceFavicon account={account} />
)}
2022-04-18 18:42:48 +00:00
2022-07-01 20:07:01 +00:00
{(timestamp) ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
2022-03-21 18:09:01 +00:00
2022-07-01 20:07:01 +00:00
{timestampUrl ? (
<Link to={timestampUrl} className='hover:underline'>
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' futureDate={futureTimestamp} />
</Link>
) : (
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' futureDate={futureTimestamp} />
2022-07-01 20:07:01 +00:00
)}
</>
) : null}
{showEdit ? (
<>
<Text tag='span' theme='muted' size='sm'>&middot;</Text>
<Icon className='h-5 w-5 stroke-[1.35]' src={require('@tabler/icons/pencil.svg')} />
2022-07-01 20:07:01 +00:00
</>
) : null}
</HStack>
{withAccountNote && (
<Text
size='sm'
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
className='mr-2'
/>
)}
</Stack>
2022-03-21 18:09:01 +00:00
</div>
</HStack>
<div ref={actionRef}>
{withRelationship ? renderAction() : null}
</div>
</HStack>
</div>
);
};
export default Account;