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
92cd207c50
commit
fe8dd58bc1
|
@ -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' },
|
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} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.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",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue