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:
		
							
								
								
									
										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;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user