254 lines
7.5 KiB
TypeScript
254 lines
7.5 KiB
TypeScript
import { useCallback } from 'react';
|
|
|
|
import classNames from 'classnames';
|
|
import { Helmet } from 'react-helmet';
|
|
|
|
import { openModal } from '@/mastodon/actions/modal';
|
|
import { AccountBio } from '@/mastodon/components/account_bio';
|
|
import { Avatar } from '@/mastodon/components/avatar';
|
|
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
|
|
import { AccountNote } from '@/mastodon/features/account/components/account_note';
|
|
import FollowRequestNoteContainer from '@/mastodon/features/account/containers/follow_request_note_container';
|
|
import { useLayout } from '@/mastodon/hooks/useLayout';
|
|
import { useVisibility } from '@/mastodon/hooks/useVisibility';
|
|
import {
|
|
autoPlayGif,
|
|
me,
|
|
domain as localDomain,
|
|
} from '@/mastodon/initial_state';
|
|
import type { Account } from '@/mastodon/models/account';
|
|
import { getAccountHidden } from '@/mastodon/selectors/accounts';
|
|
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
|
|
|
|
import { isRedesignEnabled } from '../common';
|
|
|
|
import { AccountName } from './account_name';
|
|
import { AccountBadges } from './badges';
|
|
import { AccountButtons } from './buttons';
|
|
import { FamiliarFollowers } from './familiar_followers';
|
|
import { AccountHeaderFields } from './fields';
|
|
import { AccountInfo } from './info';
|
|
import { MemorialNote } from './memorial_note';
|
|
import { MovedNote } from './moved_note';
|
|
import { AccountNote as AccountNoteRedesign } from './note';
|
|
import { AccountNumberFields } from './number_fields';
|
|
import redesignClasses from './redesign.module.scss';
|
|
import { AccountTabs } from './tabs';
|
|
|
|
const titleFromAccount = (account: Account) => {
|
|
const displayName = account.display_name;
|
|
const acct =
|
|
account.acct === account.username
|
|
? `${account.username}@${localDomain}`
|
|
: account.acct;
|
|
const prefix =
|
|
displayName.trim().length === 0 ? account.username : displayName;
|
|
|
|
return `${prefix} (@${acct})`;
|
|
};
|
|
|
|
export const AccountHeader: React.FC<{
|
|
accountId: string;
|
|
hideTabs?: boolean;
|
|
}> = ({ accountId, hideTabs }) => {
|
|
const isRedesign = isRedesignEnabled();
|
|
|
|
const dispatch = useAppDispatch();
|
|
const account = useAppSelector((state) => state.accounts.get(accountId));
|
|
const relationship = useAppSelector((state) =>
|
|
state.relationships.get(accountId),
|
|
);
|
|
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
|
|
|
|
const handleOpenAvatar = useCallback(
|
|
(e: React.MouseEvent) => {
|
|
if (e.button !== 0 || e.ctrlKey || e.metaKey) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
if (!account) {
|
|
return;
|
|
}
|
|
|
|
dispatch(
|
|
openModal({
|
|
modalType: 'IMAGE',
|
|
modalProps: {
|
|
src: account.avatar,
|
|
alt: '',
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
[dispatch, account],
|
|
);
|
|
|
|
const { layout } = useLayout();
|
|
const { observedRef, isIntersecting } = useVisibility({
|
|
observerOptions: {
|
|
rootMargin: layout === 'mobile' ? '0px 0px -55px 0px' : '', // Height of bottom nav bar.
|
|
},
|
|
});
|
|
|
|
if (!account) {
|
|
return null;
|
|
}
|
|
|
|
const suspendedOrHidden = hidden || account.suspended;
|
|
const isLocal = !account.acct.includes('@');
|
|
const isMe = me && account.id === me;
|
|
|
|
return (
|
|
<div className='account-timeline__header'>
|
|
{!hidden && account.memorial && <MemorialNote />}
|
|
{!hidden && account.moved && (
|
|
<MovedNote accountId={account.id} targetAccountId={account.moved} />
|
|
)}
|
|
|
|
<AnimateEmojiProvider
|
|
className={classNames('account__header', {
|
|
inactive: !!account.moved,
|
|
})}
|
|
>
|
|
{!suspendedOrHidden && !account.moved && relationship?.requested_by && (
|
|
<FollowRequestNoteContainer account={account} />
|
|
)}
|
|
|
|
<div
|
|
className={classNames(
|
|
'account__header__image',
|
|
isRedesign && redesignClasses.header,
|
|
)}
|
|
>
|
|
{me !== account.id && relationship && !isRedesign && (
|
|
<AccountInfo relationship={relationship} />
|
|
)}
|
|
|
|
{!suspendedOrHidden && (
|
|
<img
|
|
src={autoPlayGif ? account.header : account.header_static}
|
|
alt=''
|
|
className='parallax'
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div
|
|
className={classNames(
|
|
'account__header__bar',
|
|
isRedesign && redesignClasses.barWrapper,
|
|
)}
|
|
>
|
|
<div
|
|
className={classNames(
|
|
'account__header__tabs',
|
|
isRedesign && redesignClasses.avatarWrapper,
|
|
)}
|
|
>
|
|
<a
|
|
className='avatar'
|
|
href={account.avatar}
|
|
rel='noopener'
|
|
target='_blank'
|
|
onClick={handleOpenAvatar}
|
|
>
|
|
<Avatar
|
|
account={suspendedOrHidden ? undefined : account}
|
|
size={isRedesign ? 80 : 92}
|
|
/>
|
|
</a>
|
|
|
|
{!isRedesign && (
|
|
<AccountButtons
|
|
accountId={accountId}
|
|
className='account__header__buttons--desktop'
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div
|
|
className={classNames(
|
|
'account__header__tabs__name',
|
|
isRedesign && redesignClasses.nameWrapper,
|
|
)}
|
|
>
|
|
<AccountName accountId={accountId} />
|
|
{isRedesign && (
|
|
<AccountButtons
|
|
accountId={accountId}
|
|
className={redesignClasses.buttonsDesktop}
|
|
noShare={!isMe || 'share' in navigator}
|
|
forceMenu={'share' in navigator}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<AccountBadges accountId={accountId} />
|
|
|
|
{!isMe && !suspendedOrHidden && (
|
|
<FamiliarFollowers accountId={accountId} />
|
|
)}
|
|
|
|
{!isRedesign && (
|
|
<AccountButtons
|
|
className='account__header__buttons--mobile'
|
|
accountId={accountId}
|
|
noShare
|
|
/>
|
|
)}
|
|
|
|
{!suspendedOrHidden && (
|
|
<div className='account__header__extra'>
|
|
<div className='account__header__bio'>
|
|
{me &&
|
|
account.id !== me &&
|
|
(isRedesign ? (
|
|
<AccountNoteRedesign accountId={accountId} />
|
|
) : (
|
|
<AccountNote accountId={accountId} />
|
|
))}
|
|
|
|
<AccountBio
|
|
accountId={accountId}
|
|
className={classNames(
|
|
'account__header__content',
|
|
isRedesign && redesignClasses.bio,
|
|
)}
|
|
/>
|
|
<AccountHeaderFields accountId={accountId} />
|
|
</div>
|
|
|
|
<AccountNumberFields accountId={accountId} />
|
|
</div>
|
|
)}
|
|
|
|
{isRedesign && (
|
|
<AccountButtons
|
|
className={classNames(
|
|
redesignClasses.buttonsMobile,
|
|
!isIntersecting && redesignClasses.buttonsMobileIsStuck,
|
|
)}
|
|
accountId={accountId}
|
|
noShare
|
|
/>
|
|
)}
|
|
</div>
|
|
</AnimateEmojiProvider>
|
|
|
|
{!hideTabs && !hidden && <AccountTabs acct={account.acct} />}
|
|
<div ref={observedRef} />
|
|
|
|
<Helmet>
|
|
<title>{titleFromAccount(account)}</title>
|
|
<meta
|
|
name='robots'
|
|
content={isLocal && !account.noindex ? 'all' : 'noindex'}
|
|
/>
|
|
<link rel='canonical' href={account.url} />
|
|
</Helmet>
|
|
</div>
|
|
);
|
|
};
|