Public profile endorsements (accounts picked by profile owner) (#8146)
This commit is contained in:
parent
80176a3814
commit
f2404de871
21 changed files with 298 additions and 6 deletions
|
@ -10,8 +10,9 @@ class AccountsController < ApplicationController
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
@body_classes = 'with-modals'
|
@body_classes = 'with-modals'
|
||||||
@pinned_statuses = []
|
@pinned_statuses = []
|
||||||
|
@endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
|
||||||
|
|
||||||
if current_account && @account.blocking?(current_account)
|
if current_account && @account.blocking?(current_account)
|
||||||
@statuses = []
|
@statuses = []
|
||||||
|
|
32
app/controllers/api/v1/accounts/pins_controller.rb
Normal file
32
app/controllers/api/v1/accounts/pins_controller.rb
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::PinsController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def create
|
||||||
|
AccountPin.create!(account: current_account, target_account: @account)
|
||||||
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
pin = AccountPin.find_by(account: current_account, target_account: @account)
|
||||||
|
pin&.destroy!
|
||||||
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def relationships_presenter
|
||||||
|
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,4 +6,36 @@ module HomeHelper
|
||||||
locale: I18n.locale,
|
locale: I18n.locale,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def account_link_to(account, button = '')
|
||||||
|
content_tag(:div, class: 'account') do
|
||||||
|
content_tag(:div, class: 'account__wrapper') do
|
||||||
|
section = if account.nil?
|
||||||
|
content_tag(:div, class: 'account__display-name') do
|
||||||
|
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||||
|
content_tag(:div, '', class: 'account__avatar', style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)})")
|
||||||
|
end +
|
||||||
|
content_tag(:span, class: 'display-name') do
|
||||||
|
content_tag(:strong, t('about.contact_missing')) +
|
||||||
|
content_tag(:span, t('about.contact_unavailable'), class: 'display-name__account')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
link_to(TagManager.instance.url_for(account), class: 'account__display-name') do
|
||||||
|
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||||
|
content_tag(:div, '', class: 'account__avatar', style: "background-image: url(#{account.avatar.url})")
|
||||||
|
end +
|
||||||
|
content_tag(:span, class: 'display-name') do
|
||||||
|
content_tag(:bdi) do
|
||||||
|
content_tag(:strong, display_name(account, custom_emojify: true), class: 'display-name__html emojify')
|
||||||
|
end +
|
||||||
|
content_tag(:span, "@#{account.acct}", class: 'display-name__account')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
section + button
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,6 +30,14 @@ export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
|
||||||
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
|
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
|
||||||
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
|
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST';
|
||||||
|
export const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS';
|
||||||
|
export const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST';
|
||||||
|
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
|
||||||
|
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';
|
||||||
|
|
||||||
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
||||||
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
||||||
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
||||||
|
@ -694,3 +702,69 @@ export function rejectFollowRequestFail(id, error) {
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function pinAccount(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(pinAccountRequest(id));
|
||||||
|
|
||||||
|
api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => {
|
||||||
|
dispatch(pinAccountSuccess(response.data));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(pinAccountFail(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpinAccount(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(unpinAccountRequest(id));
|
||||||
|
|
||||||
|
api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => {
|
||||||
|
dispatch(unpinAccountSuccess(response.data));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(unpinAccountFail(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pinAccountRequest(id) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_PIN_REQUEST,
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pinAccountSuccess(relationship) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_PIN_SUCCESS,
|
||||||
|
relationship,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pinAccountFail(error) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_PIN_FAIL,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpinAccountRequest(id) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_UNPIN_REQUEST,
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpinAccountSuccess(relationship) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_UNPIN_SUCCESS,
|
||||||
|
relationship,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpinAccountFail(error) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_UNPIN_FAIL,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -32,6 +32,8 @@ const messages = defineMessages({
|
||||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||||
|
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||||
|
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@injectIntl
|
@injectIntl
|
||||||
|
@ -48,6 +50,7 @@ export default class ActionBar extends React.PureComponent {
|
||||||
onMute: PropTypes.func.isRequired,
|
onMute: PropTypes.func.isRequired,
|
||||||
onBlockDomain: PropTypes.func.isRequired,
|
onBlockDomain: PropTypes.func.isRequired,
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
onUnblockDomain: PropTypes.func.isRequired,
|
||||||
|
onEndorseToggle: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,6 +96,9 @@ export default class ActionBar extends React.PureComponent {
|
||||||
} 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(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||||
|
menu.push(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
if (account.getIn(['relationship', 'muting'])) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onMute: PropTypes.func.isRequired,
|
onMute: PropTypes.func.isRequired,
|
||||||
onBlockDomain: PropTypes.func.isRequired,
|
onBlockDomain: PropTypes.func.isRequired,
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
onUnblockDomain: PropTypes.func.isRequired,
|
||||||
|
onEndorseToggle: PropTypes.func.isRequired,
|
||||||
hideTabs: PropTypes.bool,
|
hideTabs: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -73,6 +74,10 @@ export default class Header extends ImmutablePureComponent {
|
||||||
this.props.onUnblockDomain(domain);
|
this.props.onUnblockDomain(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEndorseToggle = () => {
|
||||||
|
this.props.onEndorseToggle(this.props.account);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, hideTabs } = this.props;
|
const { account, hideTabs } = this.props;
|
||||||
|
|
||||||
|
@ -100,6 +105,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onMute={this.handleMute}
|
onMute={this.handleMute}
|
||||||
onBlockDomain={this.handleBlockDomain}
|
onBlockDomain={this.handleBlockDomain}
|
||||||
onUnblockDomain={this.handleUnblockDomain}
|
onUnblockDomain={this.handleUnblockDomain}
|
||||||
|
onEndorseToggle={this.handleEndorseToggle}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!hideTabs && (
|
{!hideTabs && (
|
||||||
|
|
|
@ -8,6 +8,8 @@ import {
|
||||||
blockAccount,
|
blockAccount,
|
||||||
unblockAccount,
|
unblockAccount,
|
||||||
unmuteAccount,
|
unmuteAccount,
|
||||||
|
pinAccount,
|
||||||
|
unpinAccount,
|
||||||
} from '../../../actions/accounts';
|
} from '../../../actions/accounts';
|
||||||
import {
|
import {
|
||||||
mentionCompose,
|
mentionCompose,
|
||||||
|
@ -82,6 +84,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onEndorseToggle (account) {
|
||||||
|
if (account.getIn(['relationship', 'endorsed'])) {
|
||||||
|
dispatch(unpinAccount(account.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(pinAccount(account.get('id')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onReport (account) {
|
onReport (account) {
|
||||||
dispatch(initReport(account));
|
dispatch(initReport(account));
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,8 @@ import {
|
||||||
ACCOUNT_UNBLOCK_SUCCESS,
|
ACCOUNT_UNBLOCK_SUCCESS,
|
||||||
ACCOUNT_MUTE_SUCCESS,
|
ACCOUNT_MUTE_SUCCESS,
|
||||||
ACCOUNT_UNMUTE_SUCCESS,
|
ACCOUNT_UNMUTE_SUCCESS,
|
||||||
|
ACCOUNT_PIN_SUCCESS,
|
||||||
|
ACCOUNT_UNPIN_SUCCESS,
|
||||||
RELATIONSHIPS_FETCH_SUCCESS,
|
RELATIONSHIPS_FETCH_SUCCESS,
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
import {
|
import {
|
||||||
|
@ -41,6 +43,8 @@ export default function relationships(state = initialState, action) {
|
||||||
case ACCOUNT_UNBLOCK_SUCCESS:
|
case ACCOUNT_UNBLOCK_SUCCESS:
|
||||||
case ACCOUNT_MUTE_SUCCESS:
|
case ACCOUNT_MUTE_SUCCESS:
|
||||||
case ACCOUNT_UNMUTE_SUCCESS:
|
case ACCOUNT_UNMUTE_SUCCESS:
|
||||||
|
case ACCOUNT_PIN_SUCCESS:
|
||||||
|
case ACCOUNT_UNPIN_SUCCESS:
|
||||||
return normalizeRelationship(state, action.relationship);
|
return normalizeRelationship(state, action.relationship);
|
||||||
case RELATIONSHIPS_FETCH_SUCCESS:
|
case RELATIONSHIPS_FETCH_SUCCESS:
|
||||||
return normalizeRelationships(state, action.relationships);
|
return normalizeRelationships(state, action.relationships);
|
||||||
|
|
|
@ -71,6 +71,38 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.endorsements-widget {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
padding: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
color: $darker-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__display-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__avatar {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background-size: 44px 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.moved-account-widget {
|
.moved-account-widget {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
|
|
@ -89,6 +89,10 @@ class Account < ApplicationRecord
|
||||||
has_many :status_pins, inverse_of: :account, dependent: :destroy
|
has_many :status_pins, inverse_of: :account, dependent: :destroy
|
||||||
has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status
|
has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status
|
||||||
|
|
||||||
|
# Endorsements
|
||||||
|
has_many :account_pins, inverse_of: :account, dependent: :destroy
|
||||||
|
has_many :endorsed_accounts, through: :account_pins, class_name: 'Account', source: :target_account
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
has_many :media_attachments, dependent: :destroy
|
has_many :media_attachments, dependent: :destroy
|
||||||
|
|
||||||
|
|
26
app/models/account_pin.rb
Normal file
26
app/models/account_pin.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: account_pins
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# account_id :bigint(8)
|
||||||
|
# target_account_id :bigint(8)
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class AccountPin < ApplicationRecord
|
||||||
|
include RelationshipCacheable
|
||||||
|
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :target_account, class_name: 'Account'
|
||||||
|
|
||||||
|
validate :validate_follow_relationship
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_follow_relationship
|
||||||
|
errors.add(:base, I18n.t('accounts.pin_errors.following')) unless account.following?(target_account)
|
||||||
|
end
|
||||||
|
end
|
|
@ -40,6 +40,10 @@ module AccountInteractions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def endorsed_map(target_account_ids, account_id)
|
||||||
|
follow_mapping(AccountPin.where(account_id: account_id, target_account_id: target_account_ids), :target_account_id)
|
||||||
|
end
|
||||||
|
|
||||||
def domain_blocking_map(target_account_ids, account_id)
|
def domain_blocking_map(target_account_ids, account_id)
|
||||||
accounts_map = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h
|
accounts_map = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h
|
||||||
blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
|
blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
|
||||||
|
@ -190,6 +194,10 @@ module AccountInteractions
|
||||||
status_pins.where(status: status).exists?
|
status_pins.where(status: status).exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def endorsed?(account)
|
||||||
|
account_pins.where(target_account: account).exists?
|
||||||
|
end
|
||||||
|
|
||||||
def followers_for_local_distribution
|
def followers_for_local_distribution
|
||||||
followers.local
|
followers.local
|
||||||
.joins(:user)
|
.joins(:user)
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
class AccountRelationshipsPresenter
|
class AccountRelationshipsPresenter
|
||||||
attr_reader :following, :followed_by, :blocking,
|
attr_reader :following, :followed_by, :blocking,
|
||||||
:muting, :requested, :domain_blocking
|
:muting, :requested, :domain_blocking,
|
||||||
|
:endorsed
|
||||||
|
|
||||||
def initialize(account_ids, current_account_id, **options)
|
def initialize(account_ids, current_account_id, **options)
|
||||||
@account_ids = account_ids.map { |a| a.is_a?(Account) ? a.id : a }
|
@account_ids = account_ids.map { |a| a.is_a?(Account) ? a.id : a }
|
||||||
|
@ -14,6 +15,7 @@ class AccountRelationshipsPresenter
|
||||||
@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))
|
||||||
|
@endorsed = cached[:endorsed].merge(Account.endorsed_map(@uncached_account_ids, @current_account_id))
|
||||||
|
|
||||||
cache_uncached!
|
cache_uncached!
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ class AccountRelationshipsPresenter
|
||||||
@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] || {})
|
||||||
|
@endorsed.merge!(options[:endorsed_map] || {})
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -37,6 +40,7 @@ class AccountRelationshipsPresenter
|
||||||
muting: {},
|
muting: {},
|
||||||
requested: {},
|
requested: {},
|
||||||
domain_blocking: {},
|
domain_blocking: {},
|
||||||
|
endorsed: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@uncached_account_ids = []
|
@uncached_account_ids = []
|
||||||
|
@ -63,6 +67,7 @@ class AccountRelationshipsPresenter
|
||||||
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] },
|
||||||
|
endorsed: { account_id => endorsed[account_id] },
|
||||||
}
|
}
|
||||||
|
|
||||||
Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
|
Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
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,
|
||||||
:muting, :muting_notifications, :requested, :domain_blocking
|
:muting, :muting_notifications, :requested, :domain_blocking,
|
||||||
|
:endorsed
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
|
@ -41,4 +42,8 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
|
||||||
def domain_blocking
|
def domain_blocking
|
||||||
instance_options[:relationships].domain_blocking[object.id] || false
|
instance_options[:relationships].domain_blocking[object.id] || false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def endorsed
|
||||||
|
instance_options[:relationships].endorsed[object.id] || false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,4 +55,12 @@
|
||||||
= render 'moved', account: @account
|
= render 'moved', account: @account
|
||||||
|
|
||||||
= render 'bio', account: @account
|
= render 'bio', account: @account
|
||||||
|
|
||||||
|
- unless @endorsed_accounts.empty?
|
||||||
|
.endorsements-widget
|
||||||
|
%h4= t 'accounts.choices_html', name: content_tag(:bdi, display_name(@account, custom_emojify: true))
|
||||||
|
|
||||||
|
- @endorsed_accounts.each do |account|
|
||||||
|
= account_link_to account
|
||||||
|
|
||||||
= render 'application/sidebar'
|
= render 'application/sidebar'
|
||||||
|
|
|
@ -39,6 +39,7 @@ en:
|
||||||
user_count_before: Home to
|
user_count_before: Home to
|
||||||
what_is_mastodon: What is Mastodon?
|
what_is_mastodon: What is Mastodon?
|
||||||
accounts:
|
accounts:
|
||||||
|
choices_html: "%{name}'s choices:"
|
||||||
follow: Follow
|
follow: Follow
|
||||||
followers: Followers
|
followers: Followers
|
||||||
following: Following
|
following: Following
|
||||||
|
@ -49,6 +50,8 @@ en:
|
||||||
nothing_here: There is nothing here!
|
nothing_here: There is nothing here!
|
||||||
people_followed_by: People whom %{name} follows
|
people_followed_by: People whom %{name} follows
|
||||||
people_who_follow: People who follow %{name}
|
people_who_follow: People who follow %{name}
|
||||||
|
pin_errors:
|
||||||
|
following: You must be already following the person you want to endorse
|
||||||
posts: Toots
|
posts: Toots
|
||||||
posts_with_replies: Toots and replies
|
posts_with_replies: Toots and replies
|
||||||
reserved_username: The username is reserved
|
reserved_username: The username is reserved
|
||||||
|
|
|
@ -309,6 +309,9 @@ Rails.application.routes.draw do
|
||||||
post :mute
|
post :mute
|
||||||
post :unmute
|
post :unmute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resource :pin, only: :create, controller: 'accounts/pins'
|
||||||
|
post :unpin, to: 'accounts/pins#destroy'
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :lists, only: [:index, :create, :show, :update, :destroy] do
|
resources :lists, only: [:index, :create, :show, :update, :destroy] do
|
||||||
|
|
12
db/migrate/20180808175627_create_account_pins.rb
Normal file
12
db/migrate/20180808175627_create_account_pins.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateAccountPins < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :account_pins do |t|
|
||||||
|
t.belongs_to :account, foreign_key: { on_delete: :cascade }
|
||||||
|
t.belongs_to :target_account, foreign_key: { on_delete: :cascade, to_table: :accounts }
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :account_pins, [:account_id, :target_account_id], unique: true
|
||||||
|
end
|
||||||
|
end
|
16
db/schema.rb
16
db/schema.rb
|
@ -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: 2018_07_11_152640) do
|
ActiveRecord::Schema.define(version: 2018_08_08_175627) 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"
|
||||||
|
@ -33,6 +33,16 @@ ActiveRecord::Schema.define(version: 2018_07_11_152640) do
|
||||||
t.index ["target_account_id"], name: "index_account_moderation_notes_on_target_account_id"
|
t.index ["target_account_id"], name: "index_account_moderation_notes_on_target_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "account_pins", force: :cascade do |t|
|
||||||
|
t.bigint "account_id"
|
||||||
|
t.bigint "target_account_id"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id", "target_account_id"], name: "index_account_pins_on_account_id_and_target_account_id", unique: true
|
||||||
|
t.index ["account_id"], name: "index_account_pins_on_account_id"
|
||||||
|
t.index ["target_account_id"], name: "index_account_pins_on_target_account_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "accounts", force: :cascade do |t|
|
create_table "accounts", force: :cascade do |t|
|
||||||
t.string "username", default: "", null: false
|
t.string "username", default: "", null: false
|
||||||
t.string "domain"
|
t.string "domain"
|
||||||
|
@ -149,9 +159,9 @@ ActiveRecord::Schema.define(version: 2018_07_11_152640) do
|
||||||
t.text "phrase", default: "", null: false
|
t.text "phrase", default: "", null: false
|
||||||
t.string "context", default: [], null: false, array: true
|
t.string "context", default: [], null: false, array: true
|
||||||
t.boolean "irreversible", default: false, null: false
|
t.boolean "irreversible", default: false, null: false
|
||||||
t.boolean "whole_word", default: true, null: false
|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.boolean "whole_word", default: true, null: false
|
||||||
t.index ["account_id"], name: "index_custom_filters_on_account_id"
|
t.index ["account_id"], name: "index_custom_filters_on_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -575,6 +585,8 @@ ActiveRecord::Schema.define(version: 2018_07_11_152640) do
|
||||||
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
|
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
|
||||||
add_foreign_key "account_moderation_notes", "accounts"
|
add_foreign_key "account_moderation_notes", "accounts"
|
||||||
add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id"
|
add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id"
|
||||||
|
add_foreign_key "account_pins", "accounts", column: "target_account_id", on_delete: :cascade
|
||||||
|
add_foreign_key "account_pins", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
|
add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
|
||||||
add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
|
add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "backups", "users", on_delete: :nullify
|
add_foreign_key "backups", "users", on_delete: :nullify
|
||||||
|
|
4
spec/fabricators/account_pin_fabricator.rb
Normal file
4
spec/fabricators/account_pin_fabricator.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Fabricator(:account_pin) do
|
||||||
|
account nil
|
||||||
|
target_account nil
|
||||||
|
end
|
5
spec/models/account_pin_spec.rb
Normal file
5
spec/models/account_pin_spec.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe AccountPin, type: :model do
|
||||||
|
pending "add some examples to (or delete) #{__FILE__}"
|
||||||
|
end
|
Loading…
Reference in a new issue