Emoji: Fixes issue with handled link not correctly showing remote users (#36403)

pull/36404/head
Echo 1 day ago committed by GitHub
parent b8444d9bb7
commit 5bc7c4b7e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,19 +1,28 @@
import type { Meta, StoryObj } from '@storybook/react-vite'; import type { Meta, StoryObj } from '@storybook/react-vite';
import { HashtagMenuController } from '@/mastodon/features/ui/components/hashtag_menu_controller'; import { HashtagMenuController } from '@/mastodon/features/ui/components/hashtag_menu_controller';
import { accountFactoryState } from '@/testing/factories';
import { HoverCardController } from '../hover_card_controller'; import { HoverCardController } from '../hover_card_controller';
import type { HandledLinkProps } from './handled_link'; import type { HandledLinkProps } from './handled_link';
import { HandledLink } from './handled_link'; import { HandledLink } from './handled_link';
type HandledLinkStoryProps = Pick<HandledLinkProps, 'href' | 'text'> & {
mentionAccount: 'local' | 'remote' | 'none';
};
const meta = { const meta = {
title: 'Components/Status/HandledLink', title: 'Components/Status/HandledLink',
render(args) { render({ mentionAccount, ...args }) {
let mention: HandledLinkProps['mention'] | undefined;
if (mentionAccount === 'local') {
mention = { id: '1', acct: 'testuser' };
} else if (mentionAccount === 'remote') {
mention = { id: '2', acct: 'remoteuser@mastodon.social' };
}
return ( return (
<> <>
<HandledLink {...args} mentionAccountId='1' hashtagAccountId='1' /> <HandledLink {...args} mention={mention} hashtagAccountId='1' />
<HashtagMenuController /> <HashtagMenuController />
<HoverCardController /> <HoverCardController />
</> </>
@ -22,15 +31,16 @@ const meta = {
args: { args: {
href: 'https://example.com/path/subpath?query=1#hash', href: 'https://example.com/path/subpath?query=1#hash',
text: 'https://example.com', text: 'https://example.com',
mentionAccount: 'none',
}, },
parameters: { argTypes: {
state: { mentionAccount: {
accounts: { control: { type: 'select' },
'1': accountFactoryState(), options: ['local', 'remote', 'none'],
}, defaultValue: 'none',
}, },
}, },
} satisfies Meta<Pick<HandledLinkProps, 'href' | 'text'>>; } satisfies Meta<HandledLinkStoryProps>;
export default meta; export default meta;
@ -47,6 +57,7 @@ export const Hashtag: Story = {
export const Mention: Story = { export const Mention: Story = {
args: { args: {
text: '@user', text: '@user',
mentionAccount: 'local',
}, },
}; };

@ -1,22 +1,25 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { ComponentProps, FC } from 'react'; import type { ComponentProps, FC } from 'react';
import classNames from 'classnames';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { ApiMentionJSON } from '@/mastodon/api_types/statuses';
import type { OnElementHandler } from '@/mastodon/utils/html'; import type { OnElementHandler } from '@/mastodon/utils/html';
export interface HandledLinkProps { export interface HandledLinkProps {
href: string; href: string;
text: string; text: string;
hashtagAccountId?: string; hashtagAccountId?: string;
mentionAccountId?: string; mention?: Pick<ApiMentionJSON, 'id' | 'acct'>;
} }
export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
href, href,
text, text,
hashtagAccountId, hashtagAccountId,
mentionAccountId, mention,
className,
...props ...props
}) => { }) => {
// Handle hashtags // Handle hashtags
@ -24,8 +27,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
const hashtag = text.slice(1).trim(); const hashtag = text.slice(1).trim();
return ( return (
<Link <Link
{...props} className={classNames('mention hashtag', className)}
className='mention hashtag'
to={`/tags/${hashtag}`} to={`/tags/${hashtag}`}
rel='tag' rel='tag'
data-menu-hashtag={hashtagAccountId} data-menu-hashtag={hashtagAccountId}
@ -33,18 +35,16 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
#<span>{hashtag}</span> #<span>{hashtag}</span>
</Link> </Link>
); );
} else if (text.startsWith('@')) { } else if (text.startsWith('@') && mention) {
// Handle mentions // Handle mentions
const mention = text.slice(1).trim();
return ( return (
<Link <Link
{...props} className={classNames('mention', className)}
className='mention' to={`/@${mention.acct}`}
to={`/@${mention}`} title={`@${mention.acct}`}
title={`@${mention}`} data-hover-card-account={mention.id}
data-hover-card-account={mentionAccountId}
> >
@<span>{mention}</span> @<span>{text.slice(1).trim()}</span>
</Link> </Link>
); );
} }
@ -52,7 +52,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
// Non-absolute paths treated as internal links. // Non-absolute paths treated as internal links.
if (href.startsWith('/')) { if (href.startsWith('/')) {
return ( return (
<Link {...props} className='unhandled-link' to={href}> <Link className={classNames('unhandled-link', className)} to={href}>
{text} {text}
</Link> </Link>
); );
@ -66,7 +66,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
{...props} {...props}
href={href} href={href}
title={href} title={href}
className='unhandled-link' className={classNames('unhandled-link', className)}
target='_blank' target='_blank'
rel='noreferrer noopener' rel='noreferrer noopener'
translate='no' translate='no'
@ -83,15 +83,15 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
export const useElementHandledLink = ({ export const useElementHandledLink = ({
hashtagAccountId, hashtagAccountId,
hrefToMentionAccountId, hrefToMention,
}: { }: {
hashtagAccountId?: string; hashtagAccountId?: string;
hrefToMentionAccountId?: (href: string) => string | undefined; hrefToMention?: (href: string) => ApiMentionJSON | undefined;
} = {}) => { } = {}) => {
const onElement = useCallback<OnElementHandler>( const onElement = useCallback<OnElementHandler>(
(element, { key, ...props }) => { (element, { key, ...props }) => {
if (element instanceof HTMLAnchorElement) { if (element instanceof HTMLAnchorElement) {
const mentionId = hrefToMentionAccountId?.(element.href); const mention = hrefToMention?.(element.href);
return ( return (
<HandledLink <HandledLink
{...props} {...props}
@ -99,13 +99,13 @@ export const useElementHandledLink = ({
href={element.href} href={element.href}
text={element.innerText} text={element.innerText}
hashtagAccountId={hashtagAccountId} hashtagAccountId={hashtagAccountId}
mentionAccountId={mentionId} mention={mention}
/> />
); );
} }
return undefined; return undefined;
}, },
[hashtagAccountId, hrefToMentionAccountId], [hashtagAccountId, hrefToMention],
); );
return { onElement }; return { onElement };
}; };

@ -213,7 +213,7 @@ class StatusContent extends PureComponent {
href={element.href} href={element.href}
text={element.innerText} text={element.innerText}
hashtagAccountId={this.props.status.getIn(['account', 'id'])} hashtagAccountId={this.props.status.getIn(['account', 'id'])}
mentionAccountId={mention?.get('id')} mention={mention?.toJSON()}
key={key} key={key}
/> />
); );

@ -48,9 +48,8 @@ export const EmbeddedStatusContent: React.FC<{
); );
const htmlHandlers = useElementHandledLink({ const htmlHandlers = useElementHandledLink({
hashtagAccountId: status.get('account') as string | undefined, hashtagAccountId: status.get('account') as string | undefined,
hrefToMentionAccountId(href) { hrefToMention(href) {
const mention = mentions.find((item) => item.url === href); return mentions.find((item) => item.url === href);
return mention?.id;
}, },
}); });

Loading…
Cancel
Save