Adding following/followers lists to the UI
This commit is contained in:
		
							parent
							
								
									909d0d5e88
								
							
						
					
					
						commit
						1c84d505c8
					
				| @ -32,6 +32,14 @@ export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST' | |||||||
| export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS'; | export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS'; | ||||||
| export const ACCOUNT_TIMELINE_EXPAND_FAIL    = 'ACCOUNT_TIMELINE_EXPAND_FAIL'; | export const ACCOUNT_TIMELINE_EXPAND_FAIL    = 'ACCOUNT_TIMELINE_EXPAND_FAIL'; | ||||||
| 
 | 
 | ||||||
|  | export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; | ||||||
|  | export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; | ||||||
|  | export const FOLLOWERS_FETCH_FAIL    = 'FOLLOWERS_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST'; | ||||||
|  | export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS'; | ||||||
|  | export const FOLLOWING_FETCH_FAIL    = 'FOLLOWING_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
| export function setAccountSelf(account) { | export function setAccountSelf(account) { | ||||||
|   return { |   return { | ||||||
|     type: ACCOUNT_SET_SELF, |     type: ACCOUNT_SET_SELF, | ||||||
| @ -289,3 +297,73 @@ export function unblockAccountFail(error) { | |||||||
|     error: error |     error: error | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowers(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(fetchFollowersRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { | ||||||
|  |       dispatch(fetchFollowersSuccess(id, response.data)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(fetchFollowersFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowersRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_FETCH_REQUEST, | ||||||
|  |     id: id | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowersSuccess(id, accounts) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_FETCH_SUCCESS, | ||||||
|  |     id: id, | ||||||
|  |     accounts: accounts | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowersFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_FETCH_FAIL, | ||||||
|  |     id: id, | ||||||
|  |     error: error | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowing(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(fetchFollowingRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { | ||||||
|  |       dispatch(fetchFollowingSuccess(id, response.data)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(fetchFollowingFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowingRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_FETCH_REQUEST, | ||||||
|  |     id: id | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowingSuccess(id, accounts) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_FETCH_SUCCESS, | ||||||
|  |     id: id, | ||||||
|  |     accounts: accounts | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowingFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_FETCH_FAIL, | ||||||
|  |     id: id, | ||||||
|  |     error: error | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -22,10 +22,10 @@ export function fetchSuggestionsRequest() { | |||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function fetchSuggestionsSuccess(suggestions) { | export function fetchSuggestionsSuccess(accounts) { | ||||||
|   return { |   return { | ||||||
|     type: SUGGESTIONS_FETCH_SUCCESS, |     type: SUGGESTIONS_FETCH_SUCCESS, | ||||||
|     suggestions: suggestions |     accounts: accounts | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ import AccountTimeline    from '../features/account_timeline'; | |||||||
| import HomeTimeline       from '../features/home_timeline'; | import HomeTimeline       from '../features/home_timeline'; | ||||||
| import MentionsTimeline   from '../features/mentions_timeline'; | import MentionsTimeline   from '../features/mentions_timeline'; | ||||||
| import Compose            from '../features/compose'; | import Compose            from '../features/compose'; | ||||||
|  | import Followers          from '../features/followers'; | ||||||
|  | import Following          from '../features/following'; | ||||||
| 
 | 
 | ||||||
| const store = configureStore(); | const store = configureStore(); | ||||||
| 
 | 
 | ||||||
| @ -83,6 +85,8 @@ const Mastodon = React.createClass({ | |||||||
|             <Route path='/statuses/:statusId' component={Status} /> |             <Route path='/statuses/:statusId' component={Status} /> | ||||||
|             <Route path='/accounts/:accountId' component={Account}> |             <Route path='/accounts/:accountId' component={Account}> | ||||||
|               <IndexRoute component={AccountTimeline} /> |               <IndexRoute component={AccountTimeline} /> | ||||||
|  |               <Route path='/accounts/:accountId/followers' component={Followers} /> | ||||||
|  |               <Route path='/accounts/:accountId/following' component={Following} /> | ||||||
|             </Route> |             </Route> | ||||||
|           </Route> |           </Route> | ||||||
|         </Router> |         </Router> | ||||||
|  | |||||||
| @ -1,6 +1,27 @@ | |||||||
| import PureRenderMixin    from 'react-addons-pure-render-mixin'; | import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import DropdownMenu       from '../../../components/dropdown_menu'; | import DropdownMenu       from '../../../components/dropdown_menu'; | ||||||
|  | import { Link }           from 'react-router'; | ||||||
|  | 
 | ||||||
|  | const outerStyle = { | ||||||
|  |   borderTop: '1px solid #363c4b', | ||||||
|  |   borderBottom: '1px solid #363c4b', | ||||||
|  |   lineHeight: '36px', | ||||||
|  |   overflow: 'hidden', | ||||||
|  |   flex: '0 0 auto', | ||||||
|  |   display: 'flex' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const outerDropdownStyle = { | ||||||
|  |   padding: '10px', | ||||||
|  |   flex: '1 1 auto' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const outerLinksStyle = { | ||||||
|  |   flex: '1 1 auto', | ||||||
|  |   display: 'flex', | ||||||
|  |   lineHeight: '18px' | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| const ActionBar = React.createClass({ | const ActionBar = React.createClass({ | ||||||
| 
 | 
 | ||||||
| @ -34,26 +55,26 @@ const ActionBar = React.createClass({ | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', lineHeight: '36px', overflow: 'hidden', flex: '0 0 auto', display: 'flex' }}> |       <div style={outerStyle}> | ||||||
|         <div style={{ padding: '10px', flex: '1 1 auto' }}> |         <div style={outerDropdownStyle}> | ||||||
|           <DropdownMenu items={menu} icon='bars' size={24} /> |           <DropdownMenu items={menu} icon='bars' size={24} /> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <div style={{ flex: '1 1 auto', display: 'flex', lineHeight: '18px' }}> |         <div style={outerLinksStyle}> | ||||||
|           <div style={{ overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px', paddingRight: '5px' }}> |           <Link to={`/accounts/${account.get('id')}`} style={{ textDecoration: 'none', overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px', paddingRight: '5px' }}> | ||||||
|             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Posts</span> |             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Posts</span> | ||||||
|             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('statuses_count')}</span> |             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('statuses_count')}</span> | ||||||
|           </div> |           </Link> | ||||||
| 
 | 
 | ||||||
|           <div style={{ overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px 5px' }}> |           <Link to={`/accounts/${account.get('id')}/following`} style={{ textDecoration: 'none', overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px 5px' }}> | ||||||
|             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Follows</span> |             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Follows</span> | ||||||
|             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('following_count')}</span> |             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('following_count')}</span> | ||||||
|           </div> |           </Link> | ||||||
| 
 | 
 | ||||||
|           <div style={{ overflow: 'hidden', width: '80px', padding: '10px 5px', borderLeft: '1px solid #363c4b' }}> |           <Link to={`/accounts/${account.get('id')}/followers`} style={{ textDecoration: 'none', overflow: 'hidden', width: '80px', padding: '10px 5px', borderLeft: '1px solid #363c4b' }}> | ||||||
|             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Followers</span> |             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Followers</span> | ||||||
|             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('followers_count')}</span> |             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('followers_count')}</span> | ||||||
|           </div> |           </Link> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -14,17 +14,23 @@ import { mentionCompose }    from '../../actions/compose'; | |||||||
| import Header                from './components/header'; | import Header                from './components/header'; | ||||||
| import { | import { | ||||||
|   getAccountTimeline, |   getAccountTimeline, | ||||||
|   getAccount |   makeGetAccount | ||||||
| }                            from '../../selectors'; | }                            from '../../selectors'; | ||||||
| import LoadingIndicator      from '../../components/loading_indicator'; | import LoadingIndicator      from '../../components/loading_indicator'; | ||||||
| import ActionBar             from './components/action_bar'; | import ActionBar             from './components/action_bar'; | ||||||
| import Column                from '../ui/components/column'; | import Column                from '../ui/components/column'; | ||||||
| import ColumnBackButton      from '../../components/column_back_button'; | import ColumnBackButton      from '../../components/column_back_button'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, props) => ({ | const makeMapStateToProps = () => { | ||||||
|   account: getAccount(state, Number(props.params.accountId)), |   const getAccount = makeGetAccount(); | ||||||
|   me: state.getIn(['timelines', 'me']) | 
 | ||||||
| }); |   const mapStateToProps = (state, props) => ({ | ||||||
|  |     account: getAccount(state, Number(props.params.accountId)), | ||||||
|  |     me: state.getIn(['timelines', 'me']) | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return mapStateToProps; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| const Account = React.createClass({ | const Account = React.createClass({ | ||||||
| 
 | 
 | ||||||
| @ -92,4 +98,4 @@ const Account = React.createClass({ | |||||||
| 
 | 
 | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps)(Account); | export default connect(makeMapStateToProps)(Account); | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import PureRenderMixin    from 'react-addons-pure-render-mixin'; | import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import Avatar             from '../../../components/avatar'; | import Avatar             from '../../../components/avatar'; | ||||||
| import DisplayName        from '../../../components/display_name'; |  | ||||||
| import { Link }           from 'react-router'; | import { Link }           from 'react-router'; | ||||||
| 
 | 
 | ||||||
| const outerStyle = { | const outerStyle = { | ||||||
|  | |||||||
| @ -0,0 +1,66 @@ | |||||||
|  | import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||||
|  | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
|  | import Avatar             from '../../../components/avatar'; | ||||||
|  | import { Link }           from 'react-router'; | ||||||
|  | 
 | ||||||
|  | const outerStyle = { | ||||||
|  |   padding: '10px' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const displayNameStyle = { | ||||||
|  |   display: 'block', | ||||||
|  |   fontWeight: '500', | ||||||
|  |   overflow: 'hidden', | ||||||
|  |   textOverflow: 'ellipsis', | ||||||
|  |   color: '#fff' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const acctStyle = { | ||||||
|  |   display: 'block', | ||||||
|  |   overflow: 'hidden', | ||||||
|  |   textOverflow: 'ellipsis' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const itemStyle = { | ||||||
|  |   display: 'block', | ||||||
|  |   color: '#9baec8', | ||||||
|  |   overflow: 'hidden', | ||||||
|  |   textDecoration: 'none' | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const Account = React.createClass({ | ||||||
|  | 
 | ||||||
|  |   propTypes: { | ||||||
|  |     account: ImmutablePropTypes.map.isRequired, | ||||||
|  |     me: React.PropTypes.number.isRequired | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   mixins: [PureRenderMixin], | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { account } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!account) { | ||||||
|  |       return <div />; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let displayName = account.get('display_name'); | ||||||
|  | 
 | ||||||
|  |     if (displayName.length === 0) { | ||||||
|  |       displayName = account.get('username'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div style={outerStyle}> | ||||||
|  |         <Link key={account.get('id')} style={itemStyle} to={`/accounts/${account.get('id')}`}> | ||||||
|  |           <div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={36} /></div> | ||||||
|  |           <strong style={displayNameStyle}>{displayName}</strong> | ||||||
|  |           <span style={acctStyle}>{account.get('acct')}</span> | ||||||
|  |         </Link> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default Account; | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | import { connect }            from 'react-redux'; | ||||||
|  | import { makeGetAccount }     from '../../../selectors'; | ||||||
|  | import Account                from '../components/account'; | ||||||
|  | 
 | ||||||
|  | const makeMapStateToProps = () => { | ||||||
|  |   const getAccount = makeGetAccount(); | ||||||
|  | 
 | ||||||
|  |   const mapStateToProps = (state, props) => ({ | ||||||
|  |     account: getAccount(state, props.id), | ||||||
|  |     me: state.getIn(['timelines', 'me']) | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return mapStateToProps; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapDispatchToProps = (dispatch) => ({ | ||||||
|  |   // | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(makeMapStateToProps, mapDispatchToProps)(Account); | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | import { connect }            from 'react-redux'; | ||||||
|  | import PureRenderMixin        from 'react-addons-pure-render-mixin'; | ||||||
|  | import ImmutablePropTypes     from 'react-immutable-proptypes'; | ||||||
|  | import LoadingIndicator       from '../../components/loading_indicator'; | ||||||
|  | import { fetchFollowers }     from '../../actions/accounts'; | ||||||
|  | import { ScrollContainer }    from 'react-router-scroll'; | ||||||
|  | import AccountContainer       from './containers/account_container'; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = (state, props) => ({ | ||||||
|  |   accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId)]) | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const Followers = React.createClass({ | ||||||
|  | 
 | ||||||
|  |   propTypes: { | ||||||
|  |     params: React.PropTypes.object.isRequired, | ||||||
|  |     dispatch: React.PropTypes.func.isRequired, | ||||||
|  |     accountIds: ImmutablePropTypes.list | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   mixins: [PureRenderMixin], | ||||||
|  | 
 | ||||||
|  |   componentWillMount () { | ||||||
|  |     this.props.dispatch(fetchFollowers(Number(this.props.params.accountId))); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   componentWillReceiveProps(nextProps) { | ||||||
|  |     if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { | ||||||
|  |       this.props.dispatch(fetchFollowers(Number(nextProps.params.accountId))); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { accountIds } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!accountIds) { | ||||||
|  |       return <LoadingIndicator />; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <ScrollContainer scrollKey='followers'> | ||||||
|  |         <div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable'> | ||||||
|  |           {accountIds.map(id => <AccountContainer key={id} id={id} />)} | ||||||
|  |         </div> | ||||||
|  |       </ScrollContainer> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps)(Followers); | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | import { connect }            from 'react-redux'; | ||||||
|  | import PureRenderMixin        from 'react-addons-pure-render-mixin'; | ||||||
|  | import ImmutablePropTypes     from 'react-immutable-proptypes'; | ||||||
|  | import LoadingIndicator       from '../../components/loading_indicator'; | ||||||
|  | import { fetchFollowing }     from '../../actions/accounts'; | ||||||
|  | import { ScrollContainer }    from 'react-router-scroll'; | ||||||
|  | import AccountContainer       from '../followers/containers/account_container'; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = (state, props) => ({ | ||||||
|  |   accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId)]) | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const Following = React.createClass({ | ||||||
|  | 
 | ||||||
|  |   propTypes: { | ||||||
|  |     params: React.PropTypes.object.isRequired, | ||||||
|  |     dispatch: React.PropTypes.func.isRequired, | ||||||
|  |     accountIds: ImmutablePropTypes.list | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   mixins: [PureRenderMixin], | ||||||
|  | 
 | ||||||
|  |   componentWillMount () { | ||||||
|  |     this.props.dispatch(fetchFollowing(Number(this.props.params.accountId))); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   componentWillReceiveProps(nextProps) { | ||||||
|  |     if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { | ||||||
|  |       this.props.dispatch(fetchFollowing(Number(nextProps.params.accountId))); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { accountIds } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!accountIds) { | ||||||
|  |       return <LoadingIndicator />; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <ScrollContainer scrollKey='following'> | ||||||
|  |         <div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable'> | ||||||
|  |           {accountIds.map(id => <AccountContainer key={id} id={id} />)} | ||||||
|  |         </div> | ||||||
|  |       </ScrollContainer> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps)(Following); | ||||||
| @ -6,7 +6,6 @@ const GettingStarted = () => { | |||||||
|     <Column> |     <Column> | ||||||
|       <div className='static-content'> |       <div className='static-content'> | ||||||
|         <h1>Getting started</h1> |         <h1>Getting started</h1> | ||||||
|         <p>Mastodon is still in development and one of the lacking areas at the moment is user discovery.</p> |  | ||||||
|         <p>You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the form in the bottom of the sidebar.</p> |         <p>You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the form in the bottom of the sidebar.</p> | ||||||
|         <p>If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.</p> |         <p>If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.</p> | ||||||
|         <p>The developer of this project can be followed as Gargron@mastodon.social</p> |         <p>The developer of this project can be followed as Gargron@mastodon.social</p> | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ import follow                from './follow'; | |||||||
| import notifications         from './notifications'; | import notifications         from './notifications'; | ||||||
| import { loadingBarReducer } from 'react-redux-loading-bar'; | import { loadingBarReducer } from 'react-redux-loading-bar'; | ||||||
| import modal                 from './modal'; | import modal                 from './modal'; | ||||||
|  | import user_lists            from './user_lists'; | ||||||
|  | import suggestions           from './suggestions'; | ||||||
| 
 | 
 | ||||||
| export default combineReducers({ | export default combineReducers({ | ||||||
|   timelines, |   timelines, | ||||||
| @ -15,4 +17,6 @@ export default combineReducers({ | |||||||
|   notifications, |   notifications, | ||||||
|   loadingBar: loadingBarReducer, |   loadingBar: loadingBarReducer, | ||||||
|   modal, |   modal, | ||||||
|  |   user_lists, | ||||||
|  |   suggestions | ||||||
| }); | }); | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								app/assets/javascripts/components/reducers/suggestions.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/assets/javascripts/components/reducers/suggestions.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions'; | ||||||
|  | import Immutable                     from 'immutable'; | ||||||
|  | 
 | ||||||
|  | const initialState = Immutable.List(); | ||||||
|  | 
 | ||||||
|  | export default function suggestions(state = initialState, action) { | ||||||
|  |   switch(action.type) { | ||||||
|  |     case SUGGESTIONS_FETCH_SUCCESS: | ||||||
|  |       return Immutable.List(action.accounts.map(item => item.id)); | ||||||
|  |     default: | ||||||
|  |       return state; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -18,7 +18,9 @@ import { | |||||||
|   ACCOUNT_BLOCK_SUCCESS, |   ACCOUNT_BLOCK_SUCCESS, | ||||||
|   ACCOUNT_UNBLOCK_SUCCESS, |   ACCOUNT_UNBLOCK_SUCCESS, | ||||||
|   ACCOUNT_TIMELINE_FETCH_SUCCESS, |   ACCOUNT_TIMELINE_FETCH_SUCCESS, | ||||||
|   ACCOUNT_TIMELINE_EXPAND_SUCCESS |   ACCOUNT_TIMELINE_EXPAND_SUCCESS, | ||||||
|  |   FOLLOWERS_FETCH_SUCCESS, | ||||||
|  |   FOLLOWING_FETCH_SUCCESS | ||||||
| }                                from '../actions/accounts'; | }                                from '../actions/accounts'; | ||||||
| import { | import { | ||||||
|   STATUS_FETCH_SUCCESS, |   STATUS_FETCH_SUCCESS, | ||||||
| @ -206,12 +208,12 @@ function normalizeContext(state, status, ancestors, descendants) { | |||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| function normalizeSuggestions(state, accounts) { | function normalizeAccounts(state, accounts) { | ||||||
|   accounts.forEach(account => { |   accounts.forEach(account => { | ||||||
|     state = state.setIn(['accounts', account.get('id')], account); |     state = state.setIn(['accounts', account.get('id')], account); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   return state.set('suggestions', accounts.map(account => account.get('id'))); |   return state; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function timelines(state = initialState, action) { | export default function timelines(state = initialState, action) { | ||||||
| @ -247,7 +249,9 @@ export default function timelines(state = initialState, action) { | |||||||
|     case ACCOUNT_TIMELINE_EXPAND_SUCCESS: |     case ACCOUNT_TIMELINE_EXPAND_SUCCESS: | ||||||
|       return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); |       return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); | ||||||
|     case SUGGESTIONS_FETCH_SUCCESS: |     case SUGGESTIONS_FETCH_SUCCESS: | ||||||
|       return normalizeSuggestions(state, Immutable.fromJS(action.suggestions)); |     case FOLLOWERS_FETCH_SUCCESS: | ||||||
|  |     case FOLLOWING_FETCH_SUCCESS: | ||||||
|  |       return normalizeAccounts(state, Immutable.fromJS(action.accounts)); | ||||||
|     default: |     default: | ||||||
|       return state; |       return state; | ||||||
|   } |   } | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								app/assets/javascripts/components/reducers/user_lists.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/assets/javascripts/components/reducers/user_lists.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | import { | ||||||
|  |   FOLLOWERS_FETCH_SUCCESS, | ||||||
|  |   FOLLOWING_FETCH_SUCCESS | ||||||
|  | }                          from '../actions/accounts'; | ||||||
|  | import Immutable           from 'immutable'; | ||||||
|  | 
 | ||||||
|  | const initialState = Immutable.Map({ | ||||||
|  |   followers: Immutable.Map(), | ||||||
|  |   following: Immutable.Map() | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default function userLists(state = initialState, action) { | ||||||
|  |   switch(action.type) { | ||||||
|  |     case FOLLOWERS_FETCH_SUCCESS: | ||||||
|  |       return state.setIn(['followers', action.id], Immutable.List(action.accounts.map(item => item.id))); | ||||||
|  |     case FOLLOWING_FETCH_SUCCESS: | ||||||
|  |       return state.setIn(['following', action.id], Immutable.List(action.accounts.map(item => item.id))); | ||||||
|  |     default: | ||||||
|  |       return state; | ||||||
|  |   } | ||||||
|  | }; | ||||||
| @ -7,13 +7,15 @@ const getAccounts = state => state.getIn(['timelines', 'accounts']); | |||||||
| const getAccountBase         = (state, id) => state.getIn(['timelines', 'accounts', id], null); | const getAccountBase         = (state, id) => state.getIn(['timelines', 'accounts', id], null); | ||||||
| const getAccountRelationship = (state, id) => state.getIn(['timelines', 'relationships', id]); | const getAccountRelationship = (state, id) => state.getIn(['timelines', 'relationships', id]); | ||||||
| 
 | 
 | ||||||
| export const getAccount = createSelector([getAccountBase, getAccountRelationship], (base, relationship) => { | export const makeGetAccount = () => { | ||||||
|   if (base === null) { |   return createSelector([getAccountBase, getAccountRelationship], (base, relationship) => { | ||||||
|     return null; |     if (base === null) { | ||||||
|   } |       return null; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|   return base.set('relationship', relationship); |     return base.set('relationship', relationship); | ||||||
| }); |   }); | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| const getStatusBase = (state, id) => state.getIn(['timelines', 'statuses', id], null); | const getStatusBase = (state, id) => state.getIn(['timelines', 'statuses', id], null); | ||||||
| 
 | 
 | ||||||
| @ -65,7 +67,7 @@ export const getNotifications = createSelector([getNotificationsBase], (base) => | |||||||
|   return arr; |   return arr; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const getSuggestionsBase = (state) => state.getIn(['timelines', 'suggestions']); | const getSuggestionsBase = (state) => state.get('suggestions'); | ||||||
| 
 | 
 | ||||||
| export const getSuggestions = createSelector([getSuggestionsBase, getAccounts], (base, accounts) => { | export const getSuggestions = createSelector([getSuggestionsBase, getAccounts], (base, accounts) => { | ||||||
|   return base.map(accountId => accounts.get(accountId)); |   return base.map(accountId => accounts.get(accountId)); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user