[Glitch] Fix logged-out web UI on smaller screens
Port e2b561e3a5
to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
remotes/1703361221475462875/rebase/4.0.0rc1
parent
14ddb85c3b
commit
206e9593ac
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
import LinkFooter from 'flavours/glitch/features/ui/components/link_footer';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { title } from 'flavours/glitch/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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ import SearchResultsContainer from './containers/search_results_container';
|
|||
import { me, mascot } from 'flavours/glitch/initial_state';
|
||||
import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
|
||||
import HeaderContainer from './containers/header_container';
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
|
||||
|
@ -79,32 +80,41 @@ class Compose extends React.PureComponent {
|
|||
} = this.props;
|
||||
const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
|
||||
|
||||
return (
|
||||
<div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}>
|
||||
{multiColumn && <HeaderContainer />}
|
||||
if (multiColumn) {
|
||||
return (
|
||||
<div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}>
|
||||
<HeaderContainer />
|
||||
|
||||
{(multiColumn || isSearchPage) && <SearchContainer />}
|
||||
{(multiColumn || isSearchPage) && <SearchContainer />}
|
||||
|
||||
<div className='drawer__pager'>
|
||||
{!isSearchPage && <div className='drawer__inner'>
|
||||
<NavigationContainer />
|
||||
<div className='drawer__pager'>
|
||||
{!isSearchPage && <div className='drawer__inner'>
|
||||
<NavigationContainer />
|
||||
|
||||
<ComposeFormContainer />
|
||||
<ComposeFormContainer />
|
||||
|
||||
<div className='drawer__inner__mastodon'>
|
||||
{mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />}
|
||||
</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 className='drawer__inner__mastodon'>
|
||||
{mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />}
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<NavigationContainer />
|
||||
<ComposeFormContainer />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -84,11 +84,12 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
myAccount: ImmutablePropTypes.map.isRequired,
|
||||
myAccount: ImmutablePropTypes.map,
|
||||
columns: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
fetchFollowRequests: PropTypes.func.isRequired,
|
||||
|
@ -104,10 +105,10 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { fetchFollowRequests, multiColumn } = this.props;
|
||||
const { fetchFollowRequests } = this.props;
|
||||
const { signedIn } = this.context.identity;
|
||||
|
||||
if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
|
||||
this.context.router.history.replace('/home');
|
||||
if (!signedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -121,7 +122,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
let listItems = [];
|
||||
|
||||
if (multiColumn) {
|
||||
if (!columns.find(item => item.get('id') === 'HOME')) {
|
||||
if (signedIn && !columns.find(item => item.get('id') === 'HOME')) {
|
||||
navItems.push(<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />);
|
||||
}
|
||||
|
||||
|
@ -142,41 +143,47 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
navItems.push(<ColumnLink key='explore' icon='hashtag' text={intl.formatMessage(messages.explore)} to='/explore' />);
|
||||
}
|
||||
|
||||
if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) {
|
||||
navItems.push(<ColumnLink key='conversations' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />);
|
||||
if (signedIn) {
|
||||
if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) {
|
||||
navItems.push(<ColumnLink key='conversations' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />);
|
||||
}
|
||||
|
||||
if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) {
|
||||
navItems.push(<ColumnLink key='bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />);
|
||||
}
|
||||
|
||||
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' />);
|
||||
}
|
||||
|
||||
navItems.push(<ColumnLink key='getting_started' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />);
|
||||
|
||||
listItems = listItems.concat([
|
||||
<div key='9'>
|
||||
<ColumnLink key='lists' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
|
||||
{lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list =>
|
||||
<ColumnLink key={`list-${list.get('id')}`} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
|
||||
)}
|
||||
</div>,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) {
|
||||
navItems.push(<ColumnLink key='bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />);
|
||||
}
|
||||
|
||||
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' />);
|
||||
}
|
||||
|
||||
navItems.push(<ColumnLink key='getting_started' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />);
|
||||
|
||||
listItems = listItems.concat([
|
||||
<div key='9'>
|
||||
<ColumnLink key='lists' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
|
||||
{lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list =>
|
||||
<ColumnLink key={`list-${list.get('id')}`} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
|
||||
)}
|
||||
</div>,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
|
||||
<div className='scrollable optionally-scrollable'>
|
||||
<div className='getting-started__wrapper'>
|
||||
{!multiColumn && <NavigationBar account={myAccount} />}
|
||||
{!multiColumn && signedIn && <NavigationBar account={myAccount} />}
|
||||
{multiColumn && <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />}
|
||||
{navItems}
|
||||
<ColumnSubheading text={intl.formatMessage(messages.lists_subheading)} />
|
||||
{listItems}
|
||||
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
|
||||
{ preferencesLink !== undefined && <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href={preferencesLink} /> }
|
||||
<ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={openSettings} />
|
||||
{signedIn && (
|
||||
<React.Fragment>
|
||||
<ColumnSubheading text={intl.formatMessage(messages.lists_subheading)} />
|
||||
{listItems}
|
||||
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
|
||||
{ preferencesLink !== undefined && <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href={preferencesLink} /> }
|
||||
<ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={openSettings} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<LinkFooter />
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import Column from 'flavours/glitch/features/ui/components/column';
|
||||
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
|
||||
|
@ -28,8 +28,13 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||
const { intl, collapseEnabled, multiColumn } = this.props;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
<Column>
|
||||
<ColumnHeader
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
icon='question'
|
||||
multiColumn={multiColumn}
|
||||
/>
|
||||
|
||||
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
|
||||
<table>
|
||||
<thead>
|
||||
|
|
|
@ -3,13 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
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 { disableSwiping } from 'flavours/glitch/initial_state';
|
||||
|
||||
import BundleContainer from '../containers/bundle_container';
|
||||
import ColumnLoading from './column_loading';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
|
@ -72,20 +66,13 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
openSettings: PropTypes.func,
|
||||
};
|
||||
|
||||
// Corresponds to (max-width: 600px + (285px * 1) + (10px * 1)) in SCSS
|
||||
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 895px)');
|
||||
// Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS
|
||||
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)');
|
||||
|
||||
state = {
|
||||
shouldAnimate: false,
|
||||
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() {
|
||||
if (!this.props.singleColumn) {
|
||||
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
|
||||
|
@ -100,10 +87,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
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.setState({ shouldAnimate: true });
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
|
@ -116,13 +100,6 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
|
||||
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 () {
|
||||
|
@ -150,31 +127,6 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
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 = () => {
|
||||
if (typeof this._interruptScrollAnimation !== 'function') {
|
||||
return;
|
||||
|
@ -187,22 +139,6 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
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 => () => {
|
||||
return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading />;
|
||||
}
|
||||
|
@ -213,22 +149,12 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
|
||||
render () {
|
||||
const { columns, children, singleColumn, intl, navbarUnder, openSettings } = this.props;
|
||||
const { shouldAnimate, renderComposePanel } = this.state;
|
||||
const { renderComposePanel } = this.state;
|
||||
const { signedIn } = this.context.identity;
|
||||
|
||||
const columnIndex = getIndex(this.context.router.history.location.pathname);
|
||||
|
||||
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 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 (
|
||||
<div className='columns-area__panels'>
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
|
||||
|
@ -238,9 +164,8 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<div className={`columns-area__panels__main ${floatingActionButton && 'with-fab'}`}>
|
||||
{!navbarUnder && <TabsBar key='tabs' />}
|
||||
{content}
|
||||
{navbarUnder && <TabsBar key='tabs' />}
|
||||
<div className='tabs-bar__wrapper'><div id='tabs-bar__portal' /></div>
|
||||
<div className='columns-area columns-area--mobile'>{children}</div>
|
||||
</div>
|
||||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
|
||||
|
|
|
@ -34,7 +34,7 @@ class ComposePanel extends React.PureComponent {
|
|||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<LinkFooter withHotkeys />
|
||||
<LinkFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import Logo from 'flavours/glitch/components/logo';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { registrationsOpen, me } from 'flavours/glitch/initial_state';
|
||||
import Avatar from 'flavours/glitch/components/avatar';
|
||||
import Permalink from 'flavours/glitch/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,8 +3,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { limitedFederationMode, version, repository, source_url, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
|
||||
import { signOutLink, securityLink, privacyPolicyLink } from 'flavours/glitch/utils/backend_links';
|
||||
import { version, repository, source_url, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
|
||||
import { logOut } from 'flavours/glitch/utils/log_out';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
|
||||
|
@ -34,7 +33,6 @@ class LinkFooter extends React.PureComponent {
|
|||
};
|
||||
|
||||
static propTypes = {
|
||||
withHotkeys: PropTypes.bool,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
@ -49,44 +47,26 @@ class LinkFooter extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { withHotkeys } = this.props;
|
||||
const { signedIn, permissions } = this.context.identity;
|
||||
|
||||
const items = [];
|
||||
|
||||
if ((this.context.identity.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>);
|
||||
}
|
||||
|
||||
if (signedIn && withHotkeys) {
|
||||
items.push(<Link key='hotkeys' to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link>);
|
||||
}
|
||||
|
||||
if (signedIn && securityLink) {
|
||||
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>);
|
||||
}
|
||||
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='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>);
|
||||
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>);
|
||||
|
||||
if (profileDirectory) {
|
||||
items.push(<Link key='directory' to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></Link>);
|
||||
}
|
||||
|
||||
items.push(<a key='apps' href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a>);
|
||||
|
||||
if (privacyPolicyLink) {
|
||||
items.push(<a key='terms' href={privacyPolicyLink} target='_blank'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></a>);
|
||||
items.push(<Link key='directory' to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Directory' /></Link>);
|
||||
}
|
||||
|
||||
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>);
|
||||
|
||||
if (signedIn) {
|
||||
items.push(<a key='security' href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a>);
|
||||
items.push(<a key='logout' href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a>);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,10 +42,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>
|
||||
|
||||
{!signedIn && (
|
||||
<React.Fragment>
|
||||
<div className='navigation-panel__sign-in-banner'>
|
||||
<hr />
|
||||
<SignInBanner />
|
||||
</React.Fragment>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{signedIn && (
|
||||
|
@ -64,6 +64,11 @@ export default class NavigationPanel extends React.Component {
|
|||
</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 && (
|
||||
<React.Fragment>
|
||||
<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 'flavours/glitch/is_mobile';
|
||||
import Icon from 'flavours/glitch/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='/search' 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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -52,12 +52,14 @@ import {
|
|||
Directory,
|
||||
Explore,
|
||||
FollowRecommendations,
|
||||
About,
|
||||
} from './util/async-components';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { me, title } from 'flavours/glitch/initial_state';
|
||||
import { closeOnboarding, INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import Header from './components/header';
|
||||
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
|
@ -183,6 +185,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} 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={['/public', '/timelines/public']} exact component={PublicTimeline} content={children} />
|
||||
|
@ -652,6 +655,9 @@ class UI extends React.Component {
|
|||
)}}
|
||||
/>
|
||||
</div>)}
|
||||
|
||||
<Header />
|
||||
|
||||
<SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'} navbarUnder={navbarUnder}>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
|
|
|
@ -181,3 +181,7 @@ export function FilterModal () {
|
|||
export function Explore () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/explore" */'flavours/glitch/features/explore');
|
||||
}
|
||||
|
||||
export function About () {
|
||||
return import(/*webpackChunkName: "features/glitch/async/about" */'flavours/glitch/features/about');
|
||||
}
|
||||
|
|
|
@ -42,27 +42,62 @@
|
|||
&__main {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media screen and (min-width: $no-gap-breakpoint) {
|
||||
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 {
|
||||
background: darken($ui-base-color, 8%);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
top: $ui-header-height;
|
||||
z-index: 2;
|
||||
padding-top: 0;
|
||||
|
||||
@media screen and (min-width: $no-gap-breakpoint) {
|
||||
padding-top: 10px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tabs-bar {
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
}
|
||||
|
||||
.explore__search-header {
|
||||
background: $ui-base-color;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
background: darken($ui-base-color, 4%);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
|
||||
.search {
|
||||
|
@ -15,14 +14,8 @@
|
|||
}
|
||||
|
||||
.search__input {
|
||||
border-radius: 4px;
|
||||
color: $inverted-text-color;
|
||||
background: $simple-background-color;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
padding: 10px;
|
||||
|
||||
&::placeholder {
|
||||
color: $dark-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.search .fa {
|
||||
|
|
|
@ -191,7 +191,7 @@
|
|||
|
||||
path:first-child {
|
||||
fill: rgba($highlight-text-color, 0.25) !important;
|
||||
fill-opacity: 1 !important;
|
||||
fill-opacity: 100% !important;
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
|
|
|
@ -154,72 +154,70 @@
|
|||
padding-top: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 630px) {
|
||||
.detailed-status {
|
||||
padding: 15px;
|
||||
.detailed-status {
|
||||
padding: 15px;
|
||||
|
||||
.media-gallery,
|
||||
.video-player,
|
||||
.audio-player {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.media-gallery,
|
||||
.video-player,
|
||||
.audio-player {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.account__header__bar {
|
||||
padding: 5px 10px;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-bar,
|
||||
.compose-form {
|
||||
padding: 15px;
|
||||
.status {
|
||||
padding: 15px;
|
||||
min-height: 48px + 2px;
|
||||
|
||||
.media-gallery,
|
||||
&__action-bar,
|
||||
.video-player,
|
||||
.audio-player {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form .compose-form__publish .compose-form__publish-button-wrapper {
|
||||
.account {
|
||||
padding: 15px 10px;
|
||||
|
||||
&__header__bio {
|
||||
margin: 0 -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.notification {
|
||||
&__message {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.notification__report {
|
||||
padding: 15px 15px 15px (48px + 15px * 2);
|
||||
min-height: 48px + 2px;
|
||||
|
||||
&__avatar {
|
||||
left: 15px;
|
||||
top: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 15px;
|
||||
min-height: 48px + 2px;
|
||||
|
||||
.media-gallery,
|
||||
&__action-bar,
|
||||
.video-player,
|
||||
.audio-player {
|
||||
margin-top: 10px;
|
||||
}
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.account {
|
||||
padding: 15px 10px;
|
||||
|
||||
&__header__bio {
|
||||
margin: 0 -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.notification {
|
||||
&__message {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.account {
|
||||
padding-top: 8px;
|
||||
}
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -261,19 +259,7 @@
|
|||
.search {
|
||||
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,
|
||||
.tabs-bar__link.optional {
|
||||
display: none;
|
||||
|
@ -282,16 +268,74 @@
|
|||
.search-page .search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navigation-panel__legal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px + (285px * 2) + (10px * 2)) {
|
||||
@media screen and (max-width: $no-gap-breakpoint - 1px) {
|
||||
.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 {
|
||||
display: none;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px + (285px * 2) + (10px * 2)) {
|
||||
.tabs-bar {
|
||||
.explore__search-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint + 285px - 1px) {
|
||||
.columns-area__panels__pane--compositional {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.explore__search-header {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ $media-modal-media-max-width: 100%;
|
|||
// put margins on top and bottom of image to avoid the screen covered by image.
|
||||
$media-modal-media-max-height: 80%;
|
||||
|
||||
$no-gap-breakpoint: 415px;
|
||||
$no-gap-breakpoint: 890px;
|
||||
|
||||
$font-sans-serif: 'mastodon-font-sans-serif' !default;
|
||||
$font-display: 'mastodon-font-display' !default;
|
||||
|
|
Loading…
Reference in New Issue