about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/authorize_follows_controller.rb71
-rw-r--r--app/controllers/authorize_interactions_controller.rb71
-rw-r--r--app/controllers/intents_controller.rb2
-rw-r--r--app/controllers/remote_follow_controller.rb1
-rw-r--r--app/controllers/remote_interaction_controller.rb53
-rw-r--r--app/helpers/home_helper.rb10
-rw-r--r--app/helpers/settings_helper.rb1
-rw-r--r--app/javascript/core/public.js14
-rw-r--r--app/javascript/mastodon/components/media_gallery.js2
-rw-r--r--app/javascript/mastodon/components/status.js2
-rw-r--r--app/javascript/mastodon/components/status_content.js4
-rw-r--r--app/javascript/mastodon/features/compose/components/reply_indicator.js2
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/boost_modal.js2
-rw-r--r--app/javascript/mastodon/locales/ka.json311
-rw-r--r--app/javascript/mastodon/locales/whitelist_ka.json2
-rw-r--r--app/javascript/packs/public.js7
-rw-r--r--app/javascript/styles/mastodon/components.scss1
-rw-r--r--app/javascript/styles/mastodon/stream_entries.scss17
-rw-r--r--app/models/favourite.rb4
-rw-r--r--app/models/remote_follow.rb4
-rw-r--r--app/models/status.rb10
-rw-r--r--app/serializers/webfinger_serializer.rb2
-rw-r--r--app/views/authorize_follows/show.html.haml17
-rw-r--r--app/views/authorize_interactions/_post_follow_actions.html.haml (renamed from app/views/authorize_follows/_post_follow_actions.html.haml)4
-rw-r--r--app/views/authorize_interactions/error.html.haml (renamed from app/views/authorize_follows/error.html.haml)0
-rw-r--r--app/views/authorize_interactions/show.html.haml18
-rw-r--r--app/views/authorize_interactions/success.html.haml (renamed from app/views/authorize_follows/success.html.haml)6
-rw-r--r--app/views/layouts/modal.html.haml2
-rw-r--r--app/views/remote_interaction/new.html.haml17
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml15
-rw-r--r--app/views/stream_entries/_simple_status.html.haml10
-rw-r--r--app/views/stream_entries/_status.html.haml6
-rw-r--r--app/views/stream_entries/show.html.haml2
-rw-r--r--app/views/well_known/webfinger/show.xml.ruby2
35 files changed, 564 insertions, 130 deletions
diff --git a/app/controllers/authorize_follows_controller.rb b/app/controllers/authorize_follows_controller.rb
deleted file mode 100644
index 95052df7c..000000000
--- a/app/controllers/authorize_follows_controller.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-class AuthorizeFollowsController < ApplicationController
-  layout 'modal'
-
-  before_action :authenticate_user!
-  before_action :set_pack
-  before_action :set_body_classes
-
-  def show
-    @account = located_account || render(:error)
-  end
-
-  def create
-    @account = follow_attempt.try(:target_account)
-
-    if @account.nil?
-      render :error
-    else
-      render :success
-    end
-  rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
-    render :error
-  end
-
-  private
-
-  def set_pack
-    use_pack 'modal'
-  end
-
-  def follow_attempt
-    FollowService.new.call(current_account, acct_without_prefix)
-  end
-
-  def located_account
-    if acct_param_is_url?
-      account_from_remote_fetch
-    else
-      account_from_remote_follow
-    end
-  end
-
-  def account_from_remote_fetch
-    FetchRemoteAccountService.new.call(acct_without_prefix)
-  end
-
-  def account_from_remote_follow
-    ResolveAccountService.new.call(acct_without_prefix)
-  end
-
-  def acct_param_is_url?
-    parsed_uri.path && %w(http https).include?(parsed_uri.scheme)
-  end
-
-  def parsed_uri
-    Addressable::URI.parse(acct_without_prefix).normalize
-  end
-
-  def acct_without_prefix
-    acct_params.gsub(/\Aacct:/, '')
-  end
-
-  def acct_params
-    params.fetch(:acct, '')
-  end
-
-  def set_body_classes
-    @body_classes = 'modal-layout'
-  end
-end
diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb
new file mode 100644
index 000000000..20b3fa94b
--- /dev/null
+++ b/app/controllers/authorize_interactions_controller.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class AuthorizeInteractionsController < ApplicationController
+  include Authorization
+
+  layout 'modal'
+
+  before_action :authenticate_user!
+  before_action :set_body_classes
+  before_action :set_resource
+  before_action :set_pack
+
+  def show
+    if @resource.is_a?(Account)
+      render :show
+    elsif @resource.is_a?(Status)
+      redirect_to web_url("statuses/#{@resource.id}")
+    else
+      render :error
+    end
+  end
+
+  def create
+    if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource)
+      render :success
+    else
+      render :error
+    end
+  rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
+    render :error
+  end
+
+  private
+
+  def set_resource
+    @resource = located_resource || render(:error)
+    authorize(@resource, :show?) if @resource.is_a?(Status)
+  end
+
+  def located_resource
+    if uri_param_is_url?
+      ResolveURLService.new.call(uri_param)
+    else
+      account_from_remote_follow
+    end
+  end
+
+  def account_from_remote_follow
+    ResolveAccountService.new.call(uri_param)
+  end
+
+  def uri_param_is_url?
+    parsed_uri.path && %w(http https).include?(parsed_uri.scheme)
+  end
+
+  def parsed_uri
+    Addressable::URI.parse(uri_param).normalize
+  end
+
+  def uri_param
+    params[:uri] || params.fetch(:acct, '').gsub(/\Aacct:/, '')
+  end
+
+  def set_body_classes
+    @body_classes = 'modal-layout'
+  end
+
+  def set_pack
+    use_pack 'modal'
+  end
+end
diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb
index 56129d69a..9f41cf48a 100644
--- a/app/controllers/intents_controller.rb
+++ b/app/controllers/intents_controller.rb
@@ -8,7 +8,7 @@ class IntentsController < ApplicationController
     if uri.scheme == 'web+mastodon'
       case uri.host
       when 'follow'
-        return redirect_to authorize_follow_path(acct: uri.query_values['uri'].gsub(/\Aacct:/, ''))
+        return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].gsub(/\Aacct:/, ''))
       when 'share'
         return redirect_to share_path(text: uri.query_values['text'])
       end
diff --git a/app/controllers/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb
index 128e80a67..17bc1940a 100644
--- a/app/controllers/remote_follow_controller.rb
+++ b/app/controllers/remote_follow_controller.rb
@@ -47,5 +47,6 @@ class RemoteFollowController < ApplicationController
 
   def set_body_classes
     @body_classes = 'modal-layout'
+    @hide_header  = true
   end
 end
diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb
new file mode 100644
index 000000000..6861f3f21
--- /dev/null
+++ b/app/controllers/remote_interaction_controller.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class RemoteInteractionController < ApplicationController
+  include Authorization
+
+  layout 'modal'
+
+  before_action :set_status
+  before_action :set_body_classes
+  before_action :set_pack
+
+  def new
+    @remote_follow = RemoteFollow.new(session_params)
+  end
+
+  def create
+    @remote_follow = RemoteFollow.new(resource_params)
+
+    if @remote_follow.valid?
+      session[:remote_follow] = @remote_follow.acct
+      redirect_to @remote_follow.interact_address_for(@status)
+    else
+      render :new
+    end
+  end
+
+  private
+
+  def resource_params
+    params.require(:remote_follow).permit(:acct)
+  end
+
+  def session_params
+    { acct: session[:remote_follow] }
+  end
+
+  def set_status
+    @status = Status.find(params[:id])
+    authorize @status, :show?
+  rescue Mastodon::NotPermittedError
+    # Reraise in order to get a 404
+    raise ActiveRecord::RecordNotFound
+  end
+
+  def set_body_classes
+    @body_classes = 'modal-layout'
+    @hide_header  = true
+  end
+
+  def set_pack
+    use_pack 'modal'
+  end
+end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
index 8449f6c8a..f5b501235 100644
--- a/app/helpers/home_helper.rb
+++ b/app/helpers/home_helper.rb
@@ -38,4 +38,14 @@ module HomeHelper
       end
     end
   end
+
+  def obscured_counter(count)
+    if count <= 0
+      0
+    elsif count == 1
+      1
+    else
+      '1+'
+    end
+  end
 end
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 8528be267..14ca2333e 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -26,6 +26,7 @@ module SettingsHelper
     io: 'Ido',
     it: 'Italiano',
     ja: '日本語',
+    ka: 'ქართული',
     ko: '한국어',
     nl: 'Nederlands',
     no: 'Norsk',
diff --git a/app/javascript/core/public.js b/app/javascript/core/public.js
index 4ac9eab3e..b5014a8c7 100644
--- a/app/javascript/core/public.js
+++ b/app/javascript/core/public.js
@@ -37,3 +37,17 @@ delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => {
 
   return false;
 });
+
+delegate(document, '.modal-button', 'click', e => {
+  e.preventDefault();
+
+  let href;
+
+  if (e.target.nodeName !== 'A') {
+    href = e.target.parentNode.href;
+  } else {
+    href = e.target.href;
+  }
+
+  window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
+});
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 1d351279f..63bc4a59b 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -50,7 +50,7 @@ class Item extends React.PureComponent {
   handleClick = (e) => {
     const { index, onClick } = this.props;
 
-    if (e.button === 0) {
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       onClick(index);
     }
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 922b609ec..e653906f1 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -65,7 +65,7 @@ export default class Status extends ImmutablePureComponent {
   }
 
   handleAccountClick = (e) => {
-    if (this.context.router && e.button === 0) {
+    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       const id = e.currentTarget.getAttribute('data-id');
       e.preventDefault();
       this.context.router.history.push(`/accounts/${id}`);
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 9b86592f6..81013747e 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -64,7 +64,7 @@ export default class StatusContent extends React.PureComponent {
   }
 
   onMentionClick = (mention, e) => {
-    if (this.context.router && e.button === 0) {
+    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.context.router.history.push(`/accounts/${mention.get('id')}`);
     }
@@ -73,7 +73,7 @@ export default class StatusContent extends React.PureComponent {
   onHashtagClick = (hashtag, e) => {
     hashtag = hashtag.replace(/^#/, '').toLowerCase();
 
-    if (this.context.router && e.button === 0) {
+    if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.context.router.history.push(`/timelines/tag/${hashtag}`);
     }
diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js
index 5b4b81eac..6f358a98b 100644
--- a/app/javascript/mastodon/features/compose/components/reply_indicator.js
+++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js
@@ -30,7 +30,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
   }
 
   handleAccountClick = (e) => {
-    if (e.button === 0) {
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
     }
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 417719004..12ffb7579 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -26,7 +26,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
   };
 
   handleAccountClick = (e) => {
-    if (e.button === 0) {
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
     }
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js
index 0e9592c97..1c90d10dd 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.js
+++ b/app/javascript/mastodon/features/ui/components/boost_modal.js
@@ -37,7 +37,7 @@ export default class BoostModal extends ImmutablePureComponent {
   }
 
   handleAccountClick = (e) => {
-    if (e.button === 0) {
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.props.onClose();
       this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
new file mode 100644
index 000000000..494270b9d
--- /dev/null
+++ b/app/javascript/mastodon/locales/ka.json
@@ -0,0 +1,311 @@
+{
+  "account.badges.bot": "ბოტი",
+  "account.block": "დაბლოკე @{name}",
+  "account.block_domain": "დაიმალოს ყველაფერი დომენიდან {domain}",
+  "account.blocked": "დაიბლოკა",
+  "account.direct": "პირდაპირი წერილი @{name}-ს",
+  "account.disclaimer_full": "ქვემოთ მოცემულმა ინფორმაციამ შეიძლება სრულად არ ასახოს მომხმარებლის პროფილი.",
+  "account.domain_blocked": "დომენი დამალულია",
+  "account.edit_profile": "პროფილის ცვლილება",
+  "account.endorse": "გამორჩევა პროფილზე",
+  "account.follow": "გაყოლა",
+  "account.followers": "მიმდევრები",
+  "account.follows": "მიდევნებები",
+  "account.follows_you": "მოგყვებათ",
+  "account.hide_reblogs": "დაიმალოს ბუსტები @{name}-სგან",
+  "account.media": "მედია",
+  "account.mention": "ასახელეთ @{name}",
+  "account.moved_to": "{name} გადავიდა:",
+  "account.mute": "გააჩუმე @{name}",
+  "account.mute_notifications": "გააჩუმე შეტყობინებები @{name}-სგან",
+  "account.muted": "გაჩუმებული",
+  "account.posts": "ტუტები",
+  "account.posts_with_replies": "ტუტები და პასუხები",
+  "account.report": "დაარეპორტე @{name}",
+  "account.requested": "დამტკიცების მოლოდინში. დააწკაპუნეთ რომ უარყოთ დადევნების მოთხონვა",
+  "account.share": "გააზიარე @{name}-ის პროფილი",
+  "account.show_reblogs": "აჩვენე ბუსტები @{name}-სგან",
+  "account.unblock": "განბლოკე @{name}",
+  "account.unblock_domain": "გამოაჩინე {domain}",
+  "account.unendorse": "არ გამოირჩეს პროფილზე",
+  "account.unfollow": "ნუღარ მიჰყვები",
+  "account.unmute": "ნუღარ აჩუმებ @{name}-ს",
+  "account.unmute_notifications": "ნუღარ აჩუმებ შეტყობინებებს @{name}-სგან",
+  "account.view_full_profile": "სრული პროფილის ჩვენება",
+  "alert.unexpected.message": "წარმოიშვა მოულოდნელი შეცდომა.",
+  "alert.unexpected.title": "უპს!",
+  "boost_modal.combo": "შეგიძლიათ დააჭიროთ {combo}-ს რათა შემდეგ ჯერზე გამოტოვოთ ეს",
+  "bundle_column_error.body": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.",
+  "bundle_column_error.retry": "სცადეთ კიდევ ერთხელ",
+  "bundle_column_error.title": "ქსელის შეცდომა",
+  "bundle_modal_error.close": "დახურვა",
+  "bundle_modal_error.message": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.",
+  "bundle_modal_error.retry": "სცადეთ კიდევ ერთხელ",
+  "column.blocks": "დაბლოკილი მომხმარებლები",
+  "column.community": "ლოკალური თაიმლაინი",
+  "column.direct": "პირდაპირი წერილები",
+  "column.domain_blocks": "დამალული დომენები",
+  "column.favourites": "ფავორიტები",
+  "column.follow_requests": "დადევნების მოთხოვნები",
+  "column.home": "სახლი",
+  "column.lists": "სიები",
+  "column.mutes": "გაჩუმებული მომხმარებლები",
+  "column.notifications": "შეტყობინებები",
+  "column.pins": "აპინული ტუტები",
+  "column.public": "ფედერალური თაიმლაინი",
+  "column_back_button.label": "უკან",
+  "column_header.hide_settings": "პარამეტრების დამალვა",
+  "column_header.moveLeft_settings": "სვეტის მარცხნივ გადატანა",
+  "column_header.moveRight_settings": "სვეტის მარჯვნივ გადატანა",
+  "column_header.pin": "აპინვა",
+  "column_header.show_settings": "პარამეტრების ჩვენება",
+  "column_header.unpin": "პინის მოხსნა",
+  "column_subheading.settings": "პარამეტრები",
+  "community.column_settings.media_only": "მხოლოდ მედია",
+  "compose_form.direct_message_warning": "ეს ტუტი გაეგზავნება მხოლოდ ნახსენებ მომხმარებლებს.",
+  "compose_form.direct_message_warning_learn_more": "გაიგე მეტი",
+  "compose_form.hashtag_warning": "ეს ტუტი არ მოექცევა ჰეშტეგების ქვეს, რამეთუ ის არაა მითითებული. მხოლოდ ღია ტუტები მოიძებნება ჰეშტეგით.",
+  "compose_form.lock_disclaimer": "თქვენი ანგარიში არაა {locked}. ნებისმიერს შეიძლია გამოგყვეთ, რომ იხილოს თქვენი მიმდევრებზე გათვლილი პოსტები.",
+  "compose_form.lock_disclaimer.lock": "ჩაკეტილი",
+  "compose_form.placeholder": "რაზე ფიქრობ?",
+  "compose_form.publish": "ტუტი",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.marked": "მედია მონიშნულია მგრძნობიარედ",
+  "compose_form.sensitive.unmarked": "მედია არაა მონიშნული მგრძნობიარედ",
+  "compose_form.spoiler.marked": "გაფრთხილების უკან ტექსტი დამალულია",
+  "compose_form.spoiler.unmarked": "ტექსტი არაა დამალული",
+  "compose_form.spoiler_placeholder": "თქვენი გაფრთხილება დაწერეთ აქ",
+  "confirmation_modal.cancel": "უარყოფა",
+  "confirmations.block.confirm": "ბლოკი",
+  "confirmations.block.message": "დარწმუნებული ხართ, გსურთ დაბლოკოთ {name}?",
+  "confirmations.delete.confirm": "გაუქმება",
+  "confirmations.delete.message": "დარწმუნებული ხართ, გსურთ გააუქმოთ ეს სტატუსი?",
+  "confirmations.delete_list.confirm": "გაუქმება",
+  "confirmations.delete_list.message": "დარწმუნებული ხართ, გსურთ სამუდამოდ გააუქმოთ ეს სია?",
+  "confirmations.domain_block.confirm": "მთელი დომენის დამალვა",
+  "confirmations.domain_block.message": "ნაღდად, ნაღდად, დარწმუნებული ხართ, გსურთ დაბლოკოთ მთელი {domain}? უმეტეს შემთხვევაში რამდენიმე გამიზნული ბლოკი ან გაჩუმება საკმარისი და უკეთესია. კონტენტს ამ დომენიდან ვერ იხილავთ ვერც ერთ ღია თაიმლაინზე ან თქვენს შეტყობინებებში. ამ დომენიდან არსებული მიმდევრები ამოიშლება.",
+  "confirmations.mute.confirm": "გაჩუმება",
+  "confirmations.mute.message": "დარწმუნებული ხართ, გსურთ გააჩუმოთ {name}?",
+  "confirmations.redraft.confirm": "გაუქმება და გადანაწილება",
+  "confirmations.redraft.message": "დარწმუნებული ხართ, გსურთ გააუქმოთ ეს სტატუსი და გადაანაწილოთ? დაკარგავთ ყველა პასუხს, ბუსტს და მასზედ არსებულ ფავორიტს.",
+  "confirmations.unfollow.confirm": "ნუღარ მიჰყვები",
+  "confirmations.unfollow.message": "დარწმუნებული ხართ, აღარ გსურთ მიჰყვებოდეთ {name}-ს?",
+  "embed.instructions": "ეს სტატუსი ჩასვით თქვენს ვებ-საიტზე შემდეგი კოდის კოპირებით.",
+  "embed.preview": "ესაა თუ როგორც გამოჩნდება:",
+  "emoji_button.activity": "აქტივობა",
+  "emoji_button.custom": "პერსონალიზირებული",
+  "emoji_button.flags": "დროშები",
+  "emoji_button.food": "საჭმელი და სასლმელი",
+  "emoji_button.label": "ემოჯის ჩასმა",
+  "emoji_button.nature": "ბუმება",
+  "emoji_button.not_found": "არაა ემოჯი!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "ობიექტები",
+  "emoji_button.people": "ხალხი",
+  "emoji_button.recent": "ხშირად გამოყენებული",
+  "emoji_button.search": "ძებნა...",
+  "emoji_button.search_results": "ძებნის შედეგები",
+  "emoji_button.symbols": "სიმბოლოები",
+  "emoji_button.travel": "მოგზაურობა და ადგილები",
+  "empty_column.community": "ლოკალური თაიმლაინი ცარიელია. დაწერეთ რაიმე ღიად ან ქენით რაიმე სხვა!",
+  "empty_column.direct": "ჯერ პირდაპირი წერილები არ გაქვთ. როდესაც მიიღებთ ან გააგზავნით, გამოჩნდება აქ.",
+  "empty_column.hashtag": "ამ ჰეშტეგში ჯერ არაფერია.",
+  "empty_column.home": "თქვენი სახლის თაიმლაინი ცარიელია! ესტუმრეთ {public}-ს ან დასაწყისისთვის გამოიყენეთ ძებნა, რომ შეხვდეთ სხვა მომხმარებლებს.",
+  "empty_column.home.public_timeline": "ღია თაიმლაინი",
+  "empty_column.list": "ამ სიაში ჯერ არაფერია. როდესაც სიის წევრები დაპოსტავენ ახალ სტატუსებს, ისინი გამოჩნდებიან აქ.",
+  "empty_column.notifications": "ჯერ შეტყობინებები არ გაქვთ. საუბრის დასაწყებად იურთიერთქმედეთ სხვებთან.",
+  "empty_column.public": "აქ არაფერია! შესავსებად, დაწერეთ რაიმე ღიად ან ხელით გაჰყევით მომხმარებლებს სხვა ინსტანციებისგან",
+  "follow_request.authorize": "ავტორიზაცია",
+  "follow_request.reject": "უარყოფა",
+  "getting_started.developers": "დეველოპერები",
+  "getting_started.documentation": "დოკუმენტაცია",
+  "getting_started.find_friends": "იპოვეთ მეგობრები ტვიტერიდან",
+  "getting_started.heading": "დაწყება",
+  "getting_started.invite": "ხალხის მოწვევა",
+  "getting_started.open_source_notice": "მასტოდონი ღია პროგრამაა. შეგიძლიათ შეუწყოთ ხელი ან შექმნათ პრობემის რეპორტი {github}-ზე.",
+  "getting_started.security": "უსაფრთხოება",
+  "getting_started.terms": "მომსახურების პირობები",
+  "home.column_settings.basic": "ძირითადი",
+  "home.column_settings.show_reblogs": "ბუსტების ჩვენება",
+  "home.column_settings.show_replies": "პასუხების ჩვენება",
+  "keyboard_shortcuts.back": "უკან გადასასვლელად",
+  "keyboard_shortcuts.boost": "დასაბუსტად",
+  "keyboard_shortcuts.column": "ერთ-ერთი სვეტში სტატუსზე ფოკუსირებისთვის",
+  "keyboard_shortcuts.compose": "შედგენის ტექსტ-არეაზე ფოკუსირებისთვის",
+  "keyboard_shortcuts.description": "აღწერილობა",
+  "keyboard_shortcuts.down": "სიაში ქვემოთ გადასაადგილებლად",
+  "keyboard_shortcuts.enter": "სტატუსის გასახსნელად",
+  "keyboard_shortcuts.favourite": "ფავორიტად ქცევისთვის",
+  "keyboard_shortcuts.heading": "კლავიატურის სწრაფი ბმულები",
+  "keyboard_shortcuts.hotkey": "ცხელი კლავიში",
+  "keyboard_shortcuts.legend": "ამ ლეგენდის გამოსაჩენად",
+  "keyboard_shortcuts.mention": "ავტორის დასახელებლად",
+  "keyboard_shortcuts.profile": "ავტორის პროფილის გასახსნელად",
+  "keyboard_shortcuts.reply": "პასუხისთვის",
+  "keyboard_shortcuts.search": "ძიებაზე ფოკუსირებისთვის",
+  "keyboard_shortcuts.toggle_hidden": "გაფრთხილების უკან ტექსტის გამოსაჩენად/დასამალვად",
+  "keyboard_shortcuts.toot": "ახალი ტუტის დასაწყებად",
+  "keyboard_shortcuts.unfocus": "შედგენის ტექსტ-არეაზე ფოკუსის მოსაშორებლად",
+  "keyboard_shortcuts.up": "სიაში ზემოთ გადასაადგილებლად",
+  "lightbox.close": "დახურვა",
+  "lightbox.next": "შემდეგი",
+  "lightbox.previous": "წინა",
+  "lists.account.add": "სიაში დამატება",
+  "lists.account.remove": "სიიდან ამოშლა",
+  "lists.delete": "სიის წაშლა",
+  "lists.edit": "სიის შეცვლა",
+  "lists.new.create": "სიის დამატება",
+  "lists.new.title_placeholder": "ახალი სიის სათაური",
+  "lists.search": "ძებნა ადამიანებს შორის რომელთაც მიჰყვებით",
+  "lists.subheading": "თქვენი სიები",
+  "loading_indicator.label": "იტვირთება...",
+  "media_gallery.toggle_visible": "ხილვადობის ჩართვა",
+  "missing_indicator.label": "არაა ნაპოვნი",
+  "missing_indicator.sublabel": "ამ რესურსის პოვნა ვერ მოხერხდა",
+  "mute_modal.hide_notifications": "დავმალოთ შეტყობინებები ამ მომხმარებლისგან?",
+  "navigation_bar.blocks": "დაბლოკილი მომხმარებლები",
+  "navigation_bar.community_timeline": "ლოკალური თაიმლაინი",
+  "navigation_bar.direct": "პირდაპირი წერილები",
+  "navigation_bar.discover": "აღმოაჩინე",
+  "navigation_bar.domain_blocks": "დამალული დომენები",
+  "navigation_bar.edit_profile": "შეცვალე პროფილი",
+  "navigation_bar.favourites": "ფავორიტები",
+  "navigation_bar.filters": "გაჩუმებული სიტყვები",
+  "navigation_bar.follow_requests": "დადევნების მოთხოვნები",
+  "navigation_bar.info": "ამ ინსტანციის შესახებ",
+  "navigation_bar.keyboard_shortcuts": "ცხელი კლავიშები",
+  "navigation_bar.lists": "სიები",
+  "navigation_bar.logout": "გასვლა",
+  "navigation_bar.mutes": "გაჩუმებული მომხმარებლები",
+  "navigation_bar.personal": "პირადი",
+  "navigation_bar.pins": "აპინული ტუტები",
+  "navigation_bar.preferences": "პრეფერენსიები",
+  "navigation_bar.public_timeline": "ფედერალური თაიმლაინი",
+  "navigation_bar.security": "უსაფრთხოება",
+  "notification.favourite": "{name}-მა თქვენი სტატუსი აქცია ფავორიტად",
+  "notification.follow": "{name} გამოგყვათ",
+  "notification.mention": "{name}-მა გასახელათ",
+  "notification.reblog": "{name}-მა დაბუსტა თქვენი სტატუსი",
+  "notifications.clear": "შეტყობინებების გასუფთავება",
+  "notifications.clear_confirmation": "დარწმუნებული ხართ, გსურთ სამუდამოდ წაშალოთ ყველა თქვენი შეტყობინება?",
+  "notifications.column_settings.alert": "დესკტოპ შეტყობინებები",
+  "notifications.column_settings.favourite": "ფავორიტები:",
+  "notifications.column_settings.follow": "ახალი მიმდევრები:",
+  "notifications.column_settings.mention": "ხსენებები:",
+  "notifications.column_settings.push": "ფუშ შეტყობინებები",
+  "notifications.column_settings.push_meta": "ეს მოწყობილობა",
+  "notifications.column_settings.reblog": "ბუსტები:",
+  "notifications.column_settings.show": "გამოჩნდეს სვეტში",
+  "notifications.column_settings.sound": "ხმის დაკვრა",
+  "notifications.group": "{count} შეტყობინება",
+  "onboarding.done": "დასასრული",
+  "onboarding.next": "შემდეგი",
+  "onboarding.page_five.public_timelines": "ლოკალური თაიმლაინი {domain}-ზე საჯარო პოსტებს აჩვენებს ყველასგან. ფედერალური თაიმლაინი {domain}-ზე აჩვენებს საჯარო პოსტებს ყველასგან ვინც მიჰყვება. ეს საჯარო თაიმლაინებია, ახალი ადამიანების აღმოჩენის კარგი გზაა.",
+  "onboarding.page_four.home": "სახლის თაიმლაინი აჩვენებს პოსტებს ადამიანებისგან, რომლებსაც მიჰყვებით.",
+  "onboarding.page_four.notifications": "შეტყობინებების სვეტი აჩვენებს სხვის ურთიერთქმედებებს თქვენთან.",
+  "onboarding.page_one.federation": "მასტოდონი დამოუკიდებელი სერვერების ქსელია, რომლებიც ერთიანდებიან ერთი დიდი სოციალური ქსელის შექმნისთვის. ამ სერვერებს ჩვენ ვეძახით ინსტანციებს.",
+  "onboarding.page_one.full_handle": "თქვენი სრული სახელური",
+  "onboarding.page_one.handle_hint": "ეს არის ის რასაც ეტყოდით თქვენს მეგობრებს რომ მოძიონ.",
+  "onboarding.page_one.welcome": "კეთილი იყოს თქვენი მასტოდონში მობრძანება!",
+  "onboarding.page_six.admin": "თქვენი ინსტანციის ადმინისტრატორია {admin}.",
+  "onboarding.page_six.almost_done": "თითქმის დასრულდა...",
+  "onboarding.page_six.appetoot": "ბონ აპეტუტ!",
+  "onboarding.page_six.apps_available": "ხელმისაწვდომია {apps} აი-ოსისთვის, ანდროიდისთვის და სხვა პლატფორმებისთვის.",
+  "onboarding.page_six.github": "მასტოდონი უფასო ღია პროგრამაა. შეგიძლიათ დაარეპორტოთ შეცდომები, მოითხოვოთ ფუნქციები, შეუწყოთ ხელი კოდს {github}-ზე.",
+  "onboarding.page_six.guidelines": "საზოგადოების სახელმძღვანელო",
+  "onboarding.page_six.read_guidelines": "გთხოვთ გაეცნოთ {domain}-ს {guidelines}!",
+  "onboarding.page_six.various_app": "მობაილ აპები",
+  "onboarding.page_three.profile": "შეცვალეთ თქვენი პროფილი რომ შეცვალოთ ავატარი, ბიოგრაფია და დისპლეის სახელი. იქ, ასევე იხილავთ სხვა პრეფერენსიების.",
+  "onboarding.page_three.search": "გამოიყენეთ ძიება რომ იპოვნოთ ადამიანები და იხილოთ ჰეშტეგები, ისეთები როგორებიცაა {illustration} და {introductions}. რომ მოძებნოთ ადამიანი ვინც არაა ამ ინსტანციაზე, გამოიყენეთ სრული სახელური.",
+  "onboarding.page_two.compose": "პოსტები შექმენით კომპოზიციის სვეტიდან. შეგიძლიათ ატვირთოთ სურათები, შეცვალოთ კონფიდენციალურობა და ქვემოთ მოცემული პიქტოგრამით დაამატოთ კონტენტის გაფრთხილება.",
+  "onboarding.skip": "გამოტოვება",
+  "privacy.change": "სტატუსის კონფიდენციალურობის მითითება",
+  "privacy.direct.long": "დაიპოსტოს მხოლოდ დასახელებულ მომხმარებლებთან",
+  "privacy.direct.short": "პირდაპირი",
+  "privacy.private.long": "დაიპოსტოს მხოლოდ მიმდევრებთან",
+  "privacy.private.short": "მხოლოდ-მიმდევრებისთვის",
+  "privacy.public.long": "დაიპოსტოს საჯარო თაიმლაინებზე",
+  "privacy.public.short": "საჯარო",
+  "privacy.unlisted.long": "არ დაიპოსტოს საჯარო თაიმლაინებზე",
+  "privacy.unlisted.short": "ჩამოუთვლელი",
+  "regeneration_indicator.label": "იტვირთება…",
+  "regeneration_indicator.sublabel": "თქვენი სახლის ლენტა მზადდება!",
+  "relative_time.days": "{number}დღ",
+  "relative_time.hours": "{number}სთ",
+  "relative_time.just_now": "ახლა",
+  "relative_time.minutes": "{number}წთ",
+  "relative_time.seconds": "{number}წმ",
+  "reply_indicator.cancel": "უარყოფა",
+  "report.forward": "ფორვარდი {target}-ს",
+  "report.forward_hint": "ანგარიში სხვა სერვერიდანაა. გავაგზავნოთ რეპორტის ანონიმური ასლიც?",
+  "report.hint": "რეპორტი გაეგზავნება თქვენი ინსტანციის მოდერატორებს. ქვემოთ შეგიძლიათ დაამატოთ მიზეზი თუ რატომ არეპორტებთ ამ ანგარიშს:",
+  "report.placeholder": "დამატებითი კომენტარები",
+  "report.submit": "დასრულება",
+  "report.target": "არეპორტებთ {target}",
+  "search.placeholder": "ძებნა",
+  "search_popout.search_format": "დეტალური ძებნის ფორმა",
+  "search_popout.tips.full_text": "მარტივი ტექსტი აბრუნებს სტატუსებს რომლებიც შექმენით, აქციეთ ფავორიტად, დაბუსტეთ, ან რაშიც ასახელეთ, ასევე ემთხვევა მომხმარებლის სახელებს, დისპლეი სახელებს, და ჰეშტეგებს.",
+  "search_popout.tips.hashtag": "ჰეშტეგი",
+  "search_popout.tips.status": "სტატუსი",
+  "search_popout.tips.text": "მარტივი ტექსტი აბრუნებს დამთხვეულ დისპლეი სახელებს, მომხმარებლის სახელებს და ჰეშტეგებს",
+  "search_popout.tips.user": "მომხმარებელი",
+  "search_results.accounts": "ხალხი",
+  "search_results.hashtags": "ჰეშტეგები",
+  "search_results.statuses": "ტუტები",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "standalone.public_title": "შიდა ხედი...",
+  "status.block": "დაბლოკე @{name}",
+  "status.cancel_reblog_private": "ბუსტის მოშორება",
+  "status.cannot_reblog": "ეს პოსტი ვერ დაიბუსტება",
+  "status.delete": "წაშლა",
+  "status.direct": "პირდაპირი წერილი @{name}-ს",
+  "status.embed": "ჩართვა",
+  "status.favourite": "ფავორიტი",
+  "status.filtered": "ფილტრირებული",
+  "status.load_more": "მეტის ჩატვირთვა",
+  "status.media_hidden": "მედია დამალულია",
+  "status.mention": "ასახელე @{name}",
+  "status.more": "მეტი",
+  "status.mute": "გააჩუმე @{name}",
+  "status.mute_conversation": "გააჩუმე საუბარი",
+  "status.open": "ამ სტატუსის გაფართოება",
+  "status.pin": "აპინე პროფილზე",
+  "status.pinned": "აპინული ტუტი",
+  "status.reblog": "ბუსტი",
+  "status.reblog_private": "დაიბუსტოს საწყის აუდიტორიაზე",
+  "status.reblogged_by": "{name} დაიბუსტა",
+  "status.redraft": "გაუქმდეს და გადანაწილდეს",
+  "status.reply": "პასუხი",
+  "status.replyAll": "უპასუხე თემას",
+  "status.report": "დაარეპორტე @{name}",
+  "status.sensitive_toggle": "დააწკაპუნეთ სანახავად",
+  "status.sensitive_warning": "მგრძნობიარე კონტენტი",
+  "status.share": "გაზიარება",
+  "status.show_less": "აჩვენე ნაკლები",
+  "status.show_less_all": "აჩვენე ნაკლები ყველაზე",
+  "status.show_more": "აჩვენე მეტი",
+  "status.show_more_all": "აჩვენე მეტი ყველაზე",
+  "status.unmute_conversation": "საუბარზე გაჩუმების მოშორება",
+  "status.unpin": "პროფილიდან პინის მოშორება",
+  "tabs_bar.federated_timeline": "ფედერალური",
+  "tabs_bar.home": "სახლი",
+  "tabs_bar.local_timeline": "ლოკალური",
+  "tabs_bar.notifications": "შეტყობინებები",
+  "tabs_bar.search": "ძებნა",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} საუბრობს",
+  "ui.beforeunload": "თქვენი დრაფტი გაუქმდება თუ დატოვებთ მასტოდონს.",
+  "upload_area.title": "გადმოწიეთ და ჩააგდეთ ასატვირთათ",
+  "upload_button.label": "მედიის დამატება",
+  "upload_form.description": "აღწერილობა ვიზუალურად უფასურისთვის",
+  "upload_form.focus": "კროპი",
+  "upload_form.undo": "გაუქმება",
+  "upload_progress.label": "იტვირთება...",
+  "video.close": "ვიდეოს დახურვა",
+  "video.exit_fullscreen": "სრულ ეკრანზე ჩვენების გათიშვა",
+  "video.expand": "ვიდეოს გაფართოება",
+  "video.fullscreen": "ჩვენება სრულ ეკრანზე",
+  "video.hide": "ვიდეოს დამალვა",
+  "video.mute": "ხმის გაჩუმება",
+  "video.pause": "პაუზა",
+  "video.play": "დაკვრა",
+  "video.unmute": "ხმის გაჩუმების მოშორება"
+}
diff --git a/app/javascript/mastodon/locales/whitelist_ka.json b/app/javascript/mastodon/locales/whitelist_ka.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_ka.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 7d632776e..e5fdc7f83 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -51,13 +51,6 @@ function main() {
       }, datetime, now, datetime.getFullYear());
     });
 
-    [].forEach.call(document.querySelectorAll('.modal-button'), (content) => {
-      content.addEventListener('click', (e) => {
-        e.preventDefault();
-        window.open(e.target.href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
-      });
-    });
-
     const reactComponents = document.querySelectorAll('[data-component]');
     if (reactComponents.length > 0) {
       import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container')
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 547bcfd1e..57237d245 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -628,6 +628,7 @@
   overflow: hidden;
   white-space: pre-wrap;
   padding-top: 2px;
+  color: $primary-text-color;
 
   &:focus {
     outline: 0;
diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss
index 9e2aa720c..5aa809f76 100644
--- a/app/javascript/styles/mastodon/stream_entries.scss
+++ b/app/javascript/styles/mastodon/stream_entries.scss
@@ -3,6 +3,7 @@
   border-radius: 4px;
   overflow: hidden;
   margin-bottom: 10px;
+  text-align: left;
 
   @media screen and (max-width: $no-gap-breakpoint) {
     margin-bottom: 0;
@@ -36,7 +37,8 @@
 
     &:last-child {
       .detailed-status,
-      .status {
+      .status,
+      .load-more {
         border-bottom: 0;
         border-radius: 0 0 4px 4px;
       }
@@ -44,13 +46,15 @@
 
     &:first-child {
       .detailed-status,
-      .status {
+      .status,
+      .load-more {
         border-radius: 4px 4px 0 0;
       }
 
       &:last-child {
         .detailed-status,
-        .status {
+        .status,
+        .load-more {
           border-radius: 4px;
         }
       }
@@ -58,11 +62,16 @@
 
     @media screen and (max-width: 740px) {
       .detailed-status,
-      .status {
+      .status,
+      .load-more {
         border-radius: 0 !important;
       }
     }
   }
+
+  &--highlighted .entry {
+    background: lighten($ui-base-color, 8%);
+  }
 }
 
 .button.logo-button {
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
index ce7a6a336..17f8c9fa6 100644
--- a/app/models/favourite.rb
+++ b/app/models/favourite.rb
@@ -32,11 +32,11 @@ class Favourite < ApplicationRecord
   private
 
   def increment_cache_counters
-    status.increment_count!(:favourites_count)
+    status&.increment_count!(:favourites_count)
   end
 
   def decrement_cache_counters
     return if association(:status).loaded? && (status.marked_for_destruction? || status.marked_for_mass_destruction?)
-    status.decrement_count!(:favourites_count)
+    status&.decrement_count!(:favourites_count)
   end
 end
diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb
index 070144e2d..2537de36c 100644
--- a/app/models/remote_follow.rb
+++ b/app/models/remote_follow.rb
@@ -22,6 +22,10 @@ class RemoteFollow
     addressable_template.expand(uri: account.local_username_and_domain).to_s
   end
 
+  def interact_address_for(status)
+    addressable_template.expand(uri: ActivityPub::TagManager.instance.uri_for(status)).to_s
+  end
+
   private
 
   def populate_template
diff --git a/app/models/status.rb b/app/models/status.rb
index 01615c876..8cd6d3862 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -416,6 +416,8 @@ class Status < ApplicationRecord
   private
 
   def update_status_stat!(attrs)
+    return if marked_for_destruction? || destroyed?
+
     record = status_stat || build_status_stat
     record.update(attrs)
   end
@@ -482,8 +484,8 @@ class Status < ApplicationRecord
       Account.where(id: account_id).update_all('statuses_count = COALESCE(statuses_count, 0) + 1')
     end
 
-    reblog.increment_count!(:reblogs_count) if reblog?
-    thread.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
+    reblog&.increment_count!(:reblogs_count) if reblog?
+    thread&.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
   end
 
   def decrement_counter_caches
@@ -495,7 +497,7 @@ class Status < ApplicationRecord
       Account.where(id: account_id).update_all('statuses_count = GREATEST(COALESCE(statuses_count, 0) - 1, 0)')
     end
 
-    reblog.decrement_count!(:reblogs_count) if reblog?
-    thread.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
+    reblog&.decrement_count!(:reblogs_count) if reblog?
+    thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
   end
 end
diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb
index f80d12c02..8c0b07702 100644
--- a/app/serializers/webfinger_serializer.rb
+++ b/app/serializers/webfinger_serializer.rb
@@ -20,7 +20,7 @@ class WebfingerSerializer < ActiveModel::Serializer
       { rel: 'self', type: 'application/activity+json', href: account_url(object) },
       { rel: 'salmon', href: api_salmon_url(object.id) },
       { rel: 'magic-public-key', href: "data:application/magic-public-key,#{object.magic_key}" },
-      { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_follow_url}?acct={uri}" },
+      { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
     ]
   end
 end
diff --git a/app/views/authorize_follows/show.html.haml b/app/views/authorize_follows/show.html.haml
deleted file mode 100644
index 90e65b34f..000000000
--- a/app/views/authorize_follows/show.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- content_for :page_title do
-  = t('authorize_follow.title', acct: @account.acct)
-
-.form-container
-  .follow-prompt
-    = render 'application/card', account: @account
-
-  - if current_account.following?(@account)
-    .flash-message
-      %strong
-        = t('authorize_follow.already_following')
-    = render 'post_follow_actions'
-
-  - else
-    = form_tag authorize_follow_path, method: :post, class: 'simple_form' do
-      = hidden_field_tag :acct, @account.acct
-      = button_tag t('authorize_follow.follow'), type: :submit
diff --git a/app/views/authorize_follows/_post_follow_actions.html.haml b/app/views/authorize_interactions/_post_follow_actions.html.haml
index 2a9c062e9..561c60137 100644
--- a/app/views/authorize_follows/_post_follow_actions.html.haml
+++ b/app/views/authorize_interactions/_post_follow_actions.html.haml
@@ -1,4 +1,4 @@
 .post-follow-actions
-  %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block'
-  %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block'
+  %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block'
+  %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@resource), class: 'button button--block'
   %div= t('authorize_follow.post_follow.close')
diff --git a/app/views/authorize_follows/error.html.haml b/app/views/authorize_interactions/error.html.haml
index 88d33b68d..88d33b68d 100644
--- a/app/views/authorize_follows/error.html.haml
+++ b/app/views/authorize_interactions/error.html.haml
diff --git a/app/views/authorize_interactions/show.html.haml b/app/views/authorize_interactions/show.html.haml
new file mode 100644
index 000000000..7ca9b98c1
--- /dev/null
+++ b/app/views/authorize_interactions/show.html.haml
@@ -0,0 +1,18 @@
+- content_for :page_title do
+  = t('authorize_follow.title', acct: @resource.acct)
+
+.form-container
+  .follow-prompt
+    = render 'application/card', account: @resource
+
+  - if current_account.following?(@resource)
+    .flash-message
+      %strong
+        = t('authorize_follow.already_following')
+
+    = render 'post_follow_actions'
+  - else
+    = form_tag authorize_interaction_path, method: :post, class: 'simple_form' do
+      = hidden_field_tag :action, :follow
+      = hidden_field_tag :acct, @resource.acct
+      = button_tag t('authorize_follow.follow'), type: :submit
diff --git a/app/views/authorize_follows/success.html.haml b/app/views/authorize_interactions/success.html.haml
index cf9cb50ea..47fd09767 100644
--- a/app/views/authorize_follows/success.html.haml
+++ b/app/views/authorize_interactions/success.html.haml
@@ -1,13 +1,13 @@
 - content_for :page_title do
-  = t('authorize_follow.title', acct: @account.acct)
+  = t('authorize_follow.title', acct: @resource.acct)
 
 .form-container
   .follow-prompt
-    - if @account.locked?
+    - if @resource.locked?
       %h2= t('authorize_follow.follow_request')
     - else
       %h2= t('authorize_follow.following')
 
-    = render 'application/card', account: @account
+    = render 'application/card', account: @resource
 
   = render 'post_follow_actions'
diff --git a/app/views/layouts/modal.html.haml b/app/views/layouts/modal.html.haml
index e808593cd..a86b4fd3f 100644
--- a/app/views/layouts/modal.html.haml
+++ b/app/views/layouts/modal.html.haml
@@ -1,5 +1,5 @@
 - content_for :content do
-  - if user_signed_in?
+  - if user_signed_in? && !@hide_header
     .account-header
       .avatar= image_tag current_account.avatar.url(:original)
       .name
diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml
new file mode 100644
index 000000000..7357546b6
--- /dev/null
+++ b/app/views/remote_interaction/new.html.haml
@@ -0,0 +1,17 @@
+.form-container
+  .follow-prompt
+    %h2= t('remote_interaction.prompt')
+
+    .public-layout
+      .activity-stream.activity-stream--highlighted
+        = render 'stream_entries/status', status: @status
+
+  = simple_form_for @remote_follow, as: :remote_follow, url: remote_interaction_path(@status) do |f|
+    = render 'shared/error_messages', object: @remote_follow
+
+    = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' }
+
+    .actions
+      = f.button :button, t('remote_interaction.proceed'), type: :submit
+
+    %p.hint.subtle-hint= t('remote_follow.no_account_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started')
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index aa160b979..a7c767816 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -39,6 +39,11 @@
       - else
         = link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener'
       ·
+    = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do
+      = fa_icon('reply')
+      %span.detailed-status__reblogs>= number_to_human status.replies_count, strip_insignificant_zeros: true
+      = " "
+    ·
     - if status.direct_visibility?
       %span.detailed-status__link<
         = fa_icon('envelope')
@@ -46,13 +51,15 @@
       %span.detailed-status__link<
         = fa_icon('lock')
     - else
-      %span.detailed-status__link<
+      = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do
         = fa_icon('retweet')
-        %span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true
+        %span.detailed-status__reblogs>= number_to_human status.reblogs_count, strip_insignificant_zeros: true
+        = " "
     ·
-    %span.detailed-status__link<
+    = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do
       = fa_icon('star')
-      %span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true
+      %span.detailed-status__favorites>= number_to_human status.favourites_count, strip_insignificant_zeros: true
+      = " "
 
     - if user_signed_in?
       ·
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index bdbb6f387..1d61684ab 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -30,14 +30,16 @@
       = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
 
   .status__action-bar
-    .status__action-bar-button.static-icon-button<
+    .status__action-bar__counter
+      = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
+        = fa_icon 'reply fw'
+      .status__action-bar__counter__label= obscured_counter status.replies_count
+    = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
       - if status.public_visibility? || status.unlisted_visibility?
         = fa_icon 'retweet fw'
-        %span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true
       - elsif status.private_visibility?
         = fa_icon 'lock fw'
       - else
         = fa_icon 'envelope fw'
-    .status__action-bar-button.static-icon-button<
+    = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
       = fa_icon 'star fw'
-      %span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true
diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml
index 320c9bc4f..92003a48f 100644
--- a/app/views/stream_entries/_status.html.haml
+++ b/app/views/stream_entries/_status.html.haml
@@ -53,3 +53,9 @@
   - if @next_descendant_thread
     .entry{ class: entry_classes }
       = link_to_more short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1)
+
+- if include_threads && !embedded_view? && !user_signed_in?
+  .entry{ class: entry_classes }
+    = link_to new_user_session_path, class: 'load-more load-gap' do
+      = fa_icon 'comments'
+      = t('statuses.sign_in_to_participate')
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
index 9da6245dc..2edc155bf 100644
--- a/app/views/stream_entries/show.html.haml
+++ b/app/views/stream_entries/show.html.haml
@@ -19,7 +19,7 @@
 
 .grid
   .column-0
-    .activity-stream.activity-stream-headless.h-entry
+    .activity-stream.h-entry
       = render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }
   .column-1
     = render 'application/sidebar'
diff --git a/app/views/well_known/webfinger/show.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby
index 4352a24e9..968c8c138 100644
--- a/app/views/well_known/webfinger/show.xml.ruby
+++ b/app/views/well_known/webfinger/show.xml.ruby
@@ -37,7 +37,7 @@ doc << Ox::Element.new('XRD').tap do |xrd|
 
   xrd << Ox::Element.new('Link').tap do |link|
     link['rel']      = 'http://ostatus.org/schema/1.0/subscribe'
-    link['template'] = "#{authorize_follow_url}?acct={uri}"
+    link['template'] = "#{authorize_interaction_url}?acct={uri}"
   end
 end