Fix logged-out web UI on smaller screens (#19263)
This commit is contained in:
		
					parent
					
						
							
								6580ac7241
							
						
					
				
			
			
				commit
				
					
						e2b561e3a5
					
				
			
		
					 19 changed files with 489 additions and 474 deletions
				
			
		|  | @ -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; | ||||
							
								
								
									
										34
									
								
								app/javascript/mastodon/features/about/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/javascript/mastodon/features/about/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 Icon from 'mastodon/components/icon'; | ||||
| import { logOut } from 'mastodon/utils/log_out'; | ||||
| import Column from 'mastodon/components/column'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, | ||||
|  | @ -92,57 +93,59 @@ class Compose extends React.PureComponent { | |||
|   render () { | ||||
|     const { multiColumn, showSearch, isSearchPage, intl } = this.props; | ||||
| 
 | ||||
|     let header = ''; | ||||
| 
 | ||||
|     if (multiColumn) { | ||||
|       const { columns } = this.props; | ||||
|       header = ( | ||||
|         <nav className='drawer__header'> | ||||
|           <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') && ( | ||||
|             <Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link> | ||||
|           )} | ||||
|           {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && ( | ||||
|             <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') === 'COMMUNITY') && ( | ||||
|             <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') === 'PUBLIC') && ( | ||||
|             <Link to='/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link> | ||||
|           )} | ||||
|           <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> | ||||
| 
 | ||||
|       return ( | ||||
|         <div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}> | ||||
|           <nav className='drawer__header'> | ||||
|             <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') && ( | ||||
|               <Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link> | ||||
|             )} | ||||
|             {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && ( | ||||
|               <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') === 'COMMUNITY') && ( | ||||
|               <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') === 'PUBLIC') && ( | ||||
|               <Link to='/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link> | ||||
|             )} | ||||
|             <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 ( | ||||
|       <div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}> | ||||
|         {header} | ||||
| 
 | ||||
|         {(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> | ||||
|       <Column onFocus={this.onFocus}> | ||||
|         <NavigationContainer onClose={this.onBlur} /> | ||||
|         <ComposeFormContainer /> | ||||
|       </Column> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,13 +30,13 @@ class Explore extends React.PureComponent { | |||
| 
 | ||||
|   static contextTypes = { | ||||
|     router: PropTypes.object, | ||||
|     identity: PropTypes.object, | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     multiColumn: PropTypes.bool, | ||||
|     isSearching: PropTypes.bool, | ||||
|     layout: PropTypes.string, | ||||
|   }; | ||||
| 
 | ||||
|   handleHeaderClick = () => { | ||||
|  | @ -48,22 +48,21 @@ class Explore extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, multiColumn, isSearching, layout } = this.props; | ||||
|     const { intl, multiColumn, isSearching } = this.props; | ||||
|     const { signedIn } = this.context.identity; | ||||
| 
 | ||||
|     return ( | ||||
|       <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> | ||||
|         {layout === 'mobile' ? ( | ||||
|           <div className='explore__search-header'> | ||||
|             <Search /> | ||||
|           </div> | ||||
|         ) : ( | ||||
|           <ColumnHeader | ||||
|             icon={isSearching ? 'search' : 'hashtag'} | ||||
|             title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)} | ||||
|             onClick={this.handleHeaderClick} | ||||
|             multiColumn={multiColumn} | ||||
|           /> | ||||
|         )} | ||||
|         <ColumnHeader | ||||
|           icon={isSearching ? 'search' : 'hashtag'} | ||||
|           title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)} | ||||
|           onClick={this.handleHeaderClick} | ||||
|           multiColumn={multiColumn} | ||||
|         /> | ||||
| 
 | ||||
|         <div className='explore__search-header'> | ||||
|           <Search /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div className='scrollable scrollable--flex'> | ||||
|           {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/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/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> | ||||
| 
 | ||||
|               <Switch> | ||||
|  |  | |||
|  | @ -1,19 +1,20 @@ | |||
| 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 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 PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| 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 { List as ImmutableList } from 'immutable'; | ||||
| import NavigationContainer from '../compose/containers/navigation_container'; | ||||
| import Icon from 'mastodon/components/icon'; | ||||
| import LinkFooter from 'mastodon/features/ui/components/link_footer'; | ||||
| import TrendsContainer from './containers/trends_container'; | ||||
| import { Helmet } from 'react-helmet'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, | ||||
|  | @ -40,7 +41,6 @@ const messages = defineMessages({ | |||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   myAccount: state.getIn(['accounts', me]), | ||||
|   columns: state.getIn(['settings', 'columns']), | ||||
|   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) | ||||
| @injectIntl | ||||
| class GettingStarted extends ImmutablePureComponent { | ||||
| 
 | ||||
|   static contextTypes = { | ||||
|     router: PropTypes.object.isRequired, | ||||
|     identity: PropTypes.object, | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     myAccount: ImmutablePropTypes.map.isRequired, | ||||
|     columns: ImmutablePropTypes.list, | ||||
|     myAccount: ImmutablePropTypes.map, | ||||
|     multiColumn: PropTypes.bool, | ||||
|     fetchFollowRequests: PropTypes.func.isRequired, | ||||
|     unreadFollowRequests: PropTypes.number, | ||||
|  | @ -79,10 +77,10 @@ class GettingStarted extends ImmutablePureComponent { | |||
|   }; | ||||
| 
 | ||||
|   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; | ||||
|     } | ||||
| 
 | ||||
|  | @ -90,91 +88,57 @@ class GettingStarted extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, myAccount, columns, multiColumn, unreadFollowRequests } = this.props; | ||||
|     const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props; | ||||
|     const { signedIn } = this.context.identity; | ||||
| 
 | ||||
|     const navItems = []; | ||||
|     let height = (multiColumn) ? 0 : 60; | ||||
| 
 | ||||
|     if (multiColumn) { | ||||
|       navItems.push( | ||||
|         <ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />, | ||||
|       ); | ||||
|       height += 34; | ||||
|     } | ||||
| 
 | ||||
|     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='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( | ||||
|         <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='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( | ||||
|       <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' />, | ||||
|     ); | ||||
|       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*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( | ||||
|         <ColumnSubheading key='header-settings' text={intl.formatMessage(messages.settings_subheading)} />, | ||||
|         <ColumnLink key='preferences' icon='gears' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />, | ||||
|       ); | ||||
| 
 | ||||
|       height += 34 + 48; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.menu)}> | ||||
|         {multiColumn && <div className='column-header__wrapper'> | ||||
|           <h1 className='column-header'> | ||||
|             <button> | ||||
|               <Icon id='bars' className='column-header__icon' fixedWidth /> | ||||
|               <FormattedMessage id='getting_started.heading' defaultMessage='Getting started' /> | ||||
|             </button> | ||||
|           </h1> | ||||
|         </div>} | ||||
|       <Column> | ||||
|         {(signedIn && !multiColumn) ? <NavigationContainer /> : <ColumnHeader title={intl.formatMessage(messages.menu)} icon='bars' multiColumn={multiColumn} />} | ||||
| 
 | ||||
|         <div className='getting-started'> | ||||
|           <div className='getting-started__wrapper' style={{ height }}> | ||||
|             {!multiColumn && <NavigationContainer />} | ||||
|         <div className='getting-started scrollable scrollable--flex'> | ||||
|           <div className='getting-started__wrapper'> | ||||
|             {navItems} | ||||
|           </div> | ||||
| 
 | ||||
|           {!multiColumn && <div className='flex-spacer' />} | ||||
| 
 | ||||
|           <LinkFooter withHotkeys={multiColumn} /> | ||||
|           <LinkFooter /> | ||||
|         </div> | ||||
| 
 | ||||
|         {multiColumn && showTrends && <TrendsContainer />} | ||||
|         {(multiColumn && showTrends) && <TrendsContainer />} | ||||
| 
 | ||||
|         <Helmet> | ||||
|           <title>{intl.formatMessage(messages.menu)} - {title}</title> | ||||
|         </Helmet> | ||||
|       </Column> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
|  | @ -13,6 +13,9 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an | |||
| import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container'; | ||||
| import classNames from 'classnames'; | ||||
| 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({ | ||||
|   title: { id: 'column.home', defaultMessage: 'Home' }, | ||||
|  | @ -32,6 +35,10 @@ export default @connect(mapStateToProps) | |||
| @injectIntl | ||||
| class HomeTimeline extends React.PureComponent { | ||||
| 
 | ||||
|   static contextTypes = { | ||||
|     identity: PropTypes.object, | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|  | @ -113,6 +120,7 @@ class HomeTimeline extends React.PureComponent { | |||
|   render () { | ||||
|     const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; | ||||
|     const pinned = !!columnId; | ||||
|     const { signedIn } = this.context.identity; | ||||
| 
 | ||||
|     let announcementsButton = null; | ||||
| 
 | ||||
|  | @ -147,14 +155,20 @@ class HomeTimeline extends React.PureComponent { | |||
|           <ColumnSettingsContainer /> | ||||
|         </ColumnHeader> | ||||
| 
 | ||||
|         <StatusListContainer | ||||
|           trackScroll={!pinned} | ||||
|           scrollKey={`home_timeline-${columnId}`} | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           timelineId='home' | ||||
|           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} | ||||
|         /> | ||||
|         {signedIn ? ( | ||||
|           <StatusListContainer | ||||
|             trackScroll={!pinned} | ||||
|             scrollKey={`home_timeline-${columnId}`} | ||||
|             onLoadMore={this.handleLoadMore} | ||||
|             timelineId='home' | ||||
|             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> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import React from 'react'; | ||||
| import Column from '../ui/components/column'; | ||||
| import ColumnBackButtonSlim from '../../components/column_back_button_slim'; | ||||
| import Column from 'mastodon/components/column'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import ColumnHeader from 'mastodon/components/column_header'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, | ||||
|  | @ -21,8 +21,13 @@ class KeyboardShortcuts extends ImmutablePureComponent { | |||
|     const { intl, 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> | ||||
|  |  | |||
|  | @ -26,6 +26,9 @@ import LoadGap from '../../components/load_gap'; | |||
| import Icon from 'mastodon/components/icon'; | ||||
| import compareId from 'mastodon/compare_id'; | ||||
| 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({ | ||||
|   title: { id: 'column.notifications', defaultMessage: 'Notifications' }, | ||||
|  | @ -69,6 +72,10 @@ export default @connect(mapStateToProps) | |||
| @injectIntl | ||||
| class Notifications extends React.PureComponent { | ||||
| 
 | ||||
|   static contextTypes = { | ||||
|     identity: PropTypes.object, | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     columnId: PropTypes.string, | ||||
|     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 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 { signedIn } = this.context.identity; | ||||
| 
 | ||||
|     let scrollableContent = null; | ||||
| 
 | ||||
|     const filterBarContainer = showFilterBar | ||||
|     const filterBarContainer = (signedIn && showFilterBar) | ||||
|       ? (<FilterBarContainer />) | ||||
|       : null; | ||||
| 
 | ||||
|  | @ -211,26 +219,32 @@ class Notifications extends React.PureComponent { | |||
| 
 | ||||
|     this.scrollableContent = scrollableContent; | ||||
| 
 | ||||
|     const scrollContainer = ( | ||||
|       <ScrollableList | ||||
|         scrollKey={`notifications-${columnId}`} | ||||
|         trackScroll={!pinned} | ||||
|         isLoading={isLoading} | ||||
|         showLoading={isLoading && notifications.size === 0} | ||||
|         hasMore={hasMore} | ||||
|         numPending={numPending} | ||||
|         prepend={needsNotificationPermission && <NotificationsPermissionBanner />} | ||||
|         alwaysPrepend | ||||
|         emptyMessage={emptyMessage} | ||||
|         onLoadMore={this.handleLoadOlder} | ||||
|         onLoadPending={this.handleLoadPending} | ||||
|         onScrollToTop={this.handleScrollToTop} | ||||
|         onScroll={this.handleScroll} | ||||
|         bindToDocument={!multiColumn} | ||||
|       > | ||||
|         {scrollableContent} | ||||
|       </ScrollableList> | ||||
|     ); | ||||
|     let scrollContainer; | ||||
| 
 | ||||
|     if (signedIn) { | ||||
|       scrollContainer = ( | ||||
|         <ScrollableList | ||||
|           scrollKey={`notifications-${columnId}`} | ||||
|           trackScroll={!pinned} | ||||
|           isLoading={isLoading} | ||||
|           showLoading={isLoading && notifications.size === 0} | ||||
|           hasMore={hasMore} | ||||
|           numPending={numPending} | ||||
|           prepend={needsNotificationPermission && <NotificationsPermissionBanner />} | ||||
|           alwaysPrepend | ||||
|           emptyMessage={emptyMessage} | ||||
|           onLoadMore={this.handleLoadOlder} | ||||
|           onLoadPending={this.handleLoadPending} | ||||
|           onScrollToTop={this.handleScrollToTop} | ||||
|           onScroll={this.handleScroll} | ||||
|           bindToDocument={!multiColumn} | ||||
|         > | ||||
|           {scrollableContent} | ||||
|         </ScrollableList> | ||||
|       ); | ||||
|     } else { | ||||
|       scrollContainer = <NotSignedInIndicator />; | ||||
|     } | ||||
| 
 | ||||
|     let extraButton = null; | ||||
| 
 | ||||
|  | @ -262,8 +276,13 @@ class Notifications extends React.PureComponent { | |||
|         > | ||||
|           <ColumnSettingsContainer /> | ||||
|         </ColumnHeader> | ||||
| 
 | ||||
|         {filterBarContainer} | ||||
|         {scrollContainer} | ||||
| 
 | ||||
|         <Helmet> | ||||
|           <title>{intl.formatMessage(messages.title)} - {title}</title> | ||||
|         </Helmet> | ||||
|       </Column> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
|  | @ -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 'mastodon/initial_state'; | ||||
| 
 | ||||
| import BundleContainer from '../containers/bundle_container'; | ||||
| import ColumnLoading from './column_loading'; | ||||
| import DrawerLoading from './drawer_loading'; | ||||
|  | @ -71,20 +65,13 @@ class ColumnsArea extends ImmutablePureComponent { | |||
|     children: PropTypes.node, | ||||
|   }; | ||||
| 
 | ||||
|    // 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); | ||||
|  | @ -99,10 +86,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) { | ||||
|  | @ -115,13 +99,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 () { | ||||
|  | @ -149,31 +126,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; | ||||
|  | @ -186,22 +138,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 />; | ||||
|   } | ||||
|  | @ -212,22 +148,12 @@ class ColumnsArea extends ImmutablePureComponent { | |||
| 
 | ||||
|   render () { | ||||
|     const { columns, children, singleColumn, isModalOpen, intl } = 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'> | ||||
|  | @ -237,8 +163,8 @@ class ColumnsArea extends ImmutablePureComponent { | |||
|           </div> | ||||
| 
 | ||||
|           <div className={`columns-area__panels__main ${floatingActionButton && 'with-fab'}`}> | ||||
|             <TabsBar key='tabs' /> | ||||
|             {content} | ||||
|             <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'> | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ class ComposePanel extends React.PureComponent { | |||
|           </React.Fragment> | ||||
|         )} | ||||
| 
 | ||||
|         <LinkFooter withHotkeys /> | ||||
|         <LinkFooter /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										53
									
								
								app/javascript/mastodon/features/ui/components/header.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/javascript/mastodon/features/ui/components/header.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | ||||
| 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 { openModal } from 'mastodon/actions/modal'; | ||||
| import { PERMISSION_INVITE_USERS } from 'mastodon/permissions'; | ||||
|  | @ -33,7 +33,6 @@ class LinkFooter extends React.PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     withHotkeys: PropTypes.bool, | ||||
|     onLogout: PropTypes.func.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
|  | @ -48,40 +47,26 @@ class LinkFooter extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { withHotkeys } = this.props; | ||||
|     const { signedIn, permissions } = this.context.identity; | ||||
|     const items = []; | ||||
| 
 | ||||
|     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>); | ||||
|     } | ||||
| 
 | ||||
|     if (signedIn && withHotkeys) { | ||||
|       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>); | ||||
|     } | ||||
|     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(<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) { | ||||
|       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>); | ||||
|     } | ||||
| 
 | ||||
|  | @ -93,9 +78,9 @@ class LinkFooter extends React.PureComponent { | |||
| 
 | ||||
|         <p> | ||||
|           <FormattedMessage | ||||
|             id='getting_started.open_source_notice' | ||||
|             defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.' | ||||
|             values={{ github: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span> }} | ||||
|             id='getting_started.free_software_notice' | ||||
|             defaultMessage='Mastodon is free, open source software. You can view the source code, contribute or report issues at {repository}.' | ||||
|             values={{ repository: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span> }} | ||||
|           /> | ||||
|         </p> | ||||
|       </div> | ||||
|  |  | |||
|  | @ -23,9 +23,10 @@ export default class NavigationPanel extends React.Component { | |||
| 
 | ||||
|     return ( | ||||
|       <div className='navigation-panel'> | ||||
|         <Link to='/' className='column-link column-link--logo'><Logo /></Link> | ||||
| 
 | ||||
|         <hr /> | ||||
|         <div className='navigation-panel__logo'> | ||||
|           <Link to='/' className='column-link column-link--logo'><Logo /></Link> | ||||
|           <hr /> | ||||
|         </div> | ||||
| 
 | ||||
|         {signedIn && ( | ||||
|           <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> | ||||
| 
 | ||||
|         {!signedIn && ( | ||||
|           <React.Fragment> | ||||
|           <div className='navigation-panel__sign-in-banner'> | ||||
|             <hr /> | ||||
|             <SignInBanner /> | ||||
|           </React.Fragment> | ||||
|           </div> | ||||
|         )} | ||||
| 
 | ||||
|         {signedIn && ( | ||||
|  | @ -62,6 +63,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 '../../../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, | ||||
|   Explore, | ||||
|   FollowRecommendations, | ||||
|   About, | ||||
| } from './util/async-components'; | ||||
| import { me, title } from '../../initial_state'; | ||||
| import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; | ||||
| 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.
 | ||||
|  | @ -170,6 +172,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} /> | ||||
|  | @ -559,6 +562,8 @@ class UI extends React.PureComponent { | |||
|     return ( | ||||
|       <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 }}> | ||||
|           <Header /> | ||||
| 
 | ||||
|           <SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'}> | ||||
|             {children} | ||||
|           </SwitchingColumnsArea> | ||||
|  |  | |||
|  | @ -165,3 +165,7 @@ export function Explore () { | |||
| export function FilterModal () { | ||||
|   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.", | ||||
|   "generic.saved": "Saved", | ||||
|   "getting_started.developers": "Developers", | ||||
|   "getting_started.directory": "Profile directory", | ||||
|   "getting_started.directory": "Directory", | ||||
|   "getting_started.documentation": "Documentation", | ||||
|   "getting_started.heading": "Getting started", | ||||
|   "getting_started.invite": "Invite people", | ||||
|  | @ -310,7 +310,7 @@ | |||
|   "mute_modal.duration": "Duration", | ||||
|   "mute_modal.hide_notifications": "Hide notifications from this user?", | ||||
|   "mute_modal.indefinite": "Indefinite", | ||||
|   "navigation_bar.apps": "Mobile apps", | ||||
|   "navigation_bar.apps": "Get the app", | ||||
|   "navigation_bar.blocks": "Blocked users", | ||||
|   "navigation_bar.bookmarks": "Bookmarks", | ||||
|   "navigation_bar.community_timeline": "Local timeline", | ||||
|  | @ -324,7 +324,7 @@ | |||
|   "navigation_bar.filters": "Muted words", | ||||
|   "navigation_bar.follow_requests": "Follow requests", | ||||
|   "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.lists": "Lists", | ||||
|   "navigation_bar.logout": "Logout", | ||||
|  |  | |||
|  | @ -2191,27 +2191,62 @@ a.account__display-name { | |||
|     &__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 { | ||||
|  | @ -2419,100 +2454,98 @@ a.account__display-name { | |||
|     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; | ||||
|   } | ||||
| 
 | ||||
|   .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 { | ||||
|       padding: 5px 10px; | ||||
|     &__content { | ||||
|       padding-top: 5px; | ||||
|     } | ||||
| 
 | ||||
|     .navigation-bar, | ||||
|     .compose-form { | ||||
|       padding: 15px; | ||||
|     } | ||||
| 
 | ||||
|     .compose-form .compose-form__publish .compose-form__publish-button-wrapper { | ||||
|     &__prepend { | ||||
|       margin-left: 48px + 15px * 2; | ||||
|       padding-top: 15px; | ||||
|     } | ||||
| 
 | ||||
|     .notification__report { | ||||
|       padding: 15px 15px 15px (48px + 15px * 2); | ||||
|       min-height: 48px + 2px; | ||||
|     &__prepend-icon-wrapper { | ||||
|       left: -32px; | ||||
|     } | ||||
| 
 | ||||
|       &__avatar { | ||||
|         left: 15px; | ||||
|         top: 17px; | ||||
|       } | ||||
|     .media-gallery, | ||||
|     &__action-bar, | ||||
|     .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 { | ||||
|       padding: 15px 15px 15px (48px + 15px * 2); | ||||
|       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; | ||||
|       } | ||||
|       padding-top: 8px; | ||||
|     } | ||||
| 
 | ||||
|     .account { | ||||
|       padding: 15px 10px; | ||||
| 
 | ||||
|       &__header__bio { | ||||
|         margin: 0 -10px; | ||||
|       } | ||||
|       padding-top: 8px; | ||||
|     } | ||||
| 
 | ||||
|     .notification { | ||||
|       &__message { | ||||
|         margin-left: 48px + 15px * 2; | ||||
|         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; | ||||
|       } | ||||
|     .account__avatar-wrapper { | ||||
|       margin-left: 17px; | ||||
|       margin-right: 15px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -2554,19 +2587,7 @@ a.account__display-name { | |||
|   .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; | ||||
|  | @ -2575,18 +2596,76 @@ a.account__display-name { | |||
|   .search-page .search { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media screen and (max-width: 600px + (285px * 2) + (10px * 2)) { | ||||
|   .columns-area__panels__pane--navigational { | ||||
|   .navigation-panel__legal { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media screen and (min-width: 600px + (285px * 2) + (10px * 2)) { | ||||
|   .tabs-bar { | ||||
| @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 { | ||||
|     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; | ||||
|   } | ||||
| 
 | ||||
|   .explore__search-header { | ||||
|     display: flex; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .icon-with-badge { | ||||
|  | @ -7360,7 +7439,7 @@ noscript { | |||
| 
 | ||||
|       path:first-child { | ||||
|         fill: rgba($highlight-text-color, 0.25) !important; | ||||
|         fill-opacity: 1 !important; | ||||
|         fill-opacity: 100% !important; | ||||
|       } | ||||
| 
 | ||||
|       path:last-child { | ||||
|  | @ -7832,10 +7911,9 @@ noscript { | |||
| } | ||||
| 
 | ||||
| .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 { | ||||
|  | @ -7844,14 +7922,8 @@ noscript { | |||
|   } | ||||
| 
 | ||||
|   .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 { | ||||
|  |  | |||
|  | @ -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. | ||||
| $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…
	
	Add table
		Add a link
		
	
		Reference in a new issue