From 5bc7c4b7e880ee1456dc21987d8fba32e340d31e Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 8 Oct 2025 19:17:03 +0200 Subject: [PATCH] Emoji: Fixes issue with handled link not correctly showing remote users (#36403) --- .../status/handled_link.stories.tsx | 29 +++++++++----- .../components/status/handled_link.tsx | 38 +++++++++---------- .../mastodon/components/status_content.jsx | 2 +- .../components/embedded_status_content.tsx | 5 +-- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/app/javascript/mastodon/components/status/handled_link.stories.tsx b/app/javascript/mastodon/components/status/handled_link.stories.tsx index a45e33626ae..71bf8eee63d 100644 --- a/app/javascript/mastodon/components/status/handled_link.stories.tsx +++ b/app/javascript/mastodon/components/status/handled_link.stories.tsx @@ -1,19 +1,28 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { HashtagMenuController } from '@/mastodon/features/ui/components/hashtag_menu_controller'; -import { accountFactoryState } from '@/testing/factories'; import { HoverCardController } from '../hover_card_controller'; import type { HandledLinkProps } from './handled_link'; import { HandledLink } from './handled_link'; +type HandledLinkStoryProps = Pick & { + mentionAccount: 'local' | 'remote' | 'none'; +}; + const meta = { 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 ( <> - + @@ -22,15 +31,16 @@ const meta = { args: { href: 'https://example.com/path/subpath?query=1#hash', text: 'https://example.com', + mentionAccount: 'none', }, - parameters: { - state: { - accounts: { - '1': accountFactoryState(), - }, + argTypes: { + mentionAccount: { + control: { type: 'select' }, + options: ['local', 'remote', 'none'], + defaultValue: 'none', }, }, -} satisfies Meta>; +} satisfies Meta; export default meta; @@ -47,6 +57,7 @@ export const Hashtag: Story = { export const Mention: Story = { args: { text: '@user', + mentionAccount: 'local', }, }; diff --git a/app/javascript/mastodon/components/status/handled_link.tsx b/app/javascript/mastodon/components/status/handled_link.tsx index 83262886e85..0f486b33e95 100644 --- a/app/javascript/mastodon/components/status/handled_link.tsx +++ b/app/javascript/mastodon/components/status/handled_link.tsx @@ -1,22 +1,25 @@ import { useCallback } from 'react'; import type { ComponentProps, FC } from 'react'; +import classNames from 'classnames'; import { Link } from 'react-router-dom'; +import type { ApiMentionJSON } from '@/mastodon/api_types/statuses'; import type { OnElementHandler } from '@/mastodon/utils/html'; export interface HandledLinkProps { href: string; text: string; hashtagAccountId?: string; - mentionAccountId?: string; + mention?: Pick; } export const HandledLink: FC> = ({ href, text, hashtagAccountId, - mentionAccountId, + mention, + className, ...props }) => { // Handle hashtags @@ -24,8 +27,7 @@ export const HandledLink: FC> = ({ const hashtag = text.slice(1).trim(); return ( > = ({ #{hashtag} ); - } else if (text.startsWith('@')) { + } else if (text.startsWith('@') && mention) { // Handle mentions - const mention = text.slice(1).trim(); return ( - @{mention} + @{text.slice(1).trim()} ); } @@ -52,7 +52,7 @@ export const HandledLink: FC> = ({ // Non-absolute paths treated as internal links. if (href.startsWith('/')) { return ( - + {text} ); @@ -66,7 +66,7 @@ export const HandledLink: FC> = ({ {...props} href={href} title={href} - className='unhandled-link' + className={classNames('unhandled-link', className)} target='_blank' rel='noreferrer noopener' translate='no' @@ -83,15 +83,15 @@ export const HandledLink: FC> = ({ export const useElementHandledLink = ({ hashtagAccountId, - hrefToMentionAccountId, + hrefToMention, }: { hashtagAccountId?: string; - hrefToMentionAccountId?: (href: string) => string | undefined; + hrefToMention?: (href: string) => ApiMentionJSON | undefined; } = {}) => { const onElement = useCallback( (element, { key, ...props }) => { if (element instanceof HTMLAnchorElement) { - const mentionId = hrefToMentionAccountId?.(element.href); + const mention = hrefToMention?.(element.href); return ( ); } return undefined; }, - [hashtagAccountId, hrefToMentionAccountId], + [hashtagAccountId, hrefToMention], ); return { onElement }; }; diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index ede98cc81a3..54579a1134f 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -213,7 +213,7 @@ class StatusContent extends PureComponent { href={element.href} text={element.innerText} hashtagAccountId={this.props.status.getIn(['account', 'id'])} - mentionAccountId={mention?.get('id')} + mention={mention?.toJSON()} key={key} /> ); diff --git a/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx b/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx index 91c3abde38d..b7dc998a479 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx @@ -48,9 +48,8 @@ export const EmbeddedStatusContent: React.FC<{ ); const htmlHandlers = useElementHandledLink({ hashtagAccountId: status.get('account') as string | undefined, - hrefToMentionAccountId(href) { - const mention = mentions.find((item) => item.url === href); - return mention?.id; + hrefToMention(href) { + return mentions.find((item) => item.url === href); }, });