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