mirror of
https://github.com/mastodon/mastodon.git
synced 2025-10-05 16:42:47 +00:00
add stories and fix some edge cases with HandledLink
This commit is contained in:
parent
e53f58835f
commit
11fe92a5c6
|
@ -0,0 +1,65 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Status/HandledLink',
|
||||||
|
render(args) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HandledLink {...args} mentionAccountId='1' hashtagAccountId='1' />
|
||||||
|
<HashtagMenuController />
|
||||||
|
<HoverCardController />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
href: 'https://example.com/path/subpath?query=1#hash',
|
||||||
|
text: 'https://example.com',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
state: {
|
||||||
|
accounts: {
|
||||||
|
'1': accountFactoryState(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<Pick<HandledLinkProps, 'href' | 'text'>>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
export const Hashtag: Story = {
|
||||||
|
args: {
|
||||||
|
text: '#example',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mention: Story = {
|
||||||
|
args: {
|
||||||
|
text: '@user',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InternalLink: Story = {
|
||||||
|
args: {
|
||||||
|
href: '/about',
|
||||||
|
text: 'About',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InvalidURL: Story = {
|
||||||
|
args: {
|
||||||
|
href: 'ht!tp://invalid-url',
|
||||||
|
text: 'ht!tp://invalid-url -- invalid!',
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,9 +1,8 @@
|
||||||
import { useId } from 'react';
|
|
||||||
import type { ComponentProps, FC } from 'react';
|
import type { ComponentProps, FC } from 'react';
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
interface HandledLinkProps {
|
export interface HandledLinkProps {
|
||||||
href: string;
|
href: string;
|
||||||
text: string;
|
text: string;
|
||||||
hashtagAccountId?: string;
|
hashtagAccountId?: string;
|
||||||
|
@ -15,9 +14,9 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||||
text,
|
text,
|
||||||
hashtagAccountId,
|
hashtagAccountId,
|
||||||
mentionAccountId,
|
mentionAccountId,
|
||||||
|
key,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const id = useId();
|
|
||||||
// Handle hashtags
|
// Handle hashtags
|
||||||
if (text.startsWith('#')) {
|
if (text.startsWith('#')) {
|
||||||
const hashtag = text.slice(1).trim();
|
const hashtag = text.slice(1).trim();
|
||||||
|
@ -28,7 +27,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||||
to={`/tags/${hashtag}`}
|
to={`/tags/${hashtag}`}
|
||||||
rel='tag'
|
rel='tag'
|
||||||
data-menu-hashtag={hashtagAccountId}
|
data-menu-hashtag={hashtagAccountId}
|
||||||
key={id}
|
key={key}
|
||||||
>
|
>
|
||||||
#<span>{hashtag}</span>
|
#<span>{hashtag}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -39,20 +38,29 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
{...props}
|
{...props}
|
||||||
|
className='mention'
|
||||||
to={`/@${mention}`}
|
to={`/@${mention}`}
|
||||||
title={`@${mention}`}
|
title={`@${mention}`}
|
||||||
data-hover-card-account={mentionAccountId}
|
data-hover-card-account={mentionAccountId}
|
||||||
key={id}
|
key={key}
|
||||||
>
|
>
|
||||||
@<span>{mention}</span>
|
@<span>{mention}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-absolute paths treated as internal links.
|
||||||
if (href.startsWith('/')) {
|
if (href.startsWith('/')) {
|
||||||
return text;
|
return (
|
||||||
|
<Link {...props} className='unhandled-link' to={href} key={key}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = new URL(href);
|
const url = new URL(href);
|
||||||
|
const [first, ...rest] = url.pathname.split('/').slice(1); // Start at 1 to skip the leading slash.
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -62,15 +70,11 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noreferrer noopener'
|
rel='noreferrer noopener'
|
||||||
translate='no'
|
translate='no'
|
||||||
key={id}
|
key={key}
|
||||||
>
|
>
|
||||||
<span className='invisible'>{url.protocol}</span>
|
<span className='invisible'>{url.protocol + '//'}</span>
|
||||||
<span className='ellipsis'>
|
<span className='ellipsis'>{`${url.hostname}/${first ?? ''}`}</span>
|
||||||
{url.hostname + url.pathname.split('/').slice(0, 1).join('/')}
|
<span className='invisible'>{'/' + rest.join('/')}</span>
|
||||||
</span>
|
|
||||||
<span className='invisible'>
|
|
||||||
{url.pathname.split('/').slice(1).join('/') + url.search + url.hash}
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user