Add list of muted user to UI and Getting Started (#1799)
Add the same UI that already exists for blocked users for muted ones and add it to the "Getting Started" menu.
This commit is contained in:
		
							parent
							
								
									29f169f7f0
								
							
						
					
					
						commit
						30e815a78e
					
				
					 8 changed files with 183 additions and 3 deletions
				
			
		
							
								
								
									
										82
									
								
								app/assets/javascripts/components/actions/mutes.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								app/assets/javascripts/components/actions/mutes.jsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,82 @@ | |||
| import api, { getLinks } from '../api' | ||||
| import { fetchRelationships } from './accounts'; | ||||
| 
 | ||||
| export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; | ||||
| export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS'; | ||||
| export const MUTES_FETCH_FAIL    = 'MUTES_FETCH_FAIL'; | ||||
| 
 | ||||
| export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST'; | ||||
| export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; | ||||
| export const MUTES_EXPAND_FAIL    = 'MUTES_EXPAND_FAIL'; | ||||
| 
 | ||||
| export function fetchMutes() { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(fetchMutesRequest()); | ||||
| 
 | ||||
|     api(getState).get('/api/v1/mutes').then(response => { | ||||
|       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||
|       dispatch(fetchMutesSuccess(response.data, next ? next.uri : null)); | ||||
|       dispatch(fetchRelationships(response.data.map(item => item.id))); | ||||
|     }).catch(error => dispatch(fetchMutesFail(error))); | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function fetchMutesRequest() { | ||||
|   return { | ||||
|     type: MUTES_FETCH_REQUEST | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function fetchMutesSuccess(accounts, next) { | ||||
|   return { | ||||
|     type: MUTES_FETCH_SUCCESS, | ||||
|     accounts, | ||||
|     next | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function fetchMutesFail(error) { | ||||
|   return { | ||||
|     type: MUTES_FETCH_FAIL, | ||||
|     error | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function expandMutes() { | ||||
|   return (dispatch, getState) => { | ||||
|     const url = getState().getIn(['user_lists', 'mutes', 'next']); | ||||
| 
 | ||||
|     if (url === null) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     dispatch(expandMutesRequest()); | ||||
| 
 | ||||
|     api(getState).get(url).then(response => { | ||||
|       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||
|       dispatch(expandMutesSuccess(response.data, next ? next.uri : null)); | ||||
|       dispatch(fetchRelationships(response.data.map(item => item.id))); | ||||
|     }).catch(error => dispatch(expandMutesFail(error))); | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function expandMutesRequest() { | ||||
|   return { | ||||
|     type: MUTES_EXPAND_REQUEST | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function expandMutesSuccess(accounts, next) { | ||||
|   return { | ||||
|     type: MUTES_EXPAND_SUCCESS, | ||||
|     accounts, | ||||
|     next | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function expandMutesFail(error) { | ||||
|   return { | ||||
|     type: MUTES_EXPAND_FAIL, | ||||
|     error | ||||
|   }; | ||||
| }; | ||||
|  | @ -10,7 +10,8 @@ const messages = defineMessages({ | |||
|   follow: { id: 'account.follow', defaultMessage: 'Follow' }, | ||||
|   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, | ||||
|   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, | ||||
|   unblock: { id: 'account.unblock', defaultMessage: 'Unblock' } | ||||
|   unblock: { id: 'account.unblock', defaultMessage: 'Unblock' }, | ||||
|   unmute: { id: 'account.unmute', defaultMessage: 'Unmute' } | ||||
| }); | ||||
| 
 | ||||
| const buttonsStyle = { | ||||
|  | @ -25,6 +26,7 @@ const Account = React.createClass({ | |||
|     me: React.PropTypes.number.isRequired, | ||||
|     onFollow: React.PropTypes.func.isRequired, | ||||
|     onBlock: React.PropTypes.func.isRequired, | ||||
|     onMute: React.PropTypes.func.isRequired, | ||||
|     intl: React.PropTypes.object.isRequired | ||||
|   }, | ||||
| 
 | ||||
|  | @ -38,6 +40,10 @@ const Account = React.createClass({ | |||
|     this.props.onBlock(this.props.account); | ||||
|   }, | ||||
| 
 | ||||
|   handleMute () { | ||||
|     this.props.onMute(this.props.account); | ||||
|   }, | ||||
| 
 | ||||
|   render () { | ||||
|     const { account, me, intl } = this.props; | ||||
| 
 | ||||
|  | @ -51,11 +57,14 @@ const Account = React.createClass({ | |||
|       const following = account.getIn(['relationship', 'following']); | ||||
|       const requested = account.getIn(['relationship', 'requested']); | ||||
|       const blocking  = account.getIn(['relationship', 'blocking']); | ||||
|       const muting  = account.getIn(['relationship', 'muting']); | ||||
| 
 | ||||
|       if (requested) { | ||||
|         buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} /> | ||||
|       } else if (blocking) { | ||||
|         buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />; | ||||
|         buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />; | ||||
|       } else if (muting) { | ||||
|         buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />; | ||||
|       } else { | ||||
|         buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />; | ||||
|       } | ||||
|  |  | |||
|  | @ -37,6 +37,7 @@ import FollowRequests from '../features/follow_requests'; | |||
| import GenericNotFound from '../features/generic_not_found'; | ||||
| import FavouritedStatuses from '../features/favourited_statuses'; | ||||
| import Blocks from '../features/blocks'; | ||||
| import Mutes from '../features/mutes'; | ||||
| import Report from '../features/report'; | ||||
| import { IntlProvider, addLocaleData } from 'react-intl'; | ||||
| import en from 'react-intl/locale-data/en'; | ||||
|  | @ -171,6 +172,7 @@ const Mastodon = React.createClass({ | |||
| 
 | ||||
|               <Route path='follow_requests' component={FollowRequests} /> | ||||
|               <Route path='blocks' component={Blocks} /> | ||||
|               <Route path='mutes' component={Mutes} /> | ||||
|               <Route path='report' component={Report} /> | ||||
| 
 | ||||
|               <Route path='*' component={GenericNotFound} /> | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ const messages = defineMessages({ | |||
|   sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, | ||||
|   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, | ||||
|   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, | ||||
|   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, | ||||
|   info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' } | ||||
| }); | ||||
| 
 | ||||
|  | @ -37,6 +38,7 @@ const GettingStarted = ({ intl, me }) => { | |||
|         <ColumnLink icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' /> | ||||
|         {followRequests} | ||||
|         <ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' /> | ||||
|         <ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' /> | ||||
|         <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' /> | ||||
|         <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> | ||||
|       </div> | ||||
|  |  | |||
							
								
								
									
										68
									
								
								app/assets/javascripts/components/features/mutes/index.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								app/assets/javascripts/components/features/mutes/index.jsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| 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 { ScrollContainer } from 'react-router-scroll'; | ||||
| import Column from '../ui/components/column'; | ||||
| import ColumnBackButtonSlim from '../../components/column_back_button_slim'; | ||||
| import AccountContainer from '../../containers/account_container'; | ||||
| import { fetchMutes, expandMutes } from '../../actions/mutes'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   heading: { id: 'column.mutes', defaultMessage: 'Muted users' } | ||||
| }); | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   accountIds: state.getIn(['user_lists', 'mutes', 'items']) | ||||
| }); | ||||
| 
 | ||||
| const Mutes = React.createClass({ | ||||
|   propTypes: { | ||||
|     params: React.PropTypes.object.isRequired, | ||||
|     dispatch: React.PropTypes.func.isRequired, | ||||
|     accountIds: ImmutablePropTypes.list, | ||||
|     intl: React.PropTypes.object.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.props.dispatch(fetchMutes()); | ||||
|   }, | ||||
| 
 | ||||
|   handleScroll (e) { | ||||
|     const { scrollTop, scrollHeight, clientHeight } = e.target; | ||||
| 
 | ||||
|     if (scrollTop === scrollHeight - clientHeight) { | ||||
|       this.props.dispatch(expandMutes()); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   render () { | ||||
|     const { intl, accountIds } = this.props; | ||||
| 
 | ||||
|     if (!accountIds) { | ||||
|       return ( | ||||
|         <Column> | ||||
|           <LoadingIndicator /> | ||||
|         </Column> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Column icon='users' heading={intl.formatMessage(messages.heading)}> | ||||
|         <ColumnBackButtonSlim /> | ||||
|         <ScrollContainer scrollKey='mutes'> | ||||
|           <div className='scrollable' onScroll={this.handleScroll}> | ||||
|             {accountIds.map(id => | ||||
|               <AccountContainer key={id} id={id} /> | ||||
|             )} | ||||
|           </div> | ||||
|         </ScrollContainer> | ||||
|       </Column> | ||||
|     ); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps)(injectIntl(Mutes)); | ||||
|  | @ -31,6 +31,7 @@ const en = { | |||
|   "column.favourites": "Favourites", | ||||
|   "column.follow_requests": "Follow requests", | ||||
|   "column.home": "Home", | ||||
|   "column.mutes": "Muted users", | ||||
|   "column.notifications": "Notifications", | ||||
|   "column.public": "Federated timeline", | ||||
|   "compose_form.placeholder": "What is on your mind?", | ||||
|  | @ -68,6 +69,7 @@ const en = { | |||
|   "navigation_bar.follow_requests": "Follow requests", | ||||
|   "navigation_bar.info": "Extended information", | ||||
|   "navigation_bar.logout": "Logout", | ||||
|   "navigation_bar.mutes": "Muted users", | ||||
|   "navigation_bar.preferences": "Preferences", | ||||
|   "navigation_bar.public_timeline": "Federated timeline", | ||||
|   "notification.favourite": "{name} favourited your status", | ||||
|  |  | |||
|  | @ -15,6 +15,10 @@ import { | |||
|   BLOCKS_FETCH_SUCCESS, | ||||
|   BLOCKS_EXPAND_SUCCESS | ||||
| } from '../actions/blocks'; | ||||
| import { | ||||
|   MUTES_FETCH_SUCCESS, | ||||
|   MUTES_EXPAND_SUCCESS | ||||
| } from '../actions/mutes'; | ||||
| import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; | ||||
| import { | ||||
|   REBLOG_SUCCESS, | ||||
|  | @ -94,6 +98,8 @@ export default function accounts(state = initialState, action) { | |||
|   case FOLLOW_REQUESTS_EXPAND_SUCCESS: | ||||
|   case BLOCKS_FETCH_SUCCESS: | ||||
|   case BLOCKS_EXPAND_SUCCESS: | ||||
|   case MUTES_FETCH_SUCCESS: | ||||
|   case MUTES_EXPAND_SUCCESS: | ||||
|     return normalizeAccounts(state, action.accounts); | ||||
|   case NOTIFICATIONS_REFRESH_SUCCESS: | ||||
|   case NOTIFICATIONS_EXPAND_SUCCESS: | ||||
|  |  | |||
|  | @ -16,6 +16,10 @@ import { | |||
|   BLOCKS_FETCH_SUCCESS, | ||||
|   BLOCKS_EXPAND_SUCCESS | ||||
| } from '../actions/blocks'; | ||||
| import { | ||||
|   MUTES_FETCH_SUCCESS, | ||||
|   MUTES_EXPAND_SUCCESS | ||||
| } from '../actions/mutes'; | ||||
| import Immutable from 'immutable'; | ||||
| 
 | ||||
| const initialState = Immutable.Map({ | ||||
|  | @ -24,7 +28,8 @@ const initialState = Immutable.Map({ | |||
|   reblogged_by: Immutable.Map(), | ||||
|   favourited_by: Immutable.Map(), | ||||
|   follow_requests: Immutable.Map(), | ||||
|   blocks: Immutable.Map() | ||||
|   blocks: Immutable.Map(), | ||||
|   mutes: Immutable.Map() | ||||
| }); | ||||
| 
 | ||||
| const normalizeList = (state, type, id, accounts, next) => { | ||||
|  | @ -65,6 +70,10 @@ export default function userLists(state = initialState, action) { | |||
|     return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); | ||||
|   case BLOCKS_EXPAND_SUCCESS: | ||||
|     return state.updateIn(['blocks', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); | ||||
|   case MUTES_FETCH_SUCCESS: | ||||
|     return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next); | ||||
|   case MUTES_EXPAND_SUCCESS: | ||||
|     return state.updateIn(['mutes', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue