diff --git a/.rubocop.yml b/.rubocop.yml
index ac6ef0debe..2e9dd5518e 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -130,6 +130,7 @@ Metrics/ClassLength:
- 'app/models/status.rb'
- 'app/models/tag.rb'
- 'app/models/user.rb'
+ - 'app/policies/status_policy.rb'
- 'app/serializers/activitypub/actor_serializer.rb'
- 'app/serializers/activitypub/note_serializer.rb'
- 'app/serializers/rest/account_serializer.rb'
@@ -155,6 +156,7 @@ Metrics/CyclomaticComplexity:
Exclude:
- 'app/policies/status_policy.rb'
- 'app/services/activitypub/process_account_service.rb'
+ - 'app/services/post_status_service.rb'
- lib/mastodon/cli/*.rb
- db/*migrate/**/*
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index ffd4907681..83b9cf49d9 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -48,7 +48,9 @@ class AccountsController < ApplicationController
end
def default_statuses
- @account.statuses.where(visibility: [:public, :unlisted, :public_unlisted])
+ visibilities = [:public, :unlisted, :public_unlisted]
+ visibilities << :login unless current_account.nil?
+ @account.statuses.where(visibility: visibilities)
end
def only_media_scope
diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb
index 5c53f58e44..f7afc9ba31 100644
--- a/app/controllers/activitypub/replies_controller.rb
+++ b/app/controllers/activitypub/replies_controller.rb
@@ -33,7 +33,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
def set_replies
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
- @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted, :public_unlisted])
+ @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted, :public_unlisted, :login])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end
diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
index 8464f02244..5cc71a6eab 100644
--- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
+++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
@@ -26,7 +26,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
end
def paginated_statuses
- Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted, :public_unlisted]).paginate_by_max_id(
+ Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted, :public_unlisted, :login]).paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index c5429325f8..5520c53b97 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -124,6 +124,8 @@ module ApplicationHelper
fa_icon('unlock', title: I18n.t('statuses.visibilities.unlisted'))
elsif status.public_unlisted_visibility?
fa_icon('cloud', title: I18n.t('statuses.visibilities.public_unlisted'))
+ elsif status.login_visibility?
+ fa_icon('key', title: I18n.t('statuses.visibilities.login'))
elsif status.private_visibility? || status.limited_visibility?
fa_icon('lock', title: I18n.t('statuses.visibilities.private'))
elsif status.direct_visibility?
@@ -189,8 +191,8 @@ module ApplicationHelper
text: [params[:title], params[:text], params[:url]].compact.join(' '),
}
- permit_visibilities = %w(public unlisted public_unlisted private direct)
- permit_searchabilities = %w(public unlisted public_unlisted private direct)
+ permit_visibilities = %w(public unlisted public_unlisted login private direct)
+ permit_searchabilities = %w(public unlisted public_unlisted login private direct)
default_privacy = current_account&.user&.setting_default_privacy
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
index 975a4d3522..ae98681d0c 100644
--- a/app/helpers/formatting_helper.rb
+++ b/app/helpers/formatting_helper.rb
@@ -58,6 +58,6 @@ module FormattingHelper
end
def account_field_value_format(field, with_rel_me: true)
- html_aware_format(field.value, field.account.local?, markdown: true, with_rel_me: with_rel_me, with_domains: true, multiline: false)
+ html_aware_format(field.value, field.account.local?, markdown: false, with_rel_me: with_rel_me, with_domains: true, multiline: false)
end
end
diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb
index ccc42e0f43..d878029d9a 100644
--- a/app/helpers/statuses_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -100,6 +100,8 @@ module StatusesHelper
fa_icon 'unlock fw'
when 'public_unlisted'
fa_icon 'cloud fw'
+ when 'login'
+ fa_icon 'key fw'
when 'private'
fa_icon 'lock fw'
when 'direct'
diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx
index 7afcfa2fe4..8fa62405ca 100644
--- a/app/javascript/mastodon/components/status.jsx
+++ b/app/javascript/mastodon/components/status.jsx
@@ -61,6 +61,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
+ login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
@@ -369,6 +370,7 @@ class Status extends ImmutablePureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
+ 'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
};
diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx
index f544cffc2a..53adb20fe9 100644
--- a/app/javascript/mastodon/components/status_action_bar.jsx
+++ b/app/javascript/mastodon/components/status_action_bar.jsx
@@ -260,8 +260,9 @@ class StatusActionBar extends ImmutablePureComponent {
const { signedIn, permissions } = this.context.identity;
const anonymousAccess = !signedIn;
- const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
- const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'private'].includes(status.get('visibility_ex'));
+ const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
+ const anonymousStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
+ const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'login', 'private'].includes(status.get('visibility_ex'));
const mutingConversation = status.get('muted');
const account = status.get('account');
const writtenByMe = status.getIn(['account', 'id']) === me;
@@ -281,7 +282,7 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
}
- if (publicStatus) {
+ if (anonymousStatus) {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
}
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx
index cbd2803e83..f366ef5c6e 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.jsx
+++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx
@@ -245,7 +245,7 @@ class ComposeForm extends ImmutablePureComponent {
} else if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
publishText = {intl.formatMessage(messages.publish)};
} else {
- publishText = (this.props.privacy !== 'unlisted' && this.props.privacy !== 'public_unlisted') ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
+ publishText = (this.props.privacy !== 'unlisted' && this.props.privacy !== 'public_unlisted' && this.props.privacy !== 'login') ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
}
return (
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx
index 15eab3399a..2fe7d6e1f6 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx
@@ -9,6 +9,7 @@ import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';
import { Icon } from 'mastodon/components/icon';
+import { enableLoginPrivacy } from 'mastodon/initial_state';
import { IconButton } from '../../../components/icon_button';
@@ -19,6 +20,8 @@ const messages = defineMessages({
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but opted-out of discovery features' },
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
public_unlisted_long: { id: 'privacy.public_unlisted.long', defaultMessage: 'Visible for all without GTL' },
+ login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
+ login_long: { id: 'privacy.login.long', defaultMessage: 'Login user only' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
@@ -226,14 +229,19 @@ class PrivacyDropdown extends PureComponent {
this.options = [
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
+ { icon: 'key', value: 'login', text: formatMessage(messages.login_short), meta: formatMessage(messages.login_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: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
];
+ this.selectableOptions = [...this.options];
- if (!this.props.noDirect) {
- this.options.push(
- { icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
- );
+ if (!enableLoginPrivacy) {
+ this.selectableOptions = this.selectableOptions.filter((opt) => opt.value !== 'login');
+ }
+
+ if (this.props.noDirect) {
+ this.selectableOptions = this.selectableOptions.filter((opt) => opt.value !== 'direct');
}
}
@@ -253,7 +261,7 @@ class PrivacyDropdown extends PureComponent {
const { value, container, disabled, intl } = this.props;
const { open, placement } = this.state;
- const valueOption = this.options.find(item => item.value === value);
+ const valueOption = this.options.find(item => item.value === value) || this.options[0];
return (
@@ -279,7 +287,7 @@ class PrivacyDropdown extends PureComponent {
({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
- hashtagWarning: ['public', 'public_unlisted'].indexOf(state.getIn(['compose', 'privacy'])) < 0 && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
+ hashtagWarning: ['public', 'public_unlisted', 'login'].indexOf(state.getIn(['compose', 'privacy'])) < 0 && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])),
directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct',
searchabilityWarning: state.getIn(['compose', 'searchability']) === 'limited',
});
diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx
index 23704b803b..475a1a0912 100644
--- a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx
+++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx
@@ -155,7 +155,7 @@ class Footer extends ImmutablePureComponent {
render () {
const { status, intl, withOpenButton } = this.props;
- const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
+ const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private';
let replyIcon, replyTitle;
diff --git a/app/javascript/mastodon/features/report/components/status_check_box.jsx b/app/javascript/mastodon/features/report/components/status_check_box.jsx
index 9fc2655960..e16aa10865 100644
--- a/app/javascript/mastodon/features/report/components/status_check_box.jsx
+++ b/app/javascript/mastodon/features/report/components/status_check_box.jsx
@@ -18,6 +18,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
+ login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
});
@@ -48,6 +49,7 @@ class StatusCheckBox extends PureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
+ 'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
};
diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx
index ec0ab2285b..05d9561447 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.jsx
+++ b/app/javascript/mastodon/features/status/components/action_bar.jsx
@@ -200,8 +200,9 @@ class ActionBar extends PureComponent {
const { status, relationship, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
- const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
- const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'private'].includes(status.get('visibility_ex'));
+ const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
+ const anonymousStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
+ const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'login', 'private'].includes(status.get('visibility_ex'));
const mutingConversation = status.get('muted');
const account = status.get('account');
const writtenByMe = status.getIn(['account', 'id']) === me;
@@ -220,7 +221,10 @@ class ActionBar extends PureComponent {
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShare });
}
- menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
+ if (anonymousStatus) {
+ menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
+ }
+
menu.push(null);
menu.push({ text: intl.formatMessage(messages.reblog), action: this.handleReblogForceModalClick });
menu.push(null);
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx
index 18dd87f01a..90a9cb838c 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.jsx
+++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx
@@ -28,6 +28,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
+ login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
@@ -219,6 +220,7 @@ class DetailedStatus extends ImmutablePureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
+ 'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
};
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.jsx b/app/javascript/mastodon/features/ui/components/boost_modal.jsx
index 38a106fad2..d913340a54 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx
@@ -25,6 +25,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
+ login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
});
@@ -91,6 +92,7 @@ class BoostModal extends ImmutablePureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
+ 'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
};
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 524a4229c8..991af7eb24 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -58,6 +58,7 @@
* @property {string} display_media
* @property {boolean} display_media_expand
* @property {string} domain
+ * @property {boolean} enable_login_privacy
* @property {boolean=} expand_spoilers
* @property {boolean} limited_federation_mode
* @property {string} locale
@@ -111,6 +112,7 @@ export const disabledAccountId = getMeta('disabled_account_id');
export const displayMedia = getMeta('display_media');
export const displayMediaExpand = getMeta('display_media_expand');
export const domain = getMeta('domain');
+export const enableLoginPrivacy = getMeta('enable_login_privacy');
export const expandSpoilers = getMeta('expand_spoilers');
export const forceSingleColumn = !getMeta('advanced_layout');
export const limitedFederationMode = getMeta('limited_federation_mode');
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index ebb046da89..6dab3962df 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -239,7 +239,7 @@ const insertExpiration = (state, position, data) => {
};
const privacyPreference = (a, b) => {
- const order = ['public', 'public_unlisted', 'unlisted', 'private', 'direct'];
+ const order = ['public', 'public_unlisted', 'unlisted', 'login', 'private', 'direct'];
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
};
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 2b2d2c4622..03116a4935 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -1,4 +1,4 @@
-import { Map as ImmutableMap, ImmutableList, fromJS } from 'immutable';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import {
diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss
index efb23e524e..ba58aec7dc 100644
--- a/app/javascript/styles/mastodon/emoji_picker.scss
+++ b/app/javascript/styles/mastodon/emoji_picker.scss
@@ -167,9 +167,8 @@
cursor: pointer;
&.emoji-mart-emoji-custom img {
- width: 22px !important;
- overflow: hidden;
- object-fit: cover !important;
+ width: auto !important;
+ min-width: 22px;
}
span {
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index d119fd58bb..414ada0fa0 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -480,6 +480,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
nil
elsif audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
:public
+ elsif audience_searchable_by.include?('as:Limited')
+ :limited
elsif audience_searchable_by.include?(@account.followers_url)
:private
else
@@ -502,17 +504,17 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
searchability = searchability_bio[0]
return nil if searchability.nil?
- searchability = :public if searchability == 'public'
- searchability = :unlisted if searchability == 'followers'
- searchability = :direct if searchability == 'private'
- searchability = :private if searchability == 'reactors'
+ searchability = :public if searchability == 'public'
+ searchability = :private if searchability == 'followers'
+ searchability = :limited if searchability == 'private'
+ searchability = :direct if searchability == 'reactors'
end
visibility = visibility_from_audience_with_silence
- if searchability == visibility
+ if searchability == visibility || searchability == :limited || searchability == :direct
searchability
- elsif [:public, :unlisted, :private].include?(searchability) && [:public, :unlisted, :private].include?(visibility)
+ elsif [:public, :unlisted, :private].include?(searchability) && [:public, :public_unlisted, :unlisted, :login, :private].include?(visibility)
:private
else
:direct
@@ -524,6 +526,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
:public
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
:unlisted
+ elsif audience_cc.include?('as:LoginOnly')
+ :login
elsif audience_to.include?(@account.followers_url)
:private
else
diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb
index 3ba154d015..aa871656e9 100644
--- a/app/lib/activitypub/parser/status_parser.rb
+++ b/app/lib/activitypub/parser/status_parser.rb
@@ -77,6 +77,8 @@ class ActivityPub::Parser::StatusParser
:public
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
:unlisted
+ elsif audience_cc.include?('as:LoginOnly')
+ :login
elsif audience_to.include?(@magic_values[:followers_collection])
:private
else
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index 697b4f9572..091ac14601 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -90,7 +90,7 @@ class ActivityPub::TagManager
case status.visibility
when 'public'
[COLLECTIONS[:public]]
- when 'unlisted', 'public_unlisted', 'private'
+ when 'unlisted', 'public_unlisted', 'login', 'private'
[account_followers_url(status.account)]
when 'direct', 'limited'
if status.account.silenced?
@@ -128,6 +128,8 @@ class ActivityPub::TagManager
cc << account_followers_url(status.account)
when 'unlisted', 'public_unlisted'
cc << COLLECTIONS[:public]
+ when 'login'
+ cc << 'as:LoginOnly'
end
cc + cc_private_visibility(status)
@@ -221,6 +223,8 @@ class ActivityPub::TagManager
[account_followers_url(status.account)]
when 'direct'
status.conversation_id.present? ? [uri_for(status.conversation)] : []
+ when 'limited'
+ ['as:Limited']
else
[]
end
@@ -234,6 +238,8 @@ class ActivityPub::TagManager
[COLLECTIONS[:public]]
when 'private', 'direct'
[account_followers_url(account)]
+ when 'limited'
+ ['as:Limited']
else
[]
end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index aef20dc52e..8cb998909e 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -109,7 +109,7 @@ class FeedManager
def merge_into_home(from_account, into_account)
timeline_key = key(:home, into_account.id)
aggregate = into_account.user&.aggregates_reblogs?
- query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
+ query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
@@ -135,7 +135,7 @@ class FeedManager
def merge_into_list(from_account, list)
timeline_key = key(:list, list.id)
aggregate = list.account.user&.aggregates_reblogs?
- query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
+ query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
@@ -253,7 +253,7 @@ class FeedManager
next if last_status_score < oldest_home_score
end
- statuses = target_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit)
+ statuses = target_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login, :private]).includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit)
crutches = build_crutches(account.id, statuses)
statuses.each do |status|
diff --git a/app/models/account_statuses_filter.rb b/app/models/account_statuses_filter.rb
index d76e62ae72..03dd4a34f0 100644
--- a/app/models/account_statuses_filter.rb
+++ b/app/models/account_statuses_filter.rb
@@ -30,6 +30,7 @@ class AccountStatusesFilter
scope.merge!(scope.where.not(visibility: :public_unlisted)) if domain_block&.reject_send_public_unlisted || (domain_block&.detect_invalid_subscription && @account.user&.setting_reject_public_unlisted_subscription)
scope.merge!(scope.where.not(visibility: :unlisted)) if domain_block&.detect_invalid_subscription && @account.user&.setting_reject_unlisted_subscription
scope.merge!(scope.where(spoiler_text: ['', nil])) if domain_block&.reject_send_sensitive
+ scope.merge!(scope.where.not(visibility: :login)) if current_account.nil?
scope
end
@@ -51,7 +52,7 @@ class AccountStatusesFilter
def filtered_scope
scope = account.statuses.left_outer_joins(:mentions)
- scope.merge!(scope.where(visibility: follower? ? %i(public unlisted public_unlisted private) : %i(public unlisted public_unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
+ scope.merge!(scope.where(visibility: follower? ? %i(public unlisted public_unlisted login private) : %i(public unlisted public_unlisted login)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
scope
diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb
index 7d19e293b9..1c76ba997b 100644
--- a/app/models/admin/status_filter.rb
+++ b/app/models/admin/status_filter.rb
@@ -16,7 +16,7 @@ class Admin::StatusFilter
end
def results
- scope = @account.statuses.where(visibility: [:public, :unlisted, :public_unlisted])
+ scope = @account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :login])
params.each do |key, value|
next if IGNORED_PARAMS.include?(key.to_s)
diff --git a/app/models/antenna.rb b/app/models/antenna.rb
index 51a71cb763..38bf04b7b4 100644
--- a/app/models/antenna.rb
+++ b/app/models/antenna.rb
@@ -220,7 +220,7 @@ class Antenna < ApplicationRecord
private
def validate_limit
- errors.add(:base, I18n.t('scheduled_statuses.over_total_limit', limit: LIMIT)) if account.antennas.count >= LIMIT
+ errors.add(:base, I18n.t('antennas.errors.over_limit', limit: LIMIT)) if account.antennas.count >= LIMIT
end
def validate_stl_limit
@@ -228,6 +228,6 @@ class Antenna < ApplicationRecord
stls = account.antennas.where(stl: true).where.not(id: id)
- errors.add(:base, I18n.t('scheduled_statuses.over_total_limit', limit: LIMIT)) if list_id.zero? ? stls.any? { |tl| tl.list_id.zero? } : stls.any? { |tl| tl.list_id != 0 }
+ errors.add(:base, I18n.t('antennas.errors.over_stl_limit', limit: 1)) if list_id.zero? ? stls.any? { |tl| tl.list_id.zero? } : stls.any? { |tl| tl.list_id != 0 }
end
end
diff --git a/app/models/concerns/has_user_settings.rb b/app/models/concerns/has_user_settings.rb
index 19ab0bf073..0c56e724b9 100644
--- a/app/models/concerns/has_user_settings.rb
+++ b/app/models/concerns/has_user_settings.rb
@@ -23,6 +23,10 @@ module HasUserSettings
settings['web.auto_play']
end
+ def setting_enable_login_privacy
+ settings['web.enable_login_privacy']
+ end
+
def setting_default_sensitive
settings['default_sensitive']
end
diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb
index 1665c2ba5f..38d0f393b7 100644
--- a/app/models/concerns/status_threading_concern.rb
+++ b/app/models/concerns/status_threading_concern.rb
@@ -12,7 +12,7 @@ module StatusThreadingConcern
end
def self_replies(limit)
- account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted, :public_unlisted]).reorder(id: :asc).limit(limit)
+ account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted, :public_unlisted, :login]).reorder(id: :asc).limit(limit)
end
private
diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb
index 1bc47af7f2..6aa5834aca 100644
--- a/app/models/featured_tag.rb
+++ b/app/models/featured_tag.rb
@@ -45,7 +45,7 @@ class FeaturedTag < ApplicationRecord
end
def decrement(deleted_status_id)
- update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
+ update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted public_unlisted login)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
end
private
@@ -59,8 +59,8 @@ class FeaturedTag < ApplicationRecord
end
def reset_data
- self.statuses_count = account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).count
- self.last_status_at = account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).select(:created_at).first&.created_at
+ self.statuses_count = account.statuses.where(visibility: %i(public unlisted public_unlisted login)).tagged_with(tag).count
+ self.last_status_at = account.statuses.where(visibility: %i(public unlisted public_unlisted login)).tagged_with(tag).select(:created_at).first&.created_at
end
def validate_featured_tags_limit
diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb
index 51a9058a84..f4d27e53d3 100644
--- a/app/models/public_feed.rb
+++ b/app/models/public_feed.rb
@@ -29,6 +29,7 @@ class PublicFeed
scope.merge!(account_filters_scope) if account?
scope.merge!(media_only_scope) if media_only?
scope.merge!(language_scope) if account&.chosen_languages.present?
+ scope.merge!(anonymous_scope) unless account?
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
end
@@ -97,6 +98,10 @@ class PublicFeed
Status.where(language: account.chosen_languages)
end
+ def anonymous_scope
+ Status.where.not(visibility: :login)
+ end
+
def account_filters_scope
Status.not_excluded_by_account(account).tap do |scope|
scope.merge!(Status.not_domain_blocked_by_account(account)) unless local_only?
diff --git a/app/models/status.rb b/app/models/status.rb
index cc3c55a6be..b6f5ac1152 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -52,7 +52,7 @@ class Status < ApplicationRecord
update_index('statuses', :proper)
- enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10 }, _suffix: :visibility
+ enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 }, _suffix: :visibility
enum searchability: { public: 0, private: 1, direct: 2, limited: 3, public_unlisted: 10 }, _suffix: :searchability
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
@@ -102,8 +102,8 @@ class Status < ApplicationRecord
scope :with_accounts, ->(ids) { where(id: ids).includes(:account) }
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) }
- scope :with_public_visibility, -> { where(visibility: [:public, :public_unlisted]) }
- scope :with_global_timeline_visibility, -> { where(visibility: [:public]) }
+ scope :with_public_visibility, -> { where(visibility: [:public, :public_unlisted, :login]) }
+ scope :with_global_timeline_visibility, -> { where(visibility: [:public, :login]) }
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
@@ -409,7 +409,7 @@ class Status < ApplicationRecord
end
def selectable_searchabilities
- searchabilities.keys - %w(public_unlisted limited)
+ searchabilities.keys - %w(public_unlisted)
end
def favourites_map(status_ids, account_id)
@@ -526,7 +526,7 @@ class Status < ApplicationRecord
def set_searchability
return if searchability.nil?
- self.searchability = [Status.searchabilities[searchability], Status.visibilities[visibility == 'public_unlisted' ? 'public' : visibility]].max
+ self.searchability = [Status.searchabilities[searchability], Status.visibilities[visibility == 'public_unlisted' || visibility == 'login' ? 'public' : visibility]].max
end
def set_conversation
diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb
index b8cd63557e..4f9c9c6d64 100644
--- a/app/models/tag_feed.rb
+++ b/app/models/tag_feed.rb
@@ -32,6 +32,7 @@ class TagFeed < PublicFeed
scope.merge!(remote_only_scope) if remote_only?
scope.merge!(account_filters_scope) if account?
scope.merge!(media_only_scope) if media_only?
+ scope.merge!(anonymous_scope) unless account?
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
end
diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb
index 907c18b7b1..2634fc48c2 100644
--- a/app/models/user_settings.rb
+++ b/app/models/user_settings.rb
@@ -37,6 +37,7 @@ class UserSettings
setting :use_system_font, default: false
setting :disable_swiping, default: false
setting :delete_modal, default: true
+ setting :enable_login_privacy, default: false
setting :reblog_modal, default: false
setting :unfollow_modal, default: true
setting :reduce_motion, default: false
diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb
index 94bc333ede..335abe9e92 100644
--- a/app/policies/status_policy.rb
+++ b/app/policies/status_policy.rb
@@ -15,6 +15,8 @@ class StatusPolicy < ApplicationPolicy
if requires_mention?
owned? || mention_exists?
+ elsif login?
+ owned? || !current_account.nil?
elsif private?
owned? || following_author? || mention_exists?
else
@@ -58,6 +60,10 @@ class StatusPolicy < ApplicationPolicy
record.private_visibility?
end
+ def login?
+ record.login_visibility?
+ end
+
def public?
record.public_visibility? || record.public_unlisted_visibility?
end
diff --git a/app/presenters/status_relationships_presenter.rb b/app/presenters/status_relationships_presenter.rb
index 24e00adcb9..e5ec67f8c0 100644
--- a/app/presenters/status_relationships_presenter.rb
+++ b/app/presenters/status_relationships_presenter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class StatusRelationshipsPresenter
- PINNABLE_VISIBILITIES = %w(public public_unlisted unlisted private).freeze
+ PINNABLE_VISIBILITIES = %w(public public_unlisted unlisted login private).freeze
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map,
:bookmarks_map, :filters_map, :emoji_reactions_map
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 209b6d6af5..c2206eef05 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -42,7 +42,8 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:auto_play_gif] = object.current_account.user.setting_auto_play_gif
store[:display_media] = object.current_account.user.setting_display_media
store[:display_media_expand] = object.current_account.user.setting_display_media_expand
- store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
+ store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
+ store[:enable_login_privacy] = object.current_account.user.setting_enable_login_privacy
store[:reduce_motion] = object.current_account.user.setting_reduce_motion
store[:disable_swiping] = object.current_account.user.setting_disable_swiping
store[:advanced_layout] = object.current_account.user.setting_advanced_layout
diff --git a/app/serializers/rest/custom_emoji_serializer.rb b/app/serializers/rest/custom_emoji_serializer.rb
index 22f212e583..3c34f09199 100644
--- a/app/serializers/rest/custom_emoji_serializer.rb
+++ b/app/serializers/rest/custom_emoji_serializer.rb
@@ -3,13 +3,13 @@
class REST::CustomEmojiSerializer < ActiveModel::Serializer
include RoutingHelper
- attributes :shortcode, :url, :static_url, :visible_in_picker, :is_sensitive
+ attributes :shortcode, :url, :static_url, :visible_in_picker
attribute :category, if: :category_loaded?
attribute :width, if: :width?
attribute :height, if: :height?
attribute :aliases, if: :aliases?
- attribute :is_sensitive, if: :is_sensitive?
+ attribute :sensitive, if: :sensitive?
def url
full_asset_url(object.image.url)
@@ -47,7 +47,11 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer
object.respond_to?(:aliases) && object.aliases.present?
end
- def is_sensitive? # rubocop:disable Naming/PredicateName
+ def sensitive?
object.respond_to?(:is_sensitive)
end
+
+ def sensitive
+ object.is_sensitive
+ end
end
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index c065c817a6..fe9334205e 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -57,7 +57,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
# UX differences
if object.limited_visibility?
'private'
- elsif object.public_unlisted_visibility?
+ elsif object.public_unlisted_visibility? || object.login_visibility?
'public'
else
object.visibility
@@ -159,7 +159,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
current_user? &&
current_user.account_id == object.account_id &&
!object.reblog? &&
- %w(public unlisted public_unlisted private).include?(object.visibility)
+ %w(public unlisted public_unlisted login private).include?(object.visibility)
end
def reactions?
diff --git a/app/services/concerns/account_scope.rb b/app/services/concerns/account_scope.rb
index 43bef653bd..00bb9a3960 100644
--- a/app/services/concerns/account_scope.rb
+++ b/app/services/concerns/account_scope.rb
@@ -3,8 +3,7 @@
module AccountScope
def scope_status(status)
case status.visibility.to_sym
- when :public, :unlisted, :public_unlisted
- # scope_local.merge(scope_list_following_account(status.account))
+ when :public, :unlisted, :public_unlisted, :login
scope_local
when :private
scope_account_local_followers(status.account)
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index ac9f91990f..b8d37952e3 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -46,10 +46,10 @@ class FanOutOnWriteService < BaseService
notify_about_update! if update?
case @status.visibility.to_sym
- when :public, :unlisted, :public_unlisted, :private
+ when :public, :unlisted, :public_unlisted, :login, :private
deliver_to_all_followers!
deliver_to_lists!
- deliver_to_antennas! if [:public, :public_unlisted].include?(@status.visibility.to_sym) && !@account.dissubscribable
+ deliver_to_antennas! if [:public, :public_unlisted, :login].include?(@status.visibility.to_sym) && !@account.dissubscribable
deliver_to_stl_antennas!
when :limited
deliver_to_mentioned_followers!
@@ -121,7 +121,7 @@ class FanOutOnWriteService < BaseService
antennas = Antenna.available_stls
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
- antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where.not(list_id: 0) if !@account.domain.nil? || [:public, :public_unlisted].exclude?(@status.visibility.to_sym)
+ antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where.not(list_id: 0) if !@account.domain.nil? || [:public, :public_unlisted, :login].exclude?(@status.visibility.to_sym)
collection = AntennaCollection.new(@status, @options[:update])
diff --git a/app/services/group_reblog_service.rb b/app/services/group_reblog_service.rb
index a733429b2d..4c30b0677b 100644
--- a/app/services/group_reblog_service.rb
+++ b/app/services/group_reblog_service.rb
@@ -9,7 +9,7 @@ class GroupReblogService < BaseService
return nil if status.account.group?
visibility = status.visibility.to_sym
- return nil unless %i(public public_unlisted unlisted private direct).include?(visibility)
+ return nil unless %i(public public_unlisted unlisted login private direct).include?(visibility)
accounts = status.mentions.map(&:account) | status.active_mentions.map(&:account)
transcription = %i(private direct).include?(visibility)
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index ad9e6e3d63..604a34666d 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -8,6 +8,7 @@ class NotifyService < BaseService
admin.sign_up
update
poll
+ emoji_reaction
).freeze
def call(recipient, type, activity)
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 43a387d1cc..9859787d90 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -72,7 +72,7 @@ class PostStatusService < BaseService
end) || @options[:spoiler_text].present?
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
- @visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted) && @account.silenced?
+ @visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced?
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
@searchability = searchability
@markdown = @options[:markdown] || false
@@ -85,9 +85,9 @@ class PostStatusService < BaseService
def searchability
case @options[:searchability]&.to_sym
when :public
- case @visibility&.to_sym when :public, :public_unlisted then :public when :unlisted, :private then :private else :direct end
+ case @visibility&.to_sym when :public, :public_unlisted, :login then :public when :unlisted, :private then :private else :direct end
when :private
- case @visibility&.to_sym when :public, :public_unlisted, :unlisted, :private then :private else :direct end
+ case @visibility&.to_sym when :public, :public_unlisted, :login, :unlisted, :private then :private else :direct end
when :direct
:direct
when nil
diff --git a/app/services/report_service.rb b/app/services/report_service.rb
index b673c5a029..30272685a3 100644
--- a/app/services/report_service.rb
+++ b/app/services/report_service.rb
@@ -62,7 +62,7 @@ class ReportService < BaseService
# If the account making reports is remote, it is likely anonymized so we have to relax the requirements for attaching statuses.
domain = @source_account.domain.to_s.downcase
has_followers = @target_account.followers.where(Account.arel_table[:domain].lower.eq(domain)).exists?
- visibility = has_followers ? %i(public unlisted public_unlisted private) : %i(public unlisted public_unlisted)
+ visibility = has_followers ? %i(public unlisted public_unlisted login private) : %i(public unlisted public_unlisted)
scope = @target_account.statuses.with_discarded
scope.merge!(scope.where(visibility: visibility).or(scope.where('EXISTS (SELECT 1 FROM mentions m JOIN accounts a ON m.account_id = a.id WHERE lower(a.domain) = ?)', domain)))
# Allow missing posts to not drop reports that include e.g. a deleted post
diff --git a/app/views/antennas/edit.html.haml b/app/views/antennas/edit.html.haml
index 7452df8e40..ba3e4f368d 100644
--- a/app/views/antennas/edit.html.haml
+++ b/app/views/antennas/edit.html.haml
@@ -2,6 +2,7 @@
= t('antennas.edit.title')
= simple_form_for @antenna, url: antenna_path(@antenna), method: :put do |f|
+ = render 'shared/error_messages', object: @antenna
= render 'antenna_fields', f: f, lists: @lists
.actions
diff --git a/app/views/antennas/new.html.haml b/app/views/antennas/new.html.haml
index c53c7bc002..9ff46b253c 100644
--- a/app/views/antennas/new.html.haml
+++ b/app/views/antennas/new.html.haml
@@ -2,6 +2,7 @@
= t('antennas.new.title')
= simple_form_for @antenna, url: antennas_path do |f|
+ = render 'shared/error_messages', object: @antenna
= render 'antenna_fields', f: f, lists: @lists
.actions
diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml
index 69e1d8f430..38bf483f95 100644
--- a/app/views/settings/preferences/other/show.html.haml
+++ b/app/views/settings/preferences/other/show.html.haml
@@ -33,6 +33,9 @@
.fields-group.fields-row__column.fields-row__column-6
= ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: lambda { |locale| locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language')
+ .fields-group
+ = ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false
+
.fields-group
= ff.input :public_post_to_unlisted, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_public_post_to_unlisted'), hint: I18n.t('simple_form.hints.defaults.setting_public_post_to_unlisted')
diff --git a/app/workers/delivery_emoji_reaction_worker.rb b/app/workers/delivery_emoji_reaction_worker.rb
index 7115c1e9d0..5ec9dd4fba 100644
--- a/app/workers/delivery_emoji_reaction_worker.rb
+++ b/app/workers/delivery_emoji_reaction_worker.rb
@@ -16,7 +16,7 @@ class DeliveryEmojiReactionWorker
redis.publish("timeline:#{account.id}", payload_json) if !account.user&.setting_stop_emoji_reaction_streaming && redis.exists?("subscribed:timeline:#{account.id}")
end
- if [:public, :unlisted, :public_unlisted].exclude?(status.visibility.to_sym) && status.account_id != my_account_id &&
+ if [:public, :unlisted, :public_unlisted, :login].exclude?(status.visibility.to_sym) && status.account_id != my_account_id &&
redis.exists?("subscribed:timeline:#{status.account_id}") && !status.account.user&.setting_stop_emoji_reaction_streaming
redis.publish("timeline:#{status.account_id}", payload_json)
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index da59b15c7d..e20c5c3582 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1032,6 +1032,8 @@ en:
empty_contexts: No contexts! You must set any context filters
invalid_context: None or invalid context supplied
invalid_list_owner: This list is not yours
+ over_limit: You have exceeded the limit of %{limit} antennas
+ over_stl_limit: You have exceeded the limit of %{limit} stl antennas
remove_list_with_antenna: Cannot remove list because this list is related to antenna.
index:
contexts: Antennas in %{contexts}
@@ -1698,14 +1700,14 @@ en:
other: "%{count} votes"
vote: Vote
searchabilities:
- direct: Self only
- direct_long: Nobody can find, but you can
- private: Reactionners
- private_long: Reacter of this post can find
+ direct: Reactionners
+ direct_long: Reacter of this post can find
+ limited: Self only
+ limited_long: Nobody can find, but you can
+ private: Followers only
+ private_long: Your followers can find
public: Public
public_long: Anyone can find
- unlisted: Followers only
- unlisted_long: Your followers can find
show_more: Show more
show_newer: Show newer
show_older: Show older
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 8ae370a76e..cb2c23881e 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -999,6 +999,8 @@ ja:
errors:
empty_contexts: 絞り込み条件が1つも指定されていないため無効です(除外条件はカウントされません)
invalid_list_owner: これはあなたのリストではありません
+ over_limit: 所持できるアンテナ数 %{limit}を超えています
+ over_stl_limit: 所持できるSTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
remove_list_with_antenna: アンテナが関連付けられているリストは削除できません
edit:
accounts_hint: ローカルアカウントの場合は「@info」、リモートアカウントの場合は「@info@example.com」の形式で指定します。サーバーが認識していないアカウントは保存時に自動的に削除されます。
@@ -1638,14 +1640,14 @@ ja:
other: "%{count}票"
vote: 投票
searchabilities:
- direct: 自分のみ
- direct_long: この投稿はあなたしか検索できません
- private: リアクションした人
- private_long: この投稿にリアクションした人しか検索できません
+ direct: リアクションした人
+ direct_long: この投稿にリアクションした人しか検索できません
+ limited: 自分のみ
+ limited_long: この投稿はあなたしか検索できません
+ private: フォロワーのみ
+ private_long: この投稿はフォロワーのみが検索できます
public: 公開
public_long: この投稿は誰でも検索できます
- unlisted: フォロワーのみ
- unlisted_long: この投稿はフォロワーのみが検索できます
show_more: もっと見る
show_newer: 新しいものを表示
show_older: 古いものを表示
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 7613ff85b0..bfae64a85c 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -220,6 +220,7 @@ en:
setting_display_media_hide_all: Hide all
setting_display_media_show_all: Show all
setting_emoji_reaction_streaming_notify_impl2: Enable stamp notification compat with Nyastodon, Catstodon, glitch-soc
+ setting_enable_login_privacy: Enable login visibility
setting_expand_spoilers: Always expand posts marked with content warnings
setting_hide_followers_count: Hide followers count
setting_hide_following_count: Hide following count
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index c7207d18d4..817ca05d6f 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -227,6 +227,7 @@ ja:
setting_display_media_expand: 5個目以降のメディアも表示する (最大16)
setting_display_media_hide_all: 非表示
setting_display_media_show_all: 表示
+ setting_enable_login_privacy: 公開範囲「ログインユーザーのみ」をWeb UIで選択可能にする
setting_emoji_reaction_streaming_notify_impl2: Nyastodon, Catstodon, glitch-soc互換のスタンプ機能を有効にする
setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
setting_hide_followers_count: フォロワー数を隠す