"Show reblogs" per-follower UI/database changes
TODO: * Tests (particularly for FollowRequests). * Anything to respect the setting when putting reblogs in timelines.
This commit is contained in:
		| @@ -13,9 +13,11 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def follow | ||||
|     FollowService.new.call(current_user.account, @account.acct) | ||||
|     reblogs_arg = { reblogs: params[:reblogs] } | ||||
|      | ||||
|     FollowService.new.call(current_user.account, @account.acct, reblogs_arg) | ||||
|  | ||||
|     options = @account.locked? ? {} : { following_map: { @account.id => true }, requested_map: { @account.id => false } } | ||||
|     options = @account.locked? ? {} : { following_map: reblogs_arg, requested_map: { @account.id => false } } | ||||
|  | ||||
|     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options) | ||||
|   end | ||||
|   | ||||
| @@ -152,7 +152,7 @@ appropriate icon. | ||||
|             <IconButton | ||||
|               size={26} | ||||
|               icon={following ? 'user-times' : 'user-plus'} | ||||
|               active={following} | ||||
|               active={following ? true : false} | ||||
|               title={intl.formatMessage(following ? messages.unfollow : messages.follow)} | ||||
|               onClick={this.props.onFollow} | ||||
|             /> | ||||
|   | ||||
| @@ -105,11 +105,11 @@ export function fetchAccountFail(id, error) { | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function followAccount(id) { | ||||
| export function followAccount(id, reblogs = true) { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(followAccountRequest(id)); | ||||
|  | ||||
|     api(getState).post(`/api/v1/accounts/${id}/follow`).then(response => { | ||||
|     api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => { | ||||
|       dispatch(followAccountSuccess(response.data)); | ||||
|     }).catch(error => { | ||||
|       dispatch(followAccountFail(error)); | ||||
|   | ||||
| @@ -93,7 +93,7 @@ export default class Account extends ImmutablePureComponent { | ||||
|           </div> | ||||
|         ); | ||||
|       } 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 ? true : false} />; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,8 @@ const messages = defineMessages({ | ||||
|   media: { id: 'account.media', defaultMessage: 'Media' }, | ||||
|   blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' }, | ||||
|   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, | ||||
|   hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' }, | ||||
|   showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' }, | ||||
| }); | ||||
|  | ||||
| @injectIntl | ||||
| @@ -30,6 +32,7 @@ export default class ActionBar extends React.PureComponent { | ||||
|     onFollow: PropTypes.func, | ||||
|     onBlock: PropTypes.func.isRequired, | ||||
|     onMention: PropTypes.func.isRequired, | ||||
|     onReblogToggle: PropTypes.func.isRequired, | ||||
|     onReport: PropTypes.func.isRequired, | ||||
|     onMute: PropTypes.func.isRequired, | ||||
|     onBlockDomain: PropTypes.func.isRequired, | ||||
| @@ -60,6 +63,15 @@ export default class ActionBar extends React.PureComponent { | ||||
|     if (account.get('id') === me) { | ||||
|       menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); | ||||
|     } else { | ||||
|       const following = account.getIn(['relationship', 'following']); | ||||
|       if (following) { | ||||
|         if (following.get('reblogs')) { | ||||
|           menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); | ||||
|         } else { | ||||
|           menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (account.getIn(['relationship', 'muting'])) { | ||||
|         menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); | ||||
|       } else { | ||||
|   | ||||
| @@ -14,6 +14,7 @@ export default class Header extends ImmutablePureComponent { | ||||
|     onFollow: PropTypes.func.isRequired, | ||||
|     onBlock: PropTypes.func.isRequired, | ||||
|     onMention: PropTypes.func.isRequired, | ||||
|     onReblogToggle: PropTypes.func.isRequired, | ||||
|     onReport: PropTypes.func.isRequired, | ||||
|     onMute: PropTypes.func.isRequired, | ||||
|     onBlockDomain: PropTypes.func.isRequired, | ||||
| @@ -40,6 +41,10 @@ export default class Header extends ImmutablePureComponent { | ||||
|     this.props.onReport(this.props.account); | ||||
|   } | ||||
|  | ||||
|   handleReblogToggle = () => { | ||||
|     this.props.onReblogToggle(this.props.account); | ||||
|   } | ||||
|  | ||||
|   handleMute = () => { | ||||
|     this.props.onMute(this.props.account); | ||||
|   } | ||||
| @@ -80,6 +85,7 @@ export default class Header extends ImmutablePureComponent { | ||||
|           me={me} | ||||
|           onBlock={this.handleBlock} | ||||
|           onMention={this.handleMention} | ||||
|           onReblogToggle={this.handleReblogToggle} | ||||
|           onReport={this.handleReport} | ||||
|           onMute={this.handleMute} | ||||
|           onBlockDomain={this.handleBlockDomain} | ||||
|   | ||||
| @@ -68,6 +68,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | ||||
|     dispatch(mentionCompose(account, router)); | ||||
|   }, | ||||
|  | ||||
|   onReblogToggle (account) { | ||||
|     if (account.getIn(['relationship', 'following', 'reblogs'])) { | ||||
|       dispatch(followAccount(account.get('id'), false)); | ||||
|     } else { | ||||
|       dispatch(followAccount(account.get('id'), true)); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   onReport (account) { | ||||
|     dispatch(initReport(account)); | ||||
|   }, | ||||
|   | ||||
| @@ -5,7 +5,11 @@ module AccountInteractions | ||||
|  | ||||
|   class_methods do | ||||
|     def following_map(target_account_ids, account_id) | ||||
|       follow_mapping(Follow.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id) | ||||
|       Follow.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow, mapping| | ||||
|         mapping[follow.target_account_id] = { | ||||
|           reblogs: follow.show_reblogs? | ||||
|         } | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     def followed_by_map(target_account_ids, account_id) | ||||
| @@ -25,7 +29,11 @@ module AccountInteractions | ||||
|     end | ||||
|  | ||||
|     def requested_map(target_account_ids, account_id) | ||||
|       follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id) | ||||
|       FollowRequest.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow_request, mapping| | ||||
|         mapping[follow_request.target_account_id] = { | ||||
|           reblogs: follow_request.show_reblogs? | ||||
|         } | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     def domain_blocking_map(target_account_ids, account_id) | ||||
| @@ -66,8 +74,15 @@ module AccountInteractions | ||||
|     has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy | ||||
|   end | ||||
|  | ||||
|   def follow!(other_account) | ||||
|     active_relationships.find_or_create_by!(target_account: other_account) | ||||
|   def follow!(other_account, reblogs: nil) | ||||
|     reblogs = true if reblogs.nil? | ||||
|     rel = active_relationships.create_with(show_reblogs: reblogs).find_or_create_by!(target_account: other_account) | ||||
|     if rel.show_reblogs != reblogs | ||||
|       rel.show_reblogs = reblogs | ||||
|       rel.save! | ||||
|     end | ||||
|      | ||||
|     rel | ||||
|   end | ||||
|  | ||||
|   def block!(other_account) | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #  account_id        :integer          not null | ||||
| #  id                :integer          not null, primary key | ||||
| #  target_account_id :integer          not null | ||||
| #  show_reblogs      :boolean          default(TRUE), not null | ||||
| # | ||||
|  | ||||
| class Follow < ApplicationRecord | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #  account_id        :integer          not null | ||||
| #  id                :integer          not null, primary key | ||||
| #  target_account_id :integer          not null | ||||
| #  show_reblogs      :boolean          default(TRUE), not null | ||||
| # | ||||
|  | ||||
| class FollowRequest < ApplicationRecord | ||||
| @@ -21,7 +22,7 @@ class FollowRequest < ApplicationRecord | ||||
|   validates :account_id, uniqueness: { scope: :target_account_id } | ||||
|  | ||||
|   def authorize! | ||||
|     account.follow!(target_account) | ||||
|     account.follow!(target_account, reblogs: reblogs) | ||||
|     MergeWorker.perform_async(target_account.id, account.id) | ||||
|  | ||||
|     destroy! | ||||
|   | ||||
| @@ -6,25 +6,40 @@ class FollowService < BaseService | ||||
|   # Follow a remote user, notify remote user about the follow | ||||
|   # @param [Account] source_account From which to follow | ||||
|   # @param [String, Account] uri User URI to follow in the form of username@domain (or account record) | ||||
|   def call(source_account, uri) | ||||
|   def call(source_account, uri, reblogs: nil) | ||||
|     reblogs = true if reblogs.nil? | ||||
|     target_account = uri.is_a?(Account) ? uri : ResolveRemoteAccountService.new.call(uri) | ||||
|  | ||||
|     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? | ||||
|     raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) | ||||
|  | ||||
|     return if source_account.following?(target_account) || source_account.requested?(target_account) | ||||
|     if source_account.following?(target_account) | ||||
|       # We're already following this account, but we'll call follow! again to | ||||
|       # make sure the reblogs status is set correctly. | ||||
|       source_account.follow!(target_account, reblogs: reblogs) | ||||
|       return | ||||
|     elsif source_account.requested?(target_account) | ||||
|       # This isn't managed by a method in AccountInteractions, so we modify it | ||||
|       # ourselves if necessary. | ||||
|       req = follow_requests.find_by(target_account: other_account) | ||||
|       if req.show_reblogs != reblogs | ||||
|         req.show_reblogs = reblogs | ||||
|         req.save! | ||||
|       end | ||||
|       return | ||||
|     end | ||||
|  | ||||
|     if target_account.locked? || target_account.activitypub? | ||||
|       request_follow(source_account, target_account) | ||||
|       request_follow(source_account, target_account, reblogs: reblogs) | ||||
|     else | ||||
|       direct_follow(source_account, target_account) | ||||
|       direct_follow(source_account, target_account, reblogs: reblogs) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def request_follow(source_account, target_account) | ||||
|     follow_request = FollowRequest.create!(account: source_account, target_account: target_account) | ||||
|   def request_follow(source_account, target_account, reblogs: true) | ||||
|     follow_request = FollowRequest.create!(account: source_account, target_account: target_account, reblogs: reblogs) | ||||
|  | ||||
|     if target_account.local? | ||||
|       NotifyService.new.call(target_account, follow_request) | ||||
| @@ -38,8 +53,8 @@ class FollowService < BaseService | ||||
|     follow_request | ||||
|   end | ||||
|  | ||||
|   def direct_follow(source_account, target_account) | ||||
|     follow = source_account.follow!(target_account) | ||||
|   def direct_follow(source_account, target_account, reblogs: true) | ||||
|     follow = source_account.follow!(target_account, reblogs: reblogs) | ||||
|  | ||||
|     if target_account.local? | ||||
|       NotifyService.new.call(target_account, follow) | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
| # | ||||
| # It's strongly recommended that you check this file into your version control system. | ||||
|  | ||||
| ActiveRecord::Schema.define(version: 20171021191900) do | ||||
| ActiveRecord::Schema.define(version: 20171028221157) do | ||||
|  | ||||
|   # These are extensions that must be enabled in order to support this database | ||||
|   enable_extension "plpgsql" | ||||
| @@ -145,6 +145,7 @@ ActiveRecord::Schema.define(version: 20171021191900) do | ||||
|     t.datetime "updated_at", null: false | ||||
|     t.bigint "account_id", null: false | ||||
|     t.bigint "target_account_id", null: false | ||||
|     t.boolean "show_reblogs", default: true, null: false | ||||
|     t.index ["account_id", "target_account_id"], name: "index_follow_requests_on_account_id_and_target_account_id", unique: true | ||||
|   end | ||||
|  | ||||
| @@ -153,6 +154,7 @@ ActiveRecord::Schema.define(version: 20171021191900) do | ||||
|     t.datetime "updated_at", null: false | ||||
|     t.bigint "account_id", null: false | ||||
|     t.bigint "target_account_id", null: false | ||||
|     t.boolean "show_reblogs", default: true, null: false | ||||
|     t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true | ||||
|   end | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user