Fixes handled link formatting (#36410)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
@@ -1,19 +1,24 @@
|
||||
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<HandledLinkProps, 'href' | 'text'> & {
|
||||
type HandledLinkStoryProps = Pick<
|
||||
HandledLinkProps,
|
||||
'href' | 'text' | 'prevText'
|
||||
> & {
|
||||
mentionAccount: 'local' | 'remote' | 'none';
|
||||
hashtagAccount: boolean;
|
||||
};
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Status/HandledLink',
|
||||
render({ mentionAccount, ...args }) {
|
||||
render({ mentionAccount, hashtagAccount, ...args }) {
|
||||
let mention: HandledLinkProps['mention'] | undefined;
|
||||
if (mentionAccount === 'local') {
|
||||
mention = { id: '1', acct: 'testuser' };
|
||||
@@ -22,7 +27,13 @@ const meta = {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<HandledLink {...args} mention={mention} hashtagAccountId='1' />
|
||||
<HandledLink
|
||||
{...args}
|
||||
mention={mention}
|
||||
hashtagAccountId={hashtagAccount ? '1' : undefined}
|
||||
>
|
||||
<span>{args.text}</span>
|
||||
</HandledLink>
|
||||
<HashtagMenuController />
|
||||
<HoverCardController />
|
||||
</>
|
||||
@@ -32,6 +43,7 @@ const meta = {
|
||||
href: 'https://example.com/path/subpath?query=1#hash',
|
||||
text: 'https://example.com',
|
||||
mentionAccount: 'none',
|
||||
hashtagAccount: false,
|
||||
},
|
||||
argTypes: {
|
||||
mentionAccount: {
|
||||
@@ -40,6 +52,13 @@ const meta = {
|
||||
defaultValue: 'none',
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
state: {
|
||||
accounts: {
|
||||
'1': accountFactoryState({ id: '1', acct: 'hashtaguser' }),
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<HandledLinkStoryProps>;
|
||||
|
||||
export default meta;
|
||||
@@ -48,9 +67,16 @@ type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
href: 'https://example.com/test',
|
||||
},
|
||||
};
|
||||
|
||||
export const Hashtag: Story = {
|
||||
args: {
|
||||
text: '#example',
|
||||
hashtagAccount: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { OnElementHandler } from '@/mastodon/utils/html';
|
||||
export interface HandledLinkProps {
|
||||
href: string;
|
||||
text: string;
|
||||
prevText?: string;
|
||||
hashtagAccountId?: string;
|
||||
mention?: Pick<ApiMentionJSON, 'id' | 'acct'>;
|
||||
}
|
||||
@@ -17,13 +18,15 @@ export interface HandledLinkProps {
|
||||
export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
href,
|
||||
text,
|
||||
prevText,
|
||||
hashtagAccountId,
|
||||
mention,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
// Handle hashtags
|
||||
if (text.startsWith('#')) {
|
||||
if (text.startsWith('#') || prevText?.endsWith('#')) {
|
||||
const hashtag = text.slice(1).trim();
|
||||
return (
|
||||
<Link
|
||||
@@ -32,10 +35,10 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
rel='tag'
|
||||
data-menu-hashtag={hashtagAccountId}
|
||||
>
|
||||
#<span>{hashtag}</span>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
} else if (text.startsWith('@') && mention) {
|
||||
} else if ((text.startsWith('@') || prevText?.endsWith('@')) && mention) {
|
||||
// Handle mentions
|
||||
return (
|
||||
<Link
|
||||
@@ -44,23 +47,20 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
title={`@${mention.acct}`}
|
||||
data-hover-card-account={mention.id}
|
||||
>
|
||||
@<span>{text.slice(1).trim()}</span>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Non-absolute paths treated as internal links.
|
||||
// Non-absolute paths treated as internal links. This shouldn't happen, but just in case.
|
||||
if (href.startsWith('/')) {
|
||||
return (
|
||||
<Link className={classNames('unhandled-link', className)} to={href}>
|
||||
{text}
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(href);
|
||||
const [first, ...rest] = url.pathname.split('/').slice(1); // Start at 1 to skip the leading slash.
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
@@ -71,14 +71,9 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
rel='noreferrer noopener'
|
||||
translate='no'
|
||||
>
|
||||
<span className='invisible'>{url.protocol + '//'}</span>
|
||||
<span className='ellipsis'>{`${url.hostname}/${first ?? ''}`}</span>
|
||||
<span className='invisible'>{'/' + rest.join('/')}</span>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
export const useElementHandledLink = ({
|
||||
@@ -89,7 +84,7 @@ export const useElementHandledLink = ({
|
||||
hrefToMention?: (href: string) => ApiMentionJSON | undefined;
|
||||
} = {}) => {
|
||||
const onElement = useCallback<OnElementHandler>(
|
||||
(element, { key, ...props }) => {
|
||||
(element, { key, ...props }, children) => {
|
||||
if (element instanceof HTMLAnchorElement) {
|
||||
const mention = hrefToMention?.(element.href);
|
||||
return (
|
||||
@@ -98,9 +93,12 @@ export const useElementHandledLink = ({
|
||||
key={key as string} // React requires keys to not be part of spread props.
|
||||
href={element.href}
|
||||
text={element.innerText}
|
||||
prevText={element.previousSibling?.textContent ?? undefined}
|
||||
hashtagAccountId={hashtagAccountId}
|
||||
mention={mention}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</HandledLink>
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -204,7 +204,7 @@ class StatusContent extends PureComponent {
|
||||
this.node = c;
|
||||
};
|
||||
|
||||
handleElement = (element, { key, ...props }) => {
|
||||
handleElement = (element, { key, ...props }, children) => {
|
||||
if (element instanceof HTMLAnchorElement) {
|
||||
const mention = this.props.status.get('mentions').find(item => element.href === item.get('url'));
|
||||
return (
|
||||
@@ -215,7 +215,9 @@ class StatusContent extends PureComponent {
|
||||
hashtagAccountId={this.props.status.getIn(['account', 'id'])}
|
||||
mention={mention?.toJSON()}
|
||||
key={key}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</HandledLink>
|
||||
);
|
||||
} else if (element instanceof HTMLParagraphElement && element.classList.contains('quote-inline')) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user