From 37ca59815cd3a3b2662e20f0c28185eb0d604dd4 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Fri, 22 Mar 2024 17:24:04 +0100
Subject: [PATCH 1/3] Remove setting for unfollow confirmation modal, and make
 it unconditional (#29373)

---
 .../mastodon/containers/account_container.jsx | 21 +++-----
 .../containers/header_container.jsx           | 41 ++++++---------
 .../directory/components/account_card.jsx     | 52 ++++++++-----------
 app/javascript/mastodon/initial_state.js      |  2 -
 app/models/concerns/user/has_settings.rb      |  4 --
 app/models/user_settings.rb                   |  1 -
 app/serializers/initial_state_serializer.rb   |  1 -
 .../preferences/appearance/show.html.haml     |  1 -
 8 files changed, 46 insertions(+), 77 deletions(-)

diff --git a/app/javascript/mastodon/containers/account_container.jsx b/app/javascript/mastodon/containers/account_container.jsx
index a134452e772..f171fcc2fe5 100644
--- a/app/javascript/mastodon/containers/account_container.jsx
+++ b/app/javascript/mastodon/containers/account_container.jsx
@@ -13,7 +13,6 @@ import {
 import { openModal } from '../actions/modal';
 import { initMuteModal } from '../actions/mutes';
 import Account from '../components/account';
-import { unfollowModal } from '../initial_state';
 import { makeGetAccount } from '../selectors';
 
 const messages = defineMessages({
@@ -34,18 +33,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onFollow (account) {
     if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
-      if (unfollowModal) {
-        dispatch(openModal({
-          modalType: 'CONFIRM',
-          modalProps: {
-            message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-            confirm: intl.formatMessage(messages.unfollowConfirm),
-            onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
-          },
-        }));
-      } else {
-        dispatch(unfollowAccount(account.get('id')));
-      }
+      dispatch(openModal({
+        modalType: 'CONFIRM',
+        modalProps: {
+          message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+          confirm: intl.formatMessage(messages.unfollowConfirm),
+          onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
+        },
+      }));
     } else {
       dispatch(followAccount(account.get('id')));
     }
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx
index 071dbdbfb72..73fd62841b3 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx
@@ -21,7 +21,6 @@ import { initDomainBlockModal, unblockDomain } from '../../../actions/domain_blo
 import { openModal } from '../../../actions/modal';
 import { initMuteModal } from '../../../actions/mutes';
 import { initReport } from '../../../actions/reports';
-import { unfollowModal } from '../../../initial_state';
 import { makeGetAccount, getAccountHidden } from '../../../selectors';
 import Header from '../components/header';
 
@@ -47,31 +46,23 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onFollow (account) {
     if (account.getIn(['relationship', 'following'])) {
-      if (unfollowModal) {
-        dispatch(openModal({
-          modalType: 'CONFIRM',
-          modalProps: {
-            message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-            confirm: intl.formatMessage(messages.unfollowConfirm),
-            onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
-          },
-        }));
-      } else {
-        dispatch(unfollowAccount(account.get('id')));
-      }
+      dispatch(openModal({
+        modalType: 'CONFIRM',
+        modalProps: {
+          message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+          confirm: intl.formatMessage(messages.unfollowConfirm),
+          onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
+        },
+      }));
     } else if (account.getIn(['relationship', 'requested'])) {
-      if (unfollowModal) {
-        dispatch(openModal({
-          modalType: 'CONFIRM',
-          modalProps: {
-            message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-            confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
-            onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
-          },
-        }));
-      } else {
-        dispatch(unfollowAccount(account.get('id')));
-      }
+      dispatch(openModal({
+        modalType: 'CONFIRM',
+        modalProps: {
+          message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+          confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
+          onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
+        },
+      }));
     } else {
       dispatch(followAccount(account.get('id')));
     }
diff --git a/app/javascript/mastodon/features/directory/components/account_card.jsx b/app/javascript/mastodon/features/directory/components/account_card.jsx
index ff1f8a653b5..9c5e688120f 100644
--- a/app/javascript/mastodon/features/directory/components/account_card.jsx
+++ b/app/javascript/mastodon/features/directory/components/account_card.jsx
@@ -20,7 +20,7 @@ import { Avatar } from 'mastodon/components/avatar';
 import { Button } from 'mastodon/components/button';
 import { DisplayName } from 'mastodon/components/display_name';
 import { ShortNumber } from 'mastodon/components/short_number';
-import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
+import { autoPlayGif, me } from 'mastodon/initial_state';
 import { makeGetAccount } from 'mastodon/selectors';
 
 const messages = defineMessages({
@@ -48,38 +48,30 @@ const makeMapStateToProps = () => {
 const mapDispatchToProps = (dispatch, { intl }) => ({
   onFollow(account) {
     if (account.getIn(['relationship', 'following'])) {
-      if (unfollowModal) {
-        dispatch(
-          openModal({
-            modalType: 'CONFIRM',
-            modalProps: {
-              message: (
-                <FormattedMessage
-                  id='confirmations.unfollow.message'
-                  defaultMessage='Are you sure you want to unfollow {name}?'
-                  values={{ name: <strong>@{account.get('acct')}</strong> }}
-                />
-              ),
-              confirm: intl.formatMessage(messages.unfollowConfirm),
-              onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
-            } }),
-        );
-      } else {
-        dispatch(unfollowAccount(account.get('id')));
-      }
-    } else if (account.getIn(['relationship', 'requested'])) {
-      if (unfollowModal) {
-        dispatch(openModal({
+      dispatch(
+        openModal({
           modalType: 'CONFIRM',
           modalProps: {
-            message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-            confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
+            message: (
+              <FormattedMessage
+                id='confirmations.unfollow.message'
+                defaultMessage='Are you sure you want to unfollow {name}?'
+                values={{ name: <strong>@{account.get('acct')}</strong> }}
+              />
+            ),
+            confirm: intl.formatMessage(messages.unfollowConfirm),
             onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
-          },
-        }));
-      } else {
-        dispatch(unfollowAccount(account.get('id')));
-      }
+          } }),
+      );
+    } else if (account.getIn(['relationship', 'requested'])) {
+      dispatch(openModal({
+        modalType: 'CONFIRM',
+        modalProps: {
+          message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+          confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
+          onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
+        },
+      }));
     } else {
       dispatch(followAccount(account.get('id')));
     }
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 596c9ca49f3..d8c57a2a0c1 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -38,7 +38,6 @@
  * @property {string} title
  * @property {boolean} show_trends
  * @property {boolean} trends_as_landing_page
- * @property {boolean} unfollow_modal
  * @property {boolean} use_blurhash
  * @property {boolean=} use_pending_items
  * @property {string} version
@@ -99,7 +98,6 @@ export const source_url = getMeta('source_url');
 export const timelinePreview = getMeta('timeline_preview');
 export const title = getMeta('title');
 export const trendsAsLanding = getMeta('trends_as_landing_page');
-export const unfollowModal = getMeta('unfollow_modal');
 export const useBlurhash = getMeta('use_blurhash');
 export const usePendingItems = getMeta('use_pending_items');
 export const version = getMeta('version');
diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb
index bfa8aa2ca35..65373325f0b 100644
--- a/app/models/concerns/user/has_settings.rb
+++ b/app/models/concerns/user/has_settings.rb
@@ -27,10 +27,6 @@ module User::HasSettings
     settings['default_sensitive']
   end
 
-  def setting_unfollow_modal
-    settings['web.unfollow_modal']
-  end
-
   def setting_boost_modal
     settings['web.reblog_modal']
   end
diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb
index 030cbec4d82..6571632fcd7 100644
--- a/app/models/user_settings.rb
+++ b/app/models/user_settings.rb
@@ -27,7 +27,6 @@ class UserSettings
     setting :disable_swiping, default: false
     setting :delete_modal, default: true
     setting :reblog_modal, default: false
-    setting :unfollow_modal, default: true
     setting :reduce_motion, default: false
     setting :expand_content_warnings, default: false
     setting :display_media, default: 'default', in: %w(default show_all hide_all)
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 9f7921461d7..72aaabcfcb1 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -17,7 +17,6 @@ class InitialStateSerializer < ActiveModel::Serializer
 
     if object.current_account
       store[:me]                = object.current_account.id.to_s
-      store[:unfollow_modal]    = object_account_user.setting_unfollow_modal
       store[:boost_modal]       = object_account_user.setting_boost_modal
       store[:delete_modal]      = object_account_user.setting_delete_modal
       store[:auto_play_gif]     = object_account_user.setting_auto_play_gif
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index 76cd4381d8b..e89e015b298 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -66,7 +66,6 @@
     %h4= t 'appearance.confirmation_dialogs'
 
     .fields-group
-      = ff.input :'web.unfollow_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_unfollow_modal')
       = ff.input :'web.reblog_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_boost_modal')
       = ff.input :'web.delete_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_delete_modal')
 

From 81a04ac25cb5a25d8501e59a16b272a6b4e177f0 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Fri, 22 Mar 2024 17:25:36 +0100
Subject: [PATCH 2/3] Improve specs for severed relationships (#29688)

---
 spec/features/severed_relationships_spec.rb | 24 +++++++++++++++++++++
 spec/requests/severed_relationships_spec.rb | 24 ---------------------
 spec/services/block_domain_service_spec.rb  |  5 ++++-
 3 files changed, 28 insertions(+), 25 deletions(-)
 create mode 100644 spec/features/severed_relationships_spec.rb
 delete mode 100644 spec/requests/severed_relationships_spec.rb

diff --git a/spec/features/severed_relationships_spec.rb b/spec/features/severed_relationships_spec.rb
new file mode 100644
index 00000000000..b933398a083
--- /dev/null
+++ b/spec/features/severed_relationships_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Severed relationships page' do
+  include ProfileStories
+
+  describe 'GET severed_relationships#index' do
+    before do
+      as_a_logged_in_user
+
+      event = Fabricate(:relationship_severance_event)
+      Fabricate.times(3, :severed_relationship, local_account: bob.account, relationship_severance_event: event)
+      Fabricate(:account_relationship_severance_event, account: bob.account, relationship_severance_event: event)
+    end
+
+    it 'returns http success' do
+      visit severed_relationships_path
+
+      expect(page).to have_title(I18n.t('settings.severed_relationships'))
+      expect(page).to have_link(href: following_severed_relationship_path(AccountRelationshipSeveranceEvent.first, format: :csv))
+    end
+  end
+end
diff --git a/spec/requests/severed_relationships_spec.rb b/spec/requests/severed_relationships_spec.rb
deleted file mode 100644
index 05a48ca3498..00000000000
--- a/spec/requests/severed_relationships_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe 'Severed relationships page' do
-  include RoutingHelper
-
-  describe 'GET severed_relationships#index' do
-    let(:user) { Fabricate(:user) }
-    let(:event) { Fabricate(:account_relationship_severance_event, account: user.account) }
-
-    before do
-      sign_in user
-
-      Fabricate.times(3, :severed_relationship, local_account: user.account, relationship_severance_event: event.relationship_severance_event)
-    end
-
-    it 'returns http success' do
-      get severed_relationships_path
-
-      expect(response).to have_http_status(200)
-    end
-  end
-end
diff --git a/spec/services/block_domain_service_spec.rb b/spec/services/block_domain_service_spec.rb
index 26f80eaf620..d4f0c042d45 100644
--- a/spec/services/block_domain_service_spec.rb
+++ b/spec/services/block_domain_service_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe BlockDomainService do
       bystander.follow!(local_account)
     end
 
-    it 'creates a domain block, suspends remote accounts with appropriate suspension date, records severed relationships', :aggregate_failures do
+    it 'creates a domain block, suspends remote accounts with appropriate suspension date, records severed relationships and sends notification', :aggregate_failures do
       subject.call(DomainBlock.create!(domain: 'evil.org', severity: :suspend))
 
       expect(DomainBlock.blocked?('evil.org')).to be true
@@ -42,6 +42,9 @@ RSpec.describe BlockDomainService do
       expect(severed_relationships.count).to eq 2
       expect(severed_relationships[0].relationship_severance_event).to eq severed_relationships[1].relationship_severance_event
       expect(severed_relationships.map { |rel| [rel.account, rel.target_account] }).to contain_exactly([bystander, local_account], [local_account, bad_account])
+
+      # Sends severed relationships notification
+      expect(LocalNotificationWorker).to have_enqueued_sidekiq_job(local_account.id, anything, 'AccountRelationshipSeveranceEvent', 'severed_relationships')
     end
   end
 

From 6c381f20b1d52c96525cbd5c41b0c972c3394a48 Mon Sep 17 00:00:00 2001
From: Renaud Chaput <renchap@gmail.com>
Date: Sun, 24 Mar 2024 15:17:06 +0100
Subject: [PATCH 3/3] Restore advanced filter bar setting (#29737)

---
 .../notifications/components/column_settings.jsx      | 11 +++++++++++
 .../notifications/containers/filter_bar_container.js  |  2 +-
 app/javascript/mastodon/locales/en.json               |  2 ++
 3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx
index caa1b757a4b..fc737c0fe20 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx
@@ -54,6 +54,7 @@ export default class ColumnSettings extends PureComponent {
   render () {
     const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission, onRequestNotificationPermission, notificationPolicy } = this.props;
 
+    const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
     const unreadMarkersShowStr = <FormattedMessage id='notifications.column_settings.unread_notifications.highlight' defaultMessage='Highlight unread notifications' />;
     const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
     const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
@@ -116,6 +117,16 @@ export default class ColumnSettings extends PureComponent {
           </div>
         </section>
 
+        <section role='group' aria-labelledby='notifications-filter-bar'>
+          <h3 id='notifications-filter-bar'>
+            <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
+          </h3>
+
+          <div className='column-settings__row'>
+            <SettingToggle id='advanced-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} />
+          </div>
+        </section>
+
         <section role='group' aria-labelledby='notifications-follow'>
           <h3 id='notifications-follow'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></h3>
 
diff --git a/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js b/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js
index e448cd26ad9..4e0184cef30 100644
--- a/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/filter_bar_container.js
@@ -5,7 +5,7 @@ import FilterBar from '../components/filter_bar';
 
 const makeMapStateToProps = state => ({
   selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
-  advancedMode: false,
+  advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
 });
 
 const mapDispatchToProps = (dispatch) => ({
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index efda92d2132..1134b393a76 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -484,6 +484,8 @@
   "notifications.column_settings.admin.sign_up": "New sign-ups:",
   "notifications.column_settings.alert": "Desktop notifications",
   "notifications.column_settings.favourite": "Favorites:",
+  "notifications.column_settings.filter_bar.advanced": "Display all categories",
+  "notifications.column_settings.filter_bar.category": "Quick filter bar",
   "notifications.column_settings.follow": "New followers:",
   "notifications.column_settings.follow_request": "New follow requests:",
   "notifications.column_settings.mention": "Mentions:",