about summary refs log tree commit diff
path: root/app/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/about_controller.rb5
-rw-r--r--app/controllers/accounts_controller.rb3
-rw-r--r--app/controllers/admin/base_controller.rb5
-rw-r--r--app/controllers/admin/dashboard_controller.rb1
-rw-r--r--app/controllers/api/v1/bookmarks_controller.rb71
-rw-r--r--app/controllers/api/v1/lists_controller.rb2
-rw-r--r--app/controllers/api/v1/mutes_controller.rb31
-rw-r--r--app/controllers/api/v1/notifications_controller.rb9
-rw-r--r--app/controllers/api/v1/statuses/bookmarks_controller.rb39
-rw-r--r--app/controllers/api/v1/statuses_controller.rb2
-rw-r--r--app/controllers/api/v1/timelines/direct_controller.rb65
-rw-r--r--app/controllers/api/v2/search_controller.rb2
-rw-r--r--app/controllers/application_controller.rb89
-rw-r--r--app/controllers/auth/challenges_controller.rb7
-rw-r--r--app/controllers/auth/confirmations_controller.rb5
-rw-r--r--app/controllers/auth/passwords_controller.rb5
-rw-r--r--app/controllers/auth/registrations_controller.rb5
-rw-r--r--app/controllers/auth/sessions_controller.rb6
-rw-r--r--app/controllers/auth/setup_controller.rb5
-rw-r--r--app/controllers/authorize_interactions_controller.rb5
-rw-r--r--app/controllers/directories_controller.rb5
-rw-r--r--app/controllers/filters_controller.rb5
-rw-r--r--app/controllers/follower_accounts_controller.rb13
-rw-r--r--app/controllers/following_accounts_controller.rb1
-rw-r--r--app/controllers/home_controller.rb6
-rw-r--r--app/controllers/invites_controller.rb5
-rw-r--r--app/controllers/oauth/authorizations_controller.rb5
-rw-r--r--app/controllers/oauth/authorized_applications_controller.rb5
-rw-r--r--app/controllers/public_timelines_controller.rb5
-rw-r--r--app/controllers/relationships_controller.rb5
-rw-r--r--app/controllers/remote_follow_controller.rb5
-rw-r--r--app/controllers/remote_interaction_controller.rb5
-rw-r--r--app/controllers/settings/base_controller.rb5
-rw-r--r--app/controllers/settings/flavours_controller.rb38
-rw-r--r--app/controllers/settings/identity_proofs_controller.rb5
-rw-r--r--app/controllers/settings/preferences_controller.rb5
-rw-r--r--app/controllers/settings/sessions_controller.rb3
-rw-r--r--app/controllers/shares_controller.rb5
-rw-r--r--app/controllers/statuses_controller.rb3
-rw-r--r--app/controllers/tags_controller.rb1
-rw-r--r--app/controllers/well_known/keybase_proof_config_controller.rb8
41 files changed, 477 insertions, 23 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index abd1ec0cb..5d5db937c 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 class AboutController < ApplicationController
+  before_action :set_pack
   layout 'public'
 
   before_action :require_open_federation!, only: [:show, :more]
@@ -50,6 +51,10 @@ class AboutController < ApplicationController
     end
   end
 
+  def set_pack
+    use_pack 'public'
+  end
+
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 0a8015a56..c4ee22847 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -15,6 +15,7 @@ class AccountsController < ApplicationController
   def show
     respond_to do |format|
       format.html do
+        use_pack 'public'
         expires_in 0, public: true unless user_signed_in?
 
         @pinned_statuses   = []
@@ -71,7 +72,7 @@ class AccountsController < ApplicationController
   end
 
   def default_statuses
-    @account.statuses.where(visibility: [:public, :unlisted])
+    @account.statuses.not_local_only.where(visibility: [:public, :unlisted])
   end
 
   def only_media_scope
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
index 7b81a2b01..cc6cd51f0 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -8,6 +8,7 @@ module Admin
     layout 'admin'
 
     before_action :require_staff!
+    before_action :set_pack
     before_action :set_body_classes
 
     private
@@ -16,6 +17,10 @@ module Admin
       @body_classes = 'admin'
     end
 
+    def set_pack
+      use_pack 'admin'
+    end
+
     def set_user
       @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
     end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index 7c2951acb..4116f99f4 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -34,6 +34,7 @@ module Admin
       @whitelist_enabled     = whitelist_mode?
       @profile_directory     = Setting.profile_directory
       @timeline_preview      = Setting.timeline_preview
+      @keybase_integration   = Setting.enable_keybase
       @spam_check_enabled    = Setting.spam_check_enabled
       @trends_enabled        = Setting.trends
     end
diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb
new file mode 100644
index 000000000..1cab3c372
--- /dev/null
+++ b/app/controllers/api/v1/bookmarks_controller.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class Api::V1::BookmarksController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:bookmarks' }
+  before_action :require_user!
+  after_action :insert_pagination_headers
+
+  respond_to :json
+
+  def index
+    @statuses = load_statuses
+    render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
+  end
+
+  private
+
+  def load_statuses
+    cached_bookmarks
+  end
+
+  def cached_bookmarks
+    cache_collection(
+      Status.reorder(nil).joins(:bookmarks).merge(results),
+      Status
+    )
+  end
+
+  def results
+    @_results ||= account_bookmarks.paginate_by_max_id(
+      limit_param(DEFAULT_STATUSES_LIMIT),
+      params[:max_id],
+      params[:since_id]
+    )
+  end
+
+  def account_bookmarks
+    current_account.bookmarks
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    if records_continue?
+      api_v1_bookmarks_url pagination_params(max_id: pagination_max_id)
+    end
+  end
+
+  def prev_path
+    unless results.empty?
+      api_v1_bookmarks_url pagination_params(since_id: pagination_since_id)
+    end
+  end
+
+  def pagination_max_id
+    results.last.id
+  end
+
+  def pagination_since_id
+    results.first.id
+  end
+
+  def records_continue?
+    results.size == limit_param(DEFAULT_STATUSES_LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+end
diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb
index 054172bee..e5ac45fef 100644
--- a/app/controllers/api/v1/lists_controller.rb
+++ b/app/controllers/api/v1/lists_controller.rb
@@ -38,6 +38,6 @@ class Api::V1::ListsController < Api::BaseController
   end
 
   def list_params
-    params.permit(:title)
+    params.permit(:title, :replies_policy)
   end
 end
diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb
index df6c8e86c..3b3a39943 100644
--- a/app/controllers/api/v1/mutes_controller.rb
+++ b/app/controllers/api/v1/mutes_controller.rb
@@ -8,16 +8,25 @@ class Api::V1::MutesController < Api::BaseController
   respond_to :json
 
   def index
-    @accounts = load_accounts
+    @data = @accounts = load_accounts
     render json: @accounts, each_serializer: REST::AccountSerializer
   end
 
+  def details
+    @data = @mutes = load_mutes
+    render json: @mutes, each_serializer: REST::MuteSerializer
+  end 
+
   private
 
   def load_accounts
     paginated_mutes.map(&:target_account)
   end
 
+  def load_mutes
+    paginated_mutes.includes(:account, :target_account).to_a
+  end
+
   def paginated_mutes
     @paginated_mutes ||= Mute.eager_load(:target_account)
                              .where(account: current_account)
@@ -34,26 +43,34 @@ class Api::V1::MutesController < Api::BaseController
 
   def next_path
     if records_continue?
-      api_v1_mutes_url pagination_params(max_id: pagination_max_id)
+      url_for pagination_params(max_id: pagination_max_id)
     end
   end
 
   def prev_path
-    unless paginated_mutes.empty?
-      api_v1_mutes_url pagination_params(since_id: pagination_since_id)
+    unless @data.empty?
+      url_for pagination_params(since_id: pagination_since_id)
     end
   end
 
   def pagination_max_id
-    paginated_mutes.last.id
+    if params[:action] == "details"
+      @mutes.last.id
+    else
+      paginated_mutes.last.id
+    end
   end
 
   def pagination_since_id
-    paginated_mutes.first.id
+    if params[:action] == "details"
+      @mutes.first.id
+    else
+      paginated_mutes.first.id
+    end
   end
 
   def records_continue?
-    paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+    @data.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
   end
 
   def pagination_params(core_params)
diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb
index bf3002e79..c91753ae7 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -25,11 +25,20 @@ class Api::V1::NotificationsController < Api::BaseController
     render_empty
   end
 
+  def destroy
+    dismiss
+  end
+
   def dismiss
     current_account.notifications.find_by!(id: params[:id]).destroy!
     render_empty
   end
 
+  def destroy_multiple
+    current_account.notifications.where(id: params[:ids]).destroy_all
+    render_empty
+  end
+
   private
 
   def load_notifications
diff --git a/app/controllers/api/v1/statuses/bookmarks_controller.rb b/app/controllers/api/v1/statuses/bookmarks_controller.rb
new file mode 100644
index 000000000..bb9729cf5
--- /dev/null
+++ b/app/controllers/api/v1/statuses/bookmarks_controller.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class Api::V1::Statuses::BookmarksController < Api::BaseController
+  include Authorization
+
+  before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
+  before_action :require_user!
+
+  respond_to :json
+
+  def create
+    @status = bookmarked_status
+    render json: @status, serializer: REST::StatusSerializer
+  end
+
+  def destroy
+    @status = requested_status
+    @bookmarks_map = { @status.id => false }
+
+    bookmark = Bookmark.find_by!(account: current_user.account, status: @status)
+    bookmark.destroy!
+
+    render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, bookmarks_map: @bookmarks_map)
+  end
+
+  private
+
+  def bookmarked_status
+    authorize_with current_user.account, requested_status, :show?
+
+    bookmark = Bookmark.find_or_create_by!(account: current_user.account, status: requested_status)
+
+    bookmark.status.reload
+  end
+
+  def requested_status
+    Status.find(params[:status_id])
+  end
+end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index bba3c0651..486004f9c 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -44,6 +44,7 @@ class Api::V1::StatusesController < Api::BaseController
                                          scheduled_at: status_params[:scheduled_at],
                                          application: doorkeeper_token.application,
                                          poll: status_params[:poll],
+                                         content_type: status_params[:content_type],
                                          idempotency: request.headers['Idempotency-Key'])
 
     render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
@@ -76,6 +77,7 @@ class Api::V1::StatusesController < Api::BaseController
       :spoiler_text,
       :visibility,
       :scheduled_at,
+      :content_type,
       media_ids: [],
       poll: [
         :multiple,
diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb
new file mode 100644
index 000000000..6e98e9cac
--- /dev/null
+++ b/app/controllers/api/v1/timelines/direct_controller.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+class Api::V1::Timelines::DirectController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
+  before_action :require_user!, only: [:show]
+  after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
+
+  respond_to :json
+
+  def show
+    @statuses = load_statuses
+    render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
+  end
+
+  private
+
+  def load_statuses
+    cached_direct_statuses
+  end
+
+  def cached_direct_statuses
+    cache_collection direct_statuses, Status
+  end
+
+  def direct_statuses
+    direct_timeline_statuses
+  end
+
+  def direct_timeline_statuses
+    account_direct_feed.get(
+      limit_param(DEFAULT_STATUSES_LIMIT),
+      params[:max_id],
+      params[:since_id],
+      params[:min_id]
+    )
+  end
+
+  def account_direct_feed
+    DirectFeed.new(current_account)
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def pagination_params(core_params)
+    params.permit(:local, :limit).merge(core_params)
+  end
+
+  def next_path
+    api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id)
+  end
+
+  def prev_path
+    api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id)
+  end
+
+  def pagination_max_id
+    @statuses.last.id
+  end
+
+  def pagination_since_id
+    @statuses.first.id
+  end
+end
diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb
index cbd9b551d..76decdb25 100644
--- a/app/controllers/api/v2/search_controller.rb
+++ b/app/controllers/api/v2/search_controller.rb
@@ -3,7 +3,7 @@
 class Api::V2::SearchController < Api::BaseController
   include Authorization
 
-  RESULTS_LIMIT = 20
+  RESULTS_LIMIT = (ENV['MAX_SEARCH_RESULTS'] || 20).to_i
 
   before_action -> { doorkeeper_authorize! :read, :'read:search' }
   before_action :require_user!
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index bd3d13774..92339ce2f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -15,7 +15,8 @@ class ApplicationController < ActionController::Base
 
   helper_method :current_account
   helper_method :current_session
-  helper_method :current_theme
+  helper_method :current_flavour
+  helper_method :current_skin
   helper_method :single_user_mode?
   helper_method :use_seamless_external_login?
   helper_method :whitelist_mode?
@@ -72,6 +73,75 @@ class ApplicationController < ActionController::Base
     new_user_session_path
   end
 
+  def pack(data, pack_name, skin = 'default')
+    return nil unless pack?(data, pack_name)
+    pack_data = {
+      common: pack_name == 'common' ? nil : resolve_pack(data['name'] ? Themes.instance.flavour(current_flavour) : Themes.instance.core, 'common', skin),
+      flavour: data['name'],
+      pack: pack_name,
+      preload: nil,
+      skin: nil,
+      supported_locales: data['locales'],
+    }
+    if data['pack'][pack_name].is_a?(Hash)
+      pack_data[:common] = nil if data['pack'][pack_name]['use_common'] == false
+      pack_data[:pack] = nil unless data['pack'][pack_name]['filename']
+      if data['pack'][pack_name]['preload']
+        pack_data[:preload] = [data['pack'][pack_name]['preload']] if data['pack'][pack_name]['preload'].is_a?(String)
+        pack_data[:preload] = data['pack'][pack_name]['preload'] if data['pack'][pack_name]['preload'].is_a?(Array)
+      end
+      if skin != 'default' && data['skin'][skin]
+        pack_data[:skin] = skin if data['skin'][skin].include?(pack_name)
+      else  #  default skin
+        pack_data[:skin] = 'default' if data['pack'][pack_name]['stylesheet']
+      end
+    end
+    pack_data
+  end
+
+  def pack?(data, pack_name)
+    if data['pack'].is_a?(Hash) && data['pack'].key?(pack_name)
+      return true if data['pack'][pack_name].is_a?(String) || data['pack'][pack_name].is_a?(Hash)
+    end
+    false
+  end
+
+  def nil_pack(data, pack_name, skin = 'default')
+    {
+      common: pack_name == 'common' ? nil : resolve_pack(data['name'] ? Themes.instance.flavour(current_flavour) : Themes.instance.core, 'common', skin),
+      flavour: data['name'],
+      pack: nil,
+      preload: nil,
+      skin: nil,
+      supported_locales: data['locales'],
+    }
+  end
+
+  def resolve_pack(data, pack_name, skin = 'default')
+    result = pack(data, pack_name, skin)
+    unless result
+      if data['name'] && data.key?('fallback')
+        if data['fallback'].nil?
+          return nil_pack(data, pack_name, skin)
+        elsif data['fallback'].is_a?(String) && Themes.instance.flavour(data['fallback'])
+          return resolve_pack(Themes.instance.flavour(data['fallback']), pack_name)
+        elsif data['fallback'].is_a?(Array)
+          data['fallback'].each do |fallback|
+            return resolve_pack(Themes.instance.flavour(fallback), pack_name) if Themes.instance.flavour(fallback)
+          end
+        end
+        return nil_pack(data, pack_name, skin)
+      end
+      return data.key?('name') && data['name'] != Setting.default_settings['flavour'] ? resolve_pack(Themes.instance.flavour(Setting.default_settings['flavour']), pack_name) : nil_pack(data, pack_name, skin)
+    end
+    result
+  end
+
+  def use_pack(pack_name)
+    @core = resolve_pack(Themes.instance.core, pack_name)
+    @theme = resolve_pack(Themes.instance.flavour(current_flavour), pack_name, current_skin)
+  end
+
   protected
 
   def truthy_param?(key)
@@ -130,15 +200,24 @@ class ApplicationController < ActionController::Base
     @current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
   end
 
-  def current_theme
-    return Setting.theme unless Themes.instance.names.include? current_user&.setting_theme
-    current_user.setting_theme
+  def current_flavour
+    return Setting.flavour unless Themes.instance.flavours.include? current_user&.setting_flavour
+    current_user.setting_flavour
+  end
+
+  def current_skin
+    return Setting.skin unless Themes.instance.skins_for(current_flavour).include? current_user&.setting_skin
+    current_user.setting_skin
   end
 
   def respond_with_error(code)
     respond_to do |format|
       format.any  { head code }
-      format.html { render "errors/#{code}", layout: 'error', status: code }
+
+      format.html do
+        use_pack 'error'
+        render "errors/#{code}", layout: 'error', status: code
+      end
     end
   end
 end
diff --git a/app/controllers/auth/challenges_controller.rb b/app/controllers/auth/challenges_controller.rb
index 060944240..41827b21c 100644
--- a/app/controllers/auth/challenges_controller.rb
+++ b/app/controllers/auth/challenges_controller.rb
@@ -5,6 +5,7 @@ class Auth::ChallengesController < ApplicationController
 
   layout 'auth'
 
+  before_action :set_pack
   before_action :authenticate_user!
 
   skip_before_action :require_functional!
@@ -19,4 +20,10 @@ class Auth::ChallengesController < ApplicationController
       render_challenge
     end
   end
+
+  private
+
+  def set_pack
+    use_pack 'auth'
+  end
 end
diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb
index 898525269..4e89446c7 100644
--- a/app/controllers/auth/confirmations_controller.rb
+++ b/app/controllers/auth/confirmations_controller.rb
@@ -4,6 +4,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
   layout 'auth'
 
   before_action :set_body_classes
+  before_action :set_pack
   before_action :require_unconfirmed!
 
   skip_before_action :require_functional!
@@ -16,6 +17,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
 
   private
 
+  def set_pack
+    use_pack 'auth'
+  end
+
   def require_unconfirmed!
     redirect_to edit_user_registration_path if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank?
   end
diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb
index 34b98da53..a59806f0d 100644
--- a/app/controllers/auth/passwords_controller.rb
+++ b/app/controllers/auth/passwords_controller.rb
@@ -2,6 +2,7 @@
 
 class Auth::PasswordsController < Devise::PasswordsController
   before_action :check_validity_of_reset_password_token, only: :edit
+  before_action :set_pack
   before_action :set_body_classes
 
   layout 'auth'
@@ -22,4 +23,8 @@ class Auth::PasswordsController < Devise::PasswordsController
   def reset_password_token_is_valid?
     resource_class.with_reset_password_token(params[:reset_password_token]).present?
   end
+
+  def set_pack
+    use_pack 'auth'
+  end
 end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 019caf9c1..068375843 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -6,6 +6,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   before_action :set_invite, only: [:new, :create]
   before_action :check_enabled_registrations, only: [:new, :create]
   before_action :configure_sign_up_params, only: [:create]
+  before_action :set_pack
   before_action :set_sessions, only: [:edit, :update]
   before_action :set_instance_presenter, only: [:new, :create, :update]
   before_action :set_body_classes, only: [:new, :create, :edit, :update]
@@ -85,6 +86,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 
   private
 
+  def set_pack
+    use_pack %w(edit update).include?(action_name) ? 'admin' : 'auth'
+  end
+
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index f48b17c79..eac9dde6f 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -8,6 +8,7 @@ class Auth::SessionsController < Devise::SessionsController
   skip_before_action :require_no_authentication, only: [:create]
   skip_before_action :require_functional!
 
+  prepend_before_action :set_pack
   prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
 
   before_action :set_instance_presenter, only: [:new]
@@ -107,12 +108,17 @@ class Auth::SessionsController < Devise::SessionsController
 
   def prompt_for_two_factor(user)
     session[:otp_user_id] = user.id
+    use_pack 'auth'
     @body_classes = 'lighter'
     render :two_factor
   end
 
   private
 
+  def set_pack
+    use_pack 'auth'
+  end
+
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb
index 46c5f2958..db5a866f2 100644
--- a/app/controllers/auth/setup_controller.rb
+++ b/app/controllers/auth/setup_controller.rb
@@ -3,6 +3,7 @@
 class Auth::SetupController < ApplicationController
   layout 'auth'
 
+  before_action :set_pack
   before_action :authenticate_user!
   before_action :require_unconfirmed_or_pending!
   before_action :set_body_classes
@@ -55,4 +56,8 @@ class Auth::SetupController < ApplicationController
   def missing_email?
     truthy_param?(:missing_email)
   end
+
+  def set_pack
+    use_pack 'auth'
+  end
 end
diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb
index e27366ea3..20b3fa94b 100644
--- a/app/controllers/authorize_interactions_controller.rb
+++ b/app/controllers/authorize_interactions_controller.rb
@@ -8,6 +8,7 @@ class AuthorizeInteractionsController < ApplicationController
   before_action :authenticate_user!
   before_action :set_body_classes
   before_action :set_resource
+  before_action :set_pack
 
   def show
     if @resource.is_a?(Account)
@@ -63,4 +64,8 @@ class AuthorizeInteractionsController < ApplicationController
   def set_body_classes
     @body_classes = 'modal-layout'
   end
+
+  def set_pack
+    use_pack 'modal'
+  end
 end
diff --git a/app/controllers/directories_controller.rb b/app/controllers/directories_controller.rb
index 750c835dd..adf2bd014 100644
--- a/app/controllers/directories_controller.rb
+++ b/app/controllers/directories_controller.rb
@@ -8,6 +8,7 @@ class DirectoriesController < ApplicationController
   before_action :set_instance_presenter
   before_action :set_tag, only: :show
   before_action :set_accounts
+  before_action :set_pack
 
   skip_before_action :require_functional!
 
@@ -21,6 +22,10 @@ class DirectoriesController < ApplicationController
 
   private
 
+  def set_pack
+    use_pack 'share'
+  end
+
   def require_enabled!
     return not_found unless Setting.profile_directory
   end
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index d2e0fb739..f1e110d87 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -7,6 +7,7 @@ class FiltersController < ApplicationController
 
   before_action :set_filters, only: :index
   before_action :set_filter, only: [:edit, :update, :destroy]
+  before_action :set_pack
   before_action :set_body_classes
 
   def index
@@ -44,6 +45,10 @@ class FiltersController < ApplicationController
 
   private
 
+  def set_pack
+    use_pack 'settings'
+  end
+
   def set_filters
     @filters = current_account.custom_filters
   end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 705ff4122..df46f5f72 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -13,6 +13,7 @@ class FollowerAccountsController < ApplicationController
   def index
     respond_to do |format|
       format.html do
+        use_pack 'public'
         expires_in 0, public: true unless user_signed_in?
 
         next if @account.user_hides_network?
@@ -49,22 +50,22 @@ class FollowerAccountsController < ApplicationController
   end
 
   def collection_presenter
+    options = { type: :ordered }
+    options[:size] = @account.followers_count unless Setting.hide_followers_count || @account.user&.setting_hide_followers_count
     if page_requested?
       ActivityPub::CollectionPresenter.new(
         id: account_followers_url(@account, page: params.fetch(:page, 1)),
-        type: :ordered,
-        size: @account.followers_count,
         items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
         part_of: account_followers_url(@account),
         next: page_url(follows.next_page),
-        prev: page_url(follows.prev_page)
+        prev: page_url(follows.prev_page),
+        **options
       )
     else
       ActivityPub::CollectionPresenter.new(
         id: account_followers_url(@account),
-        type: :ordered,
-        size: @account.followers_count,
-        first: page_url(1)
+        first: page_url(1),
+        **options
       )
     end
   end
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index 968de980d..8cab67ff5 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -13,6 +13,7 @@ class FollowingAccountsController < ApplicationController
   def index
     respond_to do |format|
       format.html do
+        use_pack 'public'
         expires_in 0, public: true unless user_signed_in?
 
         next if @account.user_hides_network?
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 7c8a18d17..efdb1d226 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -2,6 +2,8 @@
 
 class HomeController < ApplicationController
   before_action :authenticate_user!
+
+  before_action :set_pack
   before_action :set_referrer_policy_header
 
   def index
@@ -38,6 +40,10 @@ class HomeController < ApplicationController
     redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
   end
 
+  def set_pack
+    use_pack 'home'
+  end
+
   def default_redirect_path
     if request.path.start_with?('/web') || whitelist_mode?
       new_user_session_path
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 8d92147e2..0b3c082dc 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -6,6 +6,7 @@ class InvitesController < ApplicationController
   layout 'admin'
 
   before_action :authenticate_user!
+  before_action :set_pack
   before_action :set_body_classes
 
   def index
@@ -38,6 +39,10 @@ class InvitesController < ApplicationController
 
   private
 
+  def set_pack
+    use_pack 'settings'
+  end
+
   def invites
     current_user.invites.order(id: :desc)
   end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index cebbdc4d0..f6f5d1ecc 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -5,6 +5,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
 
   before_action :store_current_location
   before_action :authenticate_resource_owner!
+  before_action :set_pack
 
   include Localized
 
@@ -14,6 +15,10 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
     store_location_for(:user, request.url)
   end
 
+  def set_pack
+    use_pack 'auth'
+  end
+
   def render_success
     if skip_authorization? || (matching_token? && !truthy_param?('force_login'))
       redirect_or_render authorize_response
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index fb8389034..c5ccece13 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
 
   before_action :store_current_location
   before_action :authenticate_resource_owner!
+  before_action :set_pack
   before_action :set_body_classes
 
   skip_before_action :require_functional!
@@ -25,4 +26,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
   def store_current_location
     store_location_for(:user, request.url)
   end
+
+  def set_pack
+    use_pack 'settings'
+  end
 end
diff --git a/app/controllers/public_timelines_controller.rb b/app/controllers/public_timelines_controller.rb
index 1332ba16c..eb5bb191b 100644
--- a/app/controllers/public_timelines_controller.rb
+++ b/app/controllers/public_timelines_controller.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 class PublicTimelinesController < ApplicationController
+  before_action :set_pack
   layout 'public'
 
   before_action :authenticate_user!, if: :whitelist_mode?
@@ -23,4 +24,8 @@ class PublicTimelinesController < ApplicationController
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
+
+  def set_pack
+    use_pack 'about'
+  end
 end
diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb
index e6705c327..c87a0cf13 100644
--- a/app/controllers/relationships_controller.rb
+++ b/app/controllers/relationships_controller.rb
@@ -5,6 +5,7 @@ class RelationshipsController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_accounts, only: :show
+  before_action :set_pack
   before_action :set_body_classes
 
   helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
@@ -101,4 +102,8 @@ class RelationshipsController < ApplicationController
   def set_body_classes
     @body_classes = 'admin'
   end
+
+  def set_pack
+    use_pack 'admin'
+  end
 end
diff --git a/app/controllers/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb
index db1604644..93a0a7476 100644
--- a/app/controllers/remote_follow_controller.rb
+++ b/app/controllers/remote_follow_controller.rb
@@ -5,6 +5,7 @@ class RemoteFollowController < ApplicationController
 
   layout 'modal'
 
+  before_action :set_pack
   before_action :set_body_classes
 
   skip_before_action :require_functional!
@@ -34,6 +35,10 @@ class RemoteFollowController < ApplicationController
     { acct: session[:remote_follow] || current_account&.username }
   end
 
+  def set_pack
+    use_pack 'modal'
+  end
+
   def set_body_classes
     @body_classes = 'modal-layout'
     @hide_header  = true
diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb
index 4073e7ac3..e058d0ed5 100644
--- a/app/controllers/remote_interaction_controller.rb
+++ b/app/controllers/remote_interaction_controller.rb
@@ -9,6 +9,7 @@ class RemoteInteractionController < ApplicationController
   before_action :set_interaction_type
   before_action :set_status
   before_action :set_body_classes
+  before_action :set_pack
 
   skip_before_action :require_functional!
 
@@ -49,6 +50,10 @@ class RemoteInteractionController < ApplicationController
     @hide_header  = true
   end
 
+  def set_pack
+    use_pack 'modal'
+  end
+
   def set_interaction_type
     @interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply'
   end
diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb
index 9bb14afa2..8c394a6d3 100644
--- a/app/controllers/settings/base_controller.rb
+++ b/app/controllers/settings/base_controller.rb
@@ -1,10 +1,15 @@
 # frozen_string_literal: true
 
 class Settings::BaseController < ApplicationController
+  before_action :set_pack
   before_action :set_body_classes
 
   private
 
+  def set_pack
+    use_pack 'settings'
+  end
+
   def set_body_classes
     @body_classes = 'admin'
   end
diff --git a/app/controllers/settings/flavours_controller.rb b/app/controllers/settings/flavours_controller.rb
new file mode 100644
index 000000000..62c52eee9
--- /dev/null
+++ b/app/controllers/settings/flavours_controller.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class Settings::FlavoursController < Settings::BaseController
+  layout 'admin'
+
+  before_action :authenticate_user!
+
+  skip_before_action :require_functional!
+
+  def index
+    redirect_to action: 'show', flavour: current_flavour
+  end
+
+  def show
+    unless Themes.instance.flavours.include?(params[:flavour]) || (params[:flavour] == current_flavour)
+      redirect_to action: 'show', flavour: current_flavour
+    end
+
+    @listing = Themes.instance.flavours
+    @selected = params[:flavour]
+  end
+
+  def update
+    user_settings.update(user_settings_params)
+    redirect_to action: 'show', flavour: params[:flavour]
+  end
+
+  private
+
+  def user_settings
+    UserSettingsDecorator.new(current_user)
+  end
+
+  def user_settings_params
+    { setting_flavour: params.require(:flavour),
+      setting_skin: params.dig(:user, :setting_skin) }.with_indifferent_access
+  end
+end
diff --git a/app/controllers/settings/identity_proofs_controller.rb b/app/controllers/settings/identity_proofs_controller.rb
index a749d8020..e84c1aca6 100644
--- a/app/controllers/settings/identity_proofs_controller.rb
+++ b/app/controllers/settings/identity_proofs_controller.rb
@@ -5,6 +5,7 @@ class Settings::IdentityProofsController < Settings::BaseController
 
   before_action :authenticate_user!
   before_action :check_required_params, only: :new
+  before_action :check_enabled, only: :new
 
   def index
     @proofs = AccountIdentityProof.where(account: current_account).order(provider: :asc, provider_username: :asc)
@@ -41,6 +42,10 @@ class Settings::IdentityProofsController < Settings::BaseController
 
   private
 
+  def check_enabled
+    not_found unless Setting.enable_keybase
+  end
+
   def check_required_params
     redirect_to settings_identity_proofs_path unless [:provider, :provider_username, :username, :token].all? { |k| params[k].present? }
   end
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index bac9b329d..75c3e2495 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -42,18 +42,21 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_default_language,
       :setting_unfollow_modal,
       :setting_boost_modal,
+      :setting_favourite_modal,
       :setting_delete_modal,
       :setting_auto_play_gif,
       :setting_display_media,
       :setting_expand_spoilers,
       :setting_reduce_motion,
       :setting_system_font_ui,
+      :setting_system_emoji_font,
       :setting_noindex,
-      :setting_theme,
       :setting_hide_network,
+      :setting_hide_followers_count,
       :setting_aggregate_reblogs,
       :setting_show_application,
       :setting_advanced_layout,
+      :setting_default_content_type,
       :setting_use_blurhash,
       :setting_use_pending_items,
       :setting_trends,
diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb
index df5ace803..f8fb4036e 100644
--- a/app/controllers/settings/sessions_controller.rb
+++ b/app/controllers/settings/sessions_controller.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
-class Settings::SessionsController < Settings::BaseController
+#  Intentionally does not inherit from BaseController
+class Settings::SessionsController < ApplicationController
   before_action :authenticate_user!
   before_action :set_session, only: :destroy
 
diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb
index 6546b8497..e13e7e8b6 100644
--- a/app/controllers/shares_controller.rb
+++ b/app/controllers/shares_controller.rb
@@ -4,12 +4,17 @@ class SharesController < ApplicationController
   layout 'modal'
 
   before_action :authenticate_user!
+  before_action :set_pack
   before_action :set_body_classes
 
   def show; end
 
   private
 
+  def set_pack
+    use_pack 'share'
+  end
+
   def set_body_classes
     @body_classes = 'modal-layout compose-standalone'
   end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 57bbeca64..1b00d38c9 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -28,6 +28,8 @@ class StatusesController < ApplicationController
   def show
     respond_to do |format|
       format.html do
+        use_pack 'public'
+
         expires_in 10.seconds, public: true if current_account.nil?
         set_ancestors
         set_descendants
@@ -46,6 +48,7 @@ class StatusesController < ApplicationController
   end
 
   def embed
+    use_pack 'embed'
     raise ActiveRecord::RecordNotFound if @status.hidden?
 
     expires_in 180, public: true
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 77d5661b8..ef61c980f 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -18,6 +18,7 @@ class TagsController < ApplicationController
   def show
     respond_to do |format|
       format.html do
+        use_pack 'about'
         expires_in 0, public: true
       end
 
diff --git a/app/controllers/well_known/keybase_proof_config_controller.rb b/app/controllers/well_known/keybase_proof_config_controller.rb
index e1d43ecbe..03232df2d 100644
--- a/app/controllers/well_known/keybase_proof_config_controller.rb
+++ b/app/controllers/well_known/keybase_proof_config_controller.rb
@@ -2,8 +2,16 @@
 
 module WellKnown
   class KeybaseProofConfigController < ActionController::Base
+    before_action :check_enabled
+
     def show
       render json: {}, serializer: ProofProvider::Keybase::ConfigSerializer, root: 'keybase_config'
     end
+
+    private
+
+    def check_enabled
+      head 404 unless Setting.enable_keybase
+    end
   end
 end