diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index 9e6daf45fa..5ccaba23fd 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -30,12 +30,19 @@ export const makeGetStatus = () => {
}
let filtered = false;
+ let mediaFiltered = false;
if ((accountReblog || accountBase).get('id') !== me && filters) {
let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList();
if (!warnInsteadOfHide && filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) {
return null;
}
- filterResults = filterResults.filter(result => filters.has(result.get('filter')));
+
+ let mediaFilters = filterResults.filter(result => filters.getIn([result.get('filter'), 'filter_action']) === 'blur');
+ if (!mediaFilters.isEmpty()) {
+ mediaFiltered = mediaFilters.map(result => filters.getIn([result.get('filter'), 'title']));
+ }
+
+ filterResults = filterResults.filter(result => filters.has(result.get('filter')) && filters.getIn([result.get('filter'), 'filter_action']) !== 'blur');
if (!filterResults.isEmpty()) {
filtered = filterResults.map(result => filters.getIn([result.get('filter'), 'title']));
}
@@ -45,6 +52,7 @@ export const makeGetStatus = () => {
map.set('reblog', statusReblog);
map.set('account', accountBase);
map.set('matched_filters', filtered);
+ map.set('matched_media_filters', mediaFiltered);
});
},
);
diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb
index a351a140a0..d653c55310 100644
--- a/app/models/custom_filter.rb
+++ b/app/models/custom_filter.rb
@@ -33,7 +33,7 @@ class CustomFilter < ApplicationRecord
include Expireable
include Redisable
- enum :action, { warn: 0, hide: 1 }, suffix: :action
+ enum :action, { warn: 0, hide: 1, blur: 2 }, suffix: :action
belongs_to :account
has_many :keywords, class_name: 'CustomFilterKeyword', inverse_of: :custom_filter, dependent: :destroy
diff --git a/app/views/filters/_filter_fields.html.haml b/app/views/filters/_filter_fields.html.haml
index 797c969b24..f5237e9586 100644
--- a/app/views/filters/_filter_fields.html.haml
+++ b/app/views/filters/_filter_fields.html.haml
@@ -26,7 +26,7 @@
.fields-group
= f.input :filter_action,
as: :radio_buttons,
- collection: %i(warn hide),
+ collection: %i(warn blur hide),
hint: t('simple_form.hints.filters.action'),
include_blank: false,
label_method: ->(action) { filter_action_label(action) },
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index cfb3adf13a..5e162a0d64 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -75,6 +75,7 @@ en:
filters:
action: Chose which action to perform when a post matches the filter
actions:
+ blur: Hide media behind a warning, without hiding the text itself
hide: Completely hide the filtered content, behaving as if it did not exist
warn: Hide the filtered content behind a warning mentioning the filter's title
form_admin_settings:
@@ -260,6 +261,7 @@ en:
name: Hashtag
filters:
actions:
+ blur: Hide media with a warning
hide: Hide completely
warn: Hide with a warning
form_admin_settings:
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 6084014ded..6a77163f7c 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -45,7 +45,7 @@ module Mastodon
def api_versions
{
- mastodon: 4,
+ mastodon: 5,
}
end
diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb
deleted file mode 100644
index 98104b8454..0000000000
--- a/spec/controllers/settings/deletes_controller_spec.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Settings::DeletesController do
- render_views
-
- describe 'GET #show' do
- context 'when signed in' do
- let(:user) { Fabricate(:user) }
-
- before do
- sign_in user, scope: :user
- get :show
- end
-
- it 'renders confirmation page with private cache control headers', :aggregate_failures do
- expect(response).to have_http_status(200)
- expect(response.headers['Cache-Control']).to include('private, no-store')
- end
-
- context 'when suspended' do
- let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
-
- it 'returns http forbidden with private cache control headers', :aggregate_failures do
- expect(response).to have_http_status(403)
- expect(response.headers['Cache-Control']).to include('private, no-store')
- end
- end
- end
-
- context 'when not signed in' do
- it 'redirects' do
- get :show
- expect(response).to redirect_to '/auth/sign_in'
- end
- end
- end
-
- describe 'DELETE #destroy' do
- context 'when signed in' do
- let(:user) { Fabricate(:user, password: 'petsmoldoggos') }
-
- before do
- sign_in user, scope: :user
- end
-
- context 'with correct password' do
- before do
- delete :destroy, params: { form_delete_confirmation: { password: 'petsmoldoggos' } }
- end
-
- it 'removes user record and redirects', :aggregate_failures, :inline_jobs do
- expect(response).to redirect_to '/auth/sign_in'
- expect(User.find_by(id: user.id)).to be_nil
- expect(user.account.reload).to be_suspended
- expect(CanonicalEmailBlock.block?(user.email)).to be false
- end
-
- context 'when suspended' do
- let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
-
- it 'returns http forbidden' do
- expect(response).to have_http_status(403)
- end
- end
- end
-
- context 'with incorrect password' do
- before do
- delete :destroy, params: { form_delete_confirmation: { password: 'blaze420' } }
- end
-
- it 'redirects back to confirmation page' do
- expect(response).to redirect_to settings_delete_path
- end
- end
- end
-
- context 'when not signed in' do
- it 'redirects' do
- delete :destroy
- expect(response).to redirect_to '/auth/sign_in'
- end
- end
- end
-end
diff --git a/spec/requests/settings/deletes_spec.rb b/spec/requests/settings/deletes_spec.rb
index 4563f639d5..c277181999 100644
--- a/spec/requests/settings/deletes_spec.rb
+++ b/spec/requests/settings/deletes_spec.rb
@@ -4,13 +4,65 @@ require 'rails_helper'
RSpec.describe 'Settings Deletes' do
describe 'DELETE /settings/delete' do
- before { sign_in Fabricate(:user) }
+ context 'when signed in' do
+ before { sign_in(user) }
- it 'gracefully handles invalid nested params' do
- delete settings_delete_path(form_delete_confirmation: 'invalid')
+ let(:user) { Fabricate(:user) }
- expect(response)
- .to have_http_status(400)
+ it 'gracefully handles invalid nested params' do
+ delete settings_delete_path(form_delete_confirmation: 'invalid')
+
+ expect(response)
+ .to have_http_status(400)
+ end
+
+ context 'when suspended' do
+ let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
+
+ it 'returns http forbidden' do
+ delete settings_delete_path
+
+ expect(response)
+ .to have_http_status(403)
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to sign in' do
+ delete settings_delete_path
+
+ expect(response)
+ .to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ describe 'GET /settings/delete' do
+ context 'when signed in' do
+ before { sign_in(user) }
+
+ context 'when suspended' do
+ let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
+
+ it 'returns http forbidden with private cache control headers' do
+ get settings_delete_path
+
+ expect(response)
+ .to have_http_status(403)
+ expect(response.headers['Cache-Control'])
+ .to include('private, no-store')
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to sign in' do
+ get settings_delete_path
+
+ expect(response)
+ .to redirect_to(new_user_session_path)
+ end
end
end
end
diff --git a/spec/system/settings/deletes_spec.rb b/spec/system/settings/deletes_spec.rb
new file mode 100644
index 0000000000..91f7104252
--- /dev/null
+++ b/spec/system/settings/deletes_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Settings Deletes' do
+ describe 'Deleting user from settings area' do
+ let(:user) { Fabricate(:user) }
+
+ before { sign_in(user) }
+
+ it 'requires password and deletes user record', :inline_jobs do
+ visit settings_delete_path
+ expect(page)
+ .to have_title(I18n.t('settings.delete'))
+ .and have_private_cache_control
+
+ # Wrong confirmation value
+ fill_in 'form_delete_confirmation_password', with: 'wrongvalue'
+ click_on I18n.t('deletes.proceed')
+ expect(page)
+ .to have_content(I18n.t('deletes.challenge_not_passed'))
+
+ # Correct confirmation value
+ fill_in 'form_delete_confirmation_password', with: user.password
+ click_on I18n.t('deletes.proceed')
+ expect(page)
+ .to have_content(I18n.t('deletes.success_msg'))
+ expect(page)
+ .to have_title(I18n.t('auth.login'))
+ expect(User.find_by(id: user.id))
+ .to be_nil
+ expect(user.account.reload)
+ .to be_suspended
+ expect(CanonicalEmailBlock.block?(user.email))
+ .to be(false)
+ end
+ end
+end