From 2b78c07ef16c8cb89f95393aa12f0d79efdc41c2 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 26 Jun 2023 05:26:41 +0200
Subject: [PATCH 01/20] Fix search not being easily findable on smaller screens
 in web UI (#25576)

---
 .../mastodon/features/ui/components/header.jsx | 18 +++++++++++++-----
 app/javascript/mastodon/locales/en.json        |  2 +-
 app/javascript/styles/mastodon/components.scss |  5 +++--
 3 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/app/javascript/mastodon/features/ui/components/header.jsx b/app/javascript/mastodon/features/ui/components/header.jsx
index 05abc1ca63..bdd1c73052 100644
--- a/app/javascript/mastodon/features/ui/components/header.jsx
+++ b/app/javascript/mastodon/features/ui/components/header.jsx
@@ -1,7 +1,7 @@
 import PropTypes from 'prop-types';
 import { PureComponent } from 'react';
 
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 
 import { Link, withRouter } from 'react-router-dom';
 
@@ -10,6 +10,7 @@ import { connect } from 'react-redux';
 import { openModal } from 'mastodon/actions/modal';
 import { fetchServer } from 'mastodon/actions/server';
 import { Avatar } from 'mastodon/components/avatar';
+import { Icon } from 'mastodon/components/icon';
 import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
 import { registrationsOpen, me } from 'mastodon/initial_state';
 
@@ -21,6 +22,10 @@ const Account = connect(state => ({
   </Link>
 ));
 
+const messages = defineMessages({
+  search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
+});
+
 const mapStateToProps = (state) => ({
   signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
 });
@@ -44,7 +49,8 @@ class Header extends PureComponent {
     openClosedRegistrationsModal: PropTypes.func,
     location: PropTypes.object,
     signupUrl: PropTypes.string.isRequired,
-    dispatchServer: PropTypes.func
+    dispatchServer: PropTypes.func,
+    intl: PropTypes.object.isRequired,
   };
 
   componentDidMount () {
@@ -54,14 +60,15 @@ class Header extends PureComponent {
 
   render () {
     const { signedIn } = this.context.identity;
-    const { location, openClosedRegistrationsModal, signupUrl } = this.props;
+    const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
 
     let content;
 
     if (signedIn) {
       content = (
         <>
-          {location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish_form' defaultMessage='Publish' /></Link>}
+          {location.pathname !== '/search' && <Link to='/search' className='button button-secondary' aria-label={intl.formatMessage(messages.search)}><Icon id='search' /></Link>}
+          {location.pathname !== '/publish' && <Link to='/publish' className='button button-secondary'><FormattedMessage id='compose_form.publish_form' defaultMessage='New post' /></Link>}
           <Account />
         </>
       );
@@ -84,6 +91,7 @@ class Header extends PureComponent {
 
       content = (
         <>
+          {location.pathname !== '/search' && <Link to='/search' className='button button-secondary' aria-label={intl.formatMessage(messages.search)}><Icon id='search' /></Link>}
           {signupButton}
           <a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
         </>
@@ -106,4 +114,4 @@ class Header extends PureComponent {
 
 }
 
-export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
+export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)));
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 63ab26bc56..da3b6e19eb 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -147,7 +147,7 @@
   "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
   "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
   "compose_form.publish": "Publish",
-  "compose_form.publish_form": "Publish",
+  "compose_form.publish_form": "New post",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.save_changes": "Save changes",
   "compose_form.sensitive.hide": "{count, plural, one {Mark media as sensitive} other {Mark media as sensitive}}",
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 81dee20d33..15a14ce57f 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -133,12 +133,13 @@
     color: $darker-text-color;
     background: transparent;
     padding: 6px 17px;
-    border: 1px solid $ui-primary-color;
+    border: 1px solid lighten($ui-base-color, 12%);
 
     &:active,
     &:focus,
     &:hover {
-      border-color: lighten($ui-primary-color, 4%);
+      background: lighten($ui-base-color, 4%);
+      border-color: lighten($ui-base-color, 16%);
       color: lighten($darker-text-color, 4%);
       text-decoration: none;
     }

From 65aa04647a6a5cabdea11acf960bc4912529c738 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 26 Jun 2023 05:26:54 +0200
Subject: [PATCH 02/20] Fix onboarding prompt flashing while home feed is
 loading in web UI (#25579)

---
 .../mastodon/features/home_timeline/index.jsx   | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/app/javascript/mastodon/features/home_timeline/index.jsx b/app/javascript/mastodon/features/home_timeline/index.jsx
index 389efcc875..41e5aa3447 100644
--- a/app/javascript/mastodon/features/home_timeline/index.jsx
+++ b/app/javascript/mastodon/features/home_timeline/index.jsx
@@ -33,9 +33,11 @@ const messages = defineMessages({
 
 const getHomeFeedSpeed = createSelector([
   state => state.getIn(['timelines', 'home', 'items'], ImmutableList()),
+  state => state.getIn(['timelines', 'home', 'pendingItems'], ImmutableList()),
   state => state.get('statuses'),
-], (statusIds, statusMap) => {
-  const statuses = statusIds.map(id => statusMap.get(id)).filter(status => status.get('account') !== me).take(20);
+], (statusIds, pendingStatusIds, statusMap) => {
+  const recentStatusIds = pendingStatusIds.size > 0 ? pendingStatusIds : statusIds;
+  const statuses = recentStatusIds.map(id => statusMap.get(id)).filter(status => status?.get('account') !== me).take(20);
   const oldest = new Date(statuses.getIn([statuses.size - 1, 'created_at'], 0));
   const newest = new Date(statuses.getIn([0, 'created_at'], 0));
   const averageGap = (newest - oldest) / (1000 * (statuses.size + 1)); // Average gap between posts on first page in seconds
@@ -46,9 +48,14 @@ const getHomeFeedSpeed = createSelector([
   };
 });
 
-const homeTooSlow = createSelector(getHomeFeedSpeed, speed =>
-  speed.gap > (30 * 60) // If the average gap between posts is more than 20 minutes
-  || (Date.now() - speed.newest) > (1000 * 3600) // If the most recent post is from over an hour ago
+const homeTooSlow = createSelector([
+  state => state.getIn(['timelines', 'home', 'isLoading']),
+  state => state.getIn(['timelines', 'home', 'isPartial']),
+  getHomeFeedSpeed,
+], (isLoading, isPartial, speed) =>
+  !isLoading && !isPartial // Only if the home feed has finished loading
+  && (speed.gap > (30 * 60) // If the average gap between posts is more than 20 minutes
+  || (Date.now() - speed.newest) > (1000 * 3600)) // If the most recent post is from over an hour ago
 );
 
 const mapStateToProps = state => ({

From 7b024baf50f58ed8062b2dbf34e3e40d9af31e62 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 26 Jun 2023 05:27:07 +0200
Subject: [PATCH 03/20] Change header backgrounds to use fewer different colors
 in web UI (#25577)

---
 .../styles/mastodon/components.scss           | 62 +++++++------------
 1 file changed, 22 insertions(+), 40 deletions(-)

diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 15a14ce57f..53b68a8434 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3147,7 +3147,7 @@ $ui-header-height: 55px;
 .column-back-button {
   box-sizing: border-box;
   width: 100%;
-  background: lighten($ui-base-color, 4%);
+  background: $ui-base-color;
   border-radius: 4px 4px 0 0;
   color: $highlight-text-color;
   cursor: pointer;
@@ -3155,6 +3155,7 @@ $ui-header-height: 55px;
   font-size: 16px;
   line-height: inherit;
   border: 0;
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   text-align: unset;
   padding: 15px;
   margin: 0;
@@ -3167,7 +3168,7 @@ $ui-header-height: 55px;
 }
 
 .column-header__back-button {
-  background: lighten($ui-base-color, 4%);
+  background: $ui-base-color;
   border: 0;
   font-family: inherit;
   color: $highlight-text-color;
@@ -3202,7 +3203,7 @@ $ui-header-height: 55px;
   padding: 15px;
   position: absolute;
   inset-inline-end: 0;
-  top: -48px;
+  top: -50px;
 }
 
 .react-toggle {
@@ -3883,7 +3884,8 @@ a.status-card.compact:hover {
 .column-header {
   display: flex;
   font-size: 16px;
-  background: lighten($ui-base-color, 4%);
+  background: $ui-base-color;
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   border-radius: 4px 4px 0 0;
   flex: 0 0 auto;
   cursor: pointer;
@@ -3938,7 +3940,7 @@ a.status-card.compact:hover {
 }
 
 .column-header__button {
-  background: lighten($ui-base-color, 4%);
+  background: $ui-base-color;
   border: 0;
   color: $darker-text-color;
   cursor: pointer;
@@ -3946,16 +3948,15 @@ a.status-card.compact:hover {
   padding: 0 15px;
 
   &:hover {
-    color: lighten($darker-text-color, 7%);
+    color: lighten($darker-text-color, 4%);
   }
 
   &.active {
     color: $primary-text-color;
-    background: lighten($ui-base-color, 8%);
+    background: lighten($ui-base-color, 4%);
 
     &:hover {
       color: $primary-text-color;
-      background: lighten($ui-base-color, 8%);
     }
   }
 
@@ -3969,6 +3970,7 @@ a.status-card.compact:hover {
   max-height: 70vh;
   overflow: hidden;
   overflow-y: auto;
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   color: $darker-text-color;
   transition: max-height 150ms ease-in-out, opacity 300ms linear;
   opacity: 1;
@@ -3988,13 +3990,13 @@ a.status-card.compact:hover {
     height: 0;
     background: transparent;
     border: 0;
-    border-top: 1px solid lighten($ui-base-color, 12%);
+    border-top: 1px solid lighten($ui-base-color, 8%);
     margin: 10px 0;
   }
 }
 
 .column-header__collapsible-inner {
-  background: lighten($ui-base-color, 8%);
+  background: $ui-base-color;
   padding: 15px;
 }
 
@@ -4407,17 +4409,13 @@ a.status-card.compact:hover {
   color: $primary-text-color;
   margin-bottom: 4px;
   display: block;
-  background-color: $base-overlay-background;
-  text-transform: uppercase;
+  background-color: rgba($black, 0.45);
+  backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
   font-size: 11px;
-  font-weight: 500;
-  padding: 4px;
+  text-transform: uppercase;
+  font-weight: 700;
+  padding: 2px 6px;
   border-radius: 4px;
-  opacity: 0.7;
-
-  &:hover {
-    opacity: 1;
-  }
 }
 
 .setting-toggle {
@@ -4477,6 +4475,7 @@ a.status-card.compact:hover {
 
 .follow_requests-unlocked_explanation {
   background: darken($ui-base-color, 4%);
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   contain: initial;
   flex-grow: 0;
 }
@@ -6161,6 +6160,7 @@ a.status-card.compact:hover {
   display: block;
   color: $white;
   background: rgba($black, 0.65);
+  backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
   padding: 2px 6px;
   border-radius: 4px;
   font-size: 11px;
@@ -6838,24 +6838,6 @@ a.status-card.compact:hover {
       }
     }
   }
-
-  &.directory__section-headline {
-    background: darken($ui-base-color, 2%);
-    border-bottom-color: transparent;
-
-    a,
-    button {
-      &.active {
-        &::before {
-          display: none;
-        }
-
-        &::after {
-          border-color: transparent transparent darken($ui-base-color, 7%);
-        }
-      }
-    }
-  }
 }
 
 .filter-form {
@@ -7370,7 +7352,6 @@ noscript {
 
 .account__header {
   overflow: hidden;
-  background: lighten($ui-base-color, 4%);
 
   &.inactive {
     opacity: 0.5;
@@ -7392,6 +7373,7 @@ noscript {
     height: 145px;
     position: relative;
     background: darken($ui-base-color, 4%);
+    border-bottom: 1px solid lighten($ui-base-color, 8%);
 
     img {
       object-fit: cover;
@@ -7405,7 +7387,7 @@ noscript {
   &__bar {
     position: relative;
     padding: 0 20px;
-    border-bottom: 1px solid lighten($ui-base-color, 12%);
+    border-bottom: 1px solid lighten($ui-base-color, 8%);
 
     .avatar {
       display: block;
@@ -7414,7 +7396,7 @@ noscript {
 
       .account__avatar {
         background: darken($ui-base-color, 8%);
-        border: 2px solid lighten($ui-base-color, 4%);
+        border: 2px solid $ui-base-color;
       }
     }
   }

From ed96e28c9e47a0b7e3cdf538772d37d9b16ddc89 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Mon, 26 Jun 2023 12:30:35 +0200
Subject: [PATCH 04/20] =?UTF-8?q?Fix=20compose=20form=20not=20being=20show?=
 =?UTF-8?q?n=20when=20clicking=20=E2=80=9CMake=20your=20first=20post?=
 =?UTF-8?q?=E2=80=9D=20on=20mobile=20(#25581)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/javascript/mastodon/actions/compose.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 2ad7678caa..99610ac31f 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -129,13 +129,13 @@ export function resetCompose() {
   };
 }
 
-export const focusCompose = (routerHistory, defaultText) => dispatch => {
+export const focusCompose = (routerHistory, defaultText) => (dispatch, getState) => {
   dispatch({
     type: COMPOSE_FOCUS,
     defaultText,
   });
 
-  ensureComposeIsVisible(routerHistory);
+  ensureComposeIsVisible(getState, routerHistory);
 };
 
 export function mentionCompose(account, routerHistory) {

From ae30a60b1f6b7f51be38fe541e42a80ee2242d79 Mon Sep 17 00:00:00 2001
From: Renaud Chaput <renchap@gmail.com>
Date: Mon, 26 Jun 2023 12:31:48 +0200
Subject: [PATCH 05/20] Improve dismissable banner buttons when they dont fit
 on 1 line (#25580)

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
---
 .../home_timeline/components/explore_prompt.jsx     | 10 ++++++----
 app/javascript/styles/mastodon/components.scss      | 13 +++++++++++--
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx b/app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx
index 172f1a96c8..a6993c6418 100644
--- a/app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx
+++ b/app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx
@@ -15,9 +15,11 @@ export const ExplorePrompt = () => (
     <h1><FormattedMessage id='home.explore_prompt.title' defaultMessage='This is your home base within Mastodon.' /></h1>
     <p><FormattedMessage id='home.explore_prompt.body' defaultMessage="Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. It's looking pretty quiet right now, so how about:" /></p>
 
-    <div className='dismissable-banner__message__actions'>
-      <Link to='/explore' className='button'><FormattedMessage id='home.actions.go_to_explore' defaultMessage="See what's trending" /></Link>
-      <Link to='/explore/suggestions' className='button button-tertiary'><FormattedMessage id='home.actions.go_to_suggestions' defaultMessage='Find people to follow' /></Link>
+    <div className='dismissable-banner__message__actions__wrapper'>
+      <div className='dismissable-banner__message__actions'>
+        <Link to='/explore' className='button'><FormattedMessage id='home.actions.go_to_explore' defaultMessage="See what's trending" /></Link>
+        <Link to='/explore/suggestions' className='button button-tertiary'><FormattedMessage id='home.actions.go_to_suggestions' defaultMessage='Find people to follow' /></Link>
+      </div>
     </div>
   </DismissableBanner>
-);
\ No newline at end of file
+);
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 53b68a8434..3bf0b10edb 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -8768,9 +8768,18 @@ noscript {
 
     &__actions {
       display: flex;
-      align-items: center;
+      flex-wrap: wrap;
       gap: 4px;
-      margin-top: 30px;
+
+      &__wrapper {
+        display: flex;
+        margin-top: 30px;
+      }
+
+      .button {
+        display: block;
+        flex-grow: 1;
+      }
     }
 
     .button-tertiary {

From bb4756c823eef72ab6f9c52808726846a85a39f3 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 26 Jun 2023 14:17:41 +0200
Subject: [PATCH 06/20] Change files to be deleted in batches instead of
 one-by-one (#23302)

---
 app/lib/attachment_batch.rb                | 103 +++++++++++++++++++++
 app/lib/vacuum/media_attachments_vacuum.rb |  10 +-
 app/services/clear_domain_media_service.rb |  30 ++----
 3 files changed, 114 insertions(+), 29 deletions(-)
 create mode 100644 app/lib/attachment_batch.rb

diff --git a/app/lib/attachment_batch.rb b/app/lib/attachment_batch.rb
new file mode 100644
index 0000000000..41dc36b64c
--- /dev/null
+++ b/app/lib/attachment_batch.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+class AttachmentBatch
+  # Maximum amount of objects you can delete in an S3 API call. It's
+  # important to remember that this does not correspond to the number
+  # of records in the batch, since records can have multiple attachments
+  LIMIT = 1_000
+
+  # Attributes generated and maintained by Paperclip (not all of them
+  # are always used on every class, however)
+  NULLABLE_ATTRIBUTES = %w(
+    file_name
+    content_type
+    file_size
+    fingerprint
+    created_at
+    updated_at
+  ).freeze
+
+  # Styles that are always present even when not explicitly defined
+  BASE_STYLES = %i(original).freeze
+
+  attr_reader :klass, :records, :storage_mode
+
+  def initialize(klass, records)
+    @klass            = klass
+    @records          = records
+    @storage_mode     = Paperclip::Attachment.default_options[:storage]
+    @attachment_names = klass.attachment_definitions.keys
+  end
+
+  def delete
+    remove_files
+    batch.delete_all
+  end
+
+  def clear
+    remove_files
+    batch.update_all(nullified_attributes) # rubocop:disable Rails/SkipsModelValidations
+  end
+
+  private
+
+  def batch
+    klass.where(id: records.map(&:id))
+  end
+
+  def remove_files
+    keys = []
+
+    logger.debug { "Preparing to delete attachments for #{records.size} records" }
+
+    records.each do |record|
+      @attachment_names.each do |attachment_name|
+        attachment = record.public_send(attachment_name)
+        styles     = BASE_STYLES | attachment.styles.keys
+
+        next if attachment.blank?
+
+        styles.each do |style|
+          case @storage_mode
+          when :s3
+            logger.debug { "Adding #{attachment.path(style)} to batch for deletion" }
+            keys << attachment.style_name_as_path(style)
+          when :filesystem
+            logger.debug { "Deleting #{attachment.path(style)}" }
+            FileUtils.remove_file(attachment.path(style))
+          when :fog
+            logger.debug { "Deleting #{attachment.path(style)}" }
+            attachment.directory.files.new(key: attachment.path(style)).destroy
+          end
+        end
+      end
+    end
+
+    return unless storage_mode == :s3
+
+    # We can batch deletes over S3, but there is a limit of how many
+    # objects can be processed at once, so we have to potentially
+    # separate them into multiple calls.
+
+    keys.each_slice(LIMIT) do |keys_slice|
+      logger.debug { "Deleting #{keys_slice.size} objects" }
+
+      bucket.delete_objects(delete: {
+        objects: keys_slice.map { |key| { key: key } },
+        quiet: true,
+      })
+    end
+  end
+
+  def bucket
+    @bucket ||= records.first.public_send(@attachment_names.first).s3_bucket
+  end
+
+  def nullified_attributes
+    @attachment_names.flat_map { |attachment_name| NULLABLE_ATTRIBUTES.map { |attribute| "#{attachment_name}_#{attribute}" } & klass.column_names }.index_with(nil)
+  end
+
+  def logger
+    Rails.logger
+  end
+end
diff --git a/app/lib/vacuum/media_attachments_vacuum.rb b/app/lib/vacuum/media_attachments_vacuum.rb
index 7c0a85a9d9..7b21c84bbc 100644
--- a/app/lib/vacuum/media_attachments_vacuum.rb
+++ b/app/lib/vacuum/media_attachments_vacuum.rb
@@ -15,15 +15,15 @@ class Vacuum::MediaAttachmentsVacuum
   private
 
   def vacuum_cached_files!
-    media_attachments_past_retention_period.find_each do |media_attachment|
-      media_attachment.file.destroy
-      media_attachment.thumbnail.destroy
-      media_attachment.save
+    media_attachments_past_retention_period.find_in_batches do |media_attachments|
+      AttachmentBatch.new(MediaAttachment, media_attachments).clear
     end
   end
 
   def vacuum_orphaned_records!
-    orphaned_media_attachments.in_batches.destroy_all
+    orphaned_media_attachments.find_in_batches do |media_attachments|
+      AttachmentBatch.new(MediaAttachment, media_attachments).delete
+    end
   end
 
   def media_attachments_past_retention_period
diff --git a/app/services/clear_domain_media_service.rb b/app/services/clear_domain_media_service.rb
index 9e70ebe51c..7bf2d62fb0 100644
--- a/app/services/clear_domain_media_service.rb
+++ b/app/services/clear_domain_media_service.rb
@@ -10,14 +10,6 @@ class ClearDomainMediaService < BaseService
 
   private
 
-  def invalidate_association_caches!(status_ids)
-    # Normally, associated models of a status are immutable (except for accounts)
-    # so they are aggressively cached. After updating the media attachments to no
-    # longer point to a local file, we need to clear the cache to make those
-    # changes appear in the API and UI
-    Rails.cache.delete_multi(status_ids.map { |id| "statuses/#{id}" })
-  end
-
   def clear_media!
     clear_account_images!
     clear_account_attachments!
@@ -25,31 +17,21 @@ class ClearDomainMediaService < BaseService
   end
 
   def clear_account_images!
-    blocked_domain_accounts.reorder(nil).find_each do |account|
-      account.avatar.destroy if account.avatar&.exists?
-      account.header.destroy if account.header&.exists?
-      account.save
+    blocked_domain_accounts.reorder(nil).find_in_batches do |accounts|
+      AttachmentBatch.new(Account, accounts).clear
     end
   end
 
   def clear_account_attachments!
     media_from_blocked_domain.reorder(nil).find_in_batches do |attachments|
-      affected_status_ids = []
-
-      attachments.each do |attachment|
-        affected_status_ids << attachment.status_id if attachment.status_id.present?
-
-        attachment.file.destroy if attachment.file&.exists?
-        attachment.type = :unknown
-        attachment.save
-      end
-
-      invalidate_association_caches!(affected_status_ids) unless affected_status_ids.empty?
+      AttachmentBatch.new(MediaAttachment, attachments).clear
     end
   end
 
   def clear_emojos!
-    emojis_from_blocked_domains.destroy_all
+    emojis_from_blocked_domains.find_in_batches do |custom_emojis|
+      AttachmentBatch.new(CustomEmoji, custom_emojis).delete
+    end
   end
 
   def blocked_domain

From 7a25af64ddcfac1f4ad3fda2b6f72b03152b202e Mon Sep 17 00:00:00 2001
From: "S.H" <gamelinks007@gmail.com>
Date: Mon, 26 Jun 2023 23:38:19 +0900
Subject: [PATCH 07/20] Remove media attachment only when file was exist
 (#25586)

---
 app/lib/attachment_batch.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/lib/attachment_batch.rb b/app/lib/attachment_batch.rb
index 41dc36b64c..6372b01319 100644
--- a/app/lib/attachment_batch.rb
+++ b/app/lib/attachment_batch.rb
@@ -64,7 +64,7 @@ class AttachmentBatch
             keys << attachment.style_name_as_path(style)
           when :filesystem
             logger.debug { "Deleting #{attachment.path(style)}" }
-            FileUtils.remove_file(attachment.path(style))
+            FileUtils.remove_file(attachment.path(style), true)
           when :fog
             logger.debug { "Deleting #{attachment.path(style)}" }
             attachment.directory.files.new(key: attachment.path(style)).destroy

From 9caa0475f891ded573739cd00e7bc915b31abb12 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 26 Jun 2023 20:59:58 +0200
Subject: [PATCH 08/20] Update dependency react-redux to v8.1.1 (#25432)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 7acecd4e3a..1bd14820a8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9567,9 +9567,9 @@ react-redux-loading-bar@^5.0.4:
     react-lifecycles-compat "^3.0.4"
 
 react-redux@^8.0.4:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.0.tgz#4e147339f00bbaac7196bc42bc99e6fc412846e7"
-  integrity sha512-CtHZzAOxi7GQvTph4dVLWwZHAWUjV2kMEQtk50OrN8z3gKxpWg3Tz7JfDw32N3Rpd7fh02z73cF6yZkK467gbQ==
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a"
+  integrity sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==
   dependencies:
     "@babel/runtime" "^7.12.1"
     "@types/hoist-non-react-statics" "^3.3.1"

From 0ccf6c0eb73f612f1e727bac64dddea62b296e30 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 27 Jun 2023 09:36:11 +0200
Subject: [PATCH 09/20] Fix batch attachment deletion leaving empty directories
 (#25587)

---
 app/lib/attachment_batch.rb | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/app/lib/attachment_batch.rb b/app/lib/attachment_batch.rb
index 6372b01319..1f87b94336 100644
--- a/app/lib/attachment_batch.rb
+++ b/app/lib/attachment_batch.rb
@@ -64,7 +64,15 @@ class AttachmentBatch
             keys << attachment.style_name_as_path(style)
           when :filesystem
             logger.debug { "Deleting #{attachment.path(style)}" }
-            FileUtils.remove_file(attachment.path(style), true)
+            path = attachment.path(style)
+            FileUtils.remove_file(path, true)
+
+            begin
+              FileUtils.rmdir(File.dirname(path), parents: true)
+            rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR, Errno::EACCES
+              # Ignore failure to delete a directory, with the same ignored errors
+              # as Paperclip
+            end
           when :fog
             logger.debug { "Deleting #{attachment.path(style)}" }
             attachment.directory.files.new(key: attachment.path(style)).destroy

From d9b07b6a11b2e050e7d423f8c1fdfef4b22e810d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 27 Jun 2023 10:19:51 +0200
Subject: [PATCH 10/20] Update dependency rails to v6.1.7.4 (#25606)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 108 +++++++++++++++++++++++++--------------------------
 1 file changed, 54 insertions(+), 54 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 5f3678fe58..c3eb9d4d71 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -18,40 +18,40 @@ GIT
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (6.1.7.3)
-      actionpack (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    actioncable (6.1.7.4)
+      actionpack (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
-    actionmailbox (6.1.7.3)
-      actionpack (= 6.1.7.3)
-      activejob (= 6.1.7.3)
-      activerecord (= 6.1.7.3)
-      activestorage (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    actionmailbox (6.1.7.4)
+      actionpack (= 6.1.7.4)
+      activejob (= 6.1.7.4)
+      activerecord (= 6.1.7.4)
+      activestorage (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       mail (>= 2.7.1)
-    actionmailer (6.1.7.3)
-      actionpack (= 6.1.7.3)
-      actionview (= 6.1.7.3)
-      activejob (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    actionmailer (6.1.7.4)
+      actionpack (= 6.1.7.4)
+      actionview (= 6.1.7.4)
+      activejob (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 2.0)
-    actionpack (6.1.7.3)
-      actionview (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    actionpack (6.1.7.4)
+      actionview (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       rack (~> 2.0, >= 2.0.9)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.2.0)
-    actiontext (6.1.7.3)
-      actionpack (= 6.1.7.3)
-      activerecord (= 6.1.7.3)
-      activestorage (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    actiontext (6.1.7.4)
+      actionpack (= 6.1.7.4)
+      activerecord (= 6.1.7.4)
+      activestorage (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       nokogiri (>= 1.8.5)
-    actionview (6.1.7.3)
-      activesupport (= 6.1.7.3)
+    actionview (6.1.7.4)
+      activesupport (= 6.1.7.4)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
@@ -61,22 +61,22 @@ GEM
       activemodel (>= 4.1, < 7.1)
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
-    activejob (6.1.7.3)
-      activesupport (= 6.1.7.3)
+    activejob (6.1.7.4)
+      activesupport (= 6.1.7.4)
       globalid (>= 0.3.6)
-    activemodel (6.1.7.3)
-      activesupport (= 6.1.7.3)
-    activerecord (6.1.7.3)
-      activemodel (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
-    activestorage (6.1.7.3)
-      actionpack (= 6.1.7.3)
-      activejob (= 6.1.7.3)
-      activerecord (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    activemodel (6.1.7.4)
+      activesupport (= 6.1.7.4)
+    activerecord (6.1.7.4)
+      activemodel (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
+    activestorage (6.1.7.4)
+      actionpack (= 6.1.7.4)
+      activejob (= 6.1.7.4)
+      activerecord (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       marcel (~> 1.0)
       mini_mime (>= 1.1.0)
-    activesupport (6.1.7.3)
+    activesupport (6.1.7.4)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
@@ -412,7 +412,7 @@ GEM
     mime-types-data (3.2023.0218.1)
     mini_mime (1.1.2)
     mini_portile2 (2.8.2)
-    minitest (5.18.0)
+    minitest (5.18.1)
     msgpack (1.7.1)
     multi_json (1.15.0)
     multipart-post (2.3.0)
@@ -511,20 +511,20 @@ GEM
       rack
     rack-test (2.1.0)
       rack (>= 1.3)
-    rails (6.1.7.3)
-      actioncable (= 6.1.7.3)
-      actionmailbox (= 6.1.7.3)
-      actionmailer (= 6.1.7.3)
-      actionpack (= 6.1.7.3)
-      actiontext (= 6.1.7.3)
-      actionview (= 6.1.7.3)
-      activejob (= 6.1.7.3)
-      activemodel (= 6.1.7.3)
-      activerecord (= 6.1.7.3)
-      activestorage (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    rails (6.1.7.4)
+      actioncable (= 6.1.7.4)
+      actionmailbox (= 6.1.7.4)
+      actionmailer (= 6.1.7.4)
+      actionpack (= 6.1.7.4)
+      actiontext (= 6.1.7.4)
+      actionview (= 6.1.7.4)
+      activejob (= 6.1.7.4)
+      activemodel (= 6.1.7.4)
+      activerecord (= 6.1.7.4)
+      activestorage (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       bundler (>= 1.15.0)
-      railties (= 6.1.7.3)
+      railties (= 6.1.7.4)
       sprockets-rails (>= 2.0.0)
     rails-controller-testing (1.0.5)
       actionpack (>= 5.0.1.rc1)
@@ -539,9 +539,9 @@ GEM
     rails-i18n (6.0.0)
       i18n (>= 0.7, < 2)
       railties (>= 6.0.0, < 7)
-    railties (6.1.7.3)
-      actionpack (= 6.1.7.3)
-      activesupport (= 6.1.7.3)
+    railties (6.1.7.4)
+      actionpack (= 6.1.7.4)
+      activesupport (= 6.1.7.4)
       method_source
       rake (>= 12.2)
       thor (~> 1.0)

From ccaa676452ac592efa07214dba5fba7137550bf8 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 27 Jun 2023 10:20:09 +0200
Subject: [PATCH 11/20] Update dependency sass to v1.63.6 (#25607)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 1bd14820a8..696df8fa8e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10156,9 +10156,9 @@ sass-loader@^10.2.0:
     semver "^7.3.2"
 
 sass@^1.62.1:
-  version "1.63.4"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.4.tgz#caf60643321044c61f6a0fe638a07abbd31cfb5d"
-  integrity sha512-Sx/+weUmK+oiIlI+9sdD0wZHsqpbgQg8wSwSnGBjwb5GwqFhYNwwnI+UWZtLjKvKyFlKkatRK235qQ3mokyPoQ==
+  version "1.63.6"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.6.tgz#481610e612902e0c31c46b46cf2dad66943283ea"
+  integrity sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"

From a90190f8137f441304dc1d2e17acd75058038054 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 27 Jun 2023 10:20:46 +0200
Subject: [PATCH 12/20] Update dependency react-textarea-autosize to v8.5.0
 (#25610)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 696df8fa8e..697e827764 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9687,9 +9687,9 @@ react-test-renderer@^18.2.0:
     scheduler "^0.23.0"
 
 react-textarea-autosize@*, react-textarea-autosize@^8.4.1:
-  version "8.4.1"
-  resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.4.1.tgz#bcfc5462727014b808b14ee916c01e275e8a8335"
-  integrity sha512-aD2C+qK6QypknC+lCMzteOdIjoMbNlgSFmJjCV+DrfTPwp59i/it9mMNf2HDzvRjQgKAyBDPyLJhcrzElf2U4Q==
+  version "8.5.0"
+  resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.0.tgz#bb0f7faf9849850f1c20b6e7fac0309d4b92f87b"
+  integrity sha512-cp488su3U9RygmHmGpJp0KEt0i/+57KCK33XVPH+50swVRBhIZYh0fGduz2YLKXwl9vSKBZ9HUXcg9PQXUXqIw==
   dependencies:
     "@babel/runtime" "^7.20.13"
     use-composed-ref "^1.3.0"

From b7f6280ef46e9e9f408b34d1e90619a029c06132 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 27 Jun 2023 10:21:54 +0200
Subject: [PATCH 13/20] Update dependency pg-connection-string to v2.6.1
 (#25605)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 697e827764..024a680e00 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8760,9 +8760,9 @@ pg-cloudflare@^1.1.0:
   integrity sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==
 
 pg-connection-string@^2.6.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.0.tgz#12a36cc4627df19c25cc1b9b736cc39ee1f73ae8"
-  integrity sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg==
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb"
+  integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==
 
 pg-int8@1.0.1:
   version "1.0.1"

From dbd37f129d152b49e13b7c9773ef07a259686be5 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 27 Jun 2023 10:36:21 +0200
Subject: [PATCH 14/20] Update dependency pg to v8.11.1 (#25604)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 024a680e00..770f33b842 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8754,16 +8754,21 @@ performance-now@^2.1.0:
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
 
-pg-cloudflare@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz#833d70870d610d14bf9df7afb40e1cba310c17a0"
-  integrity sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==
+pg-cloudflare@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
+  integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==
 
 pg-connection-string@^2.6.0:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb"
   integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==
 
+pg-connection-string@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb"
+  integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==
+
 pg-int8@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
@@ -8774,10 +8779,10 @@ pg-numeric@1.0.2:
   resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a"
   integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==
 
-pg-pool@^3.6.0:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e"
-  integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==
+pg-pool@^3.6.1:
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7"
+  integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==
 
 pg-protocol@*, pg-protocol@^1.6.0:
   version "1.6.0"
@@ -8809,19 +8814,19 @@ pg-types@^4.0.1:
     postgres-range "^1.1.1"
 
 pg@^8.5.0:
-  version "8.11.0"
-  resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.0.tgz#a37e534e94b57a7ed811e926f23a7c56385f55d9"
-  integrity sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==
+  version "8.11.1"
+  resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.1.tgz#297e0eb240306b1e9e4f55af8a3bae76ae4810b1"
+  integrity sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==
   dependencies:
     buffer-writer "2.0.0"
     packet-reader "1.0.0"
-    pg-connection-string "^2.6.0"
-    pg-pool "^3.6.0"
+    pg-connection-string "^2.6.1"
+    pg-pool "^3.6.1"
     pg-protocol "^1.6.0"
     pg-types "^2.1.0"
     pgpass "1.x"
   optionalDependencies:
-    pg-cloudflare "^1.1.0"
+    pg-cloudflare "^1.1.1"
 
 pgpass@1.x:
   version "1.0.5"

From a9ba8263a072cdf16a18dbfc0ec8e359f308d9aa Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 27 Jun 2023 11:28:07 +0200
Subject: [PATCH 15/20] Update mcr.microsoft.com/devcontainers/ruby Docker tag
 to v1 (#25613)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .devcontainer/Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 04ac9560ca..f991036add 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,5 +1,5 @@
 # For details, see https://github.com/devcontainers/images/tree/main/src/ruby
-FROM mcr.microsoft.com/devcontainers/ruby:0-3.2-bullseye
+FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
 
 # Install Rails
 # RUN gem install rails webdrivers

From ceca93d0d1146dd37da3cb947cf9cfdaeca0f80f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 27 Jun 2023 12:16:17 +0200
Subject: [PATCH 16/20] Update dependency glob to v10.3.0 (#25608)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 770f33b842..12a992ec38 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5826,9 +5826,9 @@ glob-parent@^6.0.2:
     is-glob "^4.0.3"
 
 glob@^10.2.5, glob@^10.2.6:
-  version "10.2.7"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.7.tgz#9dd2828cd5bc7bd861e7738d91e7113dda41d7d8"
-  integrity sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.0.tgz#763d02a894f3cdfc521b10bbbbc8e0309e750cce"
+  integrity sha512-AQ1/SB9HH0yCx1jXAT4vmCbTOPe5RQ+kCurjbel5xSCGhebumUv+GJZfa1rEqor3XIViqwSEmlkZCQD43RWrBg==
   dependencies:
     foreground-child "^3.1.0"
     jackspeak "^2.0.3"
@@ -8027,9 +8027,9 @@ minimatch@^5.0.1:
     brace-expansion "^2.0.1"
 
 minimatch@^9.0.1:
-  version "9.0.1"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253"
-  integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.2.tgz#397e387fff22f6795844d00badc903a3d5de7057"
+  integrity sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==
   dependencies:
     brace-expansion "^2.0.1"
 
@@ -10836,7 +10836,6 @@ stringz@^2.1.0:
     char-regex "^1.0.2"
 
 "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  name strip-ansi-cjs
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==

From c7c6f02ae61cdbaa8248f6264b22e1ce11aa1f35 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 27 Jun 2023 12:32:51 +0200
Subject: [PATCH 17/20] Fix suspending an already-limited domain (#25603)

---
 app/views/admin/domain_blocks/confirm_suspension.html.haml | 2 +-
 spec/features/admin/domain_blocks_spec.rb                  | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/app/views/admin/domain_blocks/confirm_suspension.html.haml b/app/views/admin/domain_blocks/confirm_suspension.html.haml
index fa9272c77b..1d28ba1579 100644
--- a/app/views/admin/domain_blocks/confirm_suspension.html.haml
+++ b/app/views/admin/domain_blocks/confirm_suspension.html.haml
@@ -4,7 +4,7 @@
 - content_for :page_title do
   = t('.title', domain: Addressable::IDNA.to_unicode(@domain_block.domain))
 
-= simple_form_for @domain_block, url: admin_domain_blocks_path(@domain_block) do |f|
+= simple_form_for @domain_block, url: admin_domain_blocks_path, method: :post do |f|
 
   %p.hint= t('.preamble_html', domain: Addressable::IDNA.to_unicode(@domain_block.domain))
   %ul.hint
diff --git a/spec/features/admin/domain_blocks_spec.rb b/spec/features/admin/domain_blocks_spec.rb
index 3cf60a48ae..c77d604ebd 100644
--- a/spec/features/admin/domain_blocks_spec.rb
+++ b/spec/features/admin/domain_blocks_spec.rb
@@ -53,7 +53,7 @@ describe 'blocking domains through the moderation interface' do
       # Confirming updates the block
       click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
 
-      expect(domain_block.reload.severity).to eq 'silence'
+      expect(domain_block.reload.severity).to eq 'suspend'
     end
   end
 
@@ -72,7 +72,7 @@ describe 'blocking domains through the moderation interface' do
       # Confirming updates the block
       click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
 
-      expect(domain_block.reload.severity).to eq 'silence'
+      expect(domain_block.reload.severity).to eq 'suspend'
     end
   end
 end

From 2f996375e5f459ec94adf2c7bce3b4524b7de55a Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 26 Jun 2023 05:26:41 +0200
Subject: [PATCH 18/20] [Glitch] Fix search not being easily findable on
 smaller screens in web UI

Port 2b78c07ef16c8cb89f95393aa12f0d79efdc41c2 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
---
 .../glitch/features/ui/components/header.jsx   | 18 +++++++++++++-----
 .../glitch/styles/components/misc.scss         |  5 +++--
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/app/javascript/flavours/glitch/features/ui/components/header.jsx b/app/javascript/flavours/glitch/features/ui/components/header.jsx
index 873ff20e79..f2b89f3bdc 100644
--- a/app/javascript/flavours/glitch/features/ui/components/header.jsx
+++ b/app/javascript/flavours/glitch/features/ui/components/header.jsx
@@ -1,7 +1,7 @@
 import PropTypes from 'prop-types';
 import { PureComponent } from 'react';
 
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 
 import { Link, withRouter } from 'react-router-dom';
 
@@ -10,6 +10,7 @@ import { connect } from 'react-redux';
 import { openModal } from 'flavours/glitch/actions/modal';
 import { fetchServer } from 'flavours/glitch/actions/server';
 import { Avatar } from 'flavours/glitch/components/avatar';
+import { Icon } from 'flavours/glitch/components/icon';
 import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
 import Permalink from 'flavours/glitch/components/permalink';
 import { registrationsOpen, me } from 'flavours/glitch/initial_state';
@@ -22,6 +23,10 @@ const Account = connect(state => ({
   </Permalink>
 ));
 
+const messages = defineMessages({
+  search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
+});
+
 const mapStateToProps = (state) => ({
   signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
 });
@@ -45,7 +50,8 @@ class Header extends PureComponent {
     openClosedRegistrationsModal: PropTypes.func,
     location: PropTypes.object,
     signupUrl: PropTypes.string.isRequired,
-    dispatchServer: PropTypes.func
+    dispatchServer: PropTypes.func,
+    intl: PropTypes.object.isRequired,
   };
 
   componentDidMount () {
@@ -55,14 +61,15 @@ class Header extends PureComponent {
 
   render () {
     const { signedIn } = this.context.identity;
-    const { location, openClosedRegistrationsModal, signupUrl } = this.props;
+    const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
 
     let content;
 
     if (signedIn) {
       content = (
         <>
-          {location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish_form' defaultMessage='Publish' /></Link>}
+          {location.pathname !== '/search' && <Link to='/search' className='button button-secondary' aria-label={intl.formatMessage(messages.search)}><Icon id='search' /></Link>}
+          {location.pathname !== '/publish' && <Link to='/publish' className='button button-secondary'><FormattedMessage id='compose_form.publish_form' defaultMessage='New post' /></Link>}
           <Account />
         </>
       );
@@ -85,6 +92,7 @@ class Header extends PureComponent {
 
       content = (
         <>
+          {location.pathname !== '/search' && <Link to='/search' className='button button-secondary' aria-label={intl.formatMessage(messages.search)}><Icon id='search' /></Link>}
           {signupButton}
           <a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
         </>
@@ -107,4 +115,4 @@ class Header extends PureComponent {
 
 }
 
-export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
+export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)));
diff --git a/app/javascript/flavours/glitch/styles/components/misc.scss b/app/javascript/flavours/glitch/styles/components/misc.scss
index 53620eeb3c..208204021a 100644
--- a/app/javascript/flavours/glitch/styles/components/misc.scss
+++ b/app/javascript/flavours/glitch/styles/components/misc.scss
@@ -108,12 +108,13 @@
     text-transform: none;
     background: transparent;
     padding: 6px 17px;
-    border: 1px solid $ui-primary-color;
+    border: 1px solid lighten($ui-base-color, 12%);
 
     &:active,
     &:focus,
     &:hover {
-      border-color: lighten($ui-primary-color, 4%);
+      background: lighten($ui-base-color, 4%);
+      border-color: lighten($ui-base-color, 16%);
       color: lighten($darker-text-color, 4%);
       text-decoration: none;
     }

From 4faa4eb3c45986dd0019641fbf15a9a59e9497cf Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 26 Jun 2023 05:26:54 +0200
Subject: [PATCH 19/20] [Glitch] Fix onboarding prompt flashing while home feed
 is loading in web UI

Port 65aa04647a6a5cabdea11acf960bc4912529c738 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
---
 .../glitch/features/home_timeline/index.jsx     | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.jsx b/app/javascript/flavours/glitch/features/home_timeline/index.jsx
index b22f2d886b..9a110f06e7 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.jsx
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.jsx
@@ -33,9 +33,11 @@ const messages = defineMessages({
 
 const getHomeFeedSpeed = createSelector([
   state => state.getIn(['timelines', 'home', 'items'], ImmutableList()),
+  state => state.getIn(['timelines', 'home', 'pendingItems'], ImmutableList()),
   state => state.get('statuses'),
-], (statusIds, statusMap) => {
-  const statuses = statusIds.map(id => statusMap.get(id)).filter(status => status.get('account') !== me).take(20);
+], (statusIds, pendingStatusIds, statusMap) => {
+  const recentStatusIds = pendingStatusIds.size > 0 ? pendingStatusIds : statusIds;
+  const statuses = recentStatusIds.map(id => statusMap.get(id)).filter(status => status?.get('account') !== me).take(20);
   const oldest = new Date(statuses.getIn([statuses.size - 1, 'created_at'], 0));
   const newest = new Date(statuses.getIn([0, 'created_at'], 0));
   const averageGap = (newest - oldest) / (1000 * (statuses.size + 1)); // Average gap between posts on first page in seconds
@@ -46,9 +48,14 @@ const getHomeFeedSpeed = createSelector([
   };
 });
 
-const homeTooSlow = createSelector(getHomeFeedSpeed, speed =>
-  speed.gap > (30 * 60) // If the average gap between posts is more than 20 minutes
-  || (Date.now() - speed.newest) > (1000 * 3600) // If the most recent post is from over an hour ago
+const homeTooSlow = createSelector([
+  state => state.getIn(['timelines', 'home', 'isLoading']),
+  state => state.getIn(['timelines', 'home', 'isPartial']),
+  getHomeFeedSpeed,
+], (isLoading, isPartial, speed) =>
+  !isLoading && !isPartial // Only if the home feed has finished loading
+  && (speed.gap > (30 * 60) // If the average gap between posts is more than 20 minutes
+  || (Date.now() - speed.newest) > (1000 * 3600)) // If the most recent post is from over an hour ago
 );
 
 const mapStateToProps = state => ({

From c43cfd24064120b0e75cdcfa352997382fdea6eb Mon Sep 17 00:00:00 2001
From: Renaud Chaput <renchap@gmail.com>
Date: Mon, 26 Jun 2023 12:31:48 +0200
Subject: [PATCH 20/20] [Glitch] Improve dismissable banner buttons when they
 dont fit on 1 line

Port ae30a60b1f6b7f51be38fe541e42a80ee2242d79 to glitch-soc

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
---
 .../home_timeline/components/explore_prompt.jsx     |  8 +++++---
 .../flavours/glitch/styles/components/columns.scss  | 13 +++++++++++--
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx b/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx
index 972dedd3be..3eb28c59a1 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx
+++ b/app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx
@@ -15,9 +15,11 @@ export const ExplorePrompt = () => (
     <h1><FormattedMessage id='home.explore_prompt.title' defaultMessage='This is your home base within Mastodon.' /></h1>
     <p><FormattedMessage id='home.explore_prompt.body' defaultMessage="Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. It's looking pretty quiet right now, so how about:" /></p>
 
-    <div className='dismissable-banner__message__actions'>
-      <Link to='/explore' className='button'><FormattedMessage id='home.actions.go_to_explore' defaultMessage="See what's trending" /></Link>
-      <Link to='/explore/suggestions' className='button button-tertiary'><FormattedMessage id='home.actions.go_to_suggestions' defaultMessage='Find people to follow' /></Link>
+    <div className='dismissable-banner__message__actions__wrapper'>
+      <div className='dismissable-banner__message__actions'>
+        <Link to='/explore' className='button'><FormattedMessage id='home.actions.go_to_explore' defaultMessage="See what's trending" /></Link>
+        <Link to='/explore/suggestions' className='button button-tertiary'><FormattedMessage id='home.actions.go_to_suggestions' defaultMessage='Find people to follow' /></Link>
+      </div>
     </div>
   </DismissableBanner>
 );
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index 97c8a84d69..a296a31613 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -1005,9 +1005,18 @@ $ui-header-height: 55px;
 
     &__actions {
       display: flex;
-      align-items: center;
+      flex-wrap: wrap;
       gap: 4px;
-      margin-top: 30px;
+
+      &__wrapper {
+        display: flex;
+        margin-top: 30px;
+      }
+
+      .button {
+        display: block;
+        flex-grow: 1;
+      }
     }
 
     .button-tertiary {