From bbf74498f57513751f3506e3bbf7a04be4ad3b67 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Mon, 7 Nov 2022 22:35:53 +0100
Subject: [PATCH 01/10] Fix validation error in
 SynchronizeFeaturedTagsCollectionWorker (#20018)

* Fix followers count not being updated when migrating follows

Fixes #19900

* Fix validation error in SynchronizeFeaturedTagsCollectionWorker

Also saves remote user's chosen case for hashtags

* Limit remote featured tags before validation
---
 app/models/featured_tag.rb                    |  2 +
 .../fetch_featured_tags_collection_service.rb | 20 ++--
 ...h_featured_tags_collection_service_spec.rb | 95 +++++++++++++++++++
 3 files changed, 105 insertions(+), 12 deletions(-)
 create mode 100644 spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb

diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb
index 78185b2a99..debae22121 100644
--- a/app/models/featured_tag.rb
+++ b/app/models/featured_tag.rb
@@ -63,6 +63,8 @@ class FeaturedTag < ApplicationRecord
   end
 
   def validate_featured_tags_limit
+    return unless account.local?
+
     errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= LIMIT
   end
 
diff --git a/app/services/activitypub/fetch_featured_tags_collection_service.rb b/app/services/activitypub/fetch_featured_tags_collection_service.rb
index 5559199381..ab047a0f8b 100644
--- a/app/services/activitypub/fetch_featured_tags_collection_service.rb
+++ b/app/services/activitypub/fetch_featured_tags_collection_service.rb
@@ -51,21 +51,17 @@ class ActivityPub::FetchFeaturedTagsCollectionService < BaseService
   end
 
   def process_items(items)
-    names     = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.map { |name| HashtagNormalizer.new.normalize(name) }
-    to_remove = []
-    to_add    = names
+    names            = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.take(FeaturedTag::LIMIT)
+    tags             = names.index_by { |name| HashtagNormalizer.new.normalize(name) }
+    normalized_names = tags.keys
 
-    FeaturedTag.where(account: @account).map(&:name).each do |name|
-      if names.include?(name)
-        to_add.delete(name)
-      else
-        to_remove << name
-      end
+    FeaturedTag.includes(:tag).references(:tag).where(account: @account).where.not(tag: { name: normalized_names }).delete_all
+
+    FeaturedTag.includes(:tag).references(:tag).where(account: @account, tag: { name: normalized_names }).each do |featured_tag|
+      featured_tag.update(name: tags.delete(featured_tag.tag.name))
     end
 
-    FeaturedTag.includes(:tag).where(account: @account, tags: { name: to_remove }).delete_all unless to_remove.empty?
-
-    to_add.each do |name|
+    tags.each_value do |name|
       FeaturedTag.create!(account: @account, name: name)
     end
   end
diff --git a/spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb
new file mode 100644
index 0000000000..6ca22c9fc6
--- /dev/null
+++ b/spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb
@@ -0,0 +1,95 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service do
+  let(:collection_url) { 'https://example.com/account/tags' }
+  let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/account') }
+
+  let(:items) do
+    [
+      { type: 'Hashtag', href: 'https://example.com/account/tagged/foo', name: 'Foo' },
+      { type: 'Hashtag', href: 'https://example.com/account/tagged/bar', name: 'bar' },
+      { type: 'Hashtag', href: 'https://example.com/account/tagged/baz', name: 'baZ' },
+    ]
+  end
+
+  let(:payload) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      type: 'Collection',
+      id: collection_url,
+      items: items,
+    }.with_indifferent_access
+  end
+
+  subject { described_class.new }
+
+  shared_examples 'sets featured tags' do
+    before do
+      subject.call(actor, collection_url)
+    end
+
+    it 'sets expected tags as pinned tags' do
+      expect(actor.featured_tags.map(&:display_name)).to match_array ['Foo', 'bar', 'baZ']
+    end
+  end
+
+  describe '#call' do
+    context 'when the endpoint is a Collection' do
+      before do
+        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
+      end
+
+      it_behaves_like 'sets featured tags'
+    end
+
+    context 'when the account already has featured tags' do
+      before do
+        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
+
+        actor.featured_tags.create!(name: 'FoO')
+        actor.featured_tags.create!(name: 'baz')
+        actor.featured_tags.create!(name: 'oh').update(name: nil)
+      end
+
+      it_behaves_like 'sets featured tags'
+    end
+
+    context 'when the endpoint is an OrderedCollection' do
+      let(:payload) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          type: 'OrderedCollection',
+          id: collection_url,
+          orderedItems: items,
+        }.with_indifferent_access
+      end
+
+      before do
+        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
+      end
+
+      it_behaves_like 'sets featured tags'
+    end
+
+    context 'when the endpoint is a paginated Collection' do
+      let(:payload) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          type: 'Collection',
+          id: collection_url,
+          first: {
+            type: 'CollectionPage',
+            partOf: collection_url,
+            items: items,
+          }
+        }.with_indifferent_access
+      end
+
+      before do
+        stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
+      end
+
+      it_behaves_like 'sets featured tags'
+    end
+  end
+end

From 0beb095a4bfbcce55acb016eaaa54b5e93a56023 Mon Sep 17 00:00:00 2001
From: Zach Flanders <zachflanders@gmail.com>
Date: Mon, 7 Nov 2022 15:37:36 -0600
Subject: [PATCH 02/10] Fix spoiler buttons css not rendering correct color in
 light theme (#19960)

* Updating status__content__spoiler-link css for mastodon-light theme to ensure correct rendering precedence

* Adding focus css selector to status__content__spoiler-link mastodon-light theme

* reformatting code to match convention of having css selectors on separate lines

* fixing code format for  scss linting issue
---
 app/javascript/styles/mastodon-light/diff.scss | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index d928a55ed5..d960070d62 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -268,7 +268,8 @@ html {
 .status__content .status__content__spoiler-link {
   background: $ui-base-color;
 
-  &:hover {
+  &:hover,
+  &:focus {
     background: lighten($ui-base-color, 4%);
   }
 }

From ca80beb6530b3bbeff795c4832e2b4ab7bc8f672 Mon Sep 17 00:00:00 2001
From: Postmodern <postmodern.mod3@gmail.com>
Date: Mon, 7 Nov 2022 18:50:47 -0800
Subject: [PATCH 03/10] Micro-optimization: use `if`/`else` instead of
 `Array#compact` and `Array#min` (#19906)

* Technically `if`/`else` is faster than using `[value1, value2].compact.min` to find the lesser of two values, one of which may be `nil`.
---
 app/models/account_statuses_cleanup_policy.rb | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/app/models/account_statuses_cleanup_policy.rb b/app/models/account_statuses_cleanup_policy.rb
index 3651236531..49adc6ad05 100644
--- a/app/models/account_statuses_cleanup_policy.rb
+++ b/app/models/account_statuses_cleanup_policy.rb
@@ -139,7 +139,12 @@ class AccountStatusesCleanupPolicy < ApplicationRecord
     # Filtering on `id` rather than `min_status_age` ago will treat
     # non-snowflake statuses as older than they really are, but Mastodon
     # has switched to snowflake IDs significantly over 2 years ago anyway.
-    max_id = [max_id, Mastodon::Snowflake.id_at(min_status_age.seconds.ago, with_random: false)].compact.min
+    snowflake_id = Mastodon::Snowflake.id_at(min_status_age.seconds.ago, with_random: false)
+
+    if max_id.nil? || snowflake_id < max_id
+      max_id = snowflake_id
+    end
+
     Status.where(Status.arel_table[:id].lteq(max_id))
   end
 

From 608343c135c087ab7fa9bd401dce8a705720fdb8 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 8 Nov 2022 03:52:52 +0100
Subject: [PATCH 04/10] Fix opening the language picker scrolling the
 single-column view to the top (#19983)

Fixes #19915
---
 .../features/compose/components/language_dropdown.js  | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.js b/app/javascript/mastodon/features/compose/components/language_dropdown.js
index e48fa60ffb..bf56fd0fae 100644
--- a/app/javascript/mastodon/features/compose/components/language_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/language_dropdown.js
@@ -51,6 +51,15 @@ class LanguageDropdownMenu extends React.PureComponent {
     document.addEventListener('click', this.handleDocumentClick, false);
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
     this.setState({ mounted: true });
+
+    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
+    // to wait for a frame before focusing
+    requestAnimationFrame(() => {
+      if (this.node) {
+        const element = this.node.querySelector('input[type="search"]');
+        if (element) element.focus();
+      }
+    });
   }
 
   componentWillUnmount () {
@@ -226,7 +235,7 @@ class LanguageDropdownMenu extends React.PureComponent {
           // react-overlays
           <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
             <div className='emoji-mart-search'>
-              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
+              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
               <button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
             </div>
 

From 9f4930ec11b4185fcb17e5394fd0234dfcf16ed3 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 8 Nov 2022 03:53:06 +0100
Subject: [PATCH 05/10] Add password autocomplete hints (#20071)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes #20067

Our password autocomplete hints were “off” but that does not prevent current
browsers from trying to autocomplete them anyway, so use `current-password` and
`new-password` so they don't put a newly-generated password in a password
confirmation prompt, or the old password for a password renewal prompt.
---
 app/views/auth/challenges/new.html.haml              | 2 +-
 app/views/auth/passwords/edit.html.haml              | 4 ++--
 app/views/auth/registrations/edit.html.haml          | 6 +++---
 app/views/auth/sessions/new.html.haml                | 2 +-
 app/views/settings/deletes/show.html.haml            | 2 +-
 app/views/settings/migration/redirects/new.html.haml | 2 +-
 app/views/settings/migrations/show.html.haml         | 2 +-
 7 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/app/views/auth/challenges/new.html.haml b/app/views/auth/challenges/new.html.haml
index 9aef2c35d6..ff4b7a506f 100644
--- a/app/views/auth/challenges/new.html.haml
+++ b/app/views/auth/challenges/new.html.haml
@@ -5,7 +5,7 @@
   = f.input :return_to, as: :hidden
 
   .field-group
-    = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off', :autofocus => true }, label: t('challenge.prompt'), required: true
+    = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password', :autofocus => true }, label: t('challenge.prompt'), required: true
 
   .actions
     = f.button :button, t('challenge.confirm'), type: :submit
diff --git a/app/views/auth/passwords/edit.html.haml b/app/views/auth/passwords/edit.html.haml
index 114a744542..c7dbebe756 100644
--- a/app/views/auth/passwords/edit.html.haml
+++ b/app/views/auth/passwords/edit.html.haml
@@ -8,9 +8,9 @@
     = f.input :reset_password_token, as: :hidden
 
     .fields-group
-      = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true
+      = f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true
     .fields-group
-      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, required: true
+      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, required: true
 
     .actions
       = f.button :button, t('auth.set_new_password'), type: :submit
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index df929e3e80..c642c2293b 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -13,13 +13,13 @@
       .fields-row__column.fields-group.fields-row__column-6
         = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
       .fields-row__column.fields-group.fields-row__column-6
-        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true, disabled: current_account.suspended?, hint: false
+        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'current-password' }, required: true, disabled: current_account.suspended?, hint: false
 
     .fields-row
       .fields-row__column.fields-group.fields-row__column-6
-        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
+        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
       .fields-row__column.fields-group.fields-row__column-6
-        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, disabled: current_account.suspended?
+        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, disabled: current_account.suspended?
 
     .actions
       = f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml
index a4323d1d9a..943618e390 100644
--- a/app/views/auth/sessions/new.html.haml
+++ b/app/views/auth/sessions/new.html.haml
@@ -12,7 +12,7 @@
       - else
         = f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
     .fields-group
-      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false
+      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'current-password' }, hint: false
 
     .actions
       = f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/settings/deletes/show.html.haml b/app/views/settings/deletes/show.html.haml
index ddf0908794..c08ee85b0b 100644
--- a/app/views/settings/deletes/show.html.haml
+++ b/app/views/settings/deletes/show.html.haml
@@ -21,7 +21,7 @@
   %hr.spacer/
 
   - if current_user.encrypted_password.present?
-    = f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_password')
+    = f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, hint: t('deletes.confirm_password')
   - else
     = f.input :username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_username')
 
diff --git a/app/views/settings/migration/redirects/new.html.haml b/app/views/settings/migration/redirects/new.html.haml
index 017450f4b9..d7868e900d 100644
--- a/app/views/settings/migration/redirects/new.html.haml
+++ b/app/views/settings/migration/redirects/new.html.haml
@@ -19,7 +19,7 @@
 
     .fields-row__column.fields-group.fields-row__column-6
       - if current_user.encrypted_password.present?
-        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
+        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true
       - else
         = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
 
diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml
index 14bebb19b0..1ecf7302a9 100644
--- a/app/views/settings/migrations/show.html.haml
+++ b/app/views/settings/migrations/show.html.haml
@@ -48,7 +48,7 @@
 
     .fields-row__column.fields-group.fields-row__column-6
       - if current_user.encrypted_password.present?
-        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
+        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true, disabled: on_cooldown?
       - else
         = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
 

From 833d9c2f1c66b97faf11fb285a8b639fdca24069 Mon Sep 17 00:00:00 2001
From: James Tucker <jftucker@gmail.com>
Date: Mon, 7 Nov 2022 19:00:27 -0800
Subject: [PATCH 06/10] Improve performance by avoiding method cache busts
 (#19957)

Switch to monkey-patching http.rb rather than a runtime extend of each
response, so as to avoid busting the global method cache. A guard is
included that will provide developer feedback in development and test
environments should the monkey patch ever collide.
---
 app/lib/request.rb | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/app/lib/request.rb b/app/lib/request.rb
index 648aa30850..1ea86862db 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -62,8 +62,6 @@ class Request
     end
 
     begin
-      response = response.extend(ClientLimit)
-
       # If we are using a persistent connection, we have to
       # read every response to be able to move forward at all.
       # However, simply calling #to_s or #flush may not be safe,
@@ -181,6 +179,14 @@ class Request
     end
   end
 
+  if ::HTTP::Response.methods.include?(:body_with_limit) && !Rails.env.production?
+    abort 'HTTP::Response#body_with_limit is already defined, the monkey patch will not be applied'
+  else
+    class ::HTTP::Response
+      include Request::ClientLimit
+    end
+  end
+
   class Socket < TCPSocket
     class << self
       def open(host, *args)

From 782b6835f786385c41c6455f2a251d1925b19eb5 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 8 Nov 2022 04:06:54 +0100
Subject: [PATCH 07/10] Fix redrafting a currently-editing post not leaving
 edit mode (#20023)

---
 app/javascript/mastodon/reducers/compose.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index e4601e4715..ad384bd0b5 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -452,6 +452,7 @@ export default function compose(state = initialState, action) {
       map.set('idempotencyKey', uuid());
       map.set('sensitive', action.status.get('sensitive'));
       map.set('language', action.status.get('language'));
+      map.set('id', null);
 
       if (action.status.get('spoiler_text').length > 0) {
         map.set('spoiler', true);

From 6eac1cfccdc497c4f022689cd73bf937d0607f36 Mon Sep 17 00:00:00 2001
From: Zach Flanders <zachflanders@gmail.com>
Date: Mon, 7 Nov 2022 15:37:36 -0600
Subject: [PATCH 08/10] [Glitch] Fix spoiler buttons css not rendering correct
 color in light theme

Port 0beb095a4bfbcce55acb016eaaa54b5e93a56023 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
---
 app/javascript/flavours/glitch/styles/mastodon-light/diff.scss | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index 37a49ce9f1..1fe3d7a511 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -268,7 +268,8 @@ html {
 .status__content .status__content__spoiler-link {
   background: $ui-base-color;
 
-  &:hover {
+  &:hover,
+  &:focus {
     background: lighten($ui-base-color, 4%);
   }
 }

From fe1b69412880634d8d20e0af2c847cab37169d59 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 8 Nov 2022 03:52:52 +0100
Subject: [PATCH 09/10] [Glitch] Fix opening the language picker scrolling the
 single-column view to the top

Port 608343c135c087ab7fa9bd401dce8a705720fdb8 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
---
 .../features/compose/components/language_dropdown.js  | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
index 31f1d4e730..a3256aa9b0 100644
--- a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
@@ -51,6 +51,15 @@ class LanguageDropdownMenu extends React.PureComponent {
     document.addEventListener('click', this.handleDocumentClick, false);
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
     this.setState({ mounted: true });
+
+    // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
+    // to wait for a frame before focusing
+    requestAnimationFrame(() => {
+      if (this.node) {
+        const element = this.node.querySelector('input[type="search"]');
+        if (element) element.focus();
+      }
+    });
   }
 
   componentWillUnmount () {
@@ -226,7 +235,7 @@ class LanguageDropdownMenu extends React.PureComponent {
           // react-overlays
           <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
             <div className='emoji-mart-search'>
-              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
+              <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
               <button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
             </div>
 

From 9b6d6a919f3bd908fbdf3efc9aa5d97d33e3fe6e Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 8 Nov 2022 04:06:54 +0100
Subject: [PATCH 10/10] [Glitch] Fix redrafting a currently-editing post not
 leaving edit mode

Port 782b6835f786385c41c6455f2a251d1925b19eb5 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
---
 app/javascript/flavours/glitch/reducers/compose.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index b739456da5..460af39553 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -573,6 +573,7 @@ export default function compose(state = initialState, action) {
         'advanced_options',
         map => map.merge(new ImmutableMap({ do_not_federate }))
       );
+      map.set('id', null);
 
       if (action.status.get('spoiler_text').length > 0) {
         map.set('spoiler', true);