Emoji: Fixes issue with handled link not correctly showing remote users (#36403)
This commit is contained in:
@@ -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<HandledLinkStoryProps>;
|
||||||
} satisfies Meta<Pick<HandledLinkProps, 'href' | 'text'>>;
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user