[Glitch] Add "Toots/Toots with replies/Media" tab below profile header
Port 51310125051a75ef7af4e8ffc8b6532c151e96b6 to glitch
This commit is contained in:
		
							parent
							
								
									d75a6054a0
								
							
						
					
					
						commit
						f00af46d70
					
				| @ -121,7 +121,7 @@ export const refreshHomeTimeline         = () => refreshTimeline('home', '/api/v | ||||
| export const refreshPublicTimeline       = () => refreshTimeline('public', '/api/v1/timelines/public'); | ||||
| export const refreshCommunityTimeline    = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); | ||||
| export const refreshDirectTimeline       = () => refreshTimeline('direct', '/api/v1/timelines/direct'); | ||||
| export const refreshAccountTimeline      = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); | ||||
| export const refreshAccountTimeline      = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }); | ||||
| export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); | ||||
| export const refreshHashtagTimeline      = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | ||||
| export const refreshListTimeline         = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); | ||||
| @ -163,7 +163,7 @@ export const expandHomeTimeline         = () => expandTimeline('home', '/api/v1/ | ||||
| export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public'); | ||||
| export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); | ||||
| export const expandDirectTimeline       = () => expandTimeline('direct', '/api/v1/timelines/direct'); | ||||
| export const expandAccountTimeline      = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); | ||||
| export const expandAccountTimeline      = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }) | ||||
| export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); | ||||
| export const expandHashtagTimeline      = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | ||||
| export const expandListTimeline         = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); | ||||
|  | ||||
| @ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent { | ||||
|     let extraInfo = ''; | ||||
| 
 | ||||
|     menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); | ||||
| 
 | ||||
|     if ('share' in navigator) { | ||||
|       menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); | ||||
|     } | ||||
|     menu.push(null); | ||||
|     menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` }); | ||||
| 
 | ||||
|     menu.push(null); | ||||
| 
 | ||||
|     if (account.get('id') === me) { | ||||
|  | ||||
| @ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent { | ||||
|   render () { | ||||
|     const { media } = this.props; | ||||
|     const status = media.get('status'); | ||||
|     const focusX = media.getIn(['meta', 'focus', 'x']); | ||||
|     const focusY = media.getIn(['meta', 'focus', 'y']); | ||||
|     const x = ((focusX /  2) + .5) * 100; | ||||
|     const y = ((focusY / -2) + .5) * 100; | ||||
|     const style = {}; | ||||
| 
 | ||||
|     let content, style; | ||||
|     let content; | ||||
| 
 | ||||
|     if (media.get('type') === 'gifv') { | ||||
|       content = <span className='media-gallery__gifv__label'>GIF</span>; | ||||
|     } | ||||
| 
 | ||||
|     if (!status.get('sensitive')) { | ||||
|       style = { backgroundImage: `url(${media.get('preview_url')})` }; | ||||
|       style.backgroundImage    = `url(${media.get('preview_url')})`; | ||||
|       style.backgroundPosition = `${x}% ${y}%`; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className='account-gallery__item'> | ||||
|         <Permalink | ||||
|           to={`/statuses/${status.get('id')}`} | ||||
|           href={status.get('url')} | ||||
|           style={style} | ||||
|         > | ||||
|         <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}> | ||||
|           {content} | ||||
|         </Permalink> | ||||
|       </div> | ||||
|  | ||||
| @ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { getAccountGallery } from 'flavours/glitch/selectors'; | ||||
| import MediaItem from './components/media_item'; | ||||
| import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import { ScrollContainer } from 'react-router-scroll-4'; | ||||
| import LoadMore from 'flavours/glitch/components/load_more'; | ||||
| 
 | ||||
| @ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent { | ||||
|           <div className='scrollable' onScroll={this.handleScroll}> | ||||
|             <HeaderContainer accountId={this.props.params.accountId} /> | ||||
| 
 | ||||
|             <div className='account-section-headline'> | ||||
|               <FormattedMessage id='account.media' defaultMessage='Media' /> | ||||
|             </div> | ||||
| 
 | ||||
|             <div className='account-gallery__container'> | ||||
|               {medias.map(media => | ||||
|                 (<MediaItem | ||||
|  | ||||
| @ -5,6 +5,8 @@ import InnerHeader from 'flavours/glitch/features/account/components/header'; | ||||
| import ActionBar from 'flavours/glitch/features/account/components/action_bar'; | ||||
| import MissingIndicator from 'flavours/glitch/components/missing_indicator'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import { NavLink } from 'react-router-dom'; | ||||
| 
 | ||||
| export default class Header extends ImmutablePureComponent { | ||||
| 
 | ||||
| @ -89,6 +91,12 @@ export default class Header extends ImmutablePureComponent { | ||||
|           onBlockDomain={this.handleBlockDomain} | ||||
|           onUnblockDomain={this.handleUnblockDomain} | ||||
|         /> | ||||
| 
 | ||||
|         <div className='account__section-headline'> | ||||
|           <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink> | ||||
|           <NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink> | ||||
|           <NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink> | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -12,11 +12,15 @@ import ColumnBackButton from '../../components/column_back_button'; | ||||
| import { List as ImmutableList } from 'immutable'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| const mapStateToProps = (state, props) => ({ | ||||
|   statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()), | ||||
|   isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']), | ||||
|   hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']), | ||||
| }); | ||||
| const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { | ||||
|   const path = withReplies ? `${accountId}:with_replies` : accountId; | ||||
| 
 | ||||
|   return { | ||||
|     statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), | ||||
|     isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), | ||||
|     hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']), | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| @connect(mapStateToProps) | ||||
| export default class AccountTimeline extends ImmutablePureComponent { | ||||
| @ -27,23 +31,24 @@ export default class AccountTimeline extends ImmutablePureComponent { | ||||
|     statusIds: ImmutablePropTypes.list, | ||||
|     isLoading: PropTypes.bool, | ||||
|     hasMore: PropTypes.bool, | ||||
|     withReplies: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.props.dispatch(fetchAccount(this.props.params.accountId)); | ||||
|     this.props.dispatch(refreshAccountTimeline(this.props.params.accountId)); | ||||
|     this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies)); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { | ||||
|     if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { | ||||
|       this.props.dispatch(fetchAccount(nextProps.params.accountId)); | ||||
|       this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId)); | ||||
|       this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   handleScrollToBottom = () => { | ||||
|     if (!this.props.isLoading && this.props.hasMore) { | ||||
|       this.props.dispatch(expandAccountTimeline(this.props.params.accountId)); | ||||
|       this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -431,6 +431,7 @@ export default class UI extends React.Component { | ||||
|               <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> | ||||
| 
 | ||||
|               <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} /> | ||||
|               <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} /> | ||||
|               <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} /> | ||||
|               <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} /> | ||||
|               <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} /> | ||||
|  | ||||
| @ -380,46 +380,73 @@ | ||||
| } | ||||
| 
 | ||||
| .account-gallery__container { | ||||
|   margin: -2px; | ||||
|   padding: 4px; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   flex-wrap: wrap; | ||||
|   padding: 2px; | ||||
| } | ||||
| 
 | ||||
| .account-gallery__item { | ||||
|   flex: 1 1 auto; | ||||
|   width: calc(100% / 3 - 4px); | ||||
|   height: 95px; | ||||
|   margin: 2px; | ||||
|   flex-grow: 1; | ||||
|   width: 50%; | ||||
|   overflow: hidden; | ||||
|   position: relative; | ||||
| 
 | ||||
|   &::before { | ||||
|     content: ""; | ||||
|     display: block; | ||||
|     padding-top: 100%; | ||||
|   } | ||||
| 
 | ||||
|   a { | ||||
|     display: block; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     width: calc(100% - 4px); | ||||
|     height: calc(100% - 4px); | ||||
|     margin: 2px; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     background-color: $base-overlay-background; | ||||
|     background-size: cover; | ||||
|     background-position: center; | ||||
|     position: relative; | ||||
|     position: absolute; | ||||
|     color: inherit; | ||||
|     text-decoration: none; | ||||
|     border-radius: 4px; | ||||
| 
 | ||||
|     &:hover, | ||||
|     &:active, | ||||
|     &:focus { | ||||
|       outline: 0; | ||||
| 
 | ||||
|       &::before { | ||||
|         content: ""; | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         background: rgba($base-overlay-background, 0.3); | ||||
|         border-radius: 4px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .account-section-headline { | ||||
|   color: $ui-base-lighter-color; | ||||
| .account__section-headline { | ||||
|   background: lighten($ui-base-color, 2%); | ||||
|   border-bottom: 1px solid lighten($ui-base-color, 4%); | ||||
|   cursor: default; | ||||
|   display: flex; | ||||
| 
 | ||||
|   a { | ||||
|     display: block; | ||||
|     color: $ui-base-lighter-color; | ||||
|     padding: 15px 10px; | ||||
|     font-size: 14px; | ||||
|     font-weight: 500; | ||||
|     text-decoration: none; | ||||
|     position: relative; | ||||
|   cursor: default; | ||||
| 
 | ||||
|     &.active { | ||||
|       color: $ui-highlight-color; | ||||
| 
 | ||||
|       &::before, | ||||
|       &::after { | ||||
| @ -427,9 +454,10 @@ | ||||
|         content: ""; | ||||
|         position: absolute; | ||||
|         bottom: 0; | ||||
|     left: 18px; | ||||
|         left: 50%; | ||||
|         width: 0; | ||||
|         height: 0; | ||||
|         transform: translateX(-50%); | ||||
|         border-style: solid; | ||||
|         border-width: 0 10px 10px; | ||||
|         border-color: transparent transparent lighten($ui-base-color, 4%); | ||||
| @ -440,6 +468,8 @@ | ||||
|         border-color: transparent transparent $ui-base-color; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .account__moved-note { | ||||
|   padding: 14px 10px; | ||||
|  | ||||
| @ -102,6 +102,7 @@ | ||||
|   &.standalone { | ||||
|     .media-gallery__item-gifv-thumbnail { | ||||
|       transform: none; | ||||
|       top: 0; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -35,14 +35,19 @@ export class WrappedRoute extends React.Component { | ||||
|     component: PropTypes.func.isRequired, | ||||
|     content: PropTypes.node, | ||||
|     multiColumn: PropTypes.bool, | ||||
|     componentParams: PropTypes.object, | ||||
|   } | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     componentParams: {}, | ||||
|   }; | ||||
| 
 | ||||
|   renderComponent = ({ match }) => { | ||||
|     const { component, content, multiColumn } = this.props; | ||||
|     const { component, content, multiColumn, componentParams } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}> | ||||
|         {Component => <Component params={match.params} multiColumn={multiColumn}>{content}</Component>} | ||||
|         {Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>} | ||||
|       </BundleContainer> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user