Compare commits
42 Commits
main
...
for-upstre
Author | SHA1 | Date |
---|---|---|
Eugen Rochko | e168e8afe5 | |
aschmitz | 75c21adfbf | |
aschmitz | 685fafcc27 | |
aschmitz | 0e210b5c63 | |
aschmitz | 6b70c2ca12 | |
Surinna Curtis | 8c6ecd5616 | |
Surinna Curtis | 22cf9bcff6 | |
Surinna Curtis | 21de45c7d2 | |
Surinna Curtis | 88e52f3a2a | |
Surinna Curtis | d4bb04c45c | |
Surinna Curtis | 33c56212b9 | |
Surinna Curtis | bcd4b72223 | |
Surinna Curtis | f39bba9a90 | |
Surinna Curtis | 290c6b0f2e | |
Surinna Curtis | d5d1dcab77 | |
Surinna Curtis | ef5ebdd544 | |
Surinna Curtis | da85bfc252 | |
Surinna Curtis | d17255c0e0 | |
Surinna Curtis | 74ce229101 | |
Surinna Curtis | fb8613d09b | |
Surinna Curtis | d73f437419 | |
Surinna Curtis | 343c358bb2 | |
Surinna Curtis | 8a6ad735f1 | |
Surinna Curtis | 9fb8a6f231 | |
Surinna Curtis | f90ac53dc5 | |
Surinna Curtis | c814d19672 | |
Surinna Curtis | 27eb75878e | |
Surinna Curtis | a81d3a2842 | |
Surinna Curtis | 1fc0382d29 | |
Surinna Curtis | 90db8b63b0 | |
Surinna Curtis | 15a6cb5ca9 | |
Surinna Curtis | d5dc4696a7 | |
Surinna Curtis | eb6543b36b | |
Surinna Curtis | 920c7cdaf3 | |
Surinna Curtis | f3aac23099 | |
Surinna Curtis | aded1acfda | |
Surinna Curtis | 9855529a10 | |
Surinna Curtis | 0ccfbe5747 | |
Surinna Curtis | 921a1b7b2a | |
Surinna Curtis | aa7e541780 | |
Surinna Curtis | dac67960e0 | |
Surinna Curtis | 90a0176b0b |
|
@ -26,7 +26,7 @@ class Api::V1::AccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def mute
|
||||
MuteService.new.call(current_user.account, @account)
|
||||
MuteService.new.call(current_user.account, @account, notifications: params[:notifications])
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
|
|
|
@ -241,11 +241,11 @@ export function unblockAccountFail(error) {
|
|||
};
|
||||
|
||||
|
||||
export function muteAccount(id) {
|
||||
export function muteAccount(id, notifications) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(muteAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/mute`).then(response => {
|
||||
api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then(response => {
|
||||
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
|
||||
dispatch(muteAccountSuccess(response.data, getState().get('statuses')));
|
||||
}).catch(error => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import api, { getLinks } from '../api';
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { openModal } from '../../mastodon/actions/modal';
|
||||
|
||||
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
|
||||
export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';
|
||||
|
@ -9,6 +10,9 @@ 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 const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL';
|
||||
export const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS';
|
||||
|
||||
export function fetchMutes() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchMutesRequest());
|
||||
|
@ -80,3 +84,20 @@ export function expandMutesFail(error) {
|
|||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function initMuteModal(account) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: MUTES_INIT_MODAL,
|
||||
account,
|
||||
});
|
||||
|
||||
dispatch(openModal('MUTE'));
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleHideNotifications() {
|
||||
return dispatch => {
|
||||
dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS });
|
||||
};
|
||||
}
|
|
@ -15,6 +15,8 @@ const messages = defineMessages({
|
|||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' },
|
||||
unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
|
@ -41,6 +43,14 @@ export default class Account extends ImmutablePureComponent {
|
|||
this.props.onMute(this.props.account);
|
||||
}
|
||||
|
||||
handleMuteNotifications = () => {
|
||||
this.props.onMuteNotifications(this.props.account, true);
|
||||
}
|
||||
|
||||
handleUnmuteNotifications = () => {
|
||||
this.props.onMuteNotifications(this.props.account, false);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, intl, hidden } = this.props;
|
||||
|
||||
|
@ -70,7 +80,18 @@ export default class Account extends ImmutablePureComponent {
|
|||
} else if (blocking) {
|
||||
buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||
} else if (muting) {
|
||||
buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
|
||||
let hidingNotificationsButton;
|
||||
if (muting.get('notifications')) {
|
||||
hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username') })} onClick={this.handleUnmuteNotifications} />;
|
||||
} else {
|
||||
hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username') })} onClick={this.handleMuteNotifications} />;
|
||||
}
|
||||
buttons = (
|
||||
<div>
|
||||
<IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />
|
||||
{hidingNotificationsButton}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
unmuteAccount,
|
||||
} from '../actions/accounts';
|
||||
import { openModal } from '../actions/modal';
|
||||
import { initMuteModal } from '../actions/mutes';
|
||||
import { unfollowModal } from '../initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -58,10 +59,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
if (account.getIn(['relationship', 'muting'])) {
|
||||
dispatch(unmuteAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(muteAccount(account.get('id')));
|
||||
dispatch(initMuteModal(account));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onMuteNotifications (account, notifications) {
|
||||
dispatch(muteAccount(account.get('id'), notifications));
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Account));
|
||||
|
|
|
@ -14,11 +14,9 @@ import {
|
|||
pin,
|
||||
unpin,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
blockAccount,
|
||||
muteAccount,
|
||||
} from '../actions/accounts';
|
||||
import { blockAccount } from '../actions/accounts';
|
||||
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
|
||||
import { initMuteModal } from '../actions/mutes';
|
||||
import { initReport } from '../actions/reports';
|
||||
import { openModal } from '../actions/modal';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
@ -28,7 +26,6 @@ const messages = defineMessages({
|
|||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||
muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
|
@ -120,11 +117,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
},
|
||||
|
||||
onMute (account) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.muteConfirm),
|
||||
onConfirm: () => dispatch(muteAccount(account.get('id'))),
|
||||
}));
|
||||
dispatch(initMuteModal(account));
|
||||
},
|
||||
|
||||
onMuteConversation (status) {
|
||||
|
|
|
@ -7,10 +7,10 @@ import {
|
|||
unfollowAccount,
|
||||
blockAccount,
|
||||
unblockAccount,
|
||||
muteAccount,
|
||||
unmuteAccount,
|
||||
} from '../../../actions/accounts';
|
||||
import { mentionCompose } from '../../../actions/compose';
|
||||
import { initMuteModal } from '../../../actions/mutes';
|
||||
import { initReport } from '../../../actions/reports';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
|
||||
|
@ -20,7 +20,6 @@ import { unfollowModal } from '../../../initial_state';
|
|||
const messages = defineMessages({
|
||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||
muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||
});
|
||||
|
||||
|
@ -76,11 +75,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
if (account.getIn(['relationship', 'muting'])) {
|
||||
dispatch(unmuteAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.muteConfirm),
|
||||
onConfirm: () => dispatch(muteAccount(account.get('id'))),
|
||||
}));
|
||||
dispatch(initMuteModal(account));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import BoostModal from './boost_modal';
|
|||
import ConfirmationModal from './confirmation_modal';
|
||||
import {
|
||||
OnboardingModal,
|
||||
MuteModal,
|
||||
ReportModal,
|
||||
EmbedModal,
|
||||
} from '../../../features/ui/util/async-components';
|
||||
|
@ -20,6 +21,7 @@ const MODAL_COMPONENTS = {
|
|||
'VIDEO': () => Promise.resolve({ default: VideoModal }),
|
||||
'BOOST': () => Promise.resolve({ default: BoostModal }),
|
||||
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
|
||||
'MUTE': MuteModal,
|
||||
'REPORT': ReportModal,
|
||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||
'EMBED': EmbedModal,
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Toggle from 'react-toggle';
|
||||
import Button from '../../../components/button';
|
||||
import { closeModal } from '../../../actions/modal';
|
||||
import { muteAccount } from '../../../actions/accounts';
|
||||
import { toggleHideNotifications } from '../../../actions/mutes';
|
||||
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
|
||||
account: state.getIn(['mutes', 'new', 'account']),
|
||||
notifications: state.getIn(['mutes', 'new', 'notifications']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onConfirm(account, notifications) {
|
||||
dispatch(muteAccount(account.get('id'), notifications));
|
||||
},
|
||||
|
||||
onClose() {
|
||||
dispatch(closeModal());
|
||||
},
|
||||
|
||||
onToggleNotifications() {
|
||||
dispatch(toggleHideNotifications());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
export default class MuteModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isSubmitting: PropTypes.bool.isRequired,
|
||||
account: PropTypes.object.isRequired,
|
||||
notifications: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onToggleNotifications: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.button.focus();
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onClose();
|
||||
this.props.onConfirm(this.props.account, this.props.notifications);
|
||||
}
|
||||
|
||||
handleCancel = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.button = c;
|
||||
}
|
||||
|
||||
toggleNotifications = () => {
|
||||
this.props.onToggleNotifications();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, notifications } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal mute-modal'>
|
||||
<div className='mute-modal__container'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='confirmations.mute.message'
|
||||
defaultMessage='Are you sure you want to mute {name}?'
|
||||
values={{ name: <strong>@{account.get('acct')}</strong> }}
|
||||
/>
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor='mute-modal__hide-notifications-checkbox'>
|
||||
<FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' />
|
||||
{' '}
|
||||
<Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mute-modal__action-bar'>
|
||||
<Button onClick={this.handleCancel} className='mute-modal__cancel-button'>
|
||||
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
|
||||
</Button>
|
||||
<Button onClick={this.handleClick} ref={this.setRef}>
|
||||
<FormattedMessage id='confirmations.mute.confirm' defaultMessage='Mute' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -86,6 +86,10 @@ export function OnboardingModal () {
|
|||
return import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal');
|
||||
}
|
||||
|
||||
export function MuteModal () {
|
||||
return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal');
|
||||
}
|
||||
|
||||
export function ReportModal () {
|
||||
return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal');
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import settings from './settings';
|
|||
import push_notifications from './push_notifications';
|
||||
import status_lists from './status_lists';
|
||||
import cards from './cards';
|
||||
import mutes from './mutes';
|
||||
import reports from './reports';
|
||||
import contexts from './contexts';
|
||||
import compose from './compose';
|
||||
|
@ -37,6 +38,7 @@ const reducers = {
|
|||
settings,
|
||||
push_notifications,
|
||||
cards,
|
||||
mutes,
|
||||
reports,
|
||||
contexts,
|
||||
compose,
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import Immutable from 'immutable';
|
||||
|
||||
import {
|
||||
MUTES_INIT_MODAL,
|
||||
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
|
||||
} from '../actions/mutes';
|
||||
|
||||
const initialState = Immutable.Map({
|
||||
new: Immutable.Map({
|
||||
isSubmitting: false,
|
||||
account: null,
|
||||
notifications: true,
|
||||
}),
|
||||
});
|
||||
|
||||
export default function mutes(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case MUTES_INIT_MODAL:
|
||||
return state.withMutations((state) => {
|
||||
state.setIn(['new', 'isSubmitting'], false);
|
||||
state.setIn(['new', 'account'], action.account);
|
||||
state.setIn(['new', 'notifications'], true);
|
||||
});
|
||||
case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
|
||||
return state.updateIn(['new', 'notifications'], (old) => !old);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -906,6 +906,7 @@
|
|||
.account__relationship {
|
||||
height: 18px;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.account__header {
|
||||
|
@ -3515,7 +3516,8 @@ button.icon-button.active i.fa-retweet {
|
|||
.boost-modal,
|
||||
.confirmation-modal,
|
||||
.report-modal,
|
||||
.actions-modal {
|
||||
.actions-modal,
|
||||
.mute-modal {
|
||||
background: lighten($ui-secondary-color, 8%);
|
||||
color: $ui-base-color;
|
||||
border-radius: 8px;
|
||||
|
@ -3565,6 +3567,7 @@ button.icon-button.active i.fa-retweet {
|
|||
|
||||
.boost-modal__action-bar,
|
||||
.confirmation-modal__action-bar,
|
||||
.mute-modal__action-bar,
|
||||
.report-modal__action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -3601,6 +3604,14 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
.mute-modal {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.mute-modal .react-toggle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.report-modal__statuses,
|
||||
.report-modal__comment {
|
||||
padding: 10px;
|
||||
|
@ -3673,8 +3684,10 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
}
|
||||
|
||||
.confirmation-modal__action-bar {
|
||||
.confirmation-modal__cancel-button {
|
||||
.confirmation-modal__action-bar,
|
||||
.mute-modal__action-bar {
|
||||
.confirmation-modal__cancel-button,
|
||||
.mute-modal__cancel-button {
|
||||
background-color: transparent;
|
||||
color: darken($ui-secondary-color, 34%);
|
||||
font-size: 14px;
|
||||
|
@ -3689,6 +3702,7 @@ button.icon-button.active i.fa-retweet {
|
|||
}
|
||||
|
||||
.confirmation-modal__container,
|
||||
.mute-modal__container,
|
||||
.report-modal__target {
|
||||
padding: 30px;
|
||||
font-size: 16px;
|
||||
|
|
|
@ -17,7 +17,11 @@ module AccountInteractions
|
|||
end
|
||||
|
||||
def muting_map(target_account_ids, account_id)
|
||||
follow_mapping(Mute.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
|
||||
Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping|
|
||||
mapping[mute.target_account_id] = {
|
||||
notifications: mute.hide_notifications?,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def requested_map(target_account_ids, account_id)
|
||||
|
@ -70,8 +74,13 @@ module AccountInteractions
|
|||
block_relationships.find_or_create_by!(target_account: other_account)
|
||||
end
|
||||
|
||||
def mute!(other_account)
|
||||
mute_relationships.find_or_create_by!(target_account: other_account)
|
||||
def mute!(other_account, notifications: nil)
|
||||
notifications = true if notifications.nil?
|
||||
mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account)
|
||||
# When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
|
||||
if mute.hide_notifications? != notifications
|
||||
mute.update!(hide_notifications: notifications)
|
||||
end
|
||||
end
|
||||
|
||||
def mute_conversation!(conversation)
|
||||
|
@ -127,6 +136,10 @@ module AccountInteractions
|
|||
conversation_mutes.where(conversation: conversation).exists?
|
||||
end
|
||||
|
||||
def muting_notifications?(other_account)
|
||||
mute_relationships.where(target_account: other_account, hide_notifications: true).exists?
|
||||
end
|
||||
|
||||
def requested?(other_account)
|
||||
follow_requests.where(target_account: other_account).exists?
|
||||
end
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
#
|
||||
# Table name: mutes
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# id :bigint not null, primary key
|
||||
# target_account_id :bigint not null
|
||||
# account_id :integer not null
|
||||
# target_account_id :integer not null
|
||||
# hide_notifications :boolean default(TRUE), not null
|
||||
#
|
||||
|
||||
class Mute < ApplicationRecord
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MuteService < BaseService
|
||||
def call(account, target_account)
|
||||
def call(account, target_account, notifications: nil)
|
||||
return if account.id == target_account.id
|
||||
mute = account.mute!(target_account)
|
||||
FeedManager.instance.clear_from_timeline(account, target_account)
|
||||
mute = account.mute!(target_account, notifications: notifications)
|
||||
BlockWorker.perform_async(account.id, target_account.id)
|
||||
mute
|
||||
end
|
||||
|
|
|
@ -81,7 +81,7 @@ class NotifyService < BaseService
|
|||
blocked ||= from_self? # Skip for interactions with self
|
||||
blocked ||= domain_blocking? # Skip for domain blocked accounts
|
||||
blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
|
||||
blocked ||= @recipient.muting?(@notification.from_account) # Skip for muted accounts
|
||||
blocked ||= @recipient.muting_notifications?(@notification.from_account)
|
||||
blocked ||= hellbanned? # Hellban
|
||||
blocked ||= optional_non_follower? # Options
|
||||
blocked ||= optional_non_following? # Options
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||
|
||||
class AddHideNotificationsToMute < ActiveRecord::Migration[5.1]
|
||||
include Mastodon::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_column_with_default :mutes, :hide_notifications, :boolean, default: true, allow_null: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :mutes, :hide_notifications
|
||||
end
|
||||
end
|
|
@ -203,6 +203,7 @@ ActiveRecord::Schema.define(version: 20171114080328) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.bigint "account_id", null: false
|
||||
t.bigint "target_account_id", null: false
|
||||
t.boolean "hide_notifications", default: true, null: false
|
||||
t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true
|
||||
end
|
||||
|
||||
|
|
|
@ -137,6 +137,35 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
|
|||
it 'creates a muting relation' do
|
||||
expect(user.account.muting?(other_account)).to be true
|
||||
end
|
||||
|
||||
it 'mutes notifications' do
|
||||
expect(user.account.muting_notifications?(other_account)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #mute with notifications set to false' do
|
||||
let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
|
||||
|
||||
before do
|
||||
user.account.follow!(other_account)
|
||||
post :mute, params: {id: other_account.id, notifications: false }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
|
||||
it 'does not remove the following relation between user and target user' do
|
||||
expect(user.account.following?(other_account)).to be true
|
||||
end
|
||||
|
||||
it 'creates a muting relation' do
|
||||
expect(user.account.muting?(other_account)).to be true
|
||||
end
|
||||
|
||||
it 'does not mute notifications' do
|
||||
expect(user.account.muting_notifications?(other_account)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #unmute' do
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Api::V1::MutesController, type: :controller do
|
|||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
|
||||
|
||||
before do
|
||||
Fabricate(:mute, account: user.account)
|
||||
Fabricate(:mute, account: user.account, hide_notifications: false)
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe AccountInteractions do
|
||||
describe 'muting an account' do
|
||||
let(:me) { Fabricate(:account, username: 'Me') }
|
||||
let(:you) { Fabricate(:account, username: 'You') }
|
||||
|
||||
context 'with the notifications option unspecified' do
|
||||
before do
|
||||
me.mute!(you)
|
||||
end
|
||||
|
||||
it 'defaults to muting notifications' do
|
||||
expect(me.muting_notifications?(you)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the notifications option set to false' do
|
||||
before do
|
||||
me.mute!(you, notifications: false)
|
||||
end
|
||||
|
||||
it 'does not mute notifications' do
|
||||
expect(me.muting_notifications?(you)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the notifications option set to true' do
|
||||
before do
|
||||
me.mute!(you, notifications: true)
|
||||
end
|
||||
|
||||
it 'does mute notifications' do
|
||||
expect(me.muting_notifications?(you)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,4 +32,36 @@ RSpec.describe MuteService do
|
|||
account.muting?(target_account)
|
||||
}.from(false).to(true)
|
||||
end
|
||||
|
||||
context 'without specifying a notifications parameter' do
|
||||
it 'mutes notifications from the account' do
|
||||
is_expected.to change {
|
||||
account.muting_notifications?(target_account)
|
||||
}.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a true notifications parameter' do
|
||||
subject do
|
||||
-> { described_class.new.call(account, target_account, notifications: true) }
|
||||
end
|
||||
|
||||
it 'mutes notifications from the account' do
|
||||
is_expected.to change {
|
||||
account.muting_notifications?(target_account)
|
||||
}.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a false notifications parameter' do
|
||||
subject do
|
||||
-> { described_class.new.call(account, target_account, notifications: false) }
|
||||
end
|
||||
|
||||
it 'does not mute notifications from the account' do
|
||||
is_expected.to_not change {
|
||||
account.muting_notifications?(target_account)
|
||||
}.from(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,16 @@ RSpec.describe NotifyService do
|
|||
is_expected.to_not change(Notification, :count)
|
||||
end
|
||||
|
||||
it 'does not notify when sender is muted with hide_notifications' do
|
||||
recipient.mute!(sender, notifications: true)
|
||||
is_expected.to_not change(Notification, :count)
|
||||
end
|
||||
|
||||
it 'does notify when sender is muted without hide_notifications' do
|
||||
recipient.mute!(sender, notifications: false)
|
||||
is_expected.to change(Notification, :count)
|
||||
end
|
||||
|
||||
it 'does not notify when sender\'s domain is blocked' do
|
||||
recipient.block_domain!(sender.domain)
|
||||
is_expected.to_not change(Notification, :count)
|
||||
|
|
Reference in New Issue