about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2020-05-03 21:20:42 +0200
committerThibaut Girka <thib@sitedethib.com>2020-05-03 21:23:49 +0200
commita22e6a368333f3563f8d8d56d8e98d02088e82dc (patch)
tree4146f9e8afe4257c6f33bc695a3cfbfb89aa81b6 /app
parent9c61dadc0db7009853c6b2345a02c3b219022929 (diff)
parente223fd8c6190661237ea43e7773e47513c48fd46 (diff)
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts:
- `app/controllers/statuses_controller.rb`:
  Upstream disabled the embed controller for reblogs.
  Not a real conflict, but glitch-soc has an extra line to deal
  with its theming system.
  Ported upstream changes.
- `app/javascript/packs/public.js`:
  Upstream made changes to get rid of most inline CSS, this changes
  javascript for public pages, which in glitch are split between
  different files. Ported those changes.
- `app/models/status.rb`:
  Upstream changed the block check in `Status#permitted_for` to
  include domain-block checks. Not a real conflict with glitch-soc,
  but our scope is slightly different, as our scope for
  unauthenticated access do not include instance-local toots.
  Ported upstream changes.
- `app/serializers/rest/instance_serializer.rb`:
  Not a real conflict, upstream added a new field to the instance
  serializer, the conflict is one line above since we added more of
  that.
  Ported upstream changes.
- `app/views/settings/profiles/show.html.haml`:
  Upstream got rid of most inline CSS and moved hidden elements
  to data attributes in the process, in fields were we have
  different values.
  Ported upstream changes while keeping our glitch-specific
  values.
- `app/views/statuses/_simple_status.html.haml`:
  Upstream got rid of inline CSS on an HAML line we treat
  differently, stripping empty text nodes.
  Ported upstream changes to the style attribute, keeping
  the empty text node stripping behavior.
Diffstat (limited to 'app')
-rw-r--r--app/controllers/accounts_controller.rb14
-rw-r--r--app/controllers/activitypub/collections_controller.rb17
-rw-r--r--app/controllers/activitypub/outboxes_controller.rb6
-rw-r--r--app/controllers/activitypub/replies_controller.rb21
-rw-r--r--app/controllers/api/v1/polls/votes_controller.rb2
-rw-r--r--app/controllers/api/v1/polls_controller.rb2
-rw-r--r--app/controllers/api/v1/push/subscriptions_controller.rb11
-rw-r--r--app/controllers/api/v1/statuses/mutes_controller.rb3
-rw-r--r--app/controllers/api/v1/statuses_controller.rb2
-rw-r--r--app/controllers/media_controller.rb2
-rw-r--r--app/controllers/remote_interaction_controller.rb2
-rw-r--r--app/controllers/statuses_controller.rb2
-rw-r--r--app/javascript/core/settings.js2
-rw-r--r--app/javascript/flavours/glitch/packs/public.js14
-rw-r--r--app/javascript/mastodon/actions/timelines.js2
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js2
-rw-r--r--app/javascript/mastodon/reducers/statuses.js2
-rw-r--r--app/javascript/mastodon/reducers/timelines.js10
-rw-r--r--app/javascript/packs/public.js14
-rw-r--r--app/javascript/styles/mastodon/about.scss5
-rw-r--r--app/javascript/styles/mastodon/admin.scss20
-rw-r--r--app/javascript/styles/mastodon/basics.scss16
-rw-r--r--app/javascript/styles/mastodon/components.scss12
-rw-r--r--app/javascript/styles/mastodon/forms.scss21
-rw-r--r--app/javascript/styles/mastodon/polls.scss30
-rw-r--r--app/javascript/styles/mastodon/statuses.scss17
-rw-r--r--app/models/account.rb90
-rw-r--r--app/models/concerns/omniauthable.rb2
-rw-r--r--app/models/custom_emoji.rb29
-rw-r--r--app/models/media_attachment.rb35
-rw-r--r--app/models/preview_card.rb43
-rw-r--r--app/models/status.rb8
-rw-r--r--app/serializers/rest/instance_serializer.rb6
-rw-r--r--app/services/fetch_resource_service.rb13
-rw-r--r--app/views/about/show.html.haml6
-rw-r--r--app/views/accounts/_moved.html.haml6
-rw-r--r--app/views/admin/accounts/_account.html.haml2
-rw-r--r--app/views/admin/accounts/show.html.haml26
-rw-r--r--app/views/admin/instances/index.html.haml2
-rw-r--r--app/views/admin/instances/show.html.haml6
-rw-r--r--app/views/admin/pending_accounts/index.html.haml8
-rw-r--r--app/views/admin/relationships/index.html.haml2
-rw-r--r--app/views/admin/reports/show.html.haml6
-rw-r--r--app/views/admin/statuses/index.html.haml2
-rw-r--r--app/views/admin/statuses/show.html.haml2
-rw-r--r--app/views/admin/tags/index.html.haml8
-rw-r--r--app/views/application/_card.html.haml1
-rw-r--r--app/views/auth/registrations/new.html.haml4
-rw-r--r--app/views/auth/sessions/two_factor.html.haml2
-rw-r--r--app/views/directories/index.html.haml1
-rwxr-xr-xapp/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/embedded.html.haml2
-rw-r--r--app/views/public_timelines/show.html.haml2
-rw-r--r--app/views/settings/preferences/appearance/show.html.haml4
-rw-r--r--app/views/settings/profiles/show.html.haml2
-rw-r--r--app/views/statuses/_detailed_status.html.haml6
-rw-r--r--app/views/statuses/_poll.html.haml8
-rw-r--r--app/views/statuses/_simple_status.html.haml12
59 files changed, 377 insertions, 224 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index ee48da177..52d09cff8 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -28,7 +28,7 @@ class AccountsController < ApplicationController
         end
 
         @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
-        @statuses        = filtered_status_page(params)
+        @statuses        = filtered_status_page
         @statuses        = cache_collection(@statuses, Status)
         @rss_url         = rss_url
 
@@ -141,12 +141,12 @@ class AccountsController < ApplicationController
     request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
   end
 
-  def filtered_status_page(params)
-    if params[:min_id].present?
-      filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
-    else
-      filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
-    end
+  def filtered_status_page
+    filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id))
+  end
+
+  def params_slice(*keys)
+    params.slice(*keys).permit(*keys)
   end
 
   def restrict_fields_to
diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb
index 910fefb1c..c1e7aa550 100644
--- a/app/controllers/activitypub/collections_controller.rb
+++ b/app/controllers/activitypub/collections_controller.rb
@@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
   def set_size
     case params[:id]
     when 'featured'
-      @account.pinned_statuses.count
+      @size = @account.pinned_statuses.count
     else
-      raise ActiveRecord::RecordNotFound
+      not_found
     end
   end
 
   def scope_for_collection
     case params[:id]
     when 'featured'
-      return Status.none if @account.blocking?(signed_request_account)
-
-      @account.pinned_statuses
-    else
-      raise ActiveRecord::RecordNotFound
+      # Because in public fetch mode we cache the response, there would be no
+      # benefit from performing the check below, since a blocked account or domain
+      # would likely be served the cache from the reverse proxy anyway
+      if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
+        Status.none
+      else
+        @account.pinned_statuses
+      end
     end
   end
 
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index 891756b7e..e25a4bc07 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   before_action :set_cache_headers
 
   def show
-    expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
+    expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
     render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
   end
 
@@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
     return unless page_requested?
 
     @statuses = @account.statuses.permitted_for(@account, signed_request_account)
-    @statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id])
+    @statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id))
     @statuses = cache_collection(@statuses, Status)
   end
 
   def page_requested?
-    params[:page] == 'true'
+    truthy_param?(:page)
   end
 
   def page_params
diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb
index c62061555..43bf4e657 100644
--- a/app/controllers/activitypub/replies_controller.rb
+++ b/app/controllers/activitypub/replies_controller.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class ActivityPub::RepliesController < ActivityPub::BaseController
-  include SignatureAuthentication
+  include SignatureVerification
   include Authorization
   include AccountOwnedConcern
 
@@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
 
   private
 
+  def pundit_user
+    signed_request_account
+  end
+
   def set_status
     @status = @account.statuses.find(params[:status_id])
     authorize @status, :show?
   rescue Mastodon::NotPermittedError
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def set_replies
-    @replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
+    @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
     @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
     @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
   end
@@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
       type: :unordered,
       part_of: account_status_replies_url(@account, @status),
       next: next_page,
-      items: @replies.map { |status| status.local ? status : status.uri }
+      items: @replies.map { |status| status.local? ? status : status.uri }
     )
 
     return page if page_requested?
@@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
   end
 
   def page_requested?
-    params[:page] == 'true'
+    truthy_param?(:page)
+  end
+
+  def only_other_accounts?
+    truthy_param?(:only_other_accounts)
   end
 
   def next_page
     only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
+
     account_status_replies_url(
       @account,
       @status,
       page: true,
-      min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id,
+      min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
       only_other_accounts: only_other_accounts
     )
   end
diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb
index e1d26106a..513b937ef 100644
--- a/app/controllers/api/v1/polls/votes_controller.rb
+++ b/app/controllers/api/v1/polls/votes_controller.rb
@@ -18,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
     @poll = Poll.attached.find(params[:poll_id])
     authorize @poll.status, :show?
   rescue Mastodon::NotPermittedError
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def vote_params
diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb
index 744baf7bb..6435e9f0d 100644
--- a/app/controllers/api/v1/polls_controller.rb
+++ b/app/controllers/api/v1/polls_controller.rb
@@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
     @poll = Poll.attached.find(params[:id])
     authorize @poll.status, :show?
   rescue Mastodon::NotPermittedError
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def refresh_poll
diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb
index 1cbc92b93..d34b333eb 100644
--- a/app/controllers/api/v1/push/subscriptions_controller.rb
+++ b/app/controllers/api/v1/push/subscriptions_controller.rb
@@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
   before_action -> { doorkeeper_authorize! :push }
   before_action :require_user!
   before_action :set_web_push_subscription
+  before_action :check_web_push_subscription, only: [:show, :update]
 
   def create
     @web_subscription&.destroy!
@@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
   end
 
   def show
-    raise ActiveRecord::RecordNotFound if @web_subscription.nil?
-
     render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
   end
 
   def update
-    raise ActiveRecord::RecordNotFound if @web_subscription.nil?
-
     @web_subscription.update!(data: data_params)
-
     render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
   end
 
@@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
     @web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
   end
 
+  def check_web_push_subscription
+    not_found if @web_subscription.nil?
+  end
+
   def subscription_params
     params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
   end
 
   def data_params
     return {} if params[:data].blank?
+
     params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
   end
 end
diff --git a/app/controllers/api/v1/statuses/mutes_controller.rb b/app/controllers/api/v1/statuses/mutes_controller.rb
index 43c7a525a..87071a2b9 100644
--- a/app/controllers/api/v1/statuses/mutes_controller.rb
+++ b/app/controllers/api/v1/statuses/mutes_controller.rb
@@ -28,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController
     @status = Status.find(params[:status_id])
     authorize @status, :show?
   rescue Mastodon::NotPermittedError
-    # Reraise in order to get a 404 instead of a 403 error code
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def set_conversation
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 29ae91762..b3edce676 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -68,7 +68,7 @@ class Api::V1::StatusesController < Api::BaseController
     @status = Status.find(params[:id])
     authorize @status, :show?
   rescue Mastodon::NotPermittedError
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def set_thread
diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb
index 05cf09c28..1d166d6e7 100644
--- a/app/controllers/media_controller.rb
+++ b/app/controllers/media_controller.rb
@@ -33,7 +33,7 @@ class MediaController < ApplicationController
   def verify_permitted_status!
     authorize @media_attachment.status, :show?
   rescue Mastodon::NotPermittedError
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def check_playable
diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb
index e058d0ed5..51bb9bdea 100644
--- a/app/controllers/remote_interaction_controller.rb
+++ b/app/controllers/remote_interaction_controller.rb
@@ -42,7 +42,7 @@ class RemoteInteractionController < ApplicationController
     @status = Status.find(params[:id])
     authorize @status, :show?
   rescue Mastodon::NotPermittedError
-    raise ActiveRecord::RecordNotFound
+    not_found
   end
 
   def set_body_classes
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 588063d01..a1b7f4320 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -49,7 +49,7 @@ class StatusesController < ApplicationController
 
   def embed
     use_pack 'embed'
-    return not_found if @status.hidden?
+    return not_found if @status.hidden? || @status.reblog?
 
     expires_in 180, public: true
     response.headers['X-Frame-Options'] = 'ALLOWALL'
diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js
index e02c91cc7..9fe03f90c 100644
--- a/app/javascript/core/settings.js
+++ b/app/javascript/core/settings.js
@@ -10,7 +10,7 @@ delegate(document, '#account_display_name', 'input', ({ target }) => {
     if (target.value) {
       name.innerHTML = emojify(escapeTextContentForBrowser(target.value));
     } else {
-      name.textContent = document.querySelector('#default_account_display_name').textContent;
+      name.textContent = name.textContent = target.dataset.default;
     }
   }
 });
diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js
index e5a567205..58febcf5b 100644
--- a/app/javascript/flavours/glitch/packs/public.js
+++ b/app/javascript/flavours/glitch/packs/public.js
@@ -99,15 +99,13 @@ function main() {
     delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
 
     delegate(document, '.status__content__spoiler-link', 'click', function() {
-      const contentEl = this.parentNode.parentNode.querySelector('.e-content');
+      const statusEl = this.parentNode.parentNode;
 
-      if (contentEl.style.display === 'block') {
-        contentEl.style.display = 'none';
-        this.parentNode.style.marginBottom = 0;
+      if (statusEl.dataset.spoiler === 'expanded') {
+        statusEl.dataset.spoiler = 'folded';
         this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format();
       } else {
-        contentEl.style.display = 'block';
-        this.parentNode.style.marginBottom = null;
+        statusEl.dataset.spoiler = 'expanded';
         this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format();
       }
 
@@ -115,8 +113,8 @@ function main() {
     });
 
     [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
-      const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content');
-      const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
+      const statusEl = spoilerLink.parentNode.parentNode;
+      const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
       spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
     });
   });
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index 50840cacc..861827d33 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -42,7 +42,7 @@ export function updateTimeline(timeline, status, accept) {
 export function deleteFromTimelines(id) {
   return (dispatch, getState) => {
     const accountId  = getState().getIn(['statuses', id, 'account']);
-    const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
+    const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id'));
     const reblogOf   = getState().getIn(['statuses', id, 'reblog'], null);
 
     dispatch({
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 31c02d735..4734e0f3f 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -46,7 +46,7 @@ class DropdownMenu extends React.PureComponent {
     document.addEventListener('keydown', this.handleKeyDown, false);
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
     if (this.focusedItem && this.props.openedViaKeyboard) {
-      this.focusedItem.focus();
+      this.focusedItem.focus({ preventScroll: true });
     }
     this.setState({ mounted: true });
   }
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 57588fe96..96028e042 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -100,7 +100,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
-    if (this.focusedItem) this.focusedItem.focus();
+    if (this.focusedItem) this.focusedItem.focus({ preventScroll: true });
     this.setState({ mounted: true });
   }
 
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 2554c008d..53dec9585 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -25,7 +25,7 @@ const importStatuses = (state, statuses) =>
 
 const deleteStatus = (state, id, references) => {
   references.forEach(ref => {
-    state = deleteStatus(state, ref[0], []);
+    state = deleteStatus(state, ref, []);
   });
 
   return state.delete(id);
diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js
index 63b76773d..9156db021 100644
--- a/app/javascript/mastodon/reducers/timelines.js
+++ b/app/javascript/mastodon/reducers/timelines.js
@@ -89,7 +89,7 @@ const updateTimeline = (state, timeline, status, usePendingItems) => {
   }));
 };
 
-const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
+const deleteStatus = (state, id, references, exclude_account = null) => {
   state.keySeq().forEach(timeline => {
     if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
       const helper = list => list.filterNot(item => item === id);
@@ -99,7 +99,7 @@ const deleteStatus = (state, id, accountId, references, exclude_account = null)
 
   // Remove reblogs of deleted status
   references.forEach(ref => {
-    state = deleteStatus(state, ref[0], ref[1], [], exclude_account);
+    state = deleteStatus(state, ref, [], exclude_account);
   });
 
   return state;
@@ -117,8 +117,8 @@ const filterTimelines = (state, relationship, statuses) => {
       return;
     }
 
-    references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
-    state      = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id);
+    references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id'));
+    state      = deleteStatus(state, status.get('id'), references, relationship.id);
   });
 
   return state;
@@ -150,7 +150,7 @@ export default function timelines(state = initialState, action) {
   case TIMELINE_UPDATE:
     return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
   case TIMELINE_DELETE:
-    return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
+    return deleteStatus(state, action.id, action.references, action.reblogOf);
   case TIMELINE_CLEAR:
     return clearTimeline(state, action.timeline);
   case ACCOUNT_BLOCK_SUCCESS:
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 5b699e767..3d190d2da 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -103,15 +103,13 @@ function main() {
     delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
 
     delegate(document, '.status__content__spoiler-link', 'click', function() {
-      const contentEl = this.parentNode.parentNode.querySelector('.e-content');
+      const statusEl = this.parentNode.parentNode;
 
-      if (contentEl.style.display === 'block') {
-        contentEl.style.display = 'none';
-        this.parentNode.style.marginBottom = 0;
+      if (statusEl.dataset.spoiler === 'expanded') {
+        statusEl.dataset.spoiler = 'folded';
         this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format();
       } else {
-        contentEl.style.display = 'block';
-        this.parentNode.style.marginBottom = null;
+        statusEl.dataset.spoiler = 'expanded';
         this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format();
       }
 
@@ -119,8 +117,8 @@ function main() {
     });
 
     [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
-      const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content');
-      const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
+      const statusEl = spoilerLink.parentNode.parentNode;
+      const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
       spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
     });
   });
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index cf16b54ac..711f34965 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -757,8 +757,13 @@ $small-breakpoint: 960px;
       }
     }
 
+    &__counters__wrapper {
+      display: flex;
+    }
+
     &__counter {
       padding: 10px;
+      width: 50%;
 
       strong {
         font-family: $font-display, sans-serif;
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 7bff2daa1..78dea92b9 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -583,6 +583,18 @@ body,
   }
 }
 
+.special-action-button,
+.back-link {
+  text-align: right;
+  flex: 1 1 auto;
+}
+
+.action-buttons {
+  display: flex;
+  overflow: hidden;
+  justify-content: space-between;
+}
+
 .spacer {
   flex: 1 1 auto;
 }
@@ -920,3 +932,11 @@ a.name-tag,
     }
   }
 }
+
+.account-badges {
+  margin: -2px 0;
+}
+
+.dashboard__counters.admin-account-counters {
+  margin-top: 10px;
+}
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 2b10b5ad3..a5dbe75fb 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -229,3 +229,19 @@ button {
     }
   }
 }
+
+.logo-resources {
+  display: none;
+}
+
+// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements,
+// to set the z-index to a high value, which messes with modals and dropdowns.
+// Blocked elements can in theory only be media and frames/embeds, so they
+// should only appear in statuses, under divs and articles.
+body,
+div,
+article {
+  .__ns__pop2top {
+    z-index: unset !important;
+  }
+}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index c7835b878..6c33b709d 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -1362,6 +1362,12 @@ a .account__avatar {
   &-base {
     @include avatar-radius;
     @include avatar-size(36px);
+
+    img {
+      @include avatar-radius;
+      width: 100%;
+      height: 100%;
+    }
   }
 
   &-overlay {
@@ -1372,6 +1378,12 @@ a .account__avatar {
     bottom: 0;
     right: 0;
     z-index: 1;
+
+    img {
+      @include avatar-radius;
+      width: 100%;
+      height: 100%;
+    }
   }
 }
 
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index c9ad68f94..0e5b00e8f 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -142,6 +142,10 @@ code {
     }
   }
 
+  .otp-hint {
+    margin-bottom: 25px;
+  }
+
   .card {
     margin-bottom: 15px;
   }
@@ -285,6 +289,14 @@ code {
         margin-bottom: 25px;
       }
     }
+
+    .fields-group.invited-by {
+      margin-bottom: 30px;
+
+      .hint {
+        text-align: center;
+      }
+    }
   }
 
   .input.radio_buttons .radio label {
@@ -635,6 +647,15 @@ code {
   @media screen and (max-width: 740px) and (min-width: 441px) {
     margin-top: 40px;
   }
+
+  &.translation-prompt {
+    text-align: unset;
+    color: unset;
+
+    a {
+      text-decoration: underline;
+    }
+  }
 }
 
 .form-footer {
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index 1ecc8434d..ad7088982 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -19,6 +19,36 @@
     }
   }
 
+  progress {
+    border: 0;
+    display: block;
+    width: 100%;
+    height: 5px;
+    appearance: none;
+    background: transparent;
+
+    &::-webkit-progress-bar {
+      background: transparent;
+    }
+
+    // Those rules need to be entirely separate or they won't work, hence the
+    // duplication
+    &::-moz-progress-bar {
+      border-radius: 4px;
+      background: darken($ui-primary-color, 5%);
+    }
+
+    &::-ms-fill {
+      border-radius: 4px;
+      background: darken($ui-primary-color, 5%);
+    }
+
+    &::-webkit-progress-value {
+      border-radius: 4px;
+      background: darken($ui-primary-color, 5%);
+    }
+  }
+
   &__option {
     position: relative;
     display: flex;
diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss
index 19ce0ab8f..0b7be7afd 100644
--- a/app/javascript/styles/mastodon/statuses.scss
+++ b/app/javascript/styles/mastodon/statuses.scss
@@ -128,6 +128,16 @@
 
 .embed,
 .public-layout {
+  .status__content[data-spoiler=folded] {
+    .e-content {
+      display: none;
+    }
+
+    p:first-child {
+      margin-bottom: 0;
+    }
+  }
+
   .detailed-status {
     padding: 15px;
   }
@@ -159,5 +169,12 @@
     .video-player {
       margin-top: 10px;
     }
+
+    &__action-bar-button {
+      font-size: 18px;
+      width: 23.1429px;
+      height: 23.1429px;
+      line-height: 23.15px;
+    }
   }
 }
diff --git a/app/models/account.rb b/app/models/account.rb
index e56db3126..5038d4768 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -3,50 +3,52 @@
 #
 # Table name: accounts
 #
-#  id                      :bigint(8)        not null, primary key
-#  username                :string           default(""), not null
-#  domain                  :string
-#  secret                  :string           default(""), not null
-#  private_key             :text
-#  public_key              :text             default(""), not null
-#  remote_url              :string           default(""), not null
-#  salmon_url              :string           default(""), not null
-#  hub_url                 :string           default(""), not null
-#  created_at              :datetime         not null
-#  updated_at              :datetime         not null
-#  note                    :text             default(""), not null
-#  display_name            :string           default(""), not null
-#  uri                     :string           default(""), not null
-#  url                     :string
-#  avatar_file_name        :string
-#  avatar_content_type     :string
-#  avatar_file_size        :integer
-#  avatar_updated_at       :datetime
-#  header_file_name        :string
-#  header_content_type     :string
-#  header_file_size        :integer
-#  header_updated_at       :datetime
-#  avatar_remote_url       :string
-#  subscription_expires_at :datetime
-#  locked                  :boolean          default(FALSE), not null
-#  header_remote_url       :string           default(""), not null
-#  last_webfingered_at     :datetime
-#  inbox_url               :string           default(""), not null
-#  outbox_url              :string           default(""), not null
-#  shared_inbox_url        :string           default(""), not null
-#  followers_url           :string           default(""), not null
-#  protocol                :integer          default("ostatus"), not null
-#  memorial                :boolean          default(FALSE), not null
-#  moved_to_account_id     :bigint(8)
-#  featured_collection_url :string
-#  fields                  :jsonb
-#  actor_type              :string
-#  discoverable            :boolean
-#  also_known_as           :string           is an Array
-#  silenced_at             :datetime
-#  suspended_at            :datetime
-#  trust_level             :integer
-#  hide_collections        :boolean
+#  id                            :bigint(8)        not null, primary key
+#  username                      :string           default(""), not null
+#  domain                        :string
+#  secret                        :string           default(""), not null
+#  private_key                   :text
+#  public_key                    :text             default(""), not null
+#  remote_url                    :string           default(""), not null
+#  salmon_url                    :string           default(""), not null
+#  hub_url                       :string           default(""), not null
+#  created_at                    :datetime         not null
+#  updated_at                    :datetime         not null
+#  note                          :text             default(""), not null
+#  display_name                  :string           default(""), not null
+#  uri                           :string           default(""), not null
+#  url                           :string
+#  avatar_file_name              :string
+#  avatar_content_type           :string
+#  avatar_file_size              :integer
+#  avatar_updated_at             :datetime
+#  header_file_name              :string
+#  header_content_type           :string
+#  header_file_size              :integer
+#  header_updated_at             :datetime
+#  avatar_remote_url             :string
+#  subscription_expires_at       :datetime
+#  locked                        :boolean          default(FALSE), not null
+#  header_remote_url             :string           default(""), not null
+#  last_webfingered_at           :datetime
+#  inbox_url                     :string           default(""), not null
+#  outbox_url                    :string           default(""), not null
+#  shared_inbox_url              :string           default(""), not null
+#  followers_url                 :string           default(""), not null
+#  protocol                      :integer          default("ostatus"), not null
+#  memorial                      :boolean          default(FALSE), not null
+#  moved_to_account_id           :bigint(8)
+#  featured_collection_url       :string
+#  fields                        :jsonb
+#  actor_type                    :string
+#  discoverable                  :boolean
+#  also_known_as                 :string           is an Array
+#  silenced_at                   :datetime
+#  suspended_at                  :datetime
+#  trust_level                   :integer
+#  hide_collections              :boolean
+#  avatar_storage_schema_version :integer
+#  header_storage_schema_version :integer
 #
 
 class Account < ApplicationRecord
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
index 960784222..736da6c1d 100644
--- a/app/models/concerns/omniauthable.rb
+++ b/app/models/concerns/omniauthable.rb
@@ -82,7 +82,7 @@ module Omniauthable
       username = starting_username
       i        = 0
 
-      while Account.exists?(username: username)
+      while Account.exists?(username: username, domain: nil)
         i       += 1
         username = "#{starting_username}_#{i}"
       end
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index d177cf281..7cb03b819 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -3,20 +3,21 @@
 #
 # Table name: custom_emojis
 #
-#  id                 :bigint(8)        not null, primary key
-#  shortcode          :string           default(""), not null
-#  domain             :string
-#  image_file_name    :string
-#  image_content_type :string
-#  image_file_size    :integer
-#  image_updated_at   :datetime
-#  created_at         :datetime         not null
-#  updated_at         :datetime         not null
-#  disabled           :boolean          default(FALSE), not null
-#  uri                :string
-#  image_remote_url   :string
-#  visible_in_picker  :boolean          default(TRUE), not null
-#  category_id        :bigint(8)
+#  id                           :bigint(8)        not null, primary key
+#  shortcode                    :string           default(""), not null
+#  domain                       :string
+#  image_file_name              :string
+#  image_content_type           :string
+#  image_file_size              :integer
+#  image_updated_at             :datetime
+#  created_at                   :datetime         not null
+#  updated_at                   :datetime         not null
+#  disabled                     :boolean          default(FALSE), not null
+#  uri                          :string
+#  image_remote_url             :string
+#  visible_in_picker            :boolean          default(TRUE), not null
+#  category_id                  :bigint(8)
+#  image_storage_schema_version :integer
 #
 
 class CustomEmoji < ApplicationRecord
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 40624c73c..f789bdc55 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -3,23 +3,24 @@
 #
 # Table name: media_attachments
 #
-#  id                  :bigint(8)        not null, primary key
-#  status_id           :bigint(8)
-#  file_file_name      :string
-#  file_content_type   :string
-#  file_file_size      :integer
-#  file_updated_at     :datetime
-#  remote_url          :string           default(""), not null
-#  created_at          :datetime         not null
-#  updated_at          :datetime         not null
-#  shortcode           :string
-#  type                :integer          default("image"), not null
-#  file_meta           :json
-#  account_id          :bigint(8)
-#  description         :text
-#  scheduled_status_id :bigint(8)
-#  blurhash            :string
-#  processing          :integer
+#  id                          :bigint(8)        not null, primary key
+#  status_id                   :bigint(8)
+#  file_file_name              :string
+#  file_content_type           :string
+#  file_file_size              :integer
+#  file_updated_at             :datetime
+#  remote_url                  :string           default(""), not null
+#  created_at                  :datetime         not null
+#  updated_at                  :datetime         not null
+#  shortcode                   :string
+#  type                        :integer          default("image"), not null
+#  file_meta                   :json
+#  account_id                  :bigint(8)
+#  description                 :text
+#  scheduled_status_id         :bigint(8)
+#  blurhash                    :string
+#  processing                  :integer
+#  file_storage_schema_version :integer
 #
 
 class MediaAttachment < ApplicationRecord
diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb
index 4e89fbf85..2802f4667 100644
--- a/app/models/preview_card.rb
+++ b/app/models/preview_card.rb
@@ -3,25 +3,26 @@
 #
 # Table name: preview_cards
 #
-#  id                 :bigint(8)        not null, primary key
-#  url                :string           default(""), not null
-#  title              :string           default(""), not null
-#  description        :string           default(""), not null
-#  image_file_name    :string
-#  image_content_type :string
-#  image_file_size    :integer
-#  image_updated_at   :datetime
-#  type               :integer          default("link"), not null
-#  html               :text             default(""), not null
-#  author_name        :string           default(""), not null
-#  author_url         :string           default(""), not null
-#  provider_name      :string           default(""), not null
-#  provider_url       :string           default(""), not null
-#  width              :integer          default(0), not null
-#  height             :integer          default(0), not null
-#  created_at         :datetime         not null
-#  updated_at         :datetime         not null
-#  embed_url          :string           default(""), not null
+#  id                           :bigint(8)        not null, primary key
+#  url                          :string           default(""), not null
+#  title                        :string           default(""), not null
+#  description                  :string           default(""), not null
+#  image_file_name              :string
+#  image_content_type           :string
+#  image_file_size              :integer
+#  image_updated_at             :datetime
+#  type                         :integer          default("link"), not null
+#  html                         :text             default(""), not null
+#  author_name                  :string           default(""), not null
+#  author_url                   :string           default(""), not null
+#  provider_name                :string           default(""), not null
+#  provider_url                 :string           default(""), not null
+#  width                        :integer          default(0), not null
+#  height                       :integer          default(0), not null
+#  created_at                   :datetime         not null
+#  updated_at                   :datetime         not null
+#  embed_url                    :string           default(""), not null
+#  image_storage_schema_version :integer
 #
 
 class PreviewCard < ApplicationRecord
@@ -47,6 +48,10 @@ class PreviewCard < ApplicationRecord
 
   before_save :extract_dimensions, if: :link?
 
+  def local?
+    false
+  end
+
   def missing_image?
     width.present? && height.present? && image_file_name.blank?
   end
diff --git a/app/models/status.rb b/app/models/status.rb
index 31e77770d..34fa00912 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -206,12 +206,8 @@ class Status < ApplicationRecord
   def title
     if destroyed?
       "#{account.acct} deleted status"
-    elsif reblog?
-      preview = sensitive ? '<sensitive>' : text.slice(0, 10).split("\n")[0]
-      "#{account.acct} shared #{reblog.account.acct}'s: #{preview}"
     else
-      preview = sensitive ? '<sensitive>' : text.slice(0, 20).split("\n")[0]
-      "#{account.acct}: #{preview}"
+      reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
     end
   end
 
@@ -404,7 +400,7 @@ class Status < ApplicationRecord
 
       if account.nil?
         where(visibility: visibility).not_local_only
-      elsif target_account.blocking?(account) # get rid of blocked peeps
+      elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps
         none
       elsif account.id == target_account.id # author can see own stuff
         all
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index e913f0c64..54e7c450c 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -5,7 +5,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
 
   attributes :uri, :title, :short_description, :description, :email,
              :version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits,
-             :languages, :registrations, :approval_required
+             :languages, :registrations, :approval_required, :invites_enabled
 
   has_one :contact_account, serializer: REST::AccountSerializer
 
@@ -76,6 +76,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
     Setting.registrations_mode == 'approved'
   end
 
+  def invites_enabled
+    Setting.min_invite_role == 'user'
+  end
+
   private
 
   def instance_presenter
diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb
index 880cdde92..6c0093cd4 100644
--- a/app/services/fetch_resource_service.rb
+++ b/app/services/fetch_resource_service.rb
@@ -25,7 +25,18 @@ class FetchResourceService < BaseService
   end
 
   def perform_request(&block)
-    Request.new(:get, @url).add_headers('Accept' => ACCEPT_HEADER).on_behalf_of(Account.representative).perform(&block)
+    Request.new(:get, @url).tap do |request|
+      request.add_headers('Accept' => ACCEPT_HEADER)
+
+      # In a real setting we want to sign all outgoing requests,
+      # in case the remote server has secure mode enabled and requires
+      # authentication on all resources. However, during development,
+      # sending request signatures with an inaccessible host is useless
+      # and prevents even public resources from being fetched, so
+      # don't do it
+
+      request.on_behalf_of(Account.representative) unless Rails.env.development?
+    end.perform(&block)
   end
 
   def process_response(response, terminal = false)
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index e0ec98ec9..07e06100a 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -68,11 +68,11 @@
           .hero-widget__footer__column
             %h4= t 'about.server_stats'
 
-            %div{ style: 'display: flex' }
-              .hero-widget__counter{ style: 'width: 50%' }
+            .hero-widget__counters__wrapper
+              .hero-widget__counter
                 %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true
                 %span= t 'about.user_count_after', count: @instance_presenter.user_count
-              .hero-widget__counter{ style: 'width: 50%' }
+              .hero-widget__counter
                 %strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true
                 %span
                   = t 'about.active_count_after'
diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml
index a82f277b1..4f71b062d 100644
--- a/app/views/accounts/_moved.html.haml
+++ b/app/views/accounts/_moved.html.haml
@@ -9,8 +9,10 @@
     = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do
       .detailed-status__display-avatar
         .account__avatar-overlay
-          .account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" }
-          .account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" }
+          .account__avatar-overlay-base
+            = image_tag moved_to_account.avatar_static_url
+          .account__avatar-overlay-overlay
+            = image_tag account.avatar_static_url
 
       %span.display-name
         %bdi
diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml
index 44b10af6e..c9bd8c686 100644
--- a/app/views/admin/accounts/_account.html.haml
+++ b/app/views/admin/accounts/_account.html.haml
@@ -2,7 +2,7 @@
   %td
     = admin_account_link_to(account)
   %td
-    %div{ style: 'margin: -2px 0' }= account_badge(account, all: true)
+    %div.account-badges= account_badge(account, all: true)
   %td
     - if account.user_current_sign_in_ip
       %samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 408f94eed..e6461aad0 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -31,7 +31,7 @@
       %div
         .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
 
-.dashboard__counters{ style: 'margin-top: 10px' }
+.dashboard__counters.admin-account-counters
   %div
     = link_to admin_account_statuses_path(@account.id) do
       .dashboard__counters__num= number_with_delimiter @account.statuses_count
@@ -178,18 +178,8 @@
               = @account.shared_inbox_url
               = fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times'
 
-  %div{ style: 'overflow: hidden' }
-    %div{ style: 'float: right' }
-      - if @account.local?
-        = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
-        - if @account.user&.otp_required_for_login?
-          = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
-        - if !@account.memorial? && @account.user_approved?
-          = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
-      - else
-        = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
-
-    %div{ style: 'float: left' }
+  %div.action-buttons
+    %div
       - if @account.local? && @account.user_approved?
         = link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
       - if @account.silenced?
@@ -216,6 +206,16 @@
         - else
           = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'
 
+    %div
+      - if @account.local?
+        = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
+        - if @account.user&.otp_required_for_login?
+          = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
+        - if !@account.memorial? && @account.user_approved?
+          = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
+      - else
+        = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
+
   %hr.spacer/
 
   - unless @warnings.empty?
diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml
index 0b299acc5..bd67eb4fc 100644
--- a/app/views/admin/instances/index.html.haml
+++ b/app/views/admin/instances/index.html.haml
@@ -10,7 +10,7 @@
       - unless whitelist_mode?
         %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
 
-  %div{ style: 'flex: 1 1 auto; text-align: right' }
+  %div.special-action-button
     - if whitelist_mode?
       = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button'
     - else
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index 49a666a5a..92e14c0df 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -45,11 +45,11 @@
 
 %hr.spacer/
 
-%div{ style: 'overflow: hidden' }
-  %div{ style: 'float: left' }
+%div.action-buttons
+  %div
     = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button'
 
-  %div{ style: 'float: right' }
+  %div
     - if @domain_allow
       = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
     - elsif @domain_block
diff --git a/app/views/admin/pending_accounts/index.html.haml b/app/views/admin/pending_accounts/index.html.haml
index 171976e33..8101d7f99 100644
--- a/app/views/admin/pending_accounts/index.html.haml
+++ b/app/views/admin/pending_accounts/index.html.haml
@@ -22,9 +22,9 @@
 
 %hr.spacer/
 
-%div{ style: 'overflow: hidden' }
-  %div{ style: 'float: right' }
-    = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
-
+%div.action-buttons
   %div
     = link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
+
+  %div
+    = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml
index 3afaff615..907477f24 100644
--- a/app/views/admin/relationships/index.html.haml
+++ b/app/views/admin/relationships/index.html.haml
@@ -17,7 +17,7 @@
       %li= filter_link_to t('admin.accounts.location.local'), location: 'local'
       %li= filter_link_to t('admin.accounts.location.remote'), location: 'remote'
 
-  .back-link{ style: 'flex: 1 1 auto; text-align: right' }
+  .back-link
     = link_to admin_account_path(@account.id) do
       = fa_icon 'chevron-left fw'
       = t('admin.statuses.back_to_account')
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index b12ea4270..4ecc8dc93 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -65,9 +65,11 @@
 
 %hr.spacer
 
-%div{ style: 'overflow: hidden; margin-bottom: 20px; clear: both' }
+%div.action-buttons
+  %div
+
   - if @report.unresolved?
-    %div{ style: 'float: right' }
+    %div
       - if @report.target_account.local?
         = link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
         = link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'
diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml
index 55926f3b3..5414d69d5 100644
--- a/app/views/admin/statuses/index.html.haml
+++ b/app/views/admin/statuses/index.html.haml
@@ -9,7 +9,7 @@
     %ul
       %li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected'
       %li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
-  .back-link{ style: 'flex: 1 1 auto; text-align: right' }
+  .back-link
     = link_to admin_account_path(@account.id) do
       = fa_icon 'chevron-left fw'
       = t('admin.statuses.back_to_account')
diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml
index a7a392272..e2470198d 100644
--- a/app/views/admin/statuses/show.html.haml
+++ b/app/views/admin/statuses/show.html.haml
@@ -4,7 +4,7 @@
   = "@#{@account.acct}"
 
 .filters
-  .back-link{ style: 'flex: 1 1 auto; text-align: right' }
+  .back-link
     = link_to admin_account_path(@account.id) do
       %i.fa.fa-chevron-left.fa-fw
       = t('admin.statuses.back_to_account')
diff --git a/app/views/admin/tags/index.html.haml b/app/views/admin/tags/index.html.haml
index d20ed80f8..e64802275 100644
--- a/app/views/admin/tags/index.html.haml
+++ b/app/views/admin/tags/index.html.haml
@@ -68,9 +68,9 @@
 - if params[:pending_review] == '1' || params[:unreviewed] == '1'
   %hr.spacer/
 
-  %div{ style: 'overflow: hidden' }
-    %div{ style: 'float: right' }
-      = link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
-
+  %div.action-buttons
     %div
       = link_to t('admin.accounts.approve_all'), approve_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
+
+    %div
+      = link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml
index 808dce514..e7ecfecd9 100644
--- a/app/views/application/_card.html.haml
+++ b/app/views/application/_card.html.haml
@@ -9,7 +9,6 @@
         = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
 
       .display-name
-        %span{ id: "default_account_display_name", style: "display: none" }= account.username
         %bdi
           %strong.emojify.p-name= display_name(account, custom_emojify: true)
         %span
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index bcd66fb8a..457bc1d23 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -8,8 +8,8 @@
   = render 'shared/error_messages', object: resource
 
   - if @invite.present? && @invite.autofollow?
-    .fields-group{ style: 'margin-bottom: 30px' }
-      %p.hint{ style: 'text-align: center' }= t('invites.invited_by')
+    .fields-group.invited-by
+      %p.hint= t('invites.invited_by')
       = render 'application/card', account: @invite.user.account
 
   = f.simple_fields_for :account do |ff|
diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml
index 4e6bbd7a9..b2e36f6bc 100644
--- a/app/views/auth/sessions/two_factor.html.haml
+++ b/app/views/auth/sessions/two_factor.html.haml
@@ -2,7 +2,7 @@
   = t('auth.login')
 
 = simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
-  %p.hint{ style: 'margin-bottom: 25px' }= t('simple_form.hints.sessions.otp')
+  %p.hint.otp-hint= t('simple_form.hints.sessions.otp')
 
   .fields-group
     = f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, autofocus: true
diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml
index ecf12b649..1170332ff 100644
--- a/app/views/directories/index.html.haml
+++ b/app/views/directories/index.html.haml
@@ -28,7 +28,6 @@
               = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
 
             .display-name
-              %span{ id: "default_account_display_name", style: "display: none" }= account.username
               %bdi
                 %strong.emojify.p-name= display_name(account, custom_emojify: true)
               %span= acct(account)
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 2be9427c5..99ab3729e 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -40,6 +40,6 @@
   %body{ class: body_classes }
     = content_for?(:content) ? yield(:content) : yield
 
-    %div{ style: 'display: none'}
+    .logo-resources
       = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg')
       = render file: Rails.root.join('app', 'javascript', 'images', 'logo_full.svg')
diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml
index 6695b12dd..75441b452 100644
--- a/app/views/layouts/embedded.html.haml
+++ b/app/views/layouts/embedded.html.haml
@@ -23,5 +23,5 @@
   %body.embed
     = yield
 
-    %div{ style: 'display: none'}
+    .logo-resources
       = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg')
diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml
index 063089a7f..e32bd49ec 100644
--- a/app/views/public_timelines/show.html.haml
+++ b/app/views/public_timelines/show.html.haml
@@ -12,5 +12,5 @@
   - else
     %p= t('about.browse_local_posts')
 
-#mastodon-timeline{ data: { props: Oj.dump(default_props) }}
+#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }}
 #modal-container
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index 5453177fd..5fc865814 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -9,8 +9,8 @@
     = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, selected: I18n.locale, hint: false
 
   - unless I18n.locale == :en
-    .flash-message{ style: "text-align: unset; color: unset" }
-      #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener", style: "text-decoration: underline")}
+    .flash-message.translation-prompt
+      #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener")}
 
   %h4= t 'appearance.advanced_web_interface'
 
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 841c01fd7..6061e9cfd 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -9,7 +9,7 @@
 
   .fields-row
     .fields-row__column.fields-group.fields-row__column-6
-      = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH }, hint: false
+      = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH, data: { default: @account.username } }, hint: false
       = f.input :note, wrapper: :with_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }, hint: false
 
   .fields-row
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index 021390e47..544b92330 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -15,12 +15,12 @@
 
   = account_action_button(status.account)
 
-  .status__content.emojify<
+  .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
     - if status.spoiler_text?
-      %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
+      %p<
         %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
         %button.status__content__spoiler-link= t('statuses.show_more')
-    .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
+    .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
       = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
       - if status.preloadable_poll
         = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
diff --git a/app/views/statuses/_poll.html.haml b/app/views/statuses/_poll.html.haml
index de5357e6d..64e62e97c 100644
--- a/app/views/statuses/_poll.html.haml
+++ b/app/views/statuses/_poll.html.haml
@@ -10,13 +10,15 @@
           - percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0
           %label.poll__option><
             %span.poll__number><
-              - if own_votes.include?(index)
-                %i.poll__voted__mark.fa.fa-check
               = "#{percent.round}%"
             %span.poll__option__text
               = Formatter.instance.format_poll_option(status, option, autoplay: autoplay)
+            - if own_votes.include?(index)
+              %span.poll__voted
+                %i.poll__voted__mark.fa.fa-check
 
-          %span.poll__chart{ style: "width: #{percent}%" }
+          %progress{ max: 100, value: percent < 1 ? 1 : percent, 'aria-hidden': 'true' }
+            %span.poll__chart
         - else
           %label.poll__option><
             %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}><
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index 8a418a1d5..f959056cd 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -19,12 +19,12 @@
           %span.display-name__account
             = acct(status.account)
             = fa_icon('lock') if status.account.locked?
-  .status__content.emojify<
+  .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
     - if status.spoiler_text?
-      %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
+      %p<
         %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
         %button.status__content__spoiler-link= t('statuses.show_more')
-    .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
+    .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }<
       = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
       - if status.preloadable_poll
         = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
@@ -51,18 +51,18 @@
 
   .status__action-bar
     .status__action-bar__counter
-      = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
+      = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button' do
         - if status.in_reply_to_id.nil?
           = fa_icon 'reply fw'
         - else
           = fa_icon 'reply-all fw'
       .status__action-bar__counter__label= obscured_counter status.replies_count
-    = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
+    = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do
       - if status.distributable?
         = fa_icon 'retweet fw'
       - elsif status.private_visibility? || status.limited_visibility?
         = fa_icon 'lock fw'
       - else
         = fa_icon 'envelope fw'
-    = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
+    = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button' do
       = fa_icon 'star fw'