Fix logged-out web UI on smaller screens (#19263)
parent
6580ac7241
commit
e2b561e3a5
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
const NotSignedInIndicator = () => (
|
||||||
|
<div className='scrollable scrollable--flex'>
|
||||||
|
<div className='empty-column-indicator'>
|
||||||
|
<FormattedMessage id='not_signed_in_indicator.not_signed_in' defaultMessage='You need to sign in to access this resource.' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default NotSignedInIndicator;
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
import LinkFooter from 'mastodon/features/ui/components/link_footer';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { title } from 'mastodon/initial_state';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: { id: 'column.about', defaultMessage: 'About' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class About extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { intl } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
<LinkFooter />
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.title)} - {title}</title>
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
|
||||||
import { mascot } from '../../initial_state';
|
import { mascot } from '../../initial_state';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
import { logOut } from 'mastodon/utils/log_out';
|
import { logOut } from 'mastodon/utils/log_out';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||||
|
@ -92,57 +93,59 @@ class Compose extends React.PureComponent {
|
||||||
render () {
|
render () {
|
||||||
const { multiColumn, showSearch, isSearchPage, intl } = this.props;
|
const { multiColumn, showSearch, isSearchPage, intl } = this.props;
|
||||||
|
|
||||||
let header = '';
|
|
||||||
|
|
||||||
if (multiColumn) {
|
if (multiColumn) {
|
||||||
const { columns } = this.props;
|
const { columns } = this.props;
|
||||||
header = (
|
|
||||||
<nav className='drawer__header'>
|
return (
|
||||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
|
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
|
||||||
{!columns.some(column => column.get('id') === 'HOME') && (
|
<nav className='drawer__header'>
|
||||||
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
|
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
|
||||||
)}
|
{!columns.some(column => column.get('id') === 'HOME') && (
|
||||||
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
|
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
|
||||||
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
|
)}
|
||||||
)}
|
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
|
||||||
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
|
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
|
||||||
<Link to='/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
|
)}
|
||||||
)}
|
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
|
||||||
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
<Link to='/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
|
||||||
<Link to='/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
|
)}
|
||||||
)}
|
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
||||||
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
|
<Link to='/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
|
||||||
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a>
|
)}
|
||||||
</nav>
|
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
|
||||||
|
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{(multiColumn || isSearchPage) && <SearchContainer /> }
|
||||||
|
|
||||||
|
<div className='drawer__pager'>
|
||||||
|
{!isSearchPage && <div className='drawer__inner' onFocus={this.onFocus}>
|
||||||
|
<NavigationContainer onClose={this.onBlur} />
|
||||||
|
|
||||||
|
<ComposeFormContainer />
|
||||||
|
|
||||||
|
<div className='drawer__inner__mastodon'>
|
||||||
|
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
|
||||||
|
{({ x }) => (
|
||||||
|
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
||||||
|
<SearchResultsContainer />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Motion>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
|
<Column onFocus={this.onFocus}>
|
||||||
{header}
|
<NavigationContainer onClose={this.onBlur} />
|
||||||
|
<ComposeFormContainer />
|
||||||
{(multiColumn || isSearchPage) && <SearchContainer /> }
|
</Column>
|
||||||
|
|
||||||
<div className='drawer__pager'>
|
|
||||||
{!isSearchPage && <div className='drawer__inner' onFocus={this.onFocus}>
|
|
||||||
<NavigationContainer onClose={this.onBlur} />
|
|
||||||
|
|
||||||
<ComposeFormContainer />
|
|
||||||
|
|
||||||
<div className='drawer__inner__mastodon'>
|
|
||||||
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
|
|
||||||
{({ x }) => (
|
|
||||||
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
|
||||||
<SearchResultsContainer />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,13 +30,13 @@ class Explore extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
|
identity: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
isSearching: PropTypes.bool,
|
isSearching: PropTypes.bool,
|
||||||
layout: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHeaderClick = () => {
|
handleHeaderClick = () => {
|
||||||
|
@ -48,22 +48,21 @@ class Explore extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, multiColumn, isSearching, layout } = this.props;
|
const { intl, multiColumn, isSearching } = this.props;
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
{layout === 'mobile' ? (
|
<ColumnHeader
|
||||||
<div className='explore__search-header'>
|
icon={isSearching ? 'search' : 'hashtag'}
|
||||||
<Search />
|
title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)}
|
||||||
</div>
|
onClick={this.handleHeaderClick}
|
||||||
) : (
|
multiColumn={multiColumn}
|
||||||
<ColumnHeader
|
/>
|
||||||
icon={isSearching ? 'search' : 'hashtag'}
|
|
||||||
title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)}
|
<div className='explore__search-header'>
|
||||||
onClick={this.handleHeaderClick}
|
<Search />
|
||||||
multiColumn={multiColumn}
|
</div>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='scrollable scrollable--flex'>
|
<div className='scrollable scrollable--flex'>
|
||||||
{isSearching ? (
|
{isSearching ? (
|
||||||
|
@ -74,7 +73,7 @@ class Explore extends React.PureComponent {
|
||||||
<NavLink exact to='/explore'><FormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
|
<NavLink exact to='/explore'><FormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
|
||||||
<NavLink exact to='/explore/tags'><FormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
|
<NavLink exact to='/explore/tags'><FormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
|
||||||
<NavLink exact to='/explore/links'><FormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
|
<NavLink exact to='/explore/links'><FormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
|
||||||
<NavLink exact to='/explore/suggestions'><FormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>
|
{signedIn && <NavLink exact to='/explore/suggestions'><FormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Column from '../ui/components/column';
|
import Column from 'mastodon/components/column';
|
||||||
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
import ColumnLink from '../ui/components/column_link';
|
import ColumnLink from '../ui/components/column_link';
|
||||||
import ColumnSubheading from '../ui/components/column_subheading';
|
import ColumnSubheading from '../ui/components/column_subheading';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { me, showTrends } from '../../initial_state';
|
import { me, title, showTrends } from '../../initial_state';
|
||||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import NavigationContainer from '../compose/containers/navigation_container';
|
import NavigationContainer from '../compose/containers/navigation_container';
|
||||||
import Icon from 'mastodon/components/icon';
|
|
||||||
import LinkFooter from 'mastodon/features/ui/components/link_footer';
|
import LinkFooter from 'mastodon/features/ui/components/link_footer';
|
||||||
import TrendsContainer from './containers/trends_container';
|
import TrendsContainer from './containers/trends_container';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||||
|
@ -40,7 +41,6 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
myAccount: state.getIn(['accounts', me]),
|
myAccount: state.getIn(['accounts', me]),
|
||||||
columns: state.getIn(['settings', 'columns']),
|
|
||||||
unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
|
unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,20 +58,18 @@ const badgeDisplay = (number, limit) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class GettingStarted extends ImmutablePureComponent {
|
class GettingStarted extends ImmutablePureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
|
identity: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
myAccount: ImmutablePropTypes.map.isRequired,
|
myAccount: ImmutablePropTypes.map,
|
||||||
columns: ImmutablePropTypes.list,
|
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
fetchFollowRequests: PropTypes.func.isRequired,
|
fetchFollowRequests: PropTypes.func.isRequired,
|
||||||
unreadFollowRequests: PropTypes.number,
|
unreadFollowRequests: PropTypes.number,
|
||||||
|
@ -79,10 +77,10 @@ class GettingStarted extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { fetchFollowRequests, multiColumn } = this.props;
|
const { fetchFollowRequests } = this.props;
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
|
if (!signedIn) {
|
||||||
this.context.router.history.replace('/home');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,91 +88,57 @@ class GettingStarted extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, myAccount, columns, multiColumn, unreadFollowRequests } = this.props;
|
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
const navItems = [];
|
const navItems = [];
|
||||||
let height = (multiColumn) ? 0 : 60;
|
|
||||||
|
|
||||||
if (multiColumn) {
|
|
||||||
navItems.push(
|
|
||||||
<ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />,
|
|
||||||
);
|
|
||||||
height += 34;
|
|
||||||
}
|
|
||||||
|
|
||||||
navItems.push(
|
navItems.push(
|
||||||
|
<ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />,
|
||||||
<ColumnLink key='explore' icon='hashtag' text={intl.formatMessage(messages.explore)} to='/explore' />,
|
<ColumnLink key='explore' icon='hashtag' text={intl.formatMessage(messages.explore)} to='/explore' />,
|
||||||
|
<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />,
|
||||||
|
<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />,
|
||||||
);
|
);
|
||||||
height += 48;
|
|
||||||
|
|
||||||
if (multiColumn) {
|
|
||||||
navItems.push(
|
|
||||||
<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />,
|
|
||||||
<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />,
|
|
||||||
);
|
|
||||||
|
|
||||||
height += 48*2;
|
|
||||||
|
|
||||||
|
if (signedIn) {
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ColumnSubheading key='header-personal' text={intl.formatMessage(messages.personal)} />,
|
<ColumnSubheading key='header-personal' text={intl.formatMessage(messages.personal)} />,
|
||||||
);
|
|
||||||
|
|
||||||
height += 34;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) {
|
|
||||||
navItems.push(
|
|
||||||
<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />,
|
<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />,
|
||||||
|
<ColumnLink key='direct' icon='at' text={intl.formatMessage(messages.direct)} to='/conversations' />,
|
||||||
|
<ColumnLink key='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
|
||||||
|
<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
||||||
|
<ColumnLink key='lists' icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />,
|
||||||
);
|
);
|
||||||
height += 48;
|
|
||||||
}
|
|
||||||
|
|
||||||
navItems.push(
|
if (myAccount.get('locked') || unreadFollowRequests > 0) {
|
||||||
<ColumnLink key='direct' icon='at' text={intl.formatMessage(messages.direct)} to='/conversations' />,
|
navItems.push(<ColumnLink key='follow_requests' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
|
||||||
<ColumnLink key='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
|
}
|
||||||
<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
|
||||||
<ColumnLink key='lists' icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />,
|
|
||||||
);
|
|
||||||
|
|
||||||
height += 48*4;
|
|
||||||
|
|
||||||
if (myAccount.get('locked') || unreadFollowRequests > 0) {
|
|
||||||
navItems.push(<ColumnLink key='follow_requests' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
|
|
||||||
height += 48;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!multiColumn) {
|
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ColumnSubheading key='header-settings' text={intl.formatMessage(messages.settings_subheading)} />,
|
<ColumnSubheading key='header-settings' text={intl.formatMessage(messages.settings_subheading)} />,
|
||||||
<ColumnLink key='preferences' icon='gears' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />,
|
<ColumnLink key='preferences' icon='gears' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />,
|
||||||
);
|
);
|
||||||
|
|
||||||
height += 34 + 48;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.menu)}>
|
<Column>
|
||||||
{multiColumn && <div className='column-header__wrapper'>
|
{(signedIn && !multiColumn) ? <NavigationContainer /> : <ColumnHeader title={intl.formatMessage(messages.menu)} icon='bars' multiColumn={multiColumn} />}
|
||||||
<h1 className='column-header'>
|
|
||||||
<button>
|
|
||||||
<Icon id='bars' className='column-header__icon' fixedWidth />
|
|
||||||
<FormattedMessage id='getting_started.heading' defaultMessage='Getting started' />
|
|
||||||
</button>
|
|
||||||
</h1>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
<div className='getting-started'>
|
<div className='getting-started scrollable scrollable--flex'>
|
||||||
<div className='getting-started__wrapper' style={{ height }}>
|
<div className='getting-started__wrapper'>
|
||||||
{!multiColumn && <NavigationContainer />}
|
|
||||||
{navItems}
|
{navItems}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!multiColumn && <div className='flex-spacer' />}
|
{!multiColumn && <div className='flex-spacer' />}
|
||||||
|
|
||||||
<LinkFooter withHotkeys={multiColumn} />
|
<LinkFooter />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{multiColumn && showTrends && <TrendsContainer />}
|
{(multiColumn && showTrends) && <TrendsContainer />}
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.menu)} - {title}</title>
|
||||||
|
</Helmet>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,9 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an
|
||||||
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
|
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import IconWithBadge from 'mastodon/components/icon_with_badge';
|
import IconWithBadge from 'mastodon/components/icon_with_badge';
|
||||||
|
import NotSignedInIndicator from 'mastodon/components/not_signed_in_indicator';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { title } from 'mastodon/initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.home', defaultMessage: 'Home' },
|
title: { id: 'column.home', defaultMessage: 'Home' },
|
||||||
|
@ -32,6 +35,10 @@ export default @connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class HomeTimeline extends React.PureComponent {
|
class HomeTimeline extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
identity: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
@ -113,6 +120,7 @@ class HomeTimeline extends React.PureComponent {
|
||||||
render () {
|
render () {
|
||||||
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
|
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
let announcementsButton = null;
|
let announcementsButton = null;
|
||||||
|
|
||||||
|
@ -147,14 +155,20 @@ class HomeTimeline extends React.PureComponent {
|
||||||
<ColumnSettingsContainer />
|
<ColumnSettingsContainer />
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<StatusListContainer
|
{signedIn ? (
|
||||||
trackScroll={!pinned}
|
<StatusListContainer
|
||||||
scrollKey={`home_timeline-${columnId}`}
|
trackScroll={!pinned}
|
||||||
onLoadMore={this.handleLoadMore}
|
scrollKey={`home_timeline-${columnId}`}
|
||||||
timelineId='home'
|
onLoadMore={this.handleLoadMore}
|
||||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up. {suggestions}' values={{ suggestions: <Link to='/start'><FormattedMessage id='empty_column.home.suggestions' defaultMessage='See some suggestions' /></Link> }} />}
|
timelineId='home'
|
||||||
bindToDocument={!multiColumn}
|
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up. {suggestions}' values={{ suggestions: <Link to='/start'><FormattedMessage id='empty_column.home.suggestions' defaultMessage='See some suggestions' /></Link> }} />}
|
||||||
/>
|
bindToDocument={!multiColumn}
|
||||||
|
/>
|
||||||
|
) : <NotSignedInIndicator />}
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.title)} - {title}</title>
|
||||||
|
</Helmet>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Column from '../ui/components/column';
|
import Column from 'mastodon/components/column';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
|
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
|
||||||
|
@ -21,8 +21,13 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
||||||
const { intl, multiColumn } = this.props;
|
const { intl, multiColumn } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}>
|
<Column>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnHeader
|
||||||
|
title={intl.formatMessage(messages.heading)}
|
||||||
|
icon='question'
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
|
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -26,6 +26,9 @@ import LoadGap from '../../components/load_gap';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
import compareId from 'mastodon/compare_id';
|
import compareId from 'mastodon/compare_id';
|
||||||
import NotificationsPermissionBanner from './components/notifications_permission_banner';
|
import NotificationsPermissionBanner from './components/notifications_permission_banner';
|
||||||
|
import NotSignedInIndicator from 'mastodon/components/not_signed_in_indicator';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { title } from 'mastodon/initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||||
|
@ -69,6 +72,10 @@ export default @connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class Notifications extends React.PureComponent {
|
class Notifications extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
identity: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
columnId: PropTypes.string,
|
columnId: PropTypes.string,
|
||||||
notifications: ImmutablePropTypes.list.isRequired,
|
notifications: ImmutablePropTypes.list.isRequired,
|
||||||
|
@ -178,10 +185,11 @@ class Notifications extends React.PureComponent {
|
||||||
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
|
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
let scrollableContent = null;
|
let scrollableContent = null;
|
||||||
|
|
||||||
const filterBarContainer = showFilterBar
|
const filterBarContainer = (signedIn && showFilterBar)
|
||||||
? (<FilterBarContainer />)
|
? (<FilterBarContainer />)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
@ -211,26 +219,32 @@ class Notifications extends React.PureComponent {
|
||||||
|
|
||||||
this.scrollableContent = scrollableContent;
|
this.scrollableContent = scrollableContent;
|
||||||
|
|
||||||
const scrollContainer = (
|
let scrollContainer;
|
||||||
<ScrollableList
|
|
||||||
scrollKey={`notifications-${columnId}`}
|
if (signedIn) {
|
||||||
trackScroll={!pinned}
|
scrollContainer = (
|
||||||
isLoading={isLoading}
|
<ScrollableList
|
||||||
showLoading={isLoading && notifications.size === 0}
|
scrollKey={`notifications-${columnId}`}
|
||||||
hasMore={hasMore}
|
trackScroll={!pinned}
|
||||||
numPending={numPending}
|
isLoading={isLoading}
|
||||||
prepend={needsNotificationPermission && <NotificationsPermissionBanner />}
|
showLoading={isLoading && notifications.size === 0}
|
||||||
alwaysPrepend
|
hasMore={hasMore}
|
||||||
emptyMessage={emptyMessage}
|
numPending={numPending}
|
||||||
onLoadMore={this.handleLoadOlder}
|
prepend={needsNotificationPermission && <NotificationsPermissionBanner />}
|
||||||
onLoadPending={this.handleLoadPending}
|
alwaysPrepend
|
||||||
onScrollToTop={this.handleScrollToTop}
|
emptyMessage={emptyMessage}
|
||||||
onScroll={this.handleScroll}
|
onLoadMore={this.handleLoadOlder}
|
||||||
bindToDocument={!multiColumn}
|
onLoadPending={this.handleLoadPending}
|
||||||
>
|
onScrollToTop={this.handleScrollToTop}
|
||||||
{scrollableContent}
|
onScroll={this.handleScroll}
|
||||||
</ScrollableList>
|
bindToDocument={!multiColumn}
|
||||||
);
|
>
|
||||||
|
{scrollableContent}
|
||||||
|
</ScrollableList>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
scrollContainer = <NotSignedInIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
let extraButton = null;
|
let extraButton = null;
|
||||||
|
|
||||||
|
@ -262,8 +276,13 @@ class Notifications extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<ColumnSettingsContainer />
|
<ColumnSettingsContainer />
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
{filterBarContainer}
|
{filterBarContainer}
|
||||||
{scrollContainer}
|
{scrollContainer}
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.title)} - {title}</title>
|
||||||
|
</Helmet>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import ReactSwipeableViews from 'react-swipeable-views';
|
|
||||||
import TabsBar, { links, getIndex, getLink } from './tabs_bar';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { disableSwiping } from 'mastodon/initial_state';
|
|
||||||
|
|
||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
import ColumnLoading from './column_loading';
|
import ColumnLoading from './column_loading';
|
||||||
import DrawerLoading from './drawer_loading';
|
import DrawerLoading from './drawer_loading';
|
||||||
|
@ -71,20 +65,13 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Corresponds to (max-width: 600px + (285px * 1) + (10px * 1)) in SCSS
|
// Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS
|
||||||
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 895px)');
|
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)');
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
shouldAnimate: false,
|
|
||||||
renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
|
renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps() {
|
|
||||||
if (typeof this.pendingIndex !== 'number' && this.lastIndex !== getIndex(this.context.router.history.location.pathname)) {
|
|
||||||
this.setState({ shouldAnimate: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!this.props.singleColumn) {
|
if (!this.props.singleColumn) {
|
||||||
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
|
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
|
||||||
|
@ -99,10 +86,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
this.setState({ renderComposePanel: !this.mediaQuery.matches });
|
this.setState({ renderComposePanel: !this.mediaQuery.matches });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
|
||||||
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
||||||
|
|
||||||
this.setState({ shouldAnimate: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
|
@ -115,13 +99,6 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
|
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
|
||||||
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
|
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newIndex = getIndex(this.context.router.history.location.pathname);
|
|
||||||
|
|
||||||
if (this.lastIndex !== newIndex) {
|
|
||||||
this.lastIndex = newIndex;
|
|
||||||
this.setState({ shouldAnimate: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -149,31 +126,6 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
this.setState({ renderComposePanel: !e.matches });
|
this.setState({ renderComposePanel: !e.matches });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwipe = (index) => {
|
|
||||||
this.pendingIndex = index;
|
|
||||||
|
|
||||||
const nextLinkTranslationId = links[index].props['data-preview-title-id'];
|
|
||||||
const currentLinkSelector = '.tabs-bar__link.active';
|
|
||||||
const nextLinkSelector = `.tabs-bar__link[data-preview-title-id="${nextLinkTranslationId}"]`;
|
|
||||||
|
|
||||||
// HACK: Remove the active class from the current link and set it to the next one
|
|
||||||
// React-router does this for us, but too late, feeling laggy.
|
|
||||||
document.querySelector(currentLinkSelector).classList.remove('active');
|
|
||||||
document.querySelector(nextLinkSelector).classList.add('active');
|
|
||||||
|
|
||||||
if (!this.state.shouldAnimate && typeof this.pendingIndex === 'number') {
|
|
||||||
this.context.router.history.push(getLink(this.pendingIndex));
|
|
||||||
this.pendingIndex = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAnimationEnd = () => {
|
|
||||||
if (typeof this.pendingIndex === 'number') {
|
|
||||||
this.context.router.history.push(getLink(this.pendingIndex));
|
|
||||||
this.pendingIndex = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleWheel = () => {
|
handleWheel = () => {
|
||||||
if (typeof this._interruptScrollAnimation !== 'function') {
|
if (typeof this._interruptScrollAnimation !== 'function') {
|
||||||
return;
|
return;
|
||||||
|
@ -186,22 +138,6 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderView = (link, index) => {
|
|
||||||
const columnIndex = getIndex(this.context.router.history.location.pathname);
|
|
||||||
const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] });
|
|
||||||
const icon = link.props['data-preview-icon'];
|
|
||||||
|
|
||||||
const view = (index === columnIndex) ?
|
|
||||||
React.cloneElement(this.props.children) :
|
|
||||||
<ColumnLoading title={title} icon={icon} />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='columns-area columns-area--mobile' key={index}>
|
|
||||||
{view}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLoading = columnId => () => {
|
renderLoading = columnId => () => {
|
||||||
return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading />;
|
return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading />;
|
||||||
}
|
}
|
||||||
|
@ -212,22 +148,12 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { columns, children, singleColumn, isModalOpen, intl } = this.props;
|
const { columns, children, singleColumn, isModalOpen, intl } = this.props;
|
||||||
const { shouldAnimate, renderComposePanel } = this.state;
|
const { renderComposePanel } = this.state;
|
||||||
const { signedIn } = this.context.identity;
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
const columnIndex = getIndex(this.context.router.history.location.pathname);
|
|
||||||
|
|
||||||
if (singleColumn) {
|
if (singleColumn) {
|
||||||
const floatingActionButton = (!signedIn || shouldHideFAB(this.context.router.history.location.pathname)) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
const floatingActionButton = (!signedIn || shouldHideFAB(this.context.router.history.location.pathname)) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||||
|
|
||||||
const content = columnIndex !== -1 ? (
|
|
||||||
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
|
|
||||||
{links.map(this.renderView)}
|
|
||||||
</ReactSwipeableViews>
|
|
||||||
) : (
|
|
||||||
<div key='content' className='columns-area columns-area--mobile'>{children}</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='columns-area__panels'>
|
<div className='columns-area__panels'>
|
||||||
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
|
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
|
||||||
|
@ -237,8 +163,8 @@ class ColumnsArea extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`columns-area__panels__main ${floatingActionButton && 'with-fab'}`}>
|
<div className={`columns-area__panels__main ${floatingActionButton && 'with-fab'}`}>
|
||||||
<TabsBar key='tabs' />
|
<div className='tabs-bar__wrapper'><div id='tabs-bar__portal' /></div>
|
||||||
{content}
|
<div className='columns-area columns-area--mobile'>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
|
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
|
||||||
|
|
|
@ -46,7 +46,7 @@ class ComposePanel extends React.PureComponent {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LinkFooter withHotkeys />
|
<LinkFooter />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Logo from 'mastodon/components/logo';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { registrationsOpen, me } from 'mastodon/initial_state';
|
||||||
|
import Avatar from 'mastodon/components/avatar';
|
||||||
|
import Permalink from 'mastodon/components/permalink';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
const Account = connect(state => ({
|
||||||
|
account: state.getIn(['accounts', me]),
|
||||||
|
}))(({ account }) => (
|
||||||
|
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`}>
|
||||||
|
<span style={{ display: 'none' }}>{account.get('acct')}</span>
|
||||||
|
<Avatar account={account} size={35} />
|
||||||
|
</Permalink>
|
||||||
|
));
|
||||||
|
|
||||||
|
export default class Header extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
identity: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
|
||||||
|
if (signedIn) {
|
||||||
|
content = <Account />;
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<React.Fragment>
|
||||||
|
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
||||||
|
<a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='ui__header'>
|
||||||
|
<Link to='/' className='ui__header__logo'><Logo /></Link>
|
||||||
|
|
||||||
|
<div className='ui__header__links'>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { limitedFederationMode, version, repository, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state';
|
import { version, repository, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state';
|
||||||
import { logOut } from 'mastodon/utils/log_out';
|
import { logOut } from 'mastodon/utils/log_out';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
|
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
|
||||||
|
@ -33,7 +33,6 @@ class LinkFooter extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
withHotkeys: PropTypes.bool,
|
|
||||||
onLogout: PropTypes.func.isRequired,
|
onLogout: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -48,40 +47,26 @@ class LinkFooter extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { withHotkeys } = this.props;
|
|
||||||
const { signedIn, permissions } = this.context.identity;
|
const { signedIn, permissions } = this.context.identity;
|
||||||
const items = [];
|
const items = [];
|
||||||
|
|
||||||
if ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) {
|
items.push(<a key='apps' href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Get the app' /></a>);
|
||||||
items.push(<a key='invites' href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a>);
|
items.push(<a key='about' href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About' /></a>);
|
||||||
}
|
items.push(<a key='mastodon' href='https://joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.what_is_mastodon' defaultMessage='About Mastodon' /></a>);
|
||||||
|
items.push(<a key='docs' href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a>);
|
||||||
if (signedIn && withHotkeys) {
|
items.push(<a key='privacy-policy' href='/privacy-policy' target='_blank'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></a>);
|
||||||
items.push(<Link key='hotkeys' to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link>);
|
items.push(<Link key='hotkeys' to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link>);
|
||||||
}
|
|
||||||
|
|
||||||
if (signedIn) {
|
|
||||||
items.push(<a key='security' href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!limitedFederationMode) {
|
|
||||||
items.push(<a key='about' href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (profileDirectory) {
|
if (profileDirectory) {
|
||||||
items.push(<Link key='directory' to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></Link>);
|
items.push(<Link key='directory' to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Directory' /></Link>);
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push(<a key='apps' href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a>);
|
|
||||||
items.push(<a key='privacy-policy' href='/privacy-policy' target='_blank'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></a>);
|
|
||||||
|
|
||||||
if (signedIn) {
|
if (signedIn) {
|
||||||
items.push(<a key='developers' href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a>);
|
if ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) {
|
||||||
}
|
items.push(<a key='invites' href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a>);
|
||||||
|
}
|
||||||
|
|
||||||
items.push(<a key='docs' href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a>);
|
items.push(<a key='security' href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a>);
|
||||||
|
|
||||||
if (signedIn) {
|
|
||||||
items.push(<a key='logout' href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a>);
|
items.push(<a key='logout' href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,9 +78,9 @@ class LinkFooter extends React.PureComponent {
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='getting_started.open_source_notice'
|
id='getting_started.free_software_notice'
|
||||||
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
|
defaultMessage='Mastodon is free, open source software. You can view the source code, contribute or report issues at {repository}.'
|
||||||
values={{ github: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span> }}
|
values={{ repository: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span> }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,9 +23,10 @@ export default class NavigationPanel extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='navigation-panel'>
|
<div className='navigation-panel'>
|
||||||
<Link to='/' className='column-link column-link--logo'><Logo /></Link>
|
<div className='navigation-panel__logo'>
|
||||||
|
<Link to='/' className='column-link column-link--logo'><Logo /></Link>
|
||||||
<hr />
|
<hr />
|
||||||
|
</div>
|
||||||
|
|
||||||
{signedIn && (
|
{signedIn && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -40,10 +41,10 @@ export default class NavigationPanel extends React.Component {
|
||||||
<NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
|
<NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
|
||||||
|
|
||||||
{!signedIn && (
|
{!signedIn && (
|
||||||
<React.Fragment>
|
<div className='navigation-panel__sign-in-banner'>
|
||||||
<hr />
|
<hr />
|
||||||
<SignInBanner />
|
<SignInBanner />
|
||||||
</React.Fragment>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{signedIn && (
|
{signedIn && (
|
||||||
|
@ -62,6 +63,11 @@ export default class NavigationPanel extends React.Component {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className='navigation-panel__legal'>
|
||||||
|
<hr />
|
||||||
|
<NavLink className='column-link column-link--transparent' to='/about'><Icon className='column-link__icon' id='ellipsis-h' fixedWidth /><FormattedMessage id='navigation_bar.about' defaultMessage='About' /></NavLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
{showTrends && (
|
{showTrends && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className='flex-spacer' />
|
<div className='flex-spacer' />
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { NavLink, withRouter } from 'react-router-dom';
|
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import { isUserTouching } from '../../../is_mobile';
|
|
||||||
import Icon from 'mastodon/components/icon';
|
|
||||||
import NotificationsCounterIcon from './notifications_counter_icon';
|
|
||||||
|
|
||||||
export const links = [
|
|
||||||
<NavLink className='tabs-bar__link' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
|
||||||
<NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
|
|
||||||
<NavLink className='tabs-bar__link' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
|
||||||
<NavLink className='tabs-bar__link' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
|
||||||
<NavLink className='tabs-bar__link optional' to='/explore' data-preview-title-id='tabs_bar.search' data-preview-icon='search' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
|
|
||||||
<NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
|
|
||||||
];
|
|
||||||
|
|
||||||
export function getIndex (path) {
|
|
||||||
return links.findIndex(link => link.props.to === path);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLink (index) {
|
|
||||||
return links[index].props.to;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default @injectIntl
|
|
||||||
@withRouter
|
|
||||||
class TabsBar extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = ref => {
|
|
||||||
this.node = ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = (e) => {
|
|
||||||
// Only apply optimization for touch devices, which we assume are slower
|
|
||||||
// We thus avoid the 250ms delay for non-touch devices and the lag for touch devices
|
|
||||||
if (isUserTouching()) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.persist();
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const tabs = Array(...this.node.querySelectorAll('.tabs-bar__link'));
|
|
||||||
const currentTab = tabs.find(tab => tab.classList.contains('active'));
|
|
||||||
const nextTab = tabs.find(tab => tab.contains(e.target));
|
|
||||||
const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)];
|
|
||||||
|
|
||||||
|
|
||||||
if (currentTab !== nextTab) {
|
|
||||||
if (currentTab) {
|
|
||||||
currentTab.classList.remove('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
const listener = debounce(() => {
|
|
||||||
nextTab.removeEventListener('transitionend', listener);
|
|
||||||
this.props.history.push(to);
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
nextTab.addEventListener('transitionend', listener);
|
|
||||||
nextTab.classList.add('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl: { formatMessage } } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='tabs-bar__wrapper'>
|
|
||||||
<nav className='tabs-bar' ref={this.setRef}>
|
|
||||||
{links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div id='tabs-bar__portal' />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -51,10 +51,12 @@ import {
|
||||||
Directory,
|
Directory,
|
||||||
Explore,
|
Explore,
|
||||||
FollowRecommendations,
|
FollowRecommendations,
|
||||||
|
About,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
import { me, title } from '../../initial_state';
|
import { me, title } from '../../initial_state';
|
||||||
import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import Header from './components/header';
|
||||||
|
|
||||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||||
// Without this it ends up in ~8 very commonly used bundles.
|
// Without this it ends up in ~8 very commonly used bundles.
|
||||||
|
@ -170,6 +172,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
|
|
||||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||||
|
<WrappedRoute path='/about' component={About} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} />
|
<WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} />
|
||||||
<WrappedRoute path={['/public', '/timelines/public']} exact component={PublicTimeline} content={children} />
|
<WrappedRoute path={['/public', '/timelines/public']} exact component={PublicTimeline} content={children} />
|
||||||
|
@ -559,6 +562,8 @@ class UI extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||||
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
|
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
|
||||||
|
<Header />
|
||||||
|
|
||||||
<SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}>
|
<SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}>
|
||||||
{children}
|
{children}
|
||||||
</SwitchingColumnsArea>
|
</SwitchingColumnsArea>
|
||||||
|
|
|
@ -165,3 +165,7 @@ export function Explore () {
|
||||||
export function FilterModal () {
|
export function FilterModal () {
|
||||||
return import(/*webpackChunkName: "modals/filter_modal" */'../components/filter_modal');
|
return import(/*webpackChunkName: "modals/filter_modal" */'../components/filter_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function About () {
|
||||||
|
return import(/*webpackChunkName: "features/about" */'../../about');
|
||||||
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@
|
||||||
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
|
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
|
||||||
"generic.saved": "Saved",
|
"generic.saved": "Saved",
|
||||||
"getting_started.developers": "Developers",
|
"getting_started.developers": "Developers",
|
||||||
"getting_started.directory": "Profile directory",
|
"getting_started.directory": "Directory",
|
||||||
"getting_started.documentation": "Documentation",
|
"getting_started.documentation": "Documentation",
|
||||||
"getting_started.heading": "Getting started",
|
"getting_started.heading": "Getting started",
|
||||||
"getting_started.invite": "Invite people",
|
"getting_started.invite": "Invite people",
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
"mute_modal.duration": "Duration",
|
"mute_modal.duration": "Duration",
|
||||||
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
||||||
"mute_modal.indefinite": "Indefinite",
|
"mute_modal.indefinite": "Indefinite",
|
||||||
"navigation_bar.apps": "Mobile apps",
|
"navigation_bar.apps": "Get the app",
|
||||||
"navigation_bar.blocks": "Blocked users",
|
"navigation_bar.blocks": "Blocked users",
|
||||||
"navigation_bar.bookmarks": "Bookmarks",
|
"navigation_bar.bookmarks": "Bookmarks",
|
||||||
"navigation_bar.community_timeline": "Local timeline",
|
"navigation_bar.community_timeline": "Local timeline",
|
||||||
|
@ -324,7 +324,7 @@
|
||||||
"navigation_bar.filters": "Muted words",
|
"navigation_bar.filters": "Muted words",
|
||||||
"navigation_bar.follow_requests": "Follow requests",
|
"navigation_bar.follow_requests": "Follow requests",
|
||||||
"navigation_bar.follows_and_followers": "Follows and followers",
|
"navigation_bar.follows_and_followers": "Follows and followers",
|
||||||
"navigation_bar.info": "About this server",
|
"navigation_bar.info": "About",
|
||||||
"navigation_bar.keyboard_shortcuts": "Hotkeys",
|
"navigation_bar.keyboard_shortcuts": "Hotkeys",
|
||||||
"navigation_bar.lists": "Lists",
|
"navigation_bar.lists": "Lists",
|
||||||
"navigation_bar.logout": "Logout",
|
"navigation_bar.logout": "Logout",
|
||||||
|
|
|
@ -2191,27 +2191,62 @@ a.account__display-name {
|
||||||
&__main {
|
&__main {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
@media screen and (min-width: $no-gap-breakpoint) {
|
@media screen and (min-width: $no-gap-breakpoint) {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ui-header-height: 55px;
|
||||||
|
|
||||||
|
.ui__header {
|
||||||
|
display: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: $ui-header-height;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: $ui-header-height - 30px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tabs-bar__wrapper {
|
.tabs-bar__wrapper {
|
||||||
background: darken($ui-base-color, 8%);
|
background: darken($ui-base-color, 8%);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: $ui-header-height;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
||||||
@media screen and (min-width: $no-gap-breakpoint) {
|
@media screen and (min-width: $no-gap-breakpoint) {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-bar {
|
.tabs-bar {
|
||||||
|
@ -2419,100 +2454,98 @@ a.account__display-name {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 630px) {
|
.detailed-status {
|
||||||
.detailed-status {
|
padding: 15px;
|
||||||
padding: 15px;
|
|
||||||
|
|
||||||
.media-gallery,
|
.media-gallery,
|
||||||
.video-player,
|
.video-player,
|
||||||
.audio-player {
|
.audio-player {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__header__bar {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-bar,
|
||||||
|
.compose-form {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compose-form .compose-form__publish .compose-form__publish-button-wrapper {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification__report {
|
||||||
|
padding: 15px 15px 15px (48px + 15px * 2);
|
||||||
|
min-height: 48px + 2px;
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
left: 15px;
|
||||||
|
top: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
padding: 15px 15px 15px (48px + 15px * 2);
|
||||||
|
min-height: 48px + 2px;
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
left: 15px;
|
||||||
|
top: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__header__bar {
|
&__content {
|
||||||
padding: 5px 10px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar,
|
&__prepend {
|
||||||
.compose-form {
|
margin-left: 48px + 15px * 2;
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compose-form .compose-form__publish .compose-form__publish-button-wrapper {
|
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__report {
|
&__prepend-icon-wrapper {
|
||||||
padding: 15px 15px 15px (48px + 15px * 2);
|
left: -32px;
|
||||||
min-height: 48px + 2px;
|
}
|
||||||
|
|
||||||
&__avatar {
|
.media-gallery,
|
||||||
left: 15px;
|
&__action-bar,
|
||||||
top: 17px;
|
.video-player,
|
||||||
}
|
.audio-player {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
padding: 15px 10px;
|
||||||
|
|
||||||
|
&__header__bio {
|
||||||
|
margin: 0 -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
&__message {
|
||||||
|
margin-left: 48px + 15px * 2;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__favourite-icon-wrapper {
|
||||||
|
left: -32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
padding: 15px 15px 15px (48px + 15px * 2);
|
padding-top: 8px;
|
||||||
min-height: 48px + 2px;
|
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
left: 15px;
|
|
||||||
top: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
padding-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__prepend {
|
|
||||||
margin-left: 48px + 15px * 2;
|
|
||||||
padding-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__prepend-icon-wrapper {
|
|
||||||
left: -32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-gallery,
|
|
||||||
&__action-bar,
|
|
||||||
.video-player,
|
|
||||||
.audio-player {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.account {
|
.account {
|
||||||
padding: 15px 10px;
|
padding-top: 8px;
|
||||||
|
|
||||||
&__header__bio {
|
|
||||||
margin: 0 -10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification {
|
.account__avatar-wrapper {
|
||||||
&__message {
|
margin-left: 17px;
|
||||||
margin-left: 48px + 15px * 2;
|
margin-right: 15px;
|
||||||
padding-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__favourite-icon-wrapper {
|
|
||||||
left: -32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account {
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__avatar-wrapper {
|
|
||||||
margin-left: 17px;
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2554,19 +2587,7 @@ a.account__display-name {
|
||||||
.search {
|
.search {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 600px + (285px * 1) + (10px * 1)) {
|
|
||||||
.columns-area__panels__pane--compositional {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.with-fab .scrollable .item-list:last-child {
|
|
||||||
padding-bottom: 5.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 600px + (285px * 1) + (10px * 1)) {
|
|
||||||
.floating-action-button,
|
.floating-action-button,
|
||||||
.tabs-bar__link.optional {
|
.tabs-bar__link.optional {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -2575,18 +2596,76 @@ a.account__display-name {
|
||||||
.search-page .search {
|
.search-page .search {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 600px + (285px * 2) + (10px * 2)) {
|
.navigation-panel__legal {
|
||||||
.columns-area__panels__pane--navigational {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 600px + (285px * 2) + (10px * 2)) {
|
@media screen and (max-width: $no-gap-breakpoint - 1px) {
|
||||||
.tabs-bar {
|
.with-fab .scrollable .item-list:last-child {
|
||||||
|
padding-bottom: 5.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns-area__panels__main {
|
||||||
|
width: calc(100% - 55px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns-area__panels {
|
||||||
|
min-height: calc(100vh - $ui-header-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns-area__panels__pane--navigational {
|
||||||
|
min-width: 55px;
|
||||||
|
|
||||||
|
.columns-area__panels__pane__inner {
|
||||||
|
width: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-panel {
|
||||||
|
margin: 0;
|
||||||
|
background: $ui-base-color;
|
||||||
|
border-left: 1px solid lighten($ui-base-color, 8%);
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-link span,
|
||||||
|
.navigation-panel__sign-in-banner,
|
||||||
|
.navigation-panel__logo,
|
||||||
|
.getting-started__trends {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-link__icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui__header {
|
||||||
|
display: flex;
|
||||||
|
background: $ui-base-color;
|
||||||
|
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-header,
|
||||||
|
.column-back-button,
|
||||||
|
.scrollable {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore__search-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint + 285px - 1px) {
|
||||||
|
.columns-area__panels__pane--compositional {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.explore__search-header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-with-badge {
|
.icon-with-badge {
|
||||||
|
@ -7360,7 +7439,7 @@ noscript {
|
||||||
|
|
||||||
path:first-child {
|
path:first-child {
|
||||||
fill: rgba($highlight-text-color, 0.25) !important;
|
fill: rgba($highlight-text-color, 0.25) !important;
|
||||||
fill-opacity: 1 !important;
|
fill-opacity: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
path:last-child {
|
path:last-child {
|
||||||
|
@ -7832,10 +7911,9 @@ noscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
.explore__search-header {
|
.explore__search-header {
|
||||||
background: $ui-base-color;
|
background: darken($ui-base-color, 4%);
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
|
@ -7844,14 +7922,8 @@ noscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__input {
|
.search__input {
|
||||||
border-radius: 4px;
|
border: 1px solid lighten($ui-base-color, 8%);
|
||||||
color: $inverted-text-color;
|
|
||||||
background: $simple-background-color;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $dark-text-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search .fa {
|
.search .fa {
|
||||||
|
|
|
@ -53,7 +53,7 @@ $media-modal-media-max-width: 100%;
|
||||||
// put margins on top and bottom of image to avoid the screen covered by image.
|
// put margins on top and bottom of image to avoid the screen covered by image.
|
||||||
$media-modal-media-max-height: 80%;
|
$media-modal-media-max-height: 80%;
|
||||||
|
|
||||||
$no-gap-breakpoint: 415px;
|
$no-gap-breakpoint: 890px;
|
||||||
|
|
||||||
$font-sans-serif: 'mastodon-font-sans-serif' !default;
|
$font-sans-serif: 'mastodon-font-sans-serif' !default;
|
||||||
$font-display: 'mastodon-font-display' !default;
|
$font-display: 'mastodon-font-display' !default;
|
||||||
|
|
Reference in New Issue