Merge pull request #388 from ThibG/glitch-soc/federated-pinned-toots
Port WebUI toots/with reply/media tabs and pinned toots to glitch
This commit is contained in:
		
						commit
						8d5abcb749
					
				
					 15 changed files with 159 additions and 79 deletions
				
			
		|  | @ -117,14 +117,15 @@ export function refreshTimeline(timelineId, path, params = {}) { | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const refreshHomeTimeline         = () => refreshTimeline('home', '/api/v1/timelines/home'); | export const refreshHomeTimeline            = () => refreshTimeline('home', '/api/v1/timelines/home'); | ||||||
| export const refreshPublicTimeline       = () => refreshTimeline('public', '/api/v1/timelines/public'); | export const refreshPublicTimeline          = () => refreshTimeline('public', '/api/v1/timelines/public'); | ||||||
| export const refreshCommunityTimeline    = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); | export const refreshCommunityTimeline       = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); | ||||||
| export const refreshDirectTimeline       = () => refreshTimeline('direct', '/api/v1/timelines/direct'); | 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 refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); | ||||||
| export const refreshHashtagTimeline      = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | export const refreshAccountMediaTimeline    = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); | ||||||
| export const refreshListTimeline         = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); | export const refreshHashtagTimeline         = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | ||||||
|  | export const refreshListTimeline            = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); | ||||||
| 
 | 
 | ||||||
| export function refreshTimelineFail(timeline, error, skipLoading) { | export function refreshTimelineFail(timeline, error, skipLoading) { | ||||||
|   return { |   return { | ||||||
|  | @ -163,7 +164,7 @@ export const expandHomeTimeline         = () => expandTimeline('home', '/api/v1/ | ||||||
| export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public'); | export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public'); | ||||||
| export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); | export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); | ||||||
| export const expandDirectTimeline       = () => expandTimeline('direct', '/api/v1/timelines/direct'); | 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 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 expandHashtagTimeline      = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | ||||||
| export const expandListTimeline         = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); | export const expandListTimeline         = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ export default class StatusList extends ImmutablePureComponent { | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     scrollKey: PropTypes.string.isRequired, |     scrollKey: PropTypes.string.isRequired, | ||||||
|     statusIds: ImmutablePropTypes.list.isRequired, |     statusIds: ImmutablePropTypes.list.isRequired, | ||||||
|  |     featuredStatusIds: ImmutablePropTypes.list, | ||||||
|     onScrollToBottom: PropTypes.func, |     onScrollToBottom: PropTypes.func, | ||||||
|     onScrollToTop: PropTypes.func, |     onScrollToTop: PropTypes.func, | ||||||
|     onScroll: PropTypes.func, |     onScroll: PropTypes.func, | ||||||
|  | @ -50,7 +51,7 @@ export default class StatusList extends ImmutablePureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { statusIds, ...other }  = this.props; |     const { statusIds, featuredStatusIds, ...other }  = this.props; | ||||||
|     const { isLoading, isPartial } = other; |     const { isLoading, isPartial } = other; | ||||||
| 
 | 
 | ||||||
|     if (isPartial) { |     if (isPartial) { | ||||||
|  | @ -68,8 +69,8 @@ export default class StatusList extends ImmutablePureComponent { | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const scrollableContent = (isLoading || statusIds.size > 0) ? ( |     let scrollableContent = (isLoading || statusIds.size > 0) ? ( | ||||||
|       statusIds.map((statusId) => ( |       statusIds.map(statusId => ( | ||||||
|         <StatusContainer |         <StatusContainer | ||||||
|           key={statusId} |           key={statusId} | ||||||
|           id={statusId} |           id={statusId} | ||||||
|  | @ -79,6 +80,18 @@ export default class StatusList extends ImmutablePureComponent { | ||||||
|       )) |       )) | ||||||
|     ) : null; |     ) : null; | ||||||
| 
 | 
 | ||||||
|  |     if (scrollableContent && featuredStatusIds) { | ||||||
|  |       scrollableContent = featuredStatusIds.map(statusId => ( | ||||||
|  |         <StatusContainer | ||||||
|  |           key={`f-${statusId}`} | ||||||
|  |           id={statusId} | ||||||
|  |           featured | ||||||
|  |           onMoveUp={this.handleMoveUp} | ||||||
|  |           onMoveDown={this.handleMoveDown} | ||||||
|  |         /> | ||||||
|  |       )).concat(scrollableContent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|       <ScrollableList {...other} ref={this.setRef}> |       <ScrollableList {...other} ref={this.setRef}> | ||||||
|         {scrollableContent} |         {scrollableContent} | ||||||
|  |  | ||||||
|  | @ -34,6 +34,10 @@ export default class StatusPrepend extends React.PureComponent { | ||||||
|       </a> |       </a> | ||||||
|     ); |     ); | ||||||
|     switch (type) { |     switch (type) { | ||||||
|  |     case 'featured': | ||||||
|  |       return ( | ||||||
|  |         <FormattedMessage id='status.pinned' defaultMessage='Pinned toot' /> | ||||||
|  |       ); | ||||||
|     case 'reblogged_by': |     case 'reblogged_by': | ||||||
|       return ( |       return ( | ||||||
|         <FormattedMessage |         <FormattedMessage | ||||||
|  | @ -67,11 +71,11 @@ export default class StatusPrepend extends React.PureComponent { | ||||||
|     const { type } = this.props; |     const { type } = this.props; | ||||||
| 
 | 
 | ||||||
|     return !type ? null : ( |     return !type ? null : ( | ||||||
|       <aside className={type === 'reblogged_by' ? 'status__prepend' : 'notification__message'}> |       <aside className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend' : 'notification__message'}> | ||||||
|         <div className={type === 'reblogged_by' ? 'status__prepend-icon-wrapper' : 'notification__favourite-icon-wrapper'}> |         <div className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend-icon-wrapper' : 'notification__favourite-icon-wrapper'}> | ||||||
|           <i |           <i | ||||||
|             className={`fa fa-fw fa-${ |             className={`fa fa-fw fa-${ | ||||||
|               type === 'favourite' ? 'star star-icon' : 'retweet' |               type === 'favourite' ? 'star star-icon' : (type === 'featured' ? 'thumb-tack' : 'retweet') | ||||||
|             } status__prepend-icon`}
 |             } status__prepend-icon`}
 | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  | @ -38,7 +38,10 @@ const makeMapStateToProps = () => { | ||||||
|     let account = undefined; |     let account = undefined; | ||||||
|     let prepend = undefined; |     let prepend = undefined; | ||||||
| 
 | 
 | ||||||
|     if (reblogStatus !== null && typeof reblogStatus === 'object') { |     if (props.featured) { | ||||||
|  |       account = status.get('account'); | ||||||
|  |       prepend = 'featured'; | ||||||
|  |     } else if (reblogStatus !== null && typeof reblogStatus === 'object') { | ||||||
|       account = status.get('account'); |       account = status.get('account'); | ||||||
|       status = reblogStatus; |       status = reblogStatus; | ||||||
|       prepend = 'reblogged_by'; |       prepend = 'reblogged_by'; | ||||||
|  |  | ||||||
|  | @ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent { | ||||||
|     let extraInfo = ''; |     let extraInfo = ''; | ||||||
| 
 | 
 | ||||||
|     menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); |     menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); | ||||||
|  | 
 | ||||||
|     if ('share' in navigator) { |     if ('share' in navigator) { | ||||||
|       menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); |       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); |     menu.push(null); | ||||||
| 
 | 
 | ||||||
|     if (account.get('id') === me) { |     if (account.get('id') === me) { | ||||||
|  |  | ||||||
|  | @ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent { | ||||||
|   render () { |   render () { | ||||||
|     const { media } = this.props; |     const { media } = this.props; | ||||||
|     const status = media.get('status'); |     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') { |     if (media.get('type') === 'gifv') { | ||||||
|       content = <span className='media-gallery__gifv__label'>GIF</span>; |       content = <span className='media-gallery__gifv__label'>GIF</span>; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!status.get('sensitive')) { |     if (!status.get('sensitive')) { | ||||||
|       style = { backgroundImage: `url(${media.get('preview_url')})` }; |       style.backgroundImage    = `url(${media.get('preview_url')})`; | ||||||
|  |       style.backgroundPosition = `${x}% ${y}%`; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div className='account-gallery__item'> |       <div className='account-gallery__item'> | ||||||
|         <Permalink |         <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}> | ||||||
|           to={`/statuses/${status.get('id')}`} |  | ||||||
|           href={status.get('url')} |  | ||||||
|           style={style} |  | ||||||
|         > |  | ||||||
|           {content} |           {content} | ||||||
|         </Permalink> |         </Permalink> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import { getAccountGallery } from 'flavours/glitch/selectors'; | import { getAccountGallery } from 'flavours/glitch/selectors'; | ||||||
| import MediaItem from './components/media_item'; | import MediaItem from './components/media_item'; | ||||||
| import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; | import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; | ||||||
| import { FormattedMessage } from 'react-intl'; |  | ||||||
| import { ScrollContainer } from 'react-router-scroll-4'; | import { ScrollContainer } from 'react-router-scroll-4'; | ||||||
| import LoadMore from 'flavours/glitch/components/load_more'; | import LoadMore from 'flavours/glitch/components/load_more'; | ||||||
| 
 | 
 | ||||||
|  | @ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent { | ||||||
|           <div className='scrollable' onScroll={this.handleScroll}> |           <div className='scrollable' onScroll={this.handleScroll}> | ||||||
|             <HeaderContainer accountId={this.props.params.accountId} /> |             <HeaderContainer accountId={this.props.params.accountId} /> | ||||||
| 
 | 
 | ||||||
|             <div className='account-section-headline'> |  | ||||||
|               <FormattedMessage id='account.media' defaultMessage='Media' /> |  | ||||||
|             </div> |  | ||||||
| 
 |  | ||||||
|             <div className='account-gallery__container'> |             <div className='account-gallery__container'> | ||||||
|               {medias.map(media => |               {medias.map(media => | ||||||
|                 (<MediaItem |                 (<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 ActionBar from 'flavours/glitch/features/account/components/action_bar'; | ||||||
| import MissingIndicator from 'flavours/glitch/components/missing_indicator'; | import MissingIndicator from 'flavours/glitch/components/missing_indicator'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  | import { FormattedMessage } from 'react-intl'; | ||||||
|  | import { NavLink } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| export default class Header extends ImmutablePureComponent { | export default class Header extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|  | @ -18,6 +20,7 @@ export default class Header extends ImmutablePureComponent { | ||||||
|     onMute: PropTypes.func.isRequired, |     onMute: PropTypes.func.isRequired, | ||||||
|     onBlockDomain: PropTypes.func.isRequired, |     onBlockDomain: PropTypes.func.isRequired, | ||||||
|     onUnblockDomain: PropTypes.func.isRequired, |     onUnblockDomain: PropTypes.func.isRequired, | ||||||
|  |     hideTabs: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static contextTypes = { |   static contextTypes = { | ||||||
|  | @ -65,7 +68,7 @@ export default class Header extends ImmutablePureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { account } = this.props; |     const { account, hideTabs } = this.props; | ||||||
| 
 | 
 | ||||||
|     if (account === null) { |     if (account === null) { | ||||||
|       return <MissingIndicator />; |       return <MissingIndicator />; | ||||||
|  | @ -89,6 +92,14 @@ export default class Header extends ImmutablePureComponent { | ||||||
|           onBlockDomain={this.handleBlockDomain} |           onBlockDomain={this.handleBlockDomain} | ||||||
|           onUnblockDomain={this.handleUnblockDomain} |           onUnblockDomain={this.handleUnblockDomain} | ||||||
|         /> |         /> | ||||||
|  | 
 | ||||||
|  |         {!hideTabs && ( | ||||||
|  |           <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> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { connect } from 'react-redux'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { fetchAccount } from 'flavours/glitch/actions/accounts'; | import { fetchAccount } from 'flavours/glitch/actions/accounts'; | ||||||
| import { refreshAccountTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines'; | import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines'; | ||||||
| import StatusList from '../../components/status_list'; | import StatusList from '../../components/status_list'; | ||||||
| import LoadingIndicator from '../../components/loading_indicator'; | import LoadingIndicator from '../../components/loading_indicator'; | ||||||
| import Column from '../ui/components/column'; | import Column from '../ui/components/column'; | ||||||
|  | @ -12,11 +12,16 @@ import ColumnBackButton from '../../components/column_back_button'; | ||||||
| import { List as ImmutableList } from 'immutable'; | import { List as ImmutableList } from 'immutable'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, props) => ({ | const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { | ||||||
|   statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()), |   const path = withReplies ? `${accountId}:with_replies` : accountId; | ||||||
|   isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']), | 
 | ||||||
|   hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']), |   return { | ||||||
| }); |     statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), | ||||||
|  |     featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), | ||||||
|  |     isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), | ||||||
|  |     hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']), | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| @connect(mapStateToProps) | @connect(mapStateToProps) | ||||||
| export default class AccountTimeline extends ImmutablePureComponent { | export default class AccountTimeline extends ImmutablePureComponent { | ||||||
|  | @ -25,30 +30,36 @@ export default class AccountTimeline extends ImmutablePureComponent { | ||||||
|     params: PropTypes.object.isRequired, |     params: PropTypes.object.isRequired, | ||||||
|     dispatch: PropTypes.func.isRequired, |     dispatch: PropTypes.func.isRequired, | ||||||
|     statusIds: ImmutablePropTypes.list, |     statusIds: ImmutablePropTypes.list, | ||||||
|  |     featuredStatusIds: ImmutablePropTypes.list, | ||||||
|     isLoading: PropTypes.bool, |     isLoading: PropTypes.bool, | ||||||
|     hasMore: PropTypes.bool, |     hasMore: PropTypes.bool, | ||||||
|  |     withReplies: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   componentWillMount () { | ||||||
|     this.props.dispatch(fetchAccount(this.props.params.accountId)); |     const { params: { accountId }, withReplies } = this.props; | ||||||
|     this.props.dispatch(refreshAccountTimeline(this.props.params.accountId)); | 
 | ||||||
|  |     this.props.dispatch(fetchAccount(accountId)); | ||||||
|  |     this.props.dispatch(refreshAccountFeaturedTimeline(accountId)); | ||||||
|  |     this.props.dispatch(refreshAccountTimeline(accountId, withReplies)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   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(fetchAccount(nextProps.params.accountId)); | ||||||
|       this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId)); |       this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId)); | ||||||
|  |       this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleScrollToBottom = () => { |   handleScrollToBottom = () => { | ||||||
|     if (!this.props.isLoading && this.props.hasMore) { |     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)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { statusIds, isLoading, hasMore } = this.props; |     const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props; | ||||||
| 
 | 
 | ||||||
|     if (!statusIds && isLoading) { |     if (!statusIds && isLoading) { | ||||||
|       return ( |       return ( | ||||||
|  | @ -66,6 +77,7 @@ export default class AccountTimeline extends ImmutablePureComponent { | ||||||
|           prepend={<HeaderContainer accountId={this.props.params.accountId} />} |           prepend={<HeaderContainer accountId={this.props.params.accountId} />} | ||||||
|           scrollKey='account_timeline' |           scrollKey='account_timeline' | ||||||
|           statusIds={statusIds} |           statusIds={statusIds} | ||||||
|  |           featuredStatusIds={featuredStatusIds} | ||||||
|           isLoading={isLoading} |           isLoading={isLoading} | ||||||
|           hasMore={hasMore} |           hasMore={hasMore} | ||||||
|           onScrollToBottom={this.handleScrollToBottom} |           onScrollToBottom={this.handleScrollToBottom} | ||||||
|  |  | ||||||
|  | @ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent { | ||||||
|         <ScrollContainer scrollKey='followers'> |         <ScrollContainer scrollKey='followers'> | ||||||
|           <div className='scrollable' onScroll={this.handleScroll}> |           <div className='scrollable' onScroll={this.handleScroll}> | ||||||
|             <div className='followers'> |             <div className='followers'> | ||||||
|               <HeaderContainer accountId={this.props.params.accountId} /> |               <HeaderContainer accountId={this.props.params.accountId} hideTabs /> | ||||||
|               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} |               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} | ||||||
|               {loadMore} |               {loadMore} | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|  | @ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent { | ||||||
|         <ScrollContainer scrollKey='following'> |         <ScrollContainer scrollKey='following'> | ||||||
|           <div className='scrollable' onScroll={this.handleScroll}> |           <div className='scrollable' onScroll={this.handleScroll}> | ||||||
|             <div className='following'> |             <div className='following'> | ||||||
|               <HeaderContainer accountId={this.props.params.accountId} /> |               <HeaderContainer accountId={this.props.params.accountId} hideTabs /> | ||||||
|               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} |               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} | ||||||
|               {loadMore} |               {loadMore} | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|  | @ -431,6 +431,7 @@ export default class UI extends React.Component { | ||||||
|               <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> |               <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} /> | ||||||
| 
 | 
 | ||||||
|               <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} 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/followers' component={Followers} content={children} /> | ||||||
|               <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} /> |               <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} /> | ||||||
|               <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} /> |               <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} /> | ||||||
|  |  | ||||||
|  | @ -380,64 +380,96 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .account-gallery__container { | .account-gallery__container { | ||||||
|   margin: -2px; |  | ||||||
|   padding: 4px; |  | ||||||
|   display: flex; |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|   flex-wrap: wrap; |   flex-wrap: wrap; | ||||||
|  |   padding: 2px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .account-gallery__item { | .account-gallery__item { | ||||||
|   flex: 1 1 auto; |   flex-grow: 1; | ||||||
|   width: calc(100% / 3 - 4px); |   width: 50%; | ||||||
|   height: 95px; |   overflow: hidden; | ||||||
|   margin: 2px; |   position: relative; | ||||||
|  | 
 | ||||||
|  |   &::before { | ||||||
|  |     content: ""; | ||||||
|  |     display: block; | ||||||
|  |     padding-top: 100%; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   a { |   a { | ||||||
|     display: block; |     display: block; | ||||||
|     width: 100%; |     width: calc(100% - 4px); | ||||||
|     height: 100%; |     height: calc(100% - 4px); | ||||||
|  |     margin: 2px; | ||||||
|  |     top: 0; | ||||||
|  |     left: 0; | ||||||
|     background-color: $base-overlay-background; |     background-color: $base-overlay-background; | ||||||
|     background-size: cover; |     background-size: cover; | ||||||
|     background-position: center; |     background-position: center; | ||||||
|     position: relative; |     position: absolute; | ||||||
|     color: inherit; |     color: inherit; | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|  |     border-radius: 4px; | ||||||
| 
 | 
 | ||||||
|     &:hover, |     &:hover, | ||||||
|     &:active, |     &:active, | ||||||
|     &:focus { |     &:focus { | ||||||
|       outline: 0; |       outline: 0; | ||||||
|  | 
 | ||||||
|  |       &::before { | ||||||
|  |         content: ""; | ||||||
|  |         display: block; | ||||||
|  |         width: 100%; | ||||||
|  |         height: 100%; | ||||||
|  |         background: rgba($base-overlay-background, 0.3); | ||||||
|  |         border-radius: 4px; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .account-section-headline { | .account__section-headline { | ||||||
|   color: $ui-base-lighter-color; |   background: darken($ui-base-color, 4%); | ||||||
|   background: lighten($ui-base-color, 2%); |   border-bottom: 1px solid lighten($ui-base-color, 8%); | ||||||
|   border-bottom: 1px solid lighten($ui-base-color, 4%); |  | ||||||
|   padding: 15px 10px; |  | ||||||
|   font-size: 14px; |  | ||||||
|   font-weight: 500; |  | ||||||
|   position: relative; |  | ||||||
|   cursor: default; |   cursor: default; | ||||||
|  |   display: flex; | ||||||
| 
 | 
 | ||||||
|   &::before, |   a { | ||||||
|   &::after { |  | ||||||
|     display: block; |     display: block; | ||||||
|     content: ""; |     flex: 1 1 auto; | ||||||
|     position: absolute; |     color: $ui-primary-color; | ||||||
|     bottom: 0; |     padding: 15px 0; | ||||||
|     left: 18px; |     font-size: 14px; | ||||||
|     width: 0; |     font-weight: 500; | ||||||
|     height: 0; |     text-align: center; | ||||||
|     border-style: solid; |     text-decoration: none; | ||||||
|     border-width: 0 10px 10px; |     position: relative; | ||||||
|     border-color: transparent transparent lighten($ui-base-color, 4%); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   &::after { |     &.active { | ||||||
|     bottom: -1px; |       color: $ui-secondary-color; | ||||||
|     border-color: transparent transparent $ui-base-color; | 
 | ||||||
|  |       &::before, | ||||||
|  |       &::after { | ||||||
|  |         display: block; | ||||||
|  |         content: ""; | ||||||
|  |         position: absolute; | ||||||
|  |         bottom: 0; | ||||||
|  |         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, 8%); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       &::after { | ||||||
|  |         bottom: -1px; | ||||||
|  |         border-color: transparent transparent $ui-base-color; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -102,6 +102,7 @@ | ||||||
|   &.standalone { |   &.standalone { | ||||||
|     .media-gallery__item-gifv-thumbnail { |     .media-gallery__item-gifv-thumbnail { | ||||||
|       transform: none; |       transform: none; | ||||||
|  |       top: 0; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -35,14 +35,19 @@ export class WrappedRoute extends React.Component { | ||||||
|     component: PropTypes.func.isRequired, |     component: PropTypes.func.isRequired, | ||||||
|     content: PropTypes.node, |     content: PropTypes.node, | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|  |     componentParams: PropTypes.object, | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   static defaultProps = { | ||||||
|  |     componentParams: {}, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   renderComponent = ({ match }) => { |   renderComponent = ({ match }) => { | ||||||
|     const { component, content, multiColumn } = this.props; |     const { component, content, multiColumn, componentParams } = this.props; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}> |       <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> |       </BundleContainer> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue