Merge commit 'd3f504245cab5a9a0e89262e0a1398d035dffac9' into glitch-soc/merge-upstream
This commit is contained in:
commit
92dcc50278
|
@ -2,19 +2,34 @@ import { useRef, useCallback, useEffect } from 'react';
|
|||
|
||||
export const useTimeout = () => {
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const callbackRef = useRef<() => void>();
|
||||
|
||||
const set = useCallback((callback: () => void, delay: number) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
callbackRef.current = callback;
|
||||
timeoutRef.current = setTimeout(callback, delay);
|
||||
}, []);
|
||||
|
||||
const delay = useCallback((delay: number) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
if (!callbackRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(callbackRef.current, delay);
|
||||
}, []);
|
||||
|
||||
const cancel = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = undefined;
|
||||
callbackRef.current = undefined;
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
@ -25,5 +40,5 @@ export const useTimeout = () => {
|
|||
[cancel],
|
||||
);
|
||||
|
||||
return [set, cancel] as const;
|
||||
return [set, cancel, delay] as const;
|
||||
};
|
||||
|
|
|
@ -158,6 +158,7 @@ export const expandAccountTimeline = (accountId, { maxId, withReplies, t
|
|||
export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
|
||||
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
||||
export const expandLinkTimeline = (url, { maxId } = {}, done = noOp) => expandTimeline(`link:${url}`, `/api/v1/timelines/link`, { url, max_id: maxId }, done);
|
||||
export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => {
|
||||
return expandTimeline(`hashtag:${hashtag}${local ? ':local' : ''}`, `/api/v1/timelines/tag/${hashtag}`, {
|
||||
max_id: maxId,
|
||||
|
|
|
@ -44,6 +44,7 @@ export interface ApiPreviewCardJSON {
|
|||
type: string;
|
||||
author_name: string;
|
||||
author_url: string;
|
||||
author_account?: ApiAccountJSON;
|
||||
provider_name: string;
|
||||
provider_url: string;
|
||||
html: string;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import {
|
||||
|
@ -19,15 +19,11 @@ const messages = defineMessages({
|
|||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' },
|
||||
mutual: { id: 'account.mutual', defaultMessage: 'Mutual' },
|
||||
cancel_follow_request: {
|
||||
id: 'account.cancel_follow_request',
|
||||
defaultMessage: 'Withdraw follow request',
|
||||
},
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
});
|
||||
|
||||
export const FollowButton: React.FC<{
|
||||
accountId: string;
|
||||
accountId?: string;
|
||||
}> = ({ accountId }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -36,7 +32,7 @@ export const FollowButton: React.FC<{
|
|||
accountId ? state.accounts.get(accountId) : undefined,
|
||||
);
|
||||
const relationship = useAppSelector((state) =>
|
||||
state.relationships.get(accountId),
|
||||
accountId ? state.relationships.get(accountId) : undefined,
|
||||
);
|
||||
const following = relationship?.following || relationship?.requested;
|
||||
|
||||
|
@ -65,11 +61,28 @@ export const FollowButton: React.FC<{
|
|||
if (accountId === me) {
|
||||
return;
|
||||
} else if (relationship.following || relationship.requested) {
|
||||
dispatch(unfollowAccount(accountId));
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
message: (
|
||||
<FormattedMessage
|
||||
id='confirmations.unfollow.message'
|
||||
defaultMessage='Are you sure you want to unfollow {name}?'
|
||||
values={{ name: <strong>@{account?.acct}</strong> }}
|
||||
/>
|
||||
),
|
||||
confirm: intl.formatMessage(messages.unfollow),
|
||||
onConfirm: () => {
|
||||
dispatch(unfollowAccount(accountId));
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(followAccount(accountId));
|
||||
}
|
||||
}, [dispatch, accountId, relationship, account, signedIn]);
|
||||
}, [dispatch, intl, accountId, relationship, account, signedIn]);
|
||||
|
||||
let label;
|
||||
|
||||
|
@ -79,13 +92,11 @@ export const FollowButton: React.FC<{
|
|||
label = intl.formatMessage(messages.edit_profile);
|
||||
} else if (!relationship) {
|
||||
label = <LoadingIndicator />;
|
||||
} else if (relationship.requested) {
|
||||
label = intl.formatMessage(messages.cancel_follow_request);
|
||||
} else if (relationship.following && relationship.followed_by) {
|
||||
label = intl.formatMessage(messages.mutual);
|
||||
} else if (!relationship.following && relationship.followed_by) {
|
||||
label = intl.formatMessage(messages.followBack);
|
||||
} else if (relationship.following) {
|
||||
} else if (relationship.following || relationship.requested) {
|
||||
label = intl.formatMessage(messages.unfollow);
|
||||
} else {
|
||||
label = intl.formatMessage(messages.follow);
|
||||
|
|
|
@ -17,7 +17,7 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
|||
|
||||
export const HoverCardAccount = forwardRef<
|
||||
HTMLDivElement,
|
||||
{ accountId: string }
|
||||
{ accountId?: string }
|
||||
>(({ accountId }, ref) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ import { useTimeout } from 'mastodon/../hooks/useTimeout';
|
|||
import { HoverCardAccount } from 'mastodon/components/hover_card_account';
|
||||
|
||||
const offset = [-12, 4] as OffsetValue;
|
||||
const enterDelay = 650;
|
||||
const leaveDelay = 250;
|
||||
const enterDelay = 750;
|
||||
const leaveDelay = 150;
|
||||
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
||||
|
||||
const isHoverCardAnchor = (element: HTMLElement) =>
|
||||
|
@ -23,50 +23,12 @@ export const HoverCardController: React.FC = () => {
|
|||
const [open, setOpen] = useState(false);
|
||||
const [accountId, setAccountId] = useState<string | undefined>();
|
||||
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
|
||||
const [setEnterTimeout, cancelEnterTimeout] = useTimeout();
|
||||
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
|
||||
const [setScrollTimeout] = useTimeout();
|
||||
const location = useLocation();
|
||||
|
||||
const handleAnchorMouseEnter = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
const { target } = e;
|
||||
|
||||
if (target instanceof HTMLElement && isHoverCardAnchor(target)) {
|
||||
cancelLeaveTimeout();
|
||||
|
||||
setEnterTimeout(() => {
|
||||
target.setAttribute('aria-describedby', 'hover-card');
|
||||
setAnchor(target);
|
||||
setOpen(true);
|
||||
setAccountId(
|
||||
target.getAttribute('data-hover-card-account') ?? undefined,
|
||||
);
|
||||
}, enterDelay);
|
||||
}
|
||||
|
||||
if (target === cardRef.current?.parentNode) {
|
||||
cancelLeaveTimeout();
|
||||
}
|
||||
},
|
||||
[cancelLeaveTimeout, setEnterTimeout, setOpen, setAccountId, setAnchor],
|
||||
);
|
||||
|
||||
const handleAnchorMouseLeave = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (e.target === anchor || e.target === cardRef.current?.parentNode) {
|
||||
cancelEnterTimeout();
|
||||
|
||||
setLeaveTimeout(() => {
|
||||
anchor?.removeAttribute('aria-describedby');
|
||||
setOpen(false);
|
||||
setAnchor(null);
|
||||
}, leaveDelay);
|
||||
}
|
||||
},
|
||||
[cancelEnterTimeout, setLeaveTimeout, setOpen, setAnchor, anchor],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
cancelEnterTimeout();
|
||||
cancelLeaveTimeout();
|
||||
|
@ -79,22 +41,119 @@ export const HoverCardController: React.FC = () => {
|
|||
}, [handleClose, location]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.addEventListener('mouseenter', handleAnchorMouseEnter, {
|
||||
let isScrolling = false;
|
||||
let currentAnchor: HTMLElement | null = null;
|
||||
|
||||
const open = (target: HTMLElement) => {
|
||||
target.setAttribute('aria-describedby', 'hover-card');
|
||||
setOpen(true);
|
||||
setAnchor(target);
|
||||
setAccountId(target.getAttribute('data-hover-card-account') ?? undefined);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
currentAnchor?.removeAttribute('aria-describedby');
|
||||
currentAnchor = null;
|
||||
setOpen(false);
|
||||
setAnchor(null);
|
||||
setAccountId(undefined);
|
||||
};
|
||||
|
||||
const handleMouseEnter = (e: MouseEvent) => {
|
||||
const { target } = e;
|
||||
|
||||
// We've exited the window
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
// We've entered an anchor
|
||||
if (!isScrolling && isHoverCardAnchor(target)) {
|
||||
cancelLeaveTimeout();
|
||||
|
||||
currentAnchor?.removeAttribute('aria-describedby');
|
||||
currentAnchor = target;
|
||||
|
||||
setEnterTimeout(() => {
|
||||
open(target);
|
||||
}, enterDelay);
|
||||
}
|
||||
|
||||
// We've entered the hover card
|
||||
if (
|
||||
!isScrolling &&
|
||||
(target === currentAnchor || target === cardRef.current)
|
||||
) {
|
||||
cancelLeaveTimeout();
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = (e: MouseEvent) => {
|
||||
if (!currentAnchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target === currentAnchor || e.target === cardRef.current) {
|
||||
cancelEnterTimeout();
|
||||
|
||||
setLeaveTimeout(() => {
|
||||
close();
|
||||
}, leaveDelay);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScrollEnd = () => {
|
||||
isScrolling = false;
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
isScrolling = true;
|
||||
cancelEnterTimeout();
|
||||
setScrollTimeout(handleScrollEnd, 100);
|
||||
};
|
||||
|
||||
const handleMouseMove = () => {
|
||||
delayEnterTimeout(enterDelay);
|
||||
};
|
||||
|
||||
document.body.addEventListener('mouseenter', handleMouseEnter, {
|
||||
passive: true,
|
||||
capture: true,
|
||||
});
|
||||
document.body.addEventListener('mouseleave', handleAnchorMouseLeave, {
|
||||
|
||||
document.body.addEventListener('mousemove', handleMouseMove, {
|
||||
passive: true,
|
||||
capture: false,
|
||||
});
|
||||
|
||||
document.body.addEventListener('mouseleave', handleMouseLeave, {
|
||||
passive: true,
|
||||
capture: true,
|
||||
});
|
||||
|
||||
document.addEventListener('scroll', handleScroll, {
|
||||
passive: true,
|
||||
capture: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
document.body.removeEventListener('mouseenter', handleAnchorMouseEnter);
|
||||
document.body.removeEventListener('mouseleave', handleAnchorMouseLeave);
|
||||
document.body.removeEventListener('mouseenter', handleMouseEnter);
|
||||
document.body.removeEventListener('mousemove', handleMouseMove);
|
||||
document.body.removeEventListener('mouseleave', handleMouseLeave);
|
||||
document.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [handleAnchorMouseEnter, handleAnchorMouseLeave]);
|
||||
|
||||
if (!accountId) return null;
|
||||
}, [
|
||||
setEnterTimeout,
|
||||
setLeaveTimeout,
|
||||
setScrollTimeout,
|
||||
cancelEnterTimeout,
|
||||
cancelLeaveTimeout,
|
||||
delayEnterTimeout,
|
||||
setOpen,
|
||||
setAccountId,
|
||||
setAnchor,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
|
|
|
@ -33,6 +33,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||
withCounters: PropTypes.bool,
|
||||
timelineId: PropTypes.string,
|
||||
lastId: PropTypes.string,
|
||||
bindToDocument: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -94,7 +94,7 @@ const messageForFollowButton = relationship => {
|
|||
return messages.mutual;
|
||||
} else if (!relationship.get('following') && relationship.get('followed_by')) {
|
||||
return messages.followBack;
|
||||
} else if (relationship.get('following')) {
|
||||
} else if (relationship.get('following') || relationship.get('requested')) {
|
||||
return messages.unfollow;
|
||||
} else {
|
||||
return messages.follow;
|
||||
|
@ -291,10 +291,8 @@ class Header extends ImmutablePureComponent {
|
|||
if (me !== account.get('id')) {
|
||||
if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
|
||||
actionBtn = <Button disabled><LoadingIndicator /></Button>;
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = <Button text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
|
||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(messageForFollowButton(account.get('relationship')))} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
|
||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) })} text={intl.formatMessage(messageForFollowButton(account.get('relationship')))} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import { makeGetAccount, getAccountHidden } from '../../../selectors';
|
|||
import Header from '../components/header';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' },
|
||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
|
||||
});
|
||||
|
@ -45,7 +44,7 @@ const makeMapStateToProps = () => {
|
|||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
|
||||
onFollow (account) {
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
|
@ -54,15 +53,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM',
|
||||
modalProps: {
|
||||
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
|
||||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
|
|||
menu.push({ text: intl.formatMessage(messages.delete), action: handleDelete });
|
||||
|
||||
const names = accounts.map(a => (
|
||||
<Link to={`/@${a.get('acct')}`} key={a.get('id')} title={a.get('acct')}>
|
||||
<Link to={`/@${a.get('acct')}`} key={a.get('id')} data-hover-card-account={a.get('id')}>
|
||||
<bdi>
|
||||
<strong
|
||||
className='display-name__html'
|
||||
|
|
|
@ -4,6 +4,8 @@ import { useState, useCallback } from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
|
@ -57,7 +59,7 @@ export const Story = ({
|
|||
|
||||
<div className='story__details__shared'>
|
||||
{author ? <FormattedMessage id='link_preview.author' className='story__details__shared__author' defaultMessage='By {name}' values={{ name: authorAccount ? <AuthorLink accountId={authorAccount} /> : <strong>{author}</strong> }} /> : <span />}
|
||||
{typeof sharedTimes === 'number' ? <span className='story__details__shared__pill'><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></span> : <Skeleton width='10ch' />}
|
||||
{typeof sharedTimes === 'number' ? <Link className='story__details__shared__pill' to={`/links/${encodeURIComponent(url)}`}><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></Link> : <Skeleton width='10ch' />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import { useRef, useEffect, useCallback } from 'react';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
|
||||
import { expandLinkTimeline } from 'mastodon/actions/timelines';
|
||||
import Column from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
|
||||
import type { Card } from 'mastodon/models/status';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
export const LinkTimeline: React.FC<{
|
||||
multiColumn: boolean;
|
||||
}> = ({ multiColumn }) => {
|
||||
const { url } = useParams<{ url: string }>();
|
||||
const decodedUrl = url ? decodeURIComponent(url) : undefined;
|
||||
const dispatch = useAppDispatch();
|
||||
const columnRef = useRef<Column>(null);
|
||||
const firstStatusId = useAppSelector((state) =>
|
||||
decodedUrl
|
||||
? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
(state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string)
|
||||
: undefined,
|
||||
);
|
||||
const story = useAppSelector((state) =>
|
||||
firstStatusId
|
||||
? (state.statuses.getIn([firstStatusId, 'card']) as Card)
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const handleHeaderClick = useCallback(() => {
|
||||
columnRef.current?.scrollTop();
|
||||
}, []);
|
||||
|
||||
const handleLoadMore = useCallback(
|
||||
(maxId: string) => {
|
||||
dispatch(expandLinkTimeline(decodedUrl, { maxId }));
|
||||
},
|
||||
[dispatch, decodedUrl],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(expandLinkTimeline(decodedUrl));
|
||||
}, [dispatch, decodedUrl]);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={columnRef} label={story?.title}>
|
||||
<ColumnHeader
|
||||
icon='explore'
|
||||
iconComponent={ExploreIcon}
|
||||
title={story?.title}
|
||||
onClick={handleHeaderClick}
|
||||
multiColumn={multiColumn}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
<StatusListContainer
|
||||
timelineId={`link:${decodedUrl}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
trackScroll
|
||||
scrollKey={`link_timeline-${decodedUrl}`}
|
||||
bindToDocument={!multiColumn}
|
||||
/>
|
||||
|
||||
<Helmet>
|
||||
<title>{story?.title}</title>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default LinkTimeline;
|
|
@ -56,6 +56,7 @@ import {
|
|||
FavouritedStatuses,
|
||||
BookmarkedStatuses,
|
||||
FollowedTags,
|
||||
LinkTimeline,
|
||||
ListTimeline,
|
||||
Blocks,
|
||||
DomainBlocks,
|
||||
|
@ -202,6 +203,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
<WrappedRoute path='/public/remote' exact component={Firehose} componentParams={{ feedType: 'public:remote' }} content={children} />
|
||||
<WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} />
|
||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/links/:url' component={LinkTimeline} content={children} />
|
||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} exact />
|
||||
<WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
|
||||
|
|
|
@ -201,3 +201,7 @@ export function NotificationRequests () {
|
|||
export function NotificationRequest () {
|
||||
return import(/*webpackChunkName: "features/notifications/request" */'../../notifications/request');
|
||||
}
|
||||
|
||||
export function LinkTimeline () {
|
||||
return import(/*webpackChunkName: "features/link_timeline" */'../../link_timeline');
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import type { RecordOf } from 'immutable';
|
||||
|
||||
import type { ApiPreviewCardJSON } from 'mastodon/api_types/statuses';
|
||||
|
||||
export type { StatusVisibility } from 'mastodon/api_types/statuses';
|
||||
|
||||
// Temporary until we type it correctly
|
||||
export type Status = Immutable.Map<string, unknown>;
|
||||
|
||||
type CardShape = Required<ApiPreviewCardJSON>;
|
||||
|
||||
export type Card = RecordOf<CardShape>;
|
||||
|
|
|
@ -10468,12 +10468,14 @@ noscript {
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
&.verified {
|
||||
dd {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -28,6 +28,7 @@ Rails.application.routes.draw do
|
|||
/public/remote
|
||||
/conversations
|
||||
/lists/(*any)
|
||||
/links/(*any)
|
||||
/notifications/(*any)
|
||||
/favourites
|
||||
/bookmarks
|
||||
|
|
Loading…
Reference in New Issue