Add mutual-followers-only visibility

This commit is contained in:
noellabo 2021-02-10 09:06:37 +09:00
parent 807da5d3d6
commit c3114f9a5e
14 changed files with 35 additions and 5 deletions

View file

@ -84,7 +84,16 @@ class Api::V1::StatusesController < Api::BaseController
end end
def set_circle def set_circle
@circle = status_params[:circle_id].blank? ? nil : current_account.owned_circles.find(status_params[:circle_id]) @circle = begin
if status_params[:visibility] == 'mutual'
status_params[:visibility] = 'limited'
current_account
elsif status_params[:circle_id].blank?
nil
else
current_account.owned_circles.find(status_params[:circle_id])
end
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render json: { error: I18n.t('statuses.errors.circle_not_found') }, status: 404 render json: { error: I18n.t('statuses.errors.circle_not_found') }, status: 404
end end

View file

@ -178,7 +178,7 @@ module ApplicationHelper
text: [params[:title], params[:text], params[:url]].compact.join(' '), text: [params[:title], params[:text], params[:url]].compact.join(' '),
} }
permit_visibilities = %w(public unlisted private direct) permit_visibilities = %w(public unlisted private mutual direct)
default_privacy = current_account&.user&.setting_default_privacy default_privacy = current_account&.user&.setting_default_privacy
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present? permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility] state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]

View file

@ -82,6 +82,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual-followers-only' },
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' }, limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
}); });
@ -540,6 +541,7 @@ class Status extends ImmutablePureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) }, 'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) }, 'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) }, 'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) }, 'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) }, 'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
}; };

View file

@ -16,6 +16,8 @@ const messages = defineMessages({
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but not in public timelines' }, unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but not in public timelines' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' }, private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutuals-followers-only' },
mutual_long: { id: 'privacy.mutual.long', defaultMessage: 'Visible for mutual followers only (Supported servers only)' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' }, direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' }, limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
@ -239,6 +241,7 @@ class PrivacyDropdown extends React.PureComponent {
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) }, { icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, { icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
{ icon: 'exchange', value: 'mutual', text: formatMessage(messages.mutual_short), meta: formatMessage(messages.mutual_long) },
{ icon: 'user-circle', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) }, { icon: 'user-circle', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) },
]; ];

View file

@ -22,6 +22,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual-followers-only' },
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' }, limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
}); });
@ -338,6 +339,7 @@ class DetailedStatus extends ImmutablePureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) }, 'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) }, 'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) }, 'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) }, 'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) }, 'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
}; };

View file

@ -21,6 +21,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual-followers-only' },
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' }, limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
}); });
@ -94,6 +95,7 @@ class BoostModal extends ImmutablePureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) }, 'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) }, 'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) }, 'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) }, 'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) }, 'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
}; };

View file

@ -388,6 +388,8 @@
"privacy.direct.short": "Direct", "privacy.direct.short": "Direct",
"privacy.limited.long": "Visible for circle users only", "privacy.limited.long": "Visible for circle users only",
"privacy.limited.short": "Circle", "privacy.limited.short": "Circle",
"privacy.mutual.long": "Visible for mutual followers only (Supported servers only)",
"privacy.mutual.short": "Mutual-followers-only",
"privacy.private.long": "Visible for followers only", "privacy.private.long": "Visible for followers only",
"privacy.private.short": "Followers-only", "privacy.private.short": "Followers-only",
"privacy.public.long": "Visible for all, shown in public timelines", "privacy.public.long": "Visible for all, shown in public timelines",

View file

@ -388,6 +388,8 @@
"privacy.direct.short": "ダイレクト", "privacy.direct.short": "ダイレクト",
"privacy.limited.long": "サークルで指定したユーザーのみ閲覧可", "privacy.limited.long": "サークルで指定したユーザーのみ閲覧可",
"privacy.limited.short": "サークル", "privacy.limited.short": "サークル",
"privacy.mutual.long": "相互フォローのみ閲覧可(対応サーバのみ)",
"privacy.mutual.short": "相互フォロー限定",
"privacy.private.long": "フォロワーのみ閲覧可", "privacy.private.long": "フォロワーのみ閲覧可",
"privacy.private.short": "フォロワー限定", "privacy.private.short": "フォロワー限定",
"privacy.public.long": "誰でも閲覧可、公開TLに表示", "privacy.public.long": "誰でも閲覧可、公開TLに表示",

View file

@ -219,7 +219,7 @@ const insertEmoji = (state, position, emojiData, needsSpace) => {
}; };
const privacyPreference = (a, b) => { const privacyPreference = (a, b) => {
const order = ['public', 'unlisted', 'private', 'limited', 'direct']; const order = ['public', 'unlisted', 'private', 'mutual', 'limited', 'direct'];
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)]; return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
}; };

View file

@ -341,6 +341,10 @@ module AccountInteractions
end end
end end
def mutuals
followers.merge(Account.where(id: following))
end
private private
def remove_potential_friendship(other_account, mutual = false) def remove_potential_friendship(other_account, mutual = false)

View file

@ -51,7 +51,7 @@ class Status < ApplicationRecord
update_index('statuses#status', :proper) update_index('statuses#status', :proper)
enum visibility: [:public, :unlisted, :private, :mutual, :direct, :limited], _suffix: :visibility enum visibility: [:public, :unlisted, :private, :direct, :limited, :mutual], _suffix: :visibility
enum expires_action: [:delete, :hint], _prefix: :expires enum expires_action: [:delete, :hint], _prefix: :expires
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true belongs_to :application, class_name: 'Doorkeeper::Application', optional: true

View file

@ -44,7 +44,7 @@ class ProcessMentionsService < BaseService
end end
if circle.present? if circle.present?
circle.accounts.find_each do |target_account| (circle.class.name == 'Account' ? circle.mutuals : circle.accounts).find_each do |target_account|
status.mentions.create(silent: true, account: target_account) status.mentions.create(silent: true, account: target_account)
end end
elsif status.limited_visibility? && status.thread&.limited_visibility? elsif status.limited_visibility? && status.thread&.limited_visibility?

View file

@ -1388,6 +1388,8 @@ en:
direct_long: Only show to mentioned users direct_long: Only show to mentioned users
limited: Circle limited: Circle
limited_long: Only show to circle users limited_long: Only show to circle users
mutual: Mutual
mutual_long: Only show to mutual followers
private: Followers-only private: Followers-only
private_long: Only show to followers private_long: Only show to followers
public: Public public: Public

View file

@ -1325,6 +1325,8 @@ ja:
direct_long: 送信した相手のみ閲覧可 direct_long: 送信した相手のみ閲覧可
limited: サークル limited: サークル
limited_long: サークルで指定したユーザーのみ閲覧可 limited_long: サークルで指定したユーザーのみ閲覧可
mutual: 相互フォロー限定
mutual_long: 相互フォロー相手にのみ表示されます
private: フォロワー限定 private: フォロワー限定
private_long: フォロワーにのみ表示されます private_long: フォロワーにのみ表示されます
public: 公開 public: 公開