Improve blocked view of profiles (#10491)

* Revert "Fix filtering of favourited_by, reblogged_by, followers and following (#10447)"

This reverts commit 120544067f.

* Revert "Hide blocking accounts from blocked users (#10442)"

This reverts commit 62bafa20a1.

* Improve blocked view of profiles

- Change "You are blocked" to "Profile unavailable"
- Hide following/followers in API when blocked
- Disable follow button and show "Profile unavailable" on public profile as well
This commit is contained in:
Eugen Rochko 2019-04-07 04:59:13 +02:00 committed by GitHub
parent 0e8819f0e8
commit 67b3b62b98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 97 additions and 138 deletions

View file

@ -19,13 +19,17 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end end
def load_accounts def load_accounts
return [] if @account.user_hides_network? && current_account.id != @account.id return [] if hide_results?
default_accounts.merge(paginated_follows).to_a default_accounts.merge(paginated_follows).to_a
end end
def hide_results?
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts def default_accounts
Account.without_blocking(current_account).includes(:active_relationships, :account_stat).references(:active_relationships) Account.includes(:active_relationships, :account_stat).references(:active_relationships)
end end
def paginated_follows def paginated_follows

View file

@ -19,13 +19,17 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end end
def load_accounts def load_accounts
return [] if @account.user_hides_network? && current_account.id != @account.id return [] if hide_results?
default_accounts.merge(paginated_follows).to_a default_accounts.merge(paginated_follows).to_a
end end
def hide_results?
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts def default_accounts
Account.without_blocking(current_account).includes(:passive_relationships, :account_stat).references(:passive_relationships) Account.includes(:passive_relationships, :account_stat).references(:passive_relationships)
end end
def paginated_follows def paginated_follows

View file

@ -3,8 +3,6 @@
class Api::V1::Accounts::StatusesController < Api::BaseController class Api::V1::Accounts::StatusesController < Api::BaseController
before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action -> { authorize_if_got_token! :read, :'read:statuses' }
before_action :set_account before_action :set_account
before_action :check_account_suspension
before_action :check_account_block
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json respond_to :json
@ -20,14 +18,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
@account = Account.find(params[:account_id]) @account = Account.find(params[:account_id])
end end
def check_account_suspension
gone if @account.suspended?
end
def check_account_block
gone if current_account.present? && @account.blocking?(current_account)
end
def load_statuses def load_statuses
cached_account_statuses cached_account_statuses
end end

View file

@ -10,7 +10,6 @@ class Api::V1::AccountsController < Api::BaseController
before_action :require_user!, except: [:show, :create] before_action :require_user!, except: [:show, :create]
before_action :set_account, except: [:create] before_action :set_account, except: [:create]
before_action :check_account_suspension, only: [:show] before_action :check_account_suspension, only: [:show]
before_action :check_account_block, only: [:show]
before_action :check_enabled_registrations, only: [:create] before_action :check_enabled_registrations, only: [:create]
respond_to :json respond_to :json
@ -76,10 +75,6 @@ class Api::V1::AccountsController < Api::BaseController
gone if @account.suspended? gone if @account.suspended?
end end
def check_account_block
gone if current_account.present? && @account.blocking?(current_account)
end
def account_params def account_params
params.permit(:username, :email, :password, :agreement, :locale) params.permit(:username, :email, :password, :agreement, :locale)
end end

View file

@ -22,7 +22,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
def default_accounts def default_accounts
Account Account
.without_blocking(current_account)
.includes(:favourites, :account_stat) .includes(:favourites, :account_stat)
.references(:favourites) .references(:favourites)
.where(favourites: { status_id: @status.id }) .where(favourites: { status_id: @status.id })

View file

@ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.without_blocking(current_account).includes(:statuses, :account_stat).references(:statuses) Account.includes(:statuses, :account_stat).references(:statuses)
end end
def paginated_statuses def paginated_statuses

View file

@ -23,7 +23,7 @@ module StreamEntriesHelper
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')]) safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')])
end end
elsif !(account.memorial? || account.moved?) elsif !(account.memorial? || account.moved?)
link_to account_follow_path(account), class: 'button logo-button', data: { method: :post } do link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')]) safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')])
end end
end end

View file

@ -109,7 +109,7 @@ class Header extends ImmutablePureComponent {
} else if (account.getIn(['relationship', 'requested'])) { } else if (account.getIn(['relationship', 'requested'])) {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
} else if (!account.getIn(['relationship', 'blocking'])) { } else if (!account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />; actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
} else if (account.getIn(['relationship', 'blocking'])) { } else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />; actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
} }

View file

@ -14,14 +14,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
const emptyList = ImmutableList();
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
const path = withReplies ? `${accountId}:with_replies` : accountId; const path = withReplies ? `${accountId}:with_replies` : accountId;
return { return {
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
}; };
}; };
@ -37,6 +40,7 @@ class AccountTimeline extends ImmutablePureComponent {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
withReplies: PropTypes.bool, withReplies: PropTypes.bool,
blockedBy: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -44,9 +48,11 @@ class AccountTimeline extends ImmutablePureComponent {
this.props.dispatch(fetchAccount(accountId)); this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(fetchAccountIdentityProofs(accountId)); this.props.dispatch(fetchAccountIdentityProofs(accountId));
if (!withReplies) { if (!withReplies) {
this.props.dispatch(expandAccountFeaturedTimeline(accountId)); this.props.dispatch(expandAccountFeaturedTimeline(accountId));
} }
this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); this.props.dispatch(expandAccountTimeline(accountId, { withReplies }));
} }
@ -54,9 +60,11 @@ class AccountTimeline extends ImmutablePureComponent {
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.params.accountId)); this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId)); this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
if (!nextProps.withReplies) { if (!nextProps.withReplies) {
this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
} }
this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies })); this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
} }
} }
@ -66,7 +74,7 @@ class AccountTimeline extends ImmutablePureComponent {
} }
render () { render () {
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props; const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy } = this.props;
if (!statusIds && isLoading) { if (!statusIds && isLoading) {
return ( return (
@ -76,6 +84,8 @@ class AccountTimeline extends ImmutablePureComponent {
); );
} }
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
return ( return (
<Column> <Column>
<ColumnBackButton /> <ColumnBackButton />
@ -84,13 +94,13 @@ class AccountTimeline extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} />} prepend={<HeaderContainer accountId={this.props.params.accountId} />}
alwaysPrepend alwaysPrepend
scrollKey='account_timeline' scrollKey='account_timeline'
statusIds={statusIds} statusIds={blockedBy ? emptyList : statusIds}
featuredStatusIds={featuredStatusIds} featuredStatusIds={featuredStatusIds}
isLoading={isLoading} isLoading={isLoading}
hasMore={hasMore} hasMore={hasMore}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
shouldUpdateScroll={shouldUpdateScroll} shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />} emptyMessage={emptyMessage}
/> />
</Column> </Column>
); );

View file

@ -20,6 +20,7 @@ import ScrollableList from '../../components/scrollable_list';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']), hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -31,6 +32,7 @@ class Followers extends ImmutablePureComponent {
shouldUpdateScroll: PropTypes.func, shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
blockedBy: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -50,7 +52,7 @@ class Followers extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { shouldUpdateScroll, accountIds, hasMore } = this.props; const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -60,7 +62,7 @@ class Followers extends ImmutablePureComponent {
); );
} }
const emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />; const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
return ( return (
<Column> <Column>
@ -75,7 +77,7 @@ class Followers extends ImmutablePureComponent {
alwaysPrepend alwaysPrepend
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
> >
{accountIds.map(id => {blockedBy ? [] : accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />
)} )}
</ScrollableList> </ScrollableList>

View file

@ -20,6 +20,7 @@ import ScrollableList from '../../components/scrollable_list';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']), hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -31,6 +32,7 @@ class Following extends ImmutablePureComponent {
shouldUpdateScroll: PropTypes.func, shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
blockedBy: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -50,7 +52,7 @@ class Following extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { shouldUpdateScroll, accountIds, hasMore } = this.props; const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -60,7 +62,7 @@ class Following extends ImmutablePureComponent {
); );
} }
const emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />; const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
return ( return (
<Column> <Column>
@ -75,7 +77,7 @@ class Following extends ImmutablePureComponent {
alwaysPrepend alwaysPrepend
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
> >
{accountIds.map(id => {blockedBy ? [] : accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />
)} )}
</ScrollableList> </ScrollableList>

View file

@ -46,7 +46,8 @@
} }
} }
&:disabled { &:disabled,
&.disabled {
background-color: $ui-primary-color; background-color: $ui-primary-color;
cursor: default; cursor: default;
} }

View file

@ -109,6 +109,23 @@
} }
} }
&:disabled,
&.disabled {
svg path:last-child {
fill: $ui-primary-color;
}
&:active,
&:focus,
&:hover {
background: $ui-primary-color;
svg path:last-child {
fill: $ui-primary-color;
}
}
}
&.button--destructive { &.button--destructive {
&:active, &:active,
&:focus, &:focus,

View file

@ -98,7 +98,6 @@ class Account < ApplicationRecord
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) } scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) } scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
scope :popular, -> { order('account_stats.followers_count desc') } scope :popular, -> { order('account_stats.followers_count desc') }
scope :without_blocking, ->(account) { account.nil? ? all : where.not(id: Block.where(target_account_id: account.id).pluck(:account_id)) }
delegate :email, delegate :email,
:unconfirmed_email, :unconfirmed_email,

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class AccountRelationshipsPresenter class AccountRelationshipsPresenter
attr_reader :following, :followed_by, :blocking, attr_reader :following, :followed_by, :blocking, :blocked_by,
:muting, :requested, :domain_blocking, :muting, :requested, :domain_blocking,
:endorsed :endorsed
@ -12,6 +12,7 @@ class AccountRelationshipsPresenter
@following = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id)) @following = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id))
@followed_by = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id)) @followed_by = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id))
@blocking = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id)) @blocking = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id))
@blocked_by = cached[:blocked_by].merge(Account.blocked_by_map(@uncached_account_ids, @current_account_id))
@muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id)) @muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id))
@requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id)) @requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
@domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id)) @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
@ -22,6 +23,7 @@ class AccountRelationshipsPresenter
@following.merge!(options[:following_map] || {}) @following.merge!(options[:following_map] || {})
@followed_by.merge!(options[:followed_by_map] || {}) @followed_by.merge!(options[:followed_by_map] || {})
@blocking.merge!(options[:blocking_map] || {}) @blocking.merge!(options[:blocking_map] || {})
@blocked_by.merge!(options[:blocked_by_map] || {})
@muting.merge!(options[:muting_map] || {}) @muting.merge!(options[:muting_map] || {})
@requested.merge!(options[:requested_map] || {}) @requested.merge!(options[:requested_map] || {})
@domain_blocking.merge!(options[:domain_blocking_map] || {}) @domain_blocking.merge!(options[:domain_blocking_map] || {})
@ -37,6 +39,7 @@ class AccountRelationshipsPresenter
following: {}, following: {},
followed_by: {}, followed_by: {},
blocking: {}, blocking: {},
blocked_by: {},
muting: {}, muting: {},
requested: {}, requested: {},
domain_blocking: {}, domain_blocking: {},
@ -64,6 +67,7 @@ class AccountRelationshipsPresenter
following: { account_id => following[account_id] }, following: { account_id => following[account_id] },
followed_by: { account_id => followed_by[account_id] }, followed_by: { account_id => followed_by[account_id] },
blocking: { account_id => blocking[account_id] }, blocking: { account_id => blocking[account_id] },
blocked_by: { account_id => blocked_by[account_id] },
muting: { account_id => muting[account_id] }, muting: { account_id => muting[account_id] },
requested: { account_id => requested[account_id] }, requested: { account_id => requested[account_id] },
domain_blocking: { account_id => domain_blocking[account_id] }, domain_blocking: { account_id => domain_blocking[account_id] },

View file

@ -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, :followed_by, :blocking, attributes :id, :following, :showing_reblogs, :followed_by, :blocking, :blocked_by,
:muting, :muting_notifications, :requested, :domain_blocking, :muting, :muting_notifications, :requested, :domain_blocking,
:endorsed :endorsed
@ -27,6 +27,10 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
instance_options[:relationships].blocking[object.id] || false instance_options[:relationships].blocking[object.id] || false
end end
def blocked_by
instance_options[:relationships].blocked_by[object.id] || false
end
def muting def muting
instance_options[:relationships].muting[object.id] ? true : false instance_options[:relationships].muting[object.id] ? true : false
end end

View file

@ -10,15 +10,7 @@ class AccountSearchService < BaseService
@options = options @options = options
@account = account @account = account
results = search_service_results search_service_results
unless account.nil?
account_ids = results.map(&:id)
blocked_by_map = Account.blocked_by_map(account_ids, account.id)
results.reject! { |item| blocked_by_map[item.id] }
end
results
end end
private private

View file

@ -12,8 +12,6 @@ class SearchService < BaseService
default_results.tap do |results| default_results.tap do |results|
if url_query? if url_query?
results.merge!(url_resource_results) unless url_resource.nil? results.merge!(url_resource_results) unless url_resource.nil?
results[:accounts].reject! { |item| item.blocking?(@account) }
results[:statuses].reject! { |status| StatusFilter.new(status, @account).filtered? }
elsif @query.present? elsif @query.present?
results[:accounts] = perform_accounts_search! if account_searchable? results[:accounts] = perform_accounts_search! if account_searchable?
results[:statuses] = perform_statuses_search! if full_text_searchable? results[:statuses] = perform_statuses_search! if full_text_searchable?

View file

@ -33,7 +33,9 @@
= active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
= active_link_to t('accounts.media'), short_account_media_url(@account) = active_link_to t('accounts.media'), short_account_media_url(@account)
- if @statuses.empty? - if user_signed_in? && @account.blocking?(current_account)
.nothing-here.nothing-here--under-tabs= t('accounts.unavailable')
- elsif @statuses.empty?
= nothing_here 'nothing-here--under-tabs' = nothing_here 'nothing-here--under-tabs'
- else - else
.activity-stream .activity-stream

View file

@ -9,6 +9,8 @@
- if @account.user_hides_network? - if @account.user_hides_network?
.nothing-here= t('accounts.network_hidden') .nothing-here= t('accounts.network_hidden')
- elsif user_signed_in? && @account.blocking?(current_account)
.nothing-here= t('accounts.unavailable')
- elsif @follows.empty? - elsif @follows.empty?
= nothing_here = nothing_here
- else - else

View file

@ -9,6 +9,8 @@
- if @account.user_hides_network? - if @account.user_hides_network?
.nothing-here= t('accounts.network_hidden') .nothing-here= t('accounts.network_hidden')
- elsif user_signed_in? && @account.blocking?(current_account)
.nothing-here= t('accounts.unavailable')
- elsif @follows.empty? - elsif @follows.empty?
= nothing_here = nothing_here
- else - else

View file

@ -68,6 +68,7 @@ en:
admin: Admin admin: Admin
bot: Bot bot: Bot
moderator: Mod moderator: Mod
unavailable: Profile unavailable
unfollow: Unfollow unfollow: Unfollow
admin: admin:
account_actions: account_actions:

View file

@ -7,40 +7,15 @@ describe Api::V1::Accounts::FollowerAccountsController do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
before do before do
Fabricate(:follow, target_account: user.account)
allow(controller).to receive(:doorkeeper_token) { token } allow(controller).to receive(:doorkeeper_token) { token }
end end
describe 'GET #index' do describe 'GET #index' do
let(:simon) { Fabricate(:account, username: 'simon') }
let(:lewis) { Fabricate(:account, username: 'lewis') }
before do
simon.follow!(lewis)
end
it 'returns http success' do it 'returns http success' do
get :index, params: { account_id: lewis.id, limit: 1 } get :index, params: { account_id: user.account.id, limit: 1 }
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns JSON with correct data' do
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.first[:username]).to eq 'simon'
end
it 'does not return accounts blocking you' do
simon.block!(user.account)
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.size).to eq 0
end
end end
end end

View file

@ -7,40 +7,15 @@ describe Api::V1::Accounts::FollowingAccountsController do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
before do before do
Fabricate(:follow, account: user.account)
allow(controller).to receive(:doorkeeper_token) { token } allow(controller).to receive(:doorkeeper_token) { token }
end end
describe 'GET #index' do describe 'GET #index' do
let(:simon) { Fabricate(:account, username: 'simon') }
let(:lewis) { Fabricate(:account, username: 'lewis') }
before do
lewis.follow!(simon)
end
it 'returns http success' do it 'returns http success' do
get :index, params: { account_id: lewis.id, limit: 1 } get :index, params: { account_id: user.account.id, limit: 1 }
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns JSON with correct data' do
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.first[:username]).to eq 'simon'
end
it 'does not return accounts blocking you' do
simon.block!(user.account)
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.size).to eq 0
end
end end
end end

View file

@ -156,22 +156,5 @@ describe AccountSearchService, type: :service do
expect(results).to eq [] expect(results).to eq []
end end
end end
describe 'should not include accounts blocking the requester' do
let!(:blocked) { Fabricate(:account) }
let!(:blocker) { Fabricate(:account, username: 'exact') }
before do
blocker.block!(blocked)
end
it 'returns the fuzzy match first, and does not return suspended exacts' do
partial = Fabricate(:account, username: 'exactness')
results = subject.call('exact', blocked, limit: 10)
expect(results.size).to eq 1
expect(results).to eq [partial]
end
end
end end
end end

View file

@ -3,8 +3,6 @@
require 'rails_helper' require 'rails_helper'
describe SearchService, type: :service do describe SearchService, type: :service do
let(:current_account) { Fabricate(:user).account }
subject { described_class.new } subject { described_class.new }
describe '#call' do describe '#call' do
@ -12,7 +10,7 @@ describe SearchService, type: :service do
it 'returns empty results without searching' do it 'returns empty results without searching' do
allow(AccountSearchService).to receive(:new) allow(AccountSearchService).to receive(:new)
allow(Tag).to receive(:search_for) allow(Tag).to receive(:search_for)
results = subject.call('', current_account, 10) results = subject.call('', nil, 10)
expect(results).to eq(empty_results) expect(results).to eq(empty_results)
expect(AccountSearchService).not_to have_received(:new) expect(AccountSearchService).not_to have_received(:new)
@ -29,33 +27,33 @@ describe SearchService, type: :service do
it 'returns the empty results' do it 'returns the empty results' do
service = double(call: nil) service = double(call: nil)
allow(ResolveURLService).to receive(:new).and_return(service) allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, current_account, 10) results = subject.call(@query, nil, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: current_account) expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
expect(results).to eq empty_results expect(results).to eq empty_results
end end
end end
context 'that finds an account' do context 'that finds an account' do
it 'includes the account in the results' do it 'includes the account in the results' do
account = Fabricate(:account) account = Account.new
service = double(call: account) service = double(call: account)
allow(ResolveURLService).to receive(:new).and_return(service) allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, current_account, 10) results = subject.call(@query, nil, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: current_account) expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
expect(results).to eq empty_results.merge(accounts: [account]) expect(results).to eq empty_results.merge(accounts: [account])
end end
end end
context 'that finds a status' do context 'that finds a status' do
it 'includes the status in the results' do it 'includes the status in the results' do
status = Fabricate(:status) status = Status.new
service = double(call: status) service = double(call: status)
allow(ResolveURLService).to receive(:new).and_return(service) allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, current_account, 10) results = subject.call(@query, nil, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: current_account) expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
expect(results).to eq empty_results.merge(statuses: [status]) expect(results).to eq empty_results.merge(statuses: [status])
end end
end end
@ -65,12 +63,12 @@ describe SearchService, type: :service do
context 'that matches an account' do context 'that matches an account' do
it 'includes the account in the results' do it 'includes the account in the results' do
query = 'username' query = 'username'
account = Fabricate(:account) account = Account.new
service = double(call: [account]) service = double(call: [account])
allow(AccountSearchService).to receive(:new).and_return(service) allow(AccountSearchService).to receive(:new).and_return(service)
results = subject.call(query, current_account, 10) results = subject.call(query, nil, 10)
expect(service).to have_received(:call).with(query, current_account, limit: 10, offset: 0, resolve: false) expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false)
expect(results).to eq empty_results.merge(accounts: [account]) expect(results).to eq empty_results.merge(accounts: [account])
end end
end end
@ -81,7 +79,7 @@ describe SearchService, type: :service do
tag = Tag.new tag = Tag.new
allow(Tag).to receive(:search_for).with('tag', 10, 0).and_return([tag]) allow(Tag).to receive(:search_for).with('tag', 10, 0).and_return([tag])
results = subject.call(query, current_account, 10) results = subject.call(query, nil, 10)
expect(Tag).to have_received(:search_for).with('tag', 10, 0) expect(Tag).to have_received(:search_for).with('tag', 10, 0)
expect(results).to eq empty_results.merge(hashtags: [tag]) expect(results).to eq empty_results.merge(hashtags: [tag])
end end
@ -89,7 +87,7 @@ describe SearchService, type: :service do
query = '@username' query = '@username'
allow(Tag).to receive(:search_for) allow(Tag).to receive(:search_for)
results = subject.call(query, current_account, 10) results = subject.call(query, nil, 10)
expect(Tag).not_to have_received(:search_for) expect(Tag).not_to have_received(:search_for)
expect(results).to eq empty_results expect(results).to eq empty_results
end end