Add ability to filter followed accounts' posts by language (#19095)
parent
882e54c786
commit
50948b46aa
|
@ -30,12 +30,12 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
self.response_body = Oj.dump(response.body)
|
self.response_body = Oj.dump(response.body)
|
||||||
self.status = response.status
|
self.status = response.status
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
render json: ValidationErrorFormatter.new(e, :'account.username' => :username, :'invite_request.text' => :reason).as_json, status: :unprocessable_entity
|
render json: ValidationErrorFormatter.new(e, 'account.username': :username, 'invite_request.text': :reason).as_json, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
|
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, languages: params.key?(:languages) ? params[:languages] : nil, with_rate_limit: true)
|
||||||
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
|
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify?, languages: follow.languages } }, requested_map: { @account.id => false } }
|
||||||
|
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,6 +51,7 @@ const messages = defineMessages({
|
||||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||||
|
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const dateFormatOptions = {
|
const dateFormatOptions = {
|
||||||
|
@ -85,6 +86,7 @@ class Header extends ImmutablePureComponent {
|
||||||
onEndorseToggle: PropTypes.func.isRequired,
|
onEndorseToggle: PropTypes.func.isRequired,
|
||||||
onAddToList: PropTypes.func.isRequired,
|
onAddToList: PropTypes.func.isRequired,
|
||||||
onEditAccountNote: PropTypes.func.isRequired,
|
onEditAccountNote: PropTypes.func.isRequired,
|
||||||
|
onChangeLanguages: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
|
@ -212,6 +214,9 @@ class Header extends ImmutablePureComponent {
|
||||||
} else {
|
} else {
|
||||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
|
||||||
|
menu.push(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||||
|
|
|
@ -22,6 +22,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
onUnblockDomain: PropTypes.func.isRequired,
|
||||||
onEndorseToggle: PropTypes.func.isRequired,
|
onEndorseToggle: PropTypes.func.isRequired,
|
||||||
onAddToList: PropTypes.func.isRequired,
|
onAddToList: PropTypes.func.isRequired,
|
||||||
|
onChangeLanguages: PropTypes.func.isRequired,
|
||||||
hideTabs: PropTypes.bool,
|
hideTabs: PropTypes.bool,
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
|
@ -91,6 +92,10 @@ export default class Header extends ImmutablePureComponent {
|
||||||
this.props.onEditAccountNote(this.props.account);
|
this.props.onEditAccountNote(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleChangeLanguages = () => {
|
||||||
|
this.props.onChangeLanguages(this.props.account);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, hidden, hideTabs } = this.props;
|
const { account, hidden, hideTabs } = this.props;
|
||||||
|
|
||||||
|
@ -117,6 +122,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onEndorseToggle={this.handleEndorseToggle}
|
onEndorseToggle={this.handleEndorseToggle}
|
||||||
onAddToList={this.handleAddToList}
|
onAddToList={this.handleAddToList}
|
||||||
onEditAccountNote={this.handleEditAccountNote}
|
onEditAccountNote={this.handleEditAccountNote}
|
||||||
|
onChangeLanguages={this.handleChangeLanguages}
|
||||||
domain={this.props.domain}
|
domain={this.props.domain}
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -127,6 +127,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onChangeLanguages (account) {
|
||||||
|
dispatch(openModal('SUBSCRIBED_LANGUAGES', {
|
||||||
|
accountId: account.get('id'),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable';
|
||||||
|
import { languages as preloadedLanguages } from 'mastodon/initial_state';
|
||||||
|
import Option from 'mastodon/features/report/components/option';
|
||||||
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
import IconButton from 'mastodon/components/icon_button';
|
||||||
|
import Button from 'mastodon/components/button';
|
||||||
|
import { followAccount } from 'mastodon/actions/accounts';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAccountLanguages = createSelector([
|
||||||
|
(state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()),
|
||||||
|
state => state.get('statuses'),
|
||||||
|
], (statusIds, statuses) =>
|
||||||
|
new ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { accountId }) => ({
|
||||||
|
acct: state.getIn(['accounts', accountId, 'acct']),
|
||||||
|
availableLanguages: getAccountLanguages(state, accountId),
|
||||||
|
selectedLanguages: ImmutableSet(state.getIn(['relationships', accountId, 'languages']) || ImmutableList()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||||
|
|
||||||
|
onSubmit (languages) {
|
||||||
|
dispatch(followAccount(accountId, { languages }));
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class SubscribedLanguagesModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
accountId: PropTypes.string.isRequired,
|
||||||
|
acct: PropTypes.string.isRequired,
|
||||||
|
availableLanguages: ImmutablePropTypes.setOf(PropTypes.string),
|
||||||
|
selectedLanguages: ImmutablePropTypes.setOf(PropTypes.string),
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
submit: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
languages: preloadedLanguages,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
selectedLanguages: this.props.selectedLanguages,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleLanguageToggle = (value, checked) => {
|
||||||
|
const { selectedLanguages } = this.state;
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
this.setState({ selectedLanguages: selectedLanguages.add(value) });
|
||||||
|
} else {
|
||||||
|
this.setState({ selectedLanguages: selectedLanguages.delete(value) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
this.props.onSubmit(this.state.selectedLanguages.toArray());
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem (value) {
|
||||||
|
const language = this.props.languages.find(language => language[0] === value);
|
||||||
|
const checked = this.state.selectedLanguages.includes(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Option
|
||||||
|
key={value}
|
||||||
|
name='languages'
|
||||||
|
value={value}
|
||||||
|
label={language[1]}
|
||||||
|
checked={checked}
|
||||||
|
onToggle={this.handleLanguageToggle}
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { acct, availableLanguages, selectedLanguages, intl, onClose } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal report-dialog-modal'>
|
||||||
|
<div className='report-modal__target'>
|
||||||
|
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
|
||||||
|
<FormattedMessage id='subscribed_languages.target' defaultMessage='Change subscribed languages for {target}' values={{ target: <strong>{acct}</strong> }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='report-dialog-modal__container'>
|
||||||
|
<p className='report-dialog-modal__lead'><FormattedMessage id='subscribed_languages.lead' defaultMessage='Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.' /></p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{availableLanguages.union(selectedLanguages).map(value => this.renderItem(value))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-spacer' />
|
||||||
|
|
||||||
|
<div className='report-dialog-modal__actions'>
|
||||||
|
<Button disabled={is(this.state.selectedLanguages, this.props.selectedLanguages)} onClick={this.handleSubmit}><FormattedMessage id='subscribed_languages.save' defaultMessage='Save changes' /></Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import VideoModal from './video_modal';
|
||||||
import BoostModal from './boost_modal';
|
import BoostModal from './boost_modal';
|
||||||
import AudioModal from './audio_modal';
|
import AudioModal from './audio_modal';
|
||||||
import ConfirmationModal from './confirmation_modal';
|
import ConfirmationModal from './confirmation_modal';
|
||||||
|
import SubscribedLanguagesModal from 'mastodon/features/subscribed_languages_modal';
|
||||||
import FocalPointModal from './focal_point_modal';
|
import FocalPointModal from './focal_point_modal';
|
||||||
import {
|
import {
|
||||||
MuteModal,
|
MuteModal,
|
||||||
|
@ -39,6 +40,7 @@ const MODAL_COMPONENTS = {
|
||||||
'LIST_ADDER': ListAdder,
|
'LIST_ADDER': ListAdder,
|
||||||
'COMPARE_HISTORY': CompareHistoryModal,
|
'COMPARE_HISTORY': CompareHistoryModal,
|
||||||
'FILTER': FilterModal,
|
'FILTER': FilterModal,
|
||||||
|
'SUBSCRIBED_LANGUAGES': () => Promise.resolve({ default: SubscribedLanguagesModal }),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModalRoot extends React.PureComponent {
|
export default class ModalRoot extends React.PureComponent {
|
||||||
|
|
|
@ -1030,6 +1030,10 @@
|
||||||
"defaultMessage": "Open moderation interface for @{name}",
|
"defaultMessage": "Open moderation interface for @{name}",
|
||||||
"id": "status.admin_account"
|
"id": "status.admin_account"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Change subscribed languages",
|
||||||
|
"id": "account.languages"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Follows you",
|
"defaultMessage": "Follows you",
|
||||||
"id": "account.follows_you"
|
"id": "account.follows_you"
|
||||||
|
@ -3350,6 +3354,27 @@
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/status/index.json"
|
"path": "app/javascript/mastodon/features/status/index.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"descriptors": [
|
||||||
|
{
|
||||||
|
"defaultMessage": "Close",
|
||||||
|
"id": "lightbox.close"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Change subscribed languages for {target}",
|
||||||
|
"id": "subscribed_languages.target"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
|
||||||
|
"id": "subscribed_languages.lead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Save changes",
|
||||||
|
"id": "subscribed_languages.save"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "app/javascript/mastodon/features/subscribed_languages_modal/index.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"account.follows_you": "Follows you",
|
"account.follows_you": "Follows you",
|
||||||
"account.hide_reblogs": "Hide boosts from @{name}",
|
"account.hide_reblogs": "Hide boosts from @{name}",
|
||||||
"account.joined": "Joined {date}",
|
"account.joined": "Joined {date}",
|
||||||
|
"account.languages": "Change subscribed languages",
|
||||||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||||
"account.media": "Media",
|
"account.media": "Media",
|
||||||
|
@ -522,6 +523,9 @@
|
||||||
"status.uncached_media_warning": "Not available",
|
"status.uncached_media_warning": "Not available",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
"status.unpin": "Unpin from profile",
|
"status.unpin": "Unpin from profile",
|
||||||
|
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
|
||||||
|
"subscribed_languages.save": "Save changes",
|
||||||
|
"subscribed_languages.target": "Change subscribed languages for {target}",
|
||||||
"suggestions.dismiss": "Dismiss suggestion",
|
"suggestions.dismiss": "Dismiss suggestion",
|
||||||
"suggestions.header": "You might be interested in…",
|
"suggestions.header": "You might be interested in…",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
|
|
|
@ -354,6 +354,7 @@ class FeedManager
|
||||||
def filter_from_home?(status, receiver_id, crutches)
|
def filter_from_home?(status, receiver_id, crutches)
|
||||||
return false if receiver_id == status.account_id
|
return false if receiver_id == status.account_id
|
||||||
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
|
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
|
||||||
|
return true if crutches[:languages][status.account_id].present? && status.language.present? && !crutches[:languages][status.account_id].include?(status.language)
|
||||||
|
|
||||||
check_for_blocks = crutches[:active_mentions][status.id] || []
|
check_for_blocks = crutches[:active_mentions][status.id] || []
|
||||||
check_for_blocks.concat([status.account_id])
|
check_for_blocks.concat([status.account_id])
|
||||||
|
@ -542,6 +543,7 @@ class FeedManager
|
||||||
end
|
end
|
||||||
|
|
||||||
crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).index_with(true)
|
crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).index_with(true)
|
||||||
|
crutches[:languages] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:account_id)).pluck(:target_account_id, :languages).to_h
|
||||||
crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).index_with(true)
|
crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).index_with(true)
|
||||||
crutches[:blocking] = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
|
crutches[:blocking] = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
|
||||||
crutches[:muting] = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
|
crutches[:muting] = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
|
||||||
|
|
|
@ -9,6 +9,7 @@ module AccountInteractions
|
||||||
mapping[follow.target_account_id] = {
|
mapping[follow.target_account_id] = {
|
||||||
reblogs: follow.show_reblogs?,
|
reblogs: follow.show_reblogs?,
|
||||||
notify: follow.notify?,
|
notify: follow.notify?,
|
||||||
|
languages: follow.languages,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -38,6 +39,7 @@ module AccountInteractions
|
||||||
mapping[follow_request.target_account_id] = {
|
mapping[follow_request.target_account_id] = {
|
||||||
reblogs: follow_request.show_reblogs?,
|
reblogs: follow_request.show_reblogs?,
|
||||||
notify: follow_request.notify?,
|
notify: follow_request.notify?,
|
||||||
|
languages: follow_request.languages,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -100,12 +102,13 @@ module AccountInteractions
|
||||||
has_many :announcement_mutes, dependent: :destroy
|
has_many :announcement_mutes, dependent: :destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
def follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
||||||
rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
||||||
.find_or_create_by!(target_account: other_account)
|
.find_or_create_by!(target_account: other_account)
|
||||||
|
|
||||||
rel.show_reblogs = reblogs unless reblogs.nil?
|
rel.show_reblogs = reblogs unless reblogs.nil?
|
||||||
rel.notify = notify unless notify.nil?
|
rel.notify = notify unless notify.nil?
|
||||||
|
rel.languages = languages unless languages.nil?
|
||||||
|
|
||||||
rel.save! if rel.changed?
|
rel.save! if rel.changed?
|
||||||
|
|
||||||
|
@ -114,12 +117,13 @@ module AccountInteractions
|
||||||
rel
|
rel
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
def request_follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
||||||
rel = follow_requests.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
rel = follow_requests.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
||||||
.find_or_create_by!(target_account: other_account)
|
.find_or_create_by!(target_account: other_account)
|
||||||
|
|
||||||
rel.show_reblogs = reblogs unless reblogs.nil?
|
rel.show_reblogs = reblogs unless reblogs.nil?
|
||||||
rel.notify = notify unless notify.nil?
|
rel.notify = notify unless notify.nil?
|
||||||
|
rel.languages = languages unless languages.nil?
|
||||||
|
|
||||||
rel.save! if rel.changed?
|
rel.save! if rel.changed?
|
||||||
|
|
||||||
|
@ -288,8 +292,7 @@ module AccountInteractions
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def remove_potential_friendship(other_account, mutual = false)
|
def remove_potential_friendship(other_account)
|
||||||
PotentialFriendshipTracker.remove(id, other_account.id)
|
PotentialFriendshipTracker.remove(id, other_account.id)
|
||||||
PotentialFriendshipTracker.remove(other_account.id, id) if mutual
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,9 +30,9 @@ class Export
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_following_accounts_csv
|
def to_following_accounts_csv
|
||||||
CSV.generate(headers: ['Account address', 'Show boosts'], write_headers: true) do |csv|
|
CSV.generate(headers: ['Account address', 'Show boosts', 'Notify on new posts', 'Languages'], write_headers: true) do |csv|
|
||||||
account.active_relationships.includes(:target_account).reorder(id: :desc).each do |follow|
|
account.active_relationships.includes(:target_account).reorder(id: :desc).each do |follow|
|
||||||
csv << [acct(follow.target_account), follow.show_reblogs]
|
csv << [acct(follow.target_account), follow.show_reblogs, follow.notify, follow.languages&.join(', ')]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
# show_reblogs :boolean default(TRUE), not null
|
# show_reblogs :boolean default(TRUE), not null
|
||||||
# uri :string
|
# uri :string
|
||||||
# notify :boolean default(FALSE), not null
|
# notify :boolean default(FALSE), not null
|
||||||
|
# languages :string is an Array
|
||||||
#
|
#
|
||||||
|
|
||||||
class Follow < ApplicationRecord
|
class Follow < ApplicationRecord
|
||||||
|
@ -27,6 +28,7 @@ class Follow < ApplicationRecord
|
||||||
has_one :notification, as: :activity, dependent: :destroy
|
has_one :notification, as: :activity, dependent: :destroy
|
||||||
|
|
||||||
validates :account_id, uniqueness: { scope: :target_account_id }
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
|
validates :languages, language: true
|
||||||
|
|
||||||
scope :recent, -> { reorder(id: :desc) }
|
scope :recent, -> { reorder(id: :desc) }
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ class Follow < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def revoke_request!
|
def revoke_request!
|
||||||
FollowRequest.create!(account: account, target_account: target_account, show_reblogs: show_reblogs, notify: notify, uri: uri)
|
FollowRequest.create!(account: account, target_account: target_account, show_reblogs: show_reblogs, notify: notify, languages: languages, uri: uri)
|
||||||
destroy!
|
destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
# show_reblogs :boolean default(TRUE), not null
|
# show_reblogs :boolean default(TRUE), not null
|
||||||
# uri :string
|
# uri :string
|
||||||
# notify :boolean default(FALSE), not null
|
# notify :boolean default(FALSE), not null
|
||||||
|
# languages :string is an Array
|
||||||
#
|
#
|
||||||
|
|
||||||
class FollowRequest < ApplicationRecord
|
class FollowRequest < ApplicationRecord
|
||||||
|
@ -27,9 +28,10 @@ class FollowRequest < ApplicationRecord
|
||||||
has_one :notification, as: :activity, dependent: :destroy
|
has_one :notification, as: :activity, dependent: :destroy
|
||||||
|
|
||||||
validates :account_id, uniqueness: { scope: :target_account_id }
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
|
validates :languages, language: true
|
||||||
|
|
||||||
def authorize!
|
def authorize!
|
||||||
account.follow!(target_account, reblogs: show_reblogs, notify: notify, uri: uri, bypass_limit: true)
|
account.follow!(target_account, reblogs: show_reblogs, notify: notify, languages: languages, uri: uri, bypass_limit: true)
|
||||||
MergeWorker.perform_async(target_account.id, account.id) if account.local?
|
MergeWorker.perform_async(target_account.id, account.id) if account.local?
|
||||||
destroy!
|
destroy!
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::RelationshipSerializer < ActiveModel::Serializer
|
class REST::RelationshipSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :following, :showing_reblogs, :notifying, :followed_by,
|
attributes :id, :following, :showing_reblogs, :notifying, :languages, :followed_by,
|
||||||
:blocking, :blocked_by, :muting, :muting_notifications, :requested,
|
:blocking, :blocked_by, :muting, :muting_notifications, :requested,
|
||||||
:domain_blocking, :endorsed, :note
|
:domain_blocking, :endorsed, :note
|
||||||
|
|
||||||
|
@ -25,6 +25,11 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def languages
|
||||||
|
(instance_options[:relationships].following[object.id] || {})[:languages] ||
|
||||||
|
(instance_options[:relationships].requested[object.id] || {})[:languages]
|
||||||
|
end
|
||||||
|
|
||||||
def followed_by
|
def followed_by
|
||||||
instance_options[:relationships].followed_by[object.id] || false
|
instance_options[:relationships].followed_by[object.id] || false
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,7 @@ class FollowService < BaseService
|
||||||
# @param [Hash] options
|
# @param [Hash] options
|
||||||
# @option [Boolean] :reblogs Whether or not to show reblogs, defaults to true
|
# @option [Boolean] :reblogs Whether or not to show reblogs, defaults to true
|
||||||
# @option [Boolean] :notify Whether to create notifications about new posts, defaults to false
|
# @option [Boolean] :notify Whether to create notifications about new posts, defaults to false
|
||||||
|
# @option [Array<String>] :languages Which languages to allow on the home feed from this account, defaults to all
|
||||||
# @option [Boolean] :bypass_locked
|
# @option [Boolean] :bypass_locked
|
||||||
# @option [Boolean] :bypass_limit Allow following past the total follow number
|
# @option [Boolean] :bypass_limit Allow following past the total follow number
|
||||||
# @option [Boolean] :with_rate_limit
|
# @option [Boolean] :with_rate_limit
|
||||||
|
@ -57,15 +58,15 @@ class FollowService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_follow_options!
|
def change_follow_options!
|
||||||
@source_account.follow!(@target_account, reblogs: @options[:reblogs], notify: @options[:notify])
|
@source_account.follow!(@target_account, **follow_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_follow_request_options!
|
def change_follow_request_options!
|
||||||
@source_account.request_follow!(@target_account, reblogs: @options[:reblogs], notify: @options[:notify])
|
@source_account.request_follow!(@target_account, **follow_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_follow!
|
def request_follow!
|
||||||
follow_request = @source_account.request_follow!(@target_account, reblogs: @options[:reblogs], notify: @options[:notify], rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit])
|
follow_request = @source_account.request_follow!(@target_account, **follow_options.merge(rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit]))
|
||||||
|
|
||||||
if @target_account.local?
|
if @target_account.local?
|
||||||
LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request')
|
LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request')
|
||||||
|
@ -77,7 +78,7 @@ class FollowService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def direct_follow!
|
def direct_follow!
|
||||||
follow = @source_account.follow!(@target_account, reblogs: @options[:reblogs], notify: @options[:notify], rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit])
|
follow = @source_account.follow!(@target_account, **follow_options.merge(rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit]))
|
||||||
|
|
||||||
LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, 'follow')
|
LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, 'follow')
|
||||||
MergeWorker.perform_async(@target_account.id, @source_account.id)
|
MergeWorker.perform_async(@target_account.id, @source_account.id)
|
||||||
|
@ -88,4 +89,8 @@ class FollowService < BaseService
|
||||||
def build_json(follow_request)
|
def build_json(follow_request)
|
||||||
Oj.dump(serialize_payload(follow_request, ActivityPub::FollowSerializer))
|
Oj.dump(serialize_payload(follow_request, ActivityPub::FollowSerializer))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def follow_options
|
||||||
|
@options.slice(:reblogs, :notify, :languages)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ class ImportService < BaseService
|
||||||
|
|
||||||
def import_follows!
|
def import_follows!
|
||||||
parse_import_data!(['Account address'])
|
parse_import_data!(['Account address'])
|
||||||
import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true })
|
import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true }, notify: { header: 'Notify on new posts', default: false }, languages: { header: 'Languages', default: nil })
|
||||||
end
|
end
|
||||||
|
|
||||||
def import_blocks!
|
def import_blocks!
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class LanguageValidator < ActiveModel::EachValidator
|
||||||
|
include LanguagesHelper
|
||||||
|
|
||||||
|
def validate_each(record, attribute, value)
|
||||||
|
record.errors.add(attribute, :invalid) unless valid?(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid?(str)
|
||||||
|
if str.nil?
|
||||||
|
true
|
||||||
|
elsif str.is_a?(Array)
|
||||||
|
str.all? { |x| valid_locale?(x) }
|
||||||
|
else
|
||||||
|
valid_locale?(str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,6 +12,7 @@ class RefollowWorker
|
||||||
target_account.passive_relationships.where(account: Account.where(domain: nil)).includes(:account).reorder(nil).find_each do |follow|
|
target_account.passive_relationships.where(account: Account.where(domain: nil)).includes(:account).reorder(nil).find_each do |follow|
|
||||||
reblogs = follow.show_reblogs?
|
reblogs = follow.show_reblogs?
|
||||||
notify = follow.notify?
|
notify = follow.notify?
|
||||||
|
languages = follow.languages
|
||||||
|
|
||||||
# Locally unfollow remote account
|
# Locally unfollow remote account
|
||||||
follower = follow.account
|
follower = follow.account
|
||||||
|
@ -19,7 +20,7 @@ class RefollowWorker
|
||||||
|
|
||||||
# Schedule re-follow
|
# Schedule re-follow
|
||||||
begin
|
begin
|
||||||
FollowService.new.call(follower, target_account, reblogs: reblogs, notify: notify, bypass_limit: true)
|
FollowService.new.call(follower, target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_limit: true)
|
||||||
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
|
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,8 +13,9 @@ class UnfollowFollowWorker
|
||||||
follow = follower_account.active_relationships.find_by(target_account: old_target_account)
|
follow = follower_account.active_relationships.find_by(target_account: old_target_account)
|
||||||
reblogs = follow&.show_reblogs?
|
reblogs = follow&.show_reblogs?
|
||||||
notify = follow&.notify?
|
notify = follow&.notify?
|
||||||
|
languages = follow&.languages
|
||||||
|
|
||||||
FollowService.new.call(follower_account, new_target_account, reblogs: reblogs, notify: notify, bypass_locked: bypass_locked, bypass_limit: true)
|
FollowService.new.call(follower_account, new_target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_locked: bypass_locked, bypass_limit: true)
|
||||||
UnfollowService.new.call(follower_account, old_target_account, skip_unmerge: true)
|
UnfollowService.new.call(follower_account, old_target_account, skip_unmerge: true)
|
||||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
true
|
true
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddLanguagesToFollows < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :follows, :languages, :string, array: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddLanguagesToFollowRequests < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :follow_requests, :languages, :string, array: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2022_08_27_195229) do
|
ActiveRecord::Schema.define(version: 2022_08_29_192658) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -461,6 +461,7 @@ ActiveRecord::Schema.define(version: 2022_08_27_195229) do
|
||||||
t.boolean "show_reblogs", default: true, null: false
|
t.boolean "show_reblogs", default: true, null: false
|
||||||
t.string "uri"
|
t.string "uri"
|
||||||
t.boolean "notify", default: false, null: false
|
t.boolean "notify", default: false, null: false
|
||||||
|
t.string "languages", array: true
|
||||||
t.index ["account_id", "target_account_id"], name: "index_follow_requests_on_account_id_and_target_account_id", unique: true
|
t.index ["account_id", "target_account_id"], name: "index_follow_requests_on_account_id_and_target_account_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -472,6 +473,7 @@ ActiveRecord::Schema.define(version: 2022_08_27_195229) do
|
||||||
t.boolean "show_reblogs", default: true, null: false
|
t.boolean "show_reblogs", default: true, null: false
|
||||||
t.string "uri"
|
t.string "uri"
|
||||||
t.boolean "notify", default: false, null: false
|
t.boolean "notify", default: false, null: false
|
||||||
|
t.string "languages", array: true
|
||||||
t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true
|
t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true
|
||||||
t.index ["target_account_id"], name: "index_follows_on_target_account_id"
|
t.index ["target_account_id"], name: "index_follows_on_target_account_id"
|
||||||
end
|
end
|
||||||
|
|
|
@ -145,6 +145,17 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
|
||||||
expect(json[:showing_reblogs]).to be false
|
expect(json[:showing_reblogs]).to be false
|
||||||
expect(json[:notifying]).to be true
|
expect(json[:notifying]).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'changes languages option' do
|
||||||
|
post :follow, params: { id: other_account.id, languages: %w(en es) }
|
||||||
|
|
||||||
|
json = body_as_json
|
||||||
|
|
||||||
|
expect(json[:following]).to be true
|
||||||
|
expect(json[:showing_reblogs]).to be false
|
||||||
|
expect(json[:notifying]).to be false
|
||||||
|
expect(json[:languages]).to match_array %w(en es)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe Settings::Exports::FollowingAccountsController do
|
||||||
sign_in user, scope: :user
|
sign_in user, scope: :user
|
||||||
get :index, format: :csv
|
get :index, format: :csv
|
||||||
|
|
||||||
expect(response.body).to eq "Account address,Show boosts\nusername@domain,true\n"
|
expect(response.body).to eq "Account address,Show boosts,Notify on new posts,Languages\nusername@domain,true,false,\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -127,6 +127,18 @@ RSpec.describe FeedManager do
|
||||||
reblog = Fabricate(:status, reblog: status, account: jeff)
|
reblog = Fabricate(:status, reblog: status, account: jeff)
|
||||||
expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true
|
expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns true for German post when follow is set to English only' do
|
||||||
|
alice.follow!(bob, languages: %w(en))
|
||||||
|
status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de')
|
||||||
|
expect(FeedManager.instance.filter?(:home, status, alice)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false for German post when follow is set to German' do
|
||||||
|
alice.follow!(bob, languages: %w(de))
|
||||||
|
status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de')
|
||||||
|
expect(FeedManager.instance.filter?(:home, status, alice)).to be false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'for mentions feed' do
|
context 'for mentions feed' do
|
||||||
|
|
|
@ -14,7 +14,7 @@ describe AccountInteractions do
|
||||||
context 'account with Follow' do
|
context 'account with Follow' do
|
||||||
it 'returns { target_account_id => true }' do
|
it 'returns { target_account_id => true }' do
|
||||||
Fabricate(:follow, account: account, target_account: target_account)
|
Fabricate(:follow, account: account, target_account: target_account)
|
||||||
is_expected.to eq(target_account_id => { reblogs: true, notify: false })
|
is_expected.to eq(target_account_id => { reblogs: true, notify: false, languages: nil })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ describe Export do
|
||||||
results = export.strip.split("\n")
|
results = export.strip.split("\n")
|
||||||
|
|
||||||
expect(results.size).to eq 3
|
expect(results.size).to eq 3
|
||||||
expect(results.first).to eq 'Account address,Show boosts'
|
expect(results.first).to eq 'Account address,Show boosts,Notify on new posts,Languages'
|
||||||
expect(results.second).to eq 'one@local.host,true'
|
expect(results.second).to eq 'one@local.host,true,false,'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe FollowRequest, type: :model do
|
||||||
let(:target_account) { Fabricate(:account) }
|
let(:target_account) { Fabricate(:account) }
|
||||||
|
|
||||||
it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do
|
it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do
|
||||||
expect(account).to receive(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri, bypass_limit: true)
|
expect(account).to receive(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri, languages: nil, bypass_limit: true)
|
||||||
expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id)
|
expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id)
|
||||||
expect(follow_request).to receive(:destroy!)
|
expect(follow_request).to receive(:destroy!)
|
||||||
follow_request.authorize!
|
follow_request.authorize!
|
||||||
|
|
|
@ -121,6 +121,19 @@ RSpec.describe FollowService, type: :service do
|
||||||
expect(sender.muting_reblogs?(bob)).to be false
|
expect(sender.muting_reblogs?(bob)).to be false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'already followed account, changing languages' do
|
||||||
|
let(:bob) { Fabricate(:account, username: 'bob') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sender.follow!(bob)
|
||||||
|
subject.call(sender, bob, languages: %w(en es))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'changes languages' do
|
||||||
|
expect(Follow.find_by(account: sender, target_account: bob)&.languages).to match_array %w(en es)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'remote ActivityPub account' do
|
context 'remote ActivityPub account' do
|
||||||
|
|
|
@ -23,8 +23,8 @@ describe RefollowWorker do
|
||||||
result = subject.perform(account.id)
|
result = subject.perform(account.id)
|
||||||
|
|
||||||
expect(result).to be_nil
|
expect(result).to be_nil
|
||||||
expect(service).to have_received(:call).with(alice, account, reblogs: true, notify: false, bypass_limit: true)
|
expect(service).to have_received(:call).with(alice, account, reblogs: true, notify: false, languages: nil, bypass_limit: true)
|
||||||
expect(service).to have_received(:call).with(bob, account, reblogs: false, notify: false, bypass_limit: true)
|
expect(service).to have_received(:call).with(bob, account, reblogs: false, notify: false, languages: nil, bypass_limit: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Reference in New Issue