dispatch(blockAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/block`).then(response => {
- dispatch(blockAccountSuccess(response.data));
+ // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
+ dispatch(blockAccountSuccess(response.data, getState().get('statuses')));
}).catch(error => {
dispatch(blockAccountFail(id, error));
});
};
};
-export function blockAccountSuccess(relationship) {
+export function blockAccountSuccess(relationship, statuses) {
return {
type: ACCOUNT_BLOCK_SUCCESS,
- relationship
+ relationship,
+ statuses
};
};
onReblog: React.PropTypes.func,
onDelete: React.PropTypes.func,
onOpenMedia: React.PropTypes.func,
+ onBlock: React.PropTypes.func,
me: React.PropTypes.number,
muted: React.PropTypes.bool
},
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
mention: { id: 'status.mention', defaultMessage: 'Mention' },
+ block: { id: 'account.block', defaultMessage: 'Block' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
reblog: { id: 'status.reblog', defaultMessage: 'Reblog' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }
onFavourite: React.PropTypes.func,
onReblog: React.PropTypes.func,
onDelete: React.PropTypes.func,
- onMention: React.PropTypes.func
+ onMention: React.PropTypes.func,
+ onBlock: React.PropTypes.func
},
mixins: [PureRenderMixin],
this.props.onMention(this.props.status.get('account'));
},
+ handleBlockClick () {
+ this.props.onBlock(this.props.status.get('account'));
+ },
+
render () {
const { status, me, intl } = this.props;
let menu = [];
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
} else {
menu.push({ text: intl.formatMessage(messages.mention), action: this.handleMentionClick });
+ menu.push({ text: intl.formatMessage(messages.block), action: this.handleBlockClick });
}
return (
return store.dispatch(updateTimeline(data.timeline, JSON.parse(data.message)));
case 'delete':
return store.dispatch(deleteFromTimelines(data.id));
- case 'merge':
- case 'unmerge':
- return store.dispatch(refreshTimeline('home', true));
- case 'block':
- return store.dispatch(refreshTimeline('mentions', true));
case 'notification':
return store.dispatch(updateNotifications(JSON.parse(data.message), getMessagesForLocale(locale), locale));
}
-import { connect } from 'react-redux';
-import Status from '../components/status';
+import { connect } from 'react-redux';
+import Status from '../components/status';
import { makeGetStatus } from '../selectors';
import {
replyCompose,
mentionCompose
-} from '../actions/compose';
+} from '../actions/compose';
import {
reblog,
favourite,
unreblog,
unfavourite
-} from '../actions/interactions';
-import { deleteStatus } from '../actions/statuses';
-import { openMedia } from '../actions/modal';
+} from '../actions/interactions';
+import { blockAccount } from '../actions/accounts';
+import { deleteStatus } from '../actions/statuses';
+import { openMedia } from '../actions/modal';
import { createSelector } from 'reselect'
const mapStateToProps = (state, props) => ({
onOpenMedia (url) {
dispatch(openMedia(url));
+ },
+
+ onBlock (account) {
+ dispatch(blockAccount(account.get('id')));
}
});
import AutosuggestAccountContainer from '../../compose/containers/autosuggest_account_container';
import { debounce } from 'react-decoration';
import UploadButtonContainer from '../containers/upload_button_container';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
const messages = defineMessages({
<label style={{ display: 'block', lineHeight: '24px', verticalAlign: 'middle', marginTop: '10px', borderTop: '1px solid #616b86', paddingTop: '10px' }}>
<Toggle checked={this.props.sensitive} onChange={this.handleChangeSensitivity} />
- <span style={{ display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', marginLeft: '8px', color: '#9baec8' }}>Sensitive content</span>
+ <span style={{ display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', marginLeft: '8px', color: '#9baec8' }}><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark content as sensitive' /></span>
</label>
</div>
);
"status.reblog": "Reblog",
"status.favourite": "Favourite",
"status.reblogged_by": "{name} reblogged",
+ "status.sensitive_warning": "Sensitive content",
+ "status.sensitive_toggle": "Click to view",
"video_player.toggle_sound": "Toggle sound",
"account.mention": "Mention",
"account.edit_profile": "Edit profile",
"tabs_bar.notifications": "Notifications",
"compose_form.placeholder": "What is on your mind?",
"compose_form.publish": "Toot",
+ "compose_form.sensitive": "Mark content as sensitive",
"navigation_bar.settings": "Settings",
"navigation_bar.public_timeline": "Public timeline",
"navigation_bar.logout": "Logout",
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS
} from '../actions/notifications';
+import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts';
import Immutable from 'immutable';
const initialState = Immutable.Map({
return state.update('items', list => list.push(...items)).set('next', next);
};
+const filterNotifications = (state, relationship) => {
+ return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id));
+};
+
export default function notifications(state = initialState, action) {
switch(action.type) {
case NOTIFICATIONS_UPDATE:
return normalizeNotifications(state, action.notifications, action.next);
case NOTIFICATIONS_EXPAND_SUCCESS:
return appendNormalizedNotifications(state, action.notifications, action.next);
+ case ACCOUNT_BLOCK_SUCCESS:
+ return filterNotifications(state, action.relationship);
default:
return state;
}
} from '../actions/timelines';
import {
ACCOUNT_TIMELINE_FETCH_SUCCESS,
- ACCOUNT_TIMELINE_EXPAND_SUCCESS
+ ACCOUNT_TIMELINE_EXPAND_SUCCESS,
+ ACCOUNT_BLOCK_SUCCESS
} from '../actions/accounts';
import {
NOTIFICATIONS_UPDATE,
return state.delete(id);
};
+const filterStatuses = (state, relationship) => {
+ state.forEach(status => {
+ if (status.get('account') !== relationship.id) {
+ return;
+ }
+
+ state = deleteStatus(state, status.get('id'), state.filter(item => item.get('reblog') === status.get('id')));
+ });
+
+ return state;
+};
+
const initialState = Immutable.Map();
export default function statuses(state = initialState, action) {
return normalizeStatuses(state, action.statuses);
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
+ case ACCOUNT_BLOCK_SUCCESS:
+ return filterStatuses(state, action.relationship);
default:
return state;
}
import {
ACCOUNT_FETCH_SUCCESS,
ACCOUNT_TIMELINE_FETCH_SUCCESS,
- ACCOUNT_TIMELINE_EXPAND_SUCCESS
+ ACCOUNT_TIMELINE_EXPAND_SUCCESS,
+ ACCOUNT_BLOCK_SUCCESS
} from '../actions/accounts';
import {
STATUS_FETCH_SUCCESS,
return state;
};
+const filterTimelines = (state, relationship, statuses) => {
+ let references;
+
+ statuses.forEach(status => {
+ if (status.get('account') !== relationship.id) {
+ return;
+ }
+
+ references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
+ state = deleteStatus(state, status.get('id'), status.get('account'), references);
+ });
+
+ return state;
+};
+
const normalizeContext = (state, id, ancestors, descendants) => {
const ancestorsIds = ancestors.map(ancestor => ancestor.get('id'));
const descendantsIds = descendants.map(descendant => descendant.get('id'));
return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
+ case ACCOUNT_BLOCK_SUCCESS:
+ return filterTimelines(state, action.relationship, action.statuses);
default:
return state;
}
UnfollowService.new.call(account, target_account) if account.following?(target_account)
account.block!(target_account)
- clear_mentions(account, target_account)
+ clear_timelines(account, target_account)
+ clear_notifications(account, target_account)
end
private
- def clear_mentions(account, target_account)
- timeline_key = FeedManager.instance.key(:mentions, account.id)
+ def clear_timelines(account, target_account)
+ mentions_key = FeedManager.instance.key(:mentions, account.id)
+ home_key = FeedManager.instance.key(:home, account.id)
target_account.statuses.select('id').find_each do |status|
- redis.zrem(timeline_key, status.id)
+ redis.zrem(mentions_key, status.id)
+ redis.zrem(home_key, status.id)
end
+ end
- FeedManager.instance.broadcast(account.id, type: 'block', id: target_account.id)
+ def clear_notifications(account, target_account)
+ Notification.where(account: account).joins(:follow).where(activity_type: 'Follow', follows: { account_id: target_account.id }).destroy_all
+ Notification.where(account: account).joins(mention: :status).where(activity_type: 'Mention', statuses: { account_id: target_account.id }).destroy_all
+ Notification.where(account: account).joins(:favourite).where(activity_type: 'Favourite', favourites: { account_id: target_account.id }).destroy_all
+ Notification.where(account: account).joins(:status).where(activity_type: 'Status', statuses: { account_id: target_account.id }).destroy_all
end
def redis
end
FeedManager.instance.trim(:home, into_account.id)
- FeedManager.instance.broadcast(into_account.id, type: 'merge')
end
def redis
from_account.statuses.select('id').find_each do |status|
redis.zrem(timeline_key, status.id)
+ redis.zremrangebyscore(timeline_key, status.id, status.id)
end
-
- FeedManager.instance.broadcast(into_account.id, type: 'unmerge')
end
def redis