Change order of items in navigation panel in web UI (#35029)
This commit is contained in:
parent
013c527406
commit
7c4393e719
29 changed files with 625 additions and 452 deletions
|
|
@ -25,7 +25,7 @@ import BundleColumnError from './bundle_column_error';
|
|||
import { ColumnLoading } from './column_loading';
|
||||
import { ComposePanel } from './compose_panel';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
import { NavigationPanel } from './navigation_panel';
|
||||
import { NavigationPanel } from 'mastodon/features/navigation_panel';
|
||||
|
||||
const componentMap = {
|
||||
'COMPOSE': Compose,
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { disabledAccountId, movedToAccountId, domain } from 'mastodon/initial_state';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']),
|
||||
movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onLogout () {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
|
||||
},
|
||||
});
|
||||
|
||||
class DisabledAccountBanner extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
disabledAcct: PropTypes.string.isRequired,
|
||||
movedToAcct: PropTypes.string,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleLogOutClick = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.props.onLogout();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
render () {
|
||||
const { disabledAcct, movedToAcct } = this.props;
|
||||
|
||||
const disabledAccountLink = (
|
||||
<Link to={`/@${disabledAcct}`}>
|
||||
{disabledAcct}@{domain}
|
||||
</Link>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p>
|
||||
{movedToAcct ? (
|
||||
<FormattedMessage
|
||||
id='moved_to_account_banner.text'
|
||||
defaultMessage='Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.'
|
||||
values={{
|
||||
disabledAccount: disabledAccountLink,
|
||||
movedToAccount: <Link to={`/@${movedToAcct}`}>{movedToAcct.includes('@') ? movedToAcct : `${movedToAcct}@${domain}`}</Link>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='disabled_account_banner.text'
|
||||
defaultMessage='Your account {disabledAccount} is currently disabled.'
|
||||
values={{
|
||||
disabledAccount: disabledAccountLink,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<a href='/auth/edit' className='button button--block'>
|
||||
<FormattedMessage id='disabled_account_banner.account_settings' defaultMessage='Account settings' />
|
||||
</a>
|
||||
<button type='button' className='button button--block button-tertiary' onClick={this.handleLogOutClick}>
|
||||
<FormattedMessage id='confirmations.logout.confirm' defaultMessage='Log out' />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(DisabledAccountBanner));
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
import { useEffect, useState, useCallback, useId } from 'react';
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import ArrowDropDownIcon from '@/material-icons/400-24px/arrow_drop_down.svg?react';
|
||||
import ArrowLeftIcon from '@/material-icons/400-24px/arrow_left.svg?react';
|
||||
import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { fetchLists } from 'mastodon/actions/lists';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { getOrderedLists } from 'mastodon/selectors/lists';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { ColumnLink } from './column_link';
|
||||
|
||||
const messages = defineMessages({
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
expand: {
|
||||
id: 'navigation_panel.expand_lists',
|
||||
defaultMessage: 'Expand list menu',
|
||||
},
|
||||
collapse: {
|
||||
id: 'navigation_panel.collapse_lists',
|
||||
defaultMessage: 'Collapse list menu',
|
||||
},
|
||||
});
|
||||
|
||||
export const ListPanel: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const lists = useAppSelector((state) => getOrderedLists(state));
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const accessibilityId = useId();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchLists());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setExpanded((value) => !value);
|
||||
}, [setExpanded]);
|
||||
|
||||
return (
|
||||
<div className='navigation-panel__list-panel'>
|
||||
<div className='navigation-panel__list-panel__header'>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/lists'
|
||||
icon='list-ul'
|
||||
iconComponent={ListAltIcon}
|
||||
activeIconComponent={ListAltActiveIcon}
|
||||
text={intl.formatMessage(messages.lists)}
|
||||
id={`${accessibilityId}-title`}
|
||||
/>
|
||||
|
||||
{lists.length > 0 && (
|
||||
<IconButton
|
||||
icon='down'
|
||||
expanded={expanded}
|
||||
iconComponent={expanded ? ArrowDropDownIcon : ArrowLeftIcon}
|
||||
title={intl.formatMessage(
|
||||
expanded ? messages.collapse : messages.expand,
|
||||
)}
|
||||
onClick={handleClick}
|
||||
aria-controls={`${accessibilityId}-content`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{lists.length > 0 && expanded && (
|
||||
<div
|
||||
className='navigation-panel__list-panel__items'
|
||||
role='region'
|
||||
id={`${accessibilityId}-content`}
|
||||
aria-labelledby={`${accessibilityId}-title`}
|
||||
>
|
||||
{lists.map((list) => (
|
||||
<ColumnLink
|
||||
icon='list-ul'
|
||||
key={list.get('id')}
|
||||
iconComponent={ListAltIcon}
|
||||
activeIconComponent={ListAltActiveIcon}
|
||||
text={list.get('title')}
|
||||
to={`/lists/${list.get('id')}`}
|
||||
transparent
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import type { MenuItem } from 'mastodon/models/dropdown_menu';
|
||||
import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
followedTags: {
|
||||
id: 'navigation_bar.followed_tags',
|
||||
defaultMessage: 'Followed hashtags',
|
||||
},
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domainBlocks: {
|
||||
id: 'navigation_bar.domain_blocks',
|
||||
defaultMessage: 'Blocked domains',
|
||||
},
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
|
||||
administration: {
|
||||
id: 'navigation_bar.administration',
|
||||
defaultMessage: 'Administration',
|
||||
},
|
||||
moderation: { id: 'navigation_bar.moderation', defaultMessage: 'Moderation' },
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
automatedDeletion: {
|
||||
id: 'navigation_bar.automated_deletion',
|
||||
defaultMessage: 'Automated post deletion',
|
||||
},
|
||||
accountSettings: {
|
||||
id: 'navigation_bar.account_settings',
|
||||
defaultMessage: 'Password and security',
|
||||
},
|
||||
importExport: {
|
||||
id: 'navigation_bar.import_export',
|
||||
defaultMessage: 'Import and export',
|
||||
},
|
||||
privacyAndReach: {
|
||||
id: 'navigation_bar.privacy_and_reach',
|
||||
defaultMessage: 'Privacy and reach',
|
||||
},
|
||||
});
|
||||
|
||||
export const MoreLink: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { permissions } = useIdentity();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const menu = useMemo(() => {
|
||||
const arr: MenuItem[] = [
|
||||
{
|
||||
text: intl.formatMessage(messages.followedTags),
|
||||
to: '/followed_tags',
|
||||
},
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.filters), href: '/filters' },
|
||||
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
|
||||
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
|
||||
{
|
||||
text: intl.formatMessage(messages.domainBlocks),
|
||||
to: '/domain_blocks',
|
||||
},
|
||||
];
|
||||
|
||||
arr.push(
|
||||
null,
|
||||
{
|
||||
href: '/settings/privacy',
|
||||
text: intl.formatMessage(messages.privacyAndReach),
|
||||
},
|
||||
{
|
||||
href: '/statuses_cleanup',
|
||||
text: intl.formatMessage(messages.automatedDeletion),
|
||||
},
|
||||
{
|
||||
href: '/auth/edit',
|
||||
text: intl.formatMessage(messages.accountSettings),
|
||||
},
|
||||
{
|
||||
href: '/settings/export',
|
||||
text: intl.formatMessage(messages.importExport),
|
||||
},
|
||||
);
|
||||
|
||||
if (canManageReports(permissions)) {
|
||||
arr.push(null, {
|
||||
href: '/admin/reports',
|
||||
text: intl.formatMessage(messages.moderation),
|
||||
});
|
||||
}
|
||||
|
||||
if (canViewAdminDashboard(permissions)) {
|
||||
arr.push({
|
||||
href: '/admin/dashboard',
|
||||
text: intl.formatMessage(messages.administration),
|
||||
});
|
||||
}
|
||||
|
||||
const handleLogoutClick = () => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
|
||||
};
|
||||
|
||||
arr.push(null, {
|
||||
text: intl.formatMessage(messages.logout),
|
||||
action: handleLogoutClick,
|
||||
});
|
||||
|
||||
return arr;
|
||||
}, [intl, dispatch, permissions]);
|
||||
|
||||
return (
|
||||
<Dropdown items={menu}>
|
||||
<button className='column-link column-link--transparent'>
|
||||
<Icon id='' icon={MoreHorizIcon} className='column-link__icon' />
|
||||
|
||||
<FormattedMessage id='navigation_bar.more' defaultMessage='More' />
|
||||
</button>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,452 +0,0 @@
|
|||
import { useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useDrag } from '@use-gesture/react';
|
||||
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
|
||||
import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
|
||||
import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home.svg?react';
|
||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
|
||||
import PersonAddActiveIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||
import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import TrendingUpIcon from '@/material-icons/400-24px/trending_up.svg?react';
|
||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
||||
import { openNavigation, closeNavigation } from 'mastodon/actions/navigation';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
|
||||
import { WordmarkLogo } from 'mastodon/components/logo';
|
||||
import { NavigationPortal } from 'mastodon/components/navigation_portal';
|
||||
import { useBreakpoint } from 'mastodon/features/ui/hooks/useBreakpoint';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import { timelinePreview, trendsEnabled, me } from 'mastodon/initial_state';
|
||||
import { transientSingleColumn } from 'mastodon/is_mobile';
|
||||
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { ColumnLink } from './column_link';
|
||||
import DisabledAccountBanner from './disabled_account_banner';
|
||||
import { ListPanel } from './list_panel';
|
||||
import { MoreLink } from './more_link';
|
||||
import SignInBanner from './sign_in_banner';
|
||||
|
||||
const messages = defineMessages({
|
||||
home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
notifications: {
|
||||
id: 'tabs_bar.notifications',
|
||||
defaultMessage: 'Notifications',
|
||||
},
|
||||
explore: { id: 'explore.title', defaultMessage: 'Trending' },
|
||||
firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
||||
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
preferences: {
|
||||
id: 'navigation_bar.preferences',
|
||||
defaultMessage: 'Preferences',
|
||||
},
|
||||
followsAndFollowers: {
|
||||
id: 'navigation_bar.follows_and_followers',
|
||||
defaultMessage: 'Follows and followers',
|
||||
},
|
||||
about: { id: 'navigation_bar.about', defaultMessage: 'About' },
|
||||
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
|
||||
advancedInterface: {
|
||||
id: 'navigation_bar.advanced_interface',
|
||||
defaultMessage: 'Open in advanced web interface',
|
||||
},
|
||||
openedInClassicInterface: {
|
||||
id: 'navigation_bar.opened_in_classic_interface',
|
||||
defaultMessage:
|
||||
'Posts, accounts, and other specific pages are opened by default in the classic web interface.',
|
||||
},
|
||||
followRequests: {
|
||||
id: 'navigation_bar.follow_requests',
|
||||
defaultMessage: 'Follow requests',
|
||||
},
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
compose: { id: 'tabs_bar.publish', defaultMessage: 'New Post' },
|
||||
});
|
||||
|
||||
const NotificationsLink = () => {
|
||||
const count = useAppSelector(selectUnreadNotificationGroupsCount);
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
key='notifications'
|
||||
transparent
|
||||
to='/notifications'
|
||||
icon={
|
||||
<IconWithBadge
|
||||
id='bell'
|
||||
icon={NotificationsIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
activeIcon={
|
||||
<IconWithBadge
|
||||
id='bell'
|
||||
icon={NotificationsActiveIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
text={intl.formatMessage(messages.notifications)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const FollowRequestsLink: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const count = useAppSelector(
|
||||
(state) =>
|
||||
(
|
||||
state.user_lists.getIn(['follow_requests', 'items']) as
|
||||
| ImmutableMap<string, unknown>
|
||||
| undefined
|
||||
)?.size ?? 0,
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchFollowRequests());
|
||||
}, [dispatch]);
|
||||
|
||||
if (count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/follow_requests'
|
||||
icon={
|
||||
<IconWithBadge
|
||||
id='user-plus'
|
||||
icon={PersonAddIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
activeIcon={
|
||||
<IconWithBadge
|
||||
id='user-plus'
|
||||
icon={PersonAddActiveIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
text={intl.formatMessage(messages.followRequests)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchLink: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const showAsSearch = useBreakpoint('full');
|
||||
|
||||
if (!trendsEnabled || showAsSearch) {
|
||||
return (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={trendsEnabled ? '/explore' : '/search'}
|
||||
icon='search'
|
||||
iconComponent={SearchIcon}
|
||||
text={intl.formatMessage(messages.search)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/explore'
|
||||
icon='explore'
|
||||
iconComponent={TrendingUpIcon}
|
||||
text={intl.formatMessage(messages.explore)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileCard: React.FC = () => {
|
||||
if (!me) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Account id={me} minimal size={36} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MENU_WIDTH = 284;
|
||||
|
||||
export const NavigationPanel: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { signedIn, disabledAccountId } = useIdentity();
|
||||
const open = useAppSelector((state) => state.navigation.open);
|
||||
const dispatch = useAppDispatch();
|
||||
const openable = useBreakpoint('openable');
|
||||
const location = useLocation();
|
||||
const overlayRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(closeNavigation());
|
||||
}, [dispatch, location]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleDocumentClick = (e: MouseEvent) => {
|
||||
if (overlayRef.current && e.target === overlayRef.current) {
|
||||
dispatch(closeNavigation());
|
||||
}
|
||||
};
|
||||
|
||||
const handleDocumentKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
dispatch(closeNavigation());
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleDocumentClick);
|
||||
document.addEventListener('keyup', handleDocumentKeyUp);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick);
|
||||
document.removeEventListener('keyup', handleDocumentKeyUp);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const [{ x }, spring] = useSpring(
|
||||
() => ({
|
||||
x: open ? 0 : MENU_WIDTH,
|
||||
onRest: {
|
||||
x({ value }: { value: number }) {
|
||||
if (value === 0) {
|
||||
dispatch(openNavigation());
|
||||
} else if (value > 0) {
|
||||
dispatch(closeNavigation());
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
[open],
|
||||
);
|
||||
|
||||
const bind = useDrag(
|
||||
({ last, offset: [ox], velocity: [vx], direction: [dx], cancel }) => {
|
||||
if (ox < -70) {
|
||||
cancel();
|
||||
}
|
||||
|
||||
if (last) {
|
||||
if (ox > MENU_WIDTH / 2 || (vx > 0.5 && dx > 0)) {
|
||||
void spring.start({ x: MENU_WIDTH });
|
||||
} else {
|
||||
void spring.start({ x: 0 });
|
||||
}
|
||||
} else {
|
||||
void spring.start({ x: ox, immediate: true });
|
||||
}
|
||||
},
|
||||
{
|
||||
from: () => [x.get(), 0],
|
||||
filterTaps: true,
|
||||
bounds: { left: 0 },
|
||||
rubberband: true,
|
||||
},
|
||||
);
|
||||
|
||||
const isFirehoseActive = useCallback(
|
||||
(match: unknown, location: { pathname: string }): boolean => {
|
||||
return !!match || location.pathname.startsWith('/public');
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const previouslyFocusedElementRef = useRef<HTMLElement | null>();
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const firstLink = document.querySelector<HTMLAnchorElement>(
|
||||
'.navigation-panel__menu .column-link',
|
||||
);
|
||||
previouslyFocusedElementRef.current =
|
||||
document.activeElement as HTMLElement;
|
||||
firstLink?.focus();
|
||||
} else {
|
||||
previouslyFocusedElementRef.current?.focus();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
let banner = undefined;
|
||||
|
||||
if (transientSingleColumn) {
|
||||
banner = (
|
||||
<div className='switch-to-advanced'>
|
||||
{intl.formatMessage(messages.openedInClassicInterface)}{' '}
|
||||
<a
|
||||
href={`/deck${location.pathname}`}
|
||||
className='switch-to-advanced__toggle'
|
||||
>
|
||||
{intl.formatMessage(messages.advancedInterface)}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const showOverlay = openable && open;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational',
|
||||
{ 'columns-area__panels__pane--overlay': showOverlay },
|
||||
)}
|
||||
ref={overlayRef}
|
||||
>
|
||||
<animated.div
|
||||
className='columns-area__panels__pane__inner'
|
||||
{...bind()}
|
||||
style={openable ? { x } : undefined}
|
||||
>
|
||||
<div className='navigation-panel'>
|
||||
<div className='navigation-panel__logo'>
|
||||
<Link to='/' className='column-link column-link--logo'>
|
||||
<WordmarkLogo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<ProfileCard />
|
||||
|
||||
{banner && <div className='navigation-panel__banner'>{banner}</div>}
|
||||
|
||||
<div className='navigation-panel__menu'>
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink
|
||||
to='/publish'
|
||||
icon='plus'
|
||||
iconComponent={AddIcon}
|
||||
activeIconComponent={AddIcon}
|
||||
text={intl.formatMessage(messages.compose)}
|
||||
className='button navigation-panel__compose-button'
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/home'
|
||||
icon='home'
|
||||
iconComponent={HomeIcon}
|
||||
activeIconComponent={HomeActiveIcon}
|
||||
text={intl.formatMessage(messages.home)}
|
||||
/>
|
||||
<NotificationsLink />
|
||||
<FollowRequestsLink />
|
||||
</>
|
||||
)}
|
||||
|
||||
<SearchLink />
|
||||
|
||||
{(signedIn || timelinePreview) && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/public/local'
|
||||
isActive={isFirehoseActive}
|
||||
icon='globe'
|
||||
iconComponent={PublicIcon}
|
||||
text={intl.formatMessage(messages.firehose)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!signedIn && (
|
||||
<div className='navigation-panel__sign-in-banner'>
|
||||
<hr />
|
||||
{disabledAccountId ? (
|
||||
<DisabledAccountBanner />
|
||||
) : (
|
||||
<SignInBanner />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/conversations'
|
||||
icon='at'
|
||||
iconComponent={AlternateEmailIcon}
|
||||
text={intl.formatMessage(messages.direct)}
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/bookmarks'
|
||||
icon='bookmarks'
|
||||
iconComponent={BookmarksIcon}
|
||||
activeIconComponent={BookmarksActiveIcon}
|
||||
text={intl.formatMessage(messages.bookmarks)}
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/favourites'
|
||||
icon='star'
|
||||
iconComponent={StarIcon}
|
||||
activeIconComponent={StarActiveIcon}
|
||||
text={intl.formatMessage(messages.favourites)}
|
||||
/>
|
||||
|
||||
<ListPanel />
|
||||
|
||||
<hr />
|
||||
|
||||
<ColumnLink
|
||||
transparent
|
||||
href='/settings/preferences'
|
||||
icon='cog'
|
||||
iconComponent={SettingsIcon}
|
||||
text={intl.formatMessage(messages.preferences)}
|
||||
/>
|
||||
|
||||
<MoreLink />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className='navigation-panel__legal'>
|
||||
<hr />
|
||||
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/about'
|
||||
icon='ellipsis-h'
|
||||
iconComponent={InfoIcon}
|
||||
text={intl.formatMessage(messages.about)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-spacer' />
|
||||
|
||||
<NavigationPortal />
|
||||
</div>
|
||||
</animated.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { registrationsOpen, sso_redirect } from 'mastodon/initial_state';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
const SignInBanner = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const openClosedRegistrationsModal = useCallback(
|
||||
() => dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' })),
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
let signupButton;
|
||||
|
||||
const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up');
|
||||
|
||||
if (sso_redirect) {
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p><strong><FormattedMessage id='sign_in_banner.mastodon_is' defaultMessage="Mastodon is the best way to keep up with what's happening." /></strong></p>
|
||||
<p><FormattedMessage id='sign_in_banner.follow_anyone' defaultMessage='Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.' /></p>
|
||||
<a href={sso_redirect} data-method='post' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sso_redirect' defaultMessage='Login or Register' /></a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (registrationsOpen) {
|
||||
signupButton = (
|
||||
<a href={signupUrl} className='button button--block'>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
signupButton = (
|
||||
<button className='button button--block' onClick={openClosedRegistrationsModal}>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p><strong><FormattedMessage id='sign_in_banner.mastodon_is' defaultMessage="Mastodon is the best way to keep up with what's happening." /></strong></p>
|
||||
<p><FormattedMessage id='sign_in_banner.follow_anyone' defaultMessage='Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.' /></p>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignInBanner;
|
||||
Loading…
Add table
Add a link
Reference in a new issue