|
|
|
@ -10,6 +10,7 @@ import type { OnElementHandler } from '@/mastodon/utils/html';
|
|
|
|
export interface HandledLinkProps {
|
|
|
|
export interface HandledLinkProps {
|
|
|
|
href: string;
|
|
|
|
href: string;
|
|
|
|
text: string;
|
|
|
|
text: string;
|
|
|
|
|
|
|
|
prevText?: string;
|
|
|
|
hashtagAccountId?: string;
|
|
|
|
hashtagAccountId?: string;
|
|
|
|
mention?: Pick<ApiMentionJSON, 'id' | 'acct'>;
|
|
|
|
mention?: Pick<ApiMentionJSON, 'id' | 'acct'>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -17,13 +18,15 @@ export interface HandledLinkProps {
|
|
|
|
export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
|
|
|
export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
|
|
|
href,
|
|
|
|
href,
|
|
|
|
text,
|
|
|
|
text,
|
|
|
|
|
|
|
|
prevText,
|
|
|
|
hashtagAccountId,
|
|
|
|
hashtagAccountId,
|
|
|
|
mention,
|
|
|
|
mention,
|
|
|
|
className,
|
|
|
|
className,
|
|
|
|
|
|
|
|
children,
|
|
|
|
...props
|
|
|
|
...props
|
|
|
|
}) => {
|
|
|
|
}) => {
|
|
|
|
// Handle hashtags
|
|
|
|
// Handle hashtags
|
|
|
|
if (text.startsWith('#')) {
|
|
|
|
if (text.startsWith('#') || prevText?.endsWith('#')) {
|
|
|
|
const hashtag = text.slice(1).trim();
|
|
|
|
const hashtag = text.slice(1).trim();
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Link
|
|
|
|
<Link
|
|
|
|
@ -32,10 +35,10 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
|
|
|
rel='tag'
|
|
|
|
rel='tag'
|
|
|
|
data-menu-hashtag={hashtagAccountId}
|
|
|
|
data-menu-hashtag={hashtagAccountId}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
#<span>{hashtag}</span>
|
|
|
|
{children}
|
|
|
|
</Link>
|
|
|
|
</Link>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
} else if (text.startsWith('@') && mention) {
|
|
|
|
} else if ((text.startsWith('@') || prevText?.endsWith('@')) && mention) {
|
|
|
|
// Handle mentions
|
|
|
|
// Handle mentions
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Link
|
|
|
|
<Link
|
|
|
|
@ -44,41 +47,33 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
|
|
|
title={`@${mention.acct}`}
|
|
|
|
title={`@${mention.acct}`}
|
|
|
|
data-hover-card-account={mention.id}
|
|
|
|
data-hover-card-account={mention.id}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
@<span>{text.slice(1).trim()}</span>
|
|
|
|
{children}
|
|
|
|
</Link>
|
|
|
|
</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('/')) {
|
|
|
|
if (href.startsWith('/')) {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Link className={classNames('unhandled-link', className)} to={href}>
|
|
|
|
<Link className={classNames('unhandled-link', className)} to={href}>
|
|
|
|
{text}
|
|
|
|
{children}
|
|
|
|
</Link>
|
|
|
|
</Link>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
return (
|
|
|
|
const url = new URL(href);
|
|
|
|
<a
|
|
|
|
const [first, ...rest] = url.pathname.split('/').slice(1); // Start at 1 to skip the leading slash.
|
|
|
|
{...props}
|
|
|
|
return (
|
|
|
|
href={href}
|
|
|
|
<a
|
|
|
|
title={href}
|
|
|
|
{...props}
|
|
|
|
className={classNames('unhandled-link', className)}
|
|
|
|
href={href}
|
|
|
|
target='_blank'
|
|
|
|
title={href}
|
|
|
|
rel='noreferrer noopener'
|
|
|
|
className={classNames('unhandled-link', className)}
|
|
|
|
translate='no'
|
|
|
|
target='_blank'
|
|
|
|
>
|
|
|
|
rel='noreferrer noopener'
|
|
|
|
{children}
|
|
|
|
translate='no'
|
|
|
|
</a>
|
|
|
|
>
|
|
|
|
);
|
|
|
|
<span className='invisible'>{url.protocol + '//'}</span>
|
|
|
|
|
|
|
|
<span className='ellipsis'>{`${url.hostname}/${first ?? ''}`}</span>
|
|
|
|
|
|
|
|
<span className='invisible'>{'/' + rest.join('/')}</span>
|
|
|
|
|
|
|
|
</a>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
|
|
return text;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const useElementHandledLink = ({
|
|
|
|
export const useElementHandledLink = ({
|
|
|
|
@ -89,7 +84,7 @@ export const useElementHandledLink = ({
|
|
|
|
hrefToMention?: (href: string) => ApiMentionJSON | undefined;
|
|
|
|
hrefToMention?: (href: string) => ApiMentionJSON | undefined;
|
|
|
|
} = {}) => {
|
|
|
|
} = {}) => {
|
|
|
|
const onElement = useCallback<OnElementHandler>(
|
|
|
|
const onElement = useCallback<OnElementHandler>(
|
|
|
|
(element, { key, ...props }) => {
|
|
|
|
(element, { key, ...props }, children) => {
|
|
|
|
if (element instanceof HTMLAnchorElement) {
|
|
|
|
if (element instanceof HTMLAnchorElement) {
|
|
|
|
const mention = hrefToMention?.(element.href);
|
|
|
|
const mention = hrefToMention?.(element.href);
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
@ -98,9 +93,12 @@ export const useElementHandledLink = ({
|
|
|
|
key={key as string} // React requires keys to not be part of spread props.
|
|
|
|
key={key as string} // React requires keys to not be part of spread props.
|
|
|
|
href={element.href}
|
|
|
|
href={element.href}
|
|
|
|
text={element.innerText}
|
|
|
|
text={element.innerText}
|
|
|
|
|
|
|
|
prevText={element.previousSibling?.textContent ?? undefined}
|
|
|
|
hashtagAccountId={hashtagAccountId}
|
|
|
|
hashtagAccountId={hashtagAccountId}
|
|
|
|
mention={mention}
|
|
|
|
mention={mention}
|
|
|
|
/>
|
|
|
|
>
|
|
|
|
|
|
|
|
{children}
|
|
|
|
|
|
|
|
</HandledLink>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
return undefined;
|
|
|
|
|