diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 2263fc542d9078..1a65aee51fb38f 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -73,6 +73,7 @@ const messages = defineMessages({ limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' }, mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' }, circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' }, + personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, edited: { id: 'status.edited', defaultMessage: 'Edited {date}' }, }); @@ -405,6 +406,7 @@ class Status extends ImmutablePureComponent { 'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) }, 'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) }, 'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) }, + 'personal': { icon: 'sticky-note-o', text: intl.formatMessage(messages.personal_short) }, 'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) }, }; 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 a9610db75c4401..81b58296d57b89 100644 --- a/app/javascript/mastodon/features/report/components/status_check_box.jsx +++ b/app/javascript/mastodon/features/report/components/status_check_box.jsx @@ -23,6 +23,7 @@ const messages = defineMessages({ limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' }, mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' }, circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' }, + personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, }); @@ -57,6 +58,7 @@ class StatusCheckBox extends PureComponent { 'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) }, 'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) }, 'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) }, + 'personal': { icon: 'sticky-note-o', text: intl.formatMessage(messages.personal_short) }, 'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) }, }; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 63772a83d8aed3..e9568bf9d0ccf5 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -35,6 +35,7 @@ const messages = defineMessages({ limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' }, mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' }, circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' }, + personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' }, searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' }, @@ -260,6 +261,7 @@ class DetailedStatus extends ImmutablePureComponent { 'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) }, 'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) }, 'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) }, + 'personal': { icon: 'sticky-note-o', text: intl.formatMessage(messages.personal_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 fed1ef69df095d..f1a026b2b30be6 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx @@ -30,6 +30,7 @@ const messages = defineMessages({ limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' }, mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' }, circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' }, + personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' }, direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, }); @@ -100,6 +101,7 @@ class BoostModal extends ImmutablePureComponent { 'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) }, 'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) }, 'circle': { icon: 'user-circle', text: intl.formatMessage(messages.circle_short) }, + 'personal': { icon: 'sticky-note-o', text: intl.formatMessage(messages.personal_short) }, 'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) }, }; diff --git a/app/models/status.rb b/app/models/status.rb index 7916b953267600..c16c1a7c4f7f17 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -58,7 +58,7 @@ class Status < ApplicationRecord 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, unsupported: 4, public_unlisted: 10 }, _suffix: :searchability - enum limited_scope: { none: 0, mutual: 1, circle: 2 }, _suffix: :limited + enum limited_scope: { none: 0, mutual: 1, circle: 2, personal: 3 }, _suffix: :limited belongs_to :application, class_name: 'Doorkeeper::Application', optional: true diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 981fad7d757a83..bdc1bbb576cb19 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -145,6 +145,8 @@ def process_status! process_mentions_service.call(@status, limited_type: @status.limited_visibility? ? @limited_scope : '', circle: @circle, save_records: false) safeguard_mentions!(@status) + @status.limited_scope = :personal if @status.limited_visibility? && !process_mentions_service.mentions? + UpdateStatusExpirationService.new.call(@status) # The following transaction block is needed to wrap the UPDATEs to @@ -221,7 +223,7 @@ def validate_media! end def process_mentions_service - ProcessMentionsService.new + @process_mentions_service ||= ProcessMentionsService.new end def process_hashtags_service diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 0a5b48a9d9e654..5053cf4ce3c371 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -24,6 +24,10 @@ def call(status, limited_type: '', circle: nil, save_records: true) end end + def mentions? + @current_mentions.present? + end + private def scan_text! diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index a2cf6fbfb07348..a719b37e3de681 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -188,6 +188,17 @@ expect(status.mentioned_accounts.first.id).to eq mutual_account.id end + it 'personal visibility with mutual' do + account = Fabricate(:account) + text = 'This is an English text.' + + status = subject.call(account, text: text, visibility: 'mutual') + + expect(status.visibility).to eq 'limited' + expect(status.limited_scope).to eq 'personal' + expect(status.mentioned_accounts.count).to eq 0 + end + it 'circle visibility' do account = Fabricate(:account) circle_account = Fabricate(:account) @@ -227,6 +238,18 @@ expect { subject.call(account, text: text, visibility: 'limited') }.to raise_exception ActiveRecord::RecordInvalid end + it 'personal visibility with circle' do + account = Fabricate(:account) + circle = Fabricate(:circle, account: account) + text = 'This is an English text.' + + status = subject.call(account, text: text, visibility: 'circle', circle_id: circle.id) + + expect(status.visibility).to eq 'limited' + expect(status.limited_scope).to eq 'personal' + expect(status.mentioned_accounts.count).to eq 0 + end + it 'safeguards mentions' do account = Fabricate(:account) mentioned_account = Fabricate(:account, username: 'alice')