diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features')
12 files changed, 197 insertions, 254 deletions
diff --git a/app/javascript/flavours/glitch/features/about/index.js b/app/javascript/flavours/glitch/features/about/index.js new file mode 100644 index 000000000..9f7c90d91 --- /dev/null +++ b/app/javascript/flavours/glitch/features/about/index.js @@ -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> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index 567bb3711..150e78c48 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -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 />} - - {(multiColumn || isSearchPage) && <SearchContainer />} + if (multiColumn) { + return ( + <div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}> + <HeaderContainer /> - <div className='drawer__pager'> - {!isSearchPage && <div className='drawer__inner'> - <NavigationContainer /> + {(multiColumn || isSearchPage) && <SearchContainer />} - <ComposeFormContainer /> + <div className='drawer__pager'> + {!isSearchPage && <div className='drawer__inner'> + <NavigationContainer /> - <div className='drawer__inner__mastodon'> - {mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />} - </div> - </div>} + <ComposeFormContainer /> - <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> ); } diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index f52b769a3..4d3427877 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -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 (!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' />); - } + 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' />); + 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>, - ]); + 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 /> diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js index 481f76763..06df2ed99 100644 --- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js +++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js @@ -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> diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index 718b4a27f..8037c195d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -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'> diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js index 6e1c51d74..894a00747 100644 --- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js @@ -34,7 +34,7 @@ class ComposePanel extends React.PureComponent { </React.Fragment> )} - <LinkFooter withHotkeys /> + <LinkFooter /> </div> ); } diff --git a/app/javascript/flavours/glitch/features/ui/components/header.js b/app/javascript/flavours/glitch/features/ui/components/header.js new file mode 100644 index 000000000..041bdff05 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/header.js @@ -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> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js index 39576f17b..cd32c8a3d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/link_footer.js +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.js @@ -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>); } diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js index 57fbfb285..cb884ec75 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js @@ -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' /> diff --git a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js deleted file mode 100644 index 9c82fc91d..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js +++ /dev/null @@ -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> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index c8cc905e7..8ea7a0b02 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -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> diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js index eef3a941d..7ef06ceb7 100644 --- a/app/javascript/flavours/glitch/features/ui/util/async-components.js +++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js @@ -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'); +} |