Refactor: Replace all display name usage for new component (#36137)
This commit is contained in:
parent
ff03938808
commit
dfef7d9407
24 changed files with 198 additions and 364 deletions
|
|
@ -1,27 +0,0 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<DisplayName /> > renders display name + account name 1`] = `
|
||||
<span
|
||||
className="display-name"
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<bdi>
|
||||
<strong
|
||||
className="display-name__html"
|
||||
dangerouslySetInnerHTML={
|
||||
{
|
||||
"__html": "<p>Foo</p>",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</bdi>
|
||||
|
||||
<span
|
||||
className="display-name__account"
|
||||
>
|
||||
@
|
||||
bar@baz
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { fromJS } from 'immutable';
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import { DisplayName } from '../display_name';
|
||||
|
||||
describe('<DisplayName />', () => {
|
||||
it('renders display name + account name', () => {
|
||||
const account = fromJS({
|
||||
username: 'bar',
|
||||
acct: 'bar@baz',
|
||||
display_name_html: '<p>Foo</p>',
|
||||
});
|
||||
const component = renderer.create(<DisplayName account={account} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import type { List } from 'immutable';
|
||||
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
import { Skeleton } from './skeleton';
|
||||
|
||||
interface Props {
|
||||
account?: Account;
|
||||
others?: List<Account>;
|
||||
localDomain?: string;
|
||||
}
|
||||
|
||||
export class DisplayName extends React.PureComponent<Props> {
|
||||
handleMouseEnter: React.ReactEventHandler<HTMLSpanElement> = ({
|
||||
currentTarget,
|
||||
}) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis =
|
||||
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
|
||||
|
||||
emojis.forEach((emoji) => {
|
||||
const originalSrc = emoji.getAttribute('data-original');
|
||||
if (originalSrc != null) emoji.src = originalSrc;
|
||||
});
|
||||
};
|
||||
|
||||
handleMouseLeave: React.ReactEventHandler<HTMLSpanElement> = ({
|
||||
currentTarget,
|
||||
}) => {
|
||||
if (autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis =
|
||||
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
|
||||
|
||||
emojis.forEach((emoji) => {
|
||||
const staticSrc = emoji.getAttribute('data-static');
|
||||
if (staticSrc != null) emoji.src = staticSrc;
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { others, localDomain } = this.props;
|
||||
|
||||
let displayName: React.ReactNode,
|
||||
suffix: React.ReactNode,
|
||||
account: Account | undefined;
|
||||
|
||||
if (others && others.size > 0) {
|
||||
account = others.first();
|
||||
} else if (this.props.account) {
|
||||
account = this.props.account;
|
||||
}
|
||||
|
||||
if (others && others.size > 1) {
|
||||
displayName = others
|
||||
.take(2)
|
||||
.map((a) => (
|
||||
<bdi key={a.get('id')}>
|
||||
<strong
|
||||
className='display-name__html'
|
||||
dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }}
|
||||
/>
|
||||
</bdi>
|
||||
))
|
||||
.reduce((prev, cur) => [prev, ', ', cur]);
|
||||
|
||||
if (others.size - 2 > 0) {
|
||||
suffix = `+${others.size - 2}`;
|
||||
}
|
||||
} else if (account) {
|
||||
let acct = account.get('acct');
|
||||
|
||||
if (!acct.includes('@') && localDomain) {
|
||||
acct = `${acct}@${localDomain}`;
|
||||
}
|
||||
|
||||
displayName = (
|
||||
<bdi>
|
||||
<strong
|
||||
className='display-name__html'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: account.get('display_name_html'),
|
||||
}}
|
||||
/>
|
||||
</bdi>
|
||||
);
|
||||
suffix = <span className='display-name__account'>@{acct}</span>;
|
||||
} else {
|
||||
displayName = (
|
||||
<bdi>
|
||||
<strong className='display-name__html'>
|
||||
<Skeleton width='10ch' />
|
||||
</strong>
|
||||
</bdi>
|
||||
);
|
||||
suffix = (
|
||||
<span className='display-name__account'>
|
||||
<Skeleton width='7ch' />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className='display-name'
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
{displayName} {suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
36
app/javascript/mastodon/components/display_name/default.tsx
Normal file
36
app/javascript/mastodon/components/display_name/default.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { useMemo } from 'react';
|
||||
import type { ComponentPropsWithoutRef, FC } from 'react';
|
||||
|
||||
import { Skeleton } from '../skeleton';
|
||||
|
||||
import type { DisplayNameProps } from './index';
|
||||
import { DisplayNameWithoutDomain } from './no-domain';
|
||||
|
||||
export const DisplayNameDefault: FC<
|
||||
Omit<DisplayNameProps, 'variant'> & ComponentPropsWithoutRef<'span'>
|
||||
> = ({ account, localDomain, className, ...props }) => {
|
||||
const username = useMemo(() => {
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
let acct = account.get('acct');
|
||||
|
||||
if (!acct.includes('@') && localDomain) {
|
||||
acct = `${acct}@${localDomain}`;
|
||||
}
|
||||
return `@${acct}`;
|
||||
}, [account, localDomain]);
|
||||
|
||||
return (
|
||||
<DisplayNameWithoutDomain
|
||||
account={account}
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
{' '}
|
||||
<span className='display-name__account'>
|
||||
{username ?? <Skeleton width='7ch' />}
|
||||
</span>
|
||||
</DisplayNameWithoutDomain>
|
||||
);
|
||||
};
|
||||
|
|
@ -18,8 +18,6 @@ const meta = {
|
|||
username: 'mastodon@mastodon.social',
|
||||
name: 'Test User 🧪',
|
||||
loading: false,
|
||||
simple: false,
|
||||
noDomain: false,
|
||||
localDomain: 'mastodon.social',
|
||||
},
|
||||
tags: [],
|
||||
|
|
@ -50,13 +48,13 @@ export const Loading: Story = {
|
|||
|
||||
export const NoDomain: Story = {
|
||||
args: {
|
||||
noDomain: true,
|
||||
variant: 'noDomain',
|
||||
},
|
||||
};
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
simple: true,
|
||||
variant: 'simple',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -76,6 +74,6 @@ export const Linked: Story = {
|
|||
acct: username,
|
||||
})
|
||||
: undefined;
|
||||
return <LinkedDisplayName {...args} account={account} />;
|
||||
return <LinkedDisplayName {...args} displayProps={{ account }} />;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,110 +1,37 @@
|
|||
import type { ComponentPropsWithoutRef, FC } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import type { LinkProps } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { EmojiHTML } from '@/mastodon/features/emoji/emoji_html';
|
||||
import type { Account } from '@/mastodon/models/account';
|
||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
import { Skeleton } from '../skeleton';
|
||||
import { DisplayNameDefault } from './default';
|
||||
import { DisplayNameWithoutDomain } from './no-domain';
|
||||
import { DisplayNameSimple } from './simple';
|
||||
|
||||
interface Props {
|
||||
export interface DisplayNameProps {
|
||||
account?: Account;
|
||||
localDomain?: string;
|
||||
simple?: boolean;
|
||||
noDomain?: boolean;
|
||||
variant?: 'default' | 'simple' | 'noDomain';
|
||||
}
|
||||
|
||||
export const DisplayName: FC<Props & ComponentPropsWithoutRef<'span'>> = ({
|
||||
account,
|
||||
localDomain,
|
||||
simple = false,
|
||||
noDomain = false,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const username = useMemo(() => {
|
||||
if (!account || noDomain) {
|
||||
return null;
|
||||
}
|
||||
let acct = account.get('acct');
|
||||
|
||||
if (!acct.includes('@') && localDomain) {
|
||||
acct = `${acct}@${localDomain}`;
|
||||
}
|
||||
return `@${acct}`;
|
||||
}, [account, localDomain, noDomain]);
|
||||
|
||||
if (!account) {
|
||||
if (simple) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span {...props} className={classNames('display-name', className)}>
|
||||
<bdi>
|
||||
<strong className='display-name__html'>
|
||||
<Skeleton width='10ch' />
|
||||
</strong>
|
||||
</bdi>
|
||||
{!noDomain && (
|
||||
<span className='display-name__account'>
|
||||
|
||||
<Skeleton width='7ch' />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
export const DisplayName: FC<
|
||||
DisplayNameProps & ComponentPropsWithoutRef<'span'>
|
||||
> = ({ variant = 'default', ...props }) => {
|
||||
if (variant === 'simple') {
|
||||
return <DisplayNameSimple {...props} />;
|
||||
} else if (variant === 'noDomain') {
|
||||
return <DisplayNameWithoutDomain {...props} />;
|
||||
}
|
||||
const accountName = isModernEmojiEnabled()
|
||||
? account.get('display_name')
|
||||
: account.get('display_name_html');
|
||||
if (simple) {
|
||||
return (
|
||||
<bdi>
|
||||
<EmojiHTML {...props} htmlString={accountName} shallow as='span' />
|
||||
</bdi>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span {...props} className={classNames('display-name', className)}>
|
||||
<bdi>
|
||||
<EmojiHTML
|
||||
className='display-name__html'
|
||||
htmlString={accountName}
|
||||
shallow
|
||||
as='strong'
|
||||
/>
|
||||
</bdi>
|
||||
{username && (
|
||||
<span className='display-name__account'> {username}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
return <DisplayNameDefault {...props} />;
|
||||
};
|
||||
|
||||
export const LinkedDisplayName: FC<
|
||||
Props & { asProps?: ComponentPropsWithoutRef<'span'> } & Partial<LinkProps>
|
||||
> = ({
|
||||
account,
|
||||
asProps = {},
|
||||
className,
|
||||
localDomain,
|
||||
simple,
|
||||
noDomain,
|
||||
...linkProps
|
||||
}) => {
|
||||
const displayProps = {
|
||||
account,
|
||||
className,
|
||||
localDomain,
|
||||
simple,
|
||||
noDomain,
|
||||
...asProps,
|
||||
};
|
||||
Omit<LinkProps, 'to'> & {
|
||||
displayProps: DisplayNameProps & ComponentPropsWithoutRef<'span'>;
|
||||
}
|
||||
> = ({ displayProps, children, ...linkProps }) => {
|
||||
const { account } = displayProps;
|
||||
if (!account) {
|
||||
return <DisplayName {...displayProps} />;
|
||||
}
|
||||
|
|
@ -113,9 +40,11 @@ export const LinkedDisplayName: FC<
|
|||
<Link
|
||||
to={`/@${account.acct}`}
|
||||
title={`@${account.acct}`}
|
||||
data-id={account.id}
|
||||
data-hover-card-account={account.id}
|
||||
{...linkProps}
|
||||
>
|
||||
{children}
|
||||
<DisplayName {...displayProps} />
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import type { ComponentPropsWithoutRef, FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { EmojiHTML } from '@/mastodon/features/emoji/emoji_html';
|
||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
import { Skeleton } from '../skeleton';
|
||||
|
||||
import type { DisplayNameProps } from './index';
|
||||
|
||||
export const DisplayNameWithoutDomain: FC<
|
||||
Omit<DisplayNameProps, 'variant' | 'localDomain'> &
|
||||
ComponentPropsWithoutRef<'span'>
|
||||
> = ({ account, className, children, ...props }) => {
|
||||
return (
|
||||
<span {...props} className={classNames('display-name', className)}>
|
||||
<bdi>
|
||||
{account ? (
|
||||
<EmojiHTML
|
||||
className='display-name__html'
|
||||
htmlString={
|
||||
isModernEmojiEnabled()
|
||||
? account.get('display_name')
|
||||
: account.get('display_name_html')
|
||||
}
|
||||
shallow
|
||||
as='strong'
|
||||
/>
|
||||
) : (
|
||||
<strong className='display-name__html'>
|
||||
<Skeleton width='10ch' />
|
||||
</strong>
|
||||
)}
|
||||
</bdi>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
23
app/javascript/mastodon/components/display_name/simple.tsx
Normal file
23
app/javascript/mastodon/components/display_name/simple.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import type { ComponentPropsWithoutRef, FC } from 'react';
|
||||
|
||||
import { EmojiHTML } from '@/mastodon/features/emoji/emoji_html';
|
||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
import type { DisplayNameProps } from './index';
|
||||
|
||||
export const DisplayNameSimple: FC<
|
||||
Omit<DisplayNameProps, 'variant' | 'localDomain'> &
|
||||
ComponentPropsWithoutRef<'span'>
|
||||
> = ({ account, ...props }) => {
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
const accountName = isModernEmojiEnabled()
|
||||
? account.get('display_name')
|
||||
: account.get('display_name_html');
|
||||
return (
|
||||
<bdi>
|
||||
<EmojiHTML {...props} htmlString={accountName} shallow as='span' />
|
||||
</bdi>
|
||||
);
|
||||
};
|
||||
|
|
@ -28,7 +28,7 @@ import { displayMedia } from '../initial_state';
|
|||
|
||||
import { Avatar } from './avatar';
|
||||
import { AvatarOverlay } from './avatar_overlay';
|
||||
import { DisplayName } from './display_name';
|
||||
import { LinkedDisplayName } from './display_name';
|
||||
import { getHashtagBarForStatus } from './hashtag_bar';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
import StatusActionBar from './status_action_bar';
|
||||
|
|
@ -409,12 +409,20 @@ class Status extends ImmutablePureComponent {
|
|||
const matchedFilters = status.get('matched_filters');
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
const name = (
|
||||
<LinkedDisplayName
|
||||
displayProps={{
|
||||
account: status.get('account'),
|
||||
variant: 'simple'
|
||||
}}
|
||||
className='status__display-name muted'
|
||||
/>
|
||||
)
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend__icon'><Icon id='retweet' icon={RepeatIcon} /></div>
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <Link data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} to={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></Link> }} />
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -570,13 +578,11 @@ class Status extends ImmutablePureComponent {
|
|||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
</Link>
|
||||
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name'>
|
||||
<LinkedDisplayName displayProps={{account: status.get('account')}} className='status__display-name'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar}
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</Link>
|
||||
</LinkedDisplayName>
|
||||
|
||||
{isQuotedPost && !!this.props.onQuoteCancel && (
|
||||
<IconButton
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { DisplayedName } from 'mastodon/features/notifications_v2/components/displayed_name';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { LinkedDisplayName } from './display_name';
|
||||
|
||||
export const StatusThreadLabel: React.FC<{
|
||||
accountId: string;
|
||||
inReplyToAccountId: string;
|
||||
|
|
@ -27,7 +28,13 @@ export const StatusThreadLabel: React.FC<{
|
|||
<FormattedMessage
|
||||
id='status.replied_to'
|
||||
defaultMessage='Replied to {name}'
|
||||
values={{ name: <DisplayedName accountIds={[inReplyToAccountId]} /> }}
|
||||
values={{
|
||||
name: (
|
||||
<LinkedDisplayName
|
||||
displayProps={{ account: inReplyToAccount, variant: 'simple' }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue