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:
Patrick Figel 2017-04-15 01:23:49 +02:00 committed by Eugen
parent 92cd207c50
commit fe8dd58bc1
8 changed files with 183 additions and 3 deletions

View 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
};
};

View file

@ -10,7 +10,8 @@ const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, 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 = { const buttonsStyle = {
@ -25,6 +26,7 @@ const Account = React.createClass({
me: React.PropTypes.number.isRequired, me: React.PropTypes.number.isRequired,
onFollow: React.PropTypes.func.isRequired, onFollow: React.PropTypes.func.isRequired,
onBlock: React.PropTypes.func.isRequired, onBlock: React.PropTypes.func.isRequired,
onMute: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired intl: React.PropTypes.object.isRequired
}, },
@ -38,6 +40,10 @@ const Account = React.createClass({
this.props.onBlock(this.props.account); this.props.onBlock(this.props.account);
}, },
handleMute () {
this.props.onMute(this.props.account);
},
render () { render () {
const { account, me, intl } = this.props; const { account, me, intl } = this.props;
@ -51,11 +57,14 @@ const Account = React.createClass({
const following = account.getIn(['relationship', 'following']); const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']); const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']); const blocking = account.getIn(['relationship', 'blocking']);
const muting = account.getIn(['relationship', 'muting']);
if (requested) { if (requested) {
buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} /> buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
} else if (blocking) { } 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 { } else {
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />; buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
} }

View file

@ -37,6 +37,7 @@ import FollowRequests from '../features/follow_requests';
import GenericNotFound from '../features/generic_not_found'; import GenericNotFound from '../features/generic_not_found';
import FavouritedStatuses from '../features/favourited_statuses'; import FavouritedStatuses from '../features/favourited_statuses';
import Blocks from '../features/blocks'; import Blocks from '../features/blocks';
import Mutes from '../features/mutes';
import Report from '../features/report'; import Report from '../features/report';
import { IntlProvider, addLocaleData } from 'react-intl'; import { IntlProvider, addLocaleData } from 'react-intl';
import en from 'react-intl/locale-data/en'; import en from 'react-intl/locale-data/en';
@ -171,6 +172,7 @@ const Mastodon = React.createClass({
<Route path='follow_requests' component={FollowRequests} /> <Route path='follow_requests' component={FollowRequests} />
<Route path='blocks' component={Blocks} /> <Route path='blocks' component={Blocks} />
<Route path='mutes' component={Mutes} />
<Route path='report' component={Report} /> <Route path='report' component={Report} />
<Route path='*' component={GenericNotFound} /> <Route path='*' component={GenericNotFound} />

View file

@ -14,6 +14,7 @@ const messages = defineMessages({
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' } 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' /> <ColumnLink icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />
{followRequests} {followRequests}
<ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' /> <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='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' /> <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
</div> </div>

View 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));

View file

@ -31,6 +31,7 @@ const en = {
"column.favourites": "Favourites", "column.favourites": "Favourites",
"column.follow_requests": "Follow requests", "column.follow_requests": "Follow requests",
"column.home": "Home", "column.home": "Home",
"column.mutes": "Muted users",
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.public": "Federated timeline", "column.public": "Federated timeline",
"compose_form.placeholder": "What is on your mind?", "compose_form.placeholder": "What is on your mind?",
@ -68,6 +69,7 @@ const en = {
"navigation_bar.follow_requests": "Follow requests", "navigation_bar.follow_requests": "Follow requests",
"navigation_bar.info": "Extended information", "navigation_bar.info": "Extended information",
"navigation_bar.logout": "Logout", "navigation_bar.logout": "Logout",
"navigation_bar.mutes": "Muted users",
"navigation_bar.preferences": "Preferences", "navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline", "navigation_bar.public_timeline": "Federated timeline",
"notification.favourite": "{name} favourited your status", "notification.favourite": "{name} favourited your status",

View file

@ -15,6 +15,10 @@ import {
BLOCKS_FETCH_SUCCESS, BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS BLOCKS_EXPAND_SUCCESS
} from '../actions/blocks'; } from '../actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS
} from '../actions/mutes';
import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
import { import {
REBLOG_SUCCESS, REBLOG_SUCCESS,
@ -94,6 +98,8 @@ export default function accounts(state = initialState, action) {
case FOLLOW_REQUESTS_EXPAND_SUCCESS: case FOLLOW_REQUESTS_EXPAND_SUCCESS:
case BLOCKS_FETCH_SUCCESS: case BLOCKS_FETCH_SUCCESS:
case BLOCKS_EXPAND_SUCCESS: case BLOCKS_EXPAND_SUCCESS:
case MUTES_FETCH_SUCCESS:
case MUTES_EXPAND_SUCCESS:
return normalizeAccounts(state, action.accounts); return normalizeAccounts(state, action.accounts);
case NOTIFICATIONS_REFRESH_SUCCESS: case NOTIFICATIONS_REFRESH_SUCCESS:
case NOTIFICATIONS_EXPAND_SUCCESS: case NOTIFICATIONS_EXPAND_SUCCESS:

View file

@ -16,6 +16,10 @@ import {
BLOCKS_FETCH_SUCCESS, BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS BLOCKS_EXPAND_SUCCESS
} from '../actions/blocks'; } from '../actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS
} from '../actions/mutes';
import Immutable from 'immutable'; import Immutable from 'immutable';
const initialState = Immutable.Map({ const initialState = Immutable.Map({
@ -24,7 +28,8 @@ const initialState = Immutable.Map({
reblogged_by: Immutable.Map(), reblogged_by: Immutable.Map(),
favourited_by: Immutable.Map(), favourited_by: Immutable.Map(),
follow_requests: Immutable.Map(), follow_requests: Immutable.Map(),
blocks: Immutable.Map() blocks: Immutable.Map(),
mutes: Immutable.Map()
}); });
const normalizeList = (state, type, id, accounts, next) => { 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); return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
case BLOCKS_EXPAND_SUCCESS: case BLOCKS_EXPAND_SUCCESS:
return state.updateIn(['blocks', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next); 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: default:
return state; return state;
} }