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.rb7
-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/search_controller.rb2
-rw-r--r--app/controllers/api/v1/timelines/direct_controller.rb60
-rw-r--r--app/controllers/application_controller.rb84
-rw-r--r--app/controllers/auth/confirmations_controller.rb7
-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.rb5
-rw-r--r--app/controllers/authorize_follows_controller.rb5
-rw-r--r--app/controllers/follower_accounts_controller.rb4
-rw-r--r--app/controllers/following_accounts_controller.rb4
-rw-r--r--app/controllers/home_controller.rb5
-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/remote_follow_controller.rb5
-rw-r--r--app/controllers/settings/applications_controller.rb4
-rw-r--r--app/controllers/settings/base_controller.rb12
-rw-r--r--app/controllers/settings/deletes_controller.rb6
-rw-r--r--app/controllers/settings/exports_controller.rb6
-rw-r--r--app/controllers/settings/flavours_controller.rb32
-rw-r--r--app/controllers/settings/follower_domains_controller.rb6
-rw-r--r--app/controllers/settings/imports_controller.rb5
-rw-r--r--app/controllers/settings/keyword_mutes_controller.rb61
-rw-r--r--app/controllers/settings/notifications_controller.rb6
-rw-r--r--app/controllers/settings/preferences_controller.rb8
-rw-r--r--app/controllers/settings/profiles_controller.rb5
-rw-r--r--app/controllers/settings/sessions_controller.rb1
-rw-r--r--app/controllers/settings/two_factor_authentication/confirmations_controller.rb5
-rw-r--r--app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb6
-rw-r--r--app/controllers/settings/two_factor_authentications_controller.rb5
-rw-r--r--app/controllers/shares_controller.rb5
-rw-r--r--app/controllers/statuses_controller.rb2
-rw-r--r--app/controllers/stream_entries_controller.rb3
-rw-r--r--app/controllers/tags_controller.rb1
39 files changed, 374 insertions, 66 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 4ffdfb685..7b46b2228 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
   before_action :set_body_classes
   before_action :set_instance_presenter, only: [:show, :more, :terms]
 
@@ -21,6 +22,10 @@ class AboutController < ApplicationController
 
   helper_method :new_user
 
+  def set_pack
+    use_pack action_name == 'show' ? 'about' : 'common'
+  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 69fd20e27..c9725ed00 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -8,6 +8,7 @@ class AccountsController < ApplicationController
   def show
     respond_to do |format|
       format.html do
+        use_pack 'public'
         @pinned_statuses = []
 
         if current_account && @account.blocking?(current_account)
@@ -50,7 +51,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 7fb69d578..fc299f74c 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -5,8 +5,13 @@ module Admin
     include Authorization
     include AccountableConcern
 
+    layout 'admin'
+
     before_action :require_staff!
+    before_action :set_pack
 
-    layout 'admin'
+    def set_pack
+      use_pack 'admin'
+    end
   end
 end
diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb
index 0c43cb943..92ad251ef 100644
--- a/app/controllers/api/v1/mutes_controller.rb
+++ b/app/controllers/api/v1/mutes_controller.rb
@@ -8,10 +8,15 @@ 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
@@ -22,6 +27,10 @@ class Api::V1::MutesController < Api::BaseController
     Account.includes(:muted_by).references(:muted_by)
   end
 
+  def load_mutes
+    paginated_mutes.includes(:account, :target_account).to_a
+  end
+
   def paginated_mutes
     Mute.where(account: current_account).paginate_by_max_id(
       limit_param(DEFAULT_ACCOUNTS_LIMIT),
@@ -36,26 +45,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 @accounts.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
-    @accounts.last.muted_by_ids.last
+    if params[:action] == "details"
+      @mutes.last.id
+    else
+      @accounts.last.muted_by_ids.last
+    end
   end
 
   def pagination_since_id
-    @accounts.first.muted_by_ids.first
+    if params[:action] == "details"
+      @mutes.first.id
+    else
+      @accounts.first.muted_by_ids.first
+    end
   end
 
   def records_continue?
-    @accounts.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 8910b77e9..a949752fb 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -24,11 +24,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/search_controller.rb b/app/controllers/api/v1/search_controller.rb
index 997eed6e2..d1b4e0402 100644
--- a/app/controllers/api/v1/search_controller.rb
+++ b/app/controllers/api/v1/search_controller.rb
@@ -3,7 +3,7 @@
 class Api::V1::SearchController < Api::BaseController
   include Authorization
 
-  RESULTS_LIMIT = 5
+  RESULTS_LIMIT = 10
 
   before_action -> { doorkeeper_authorize! :read }
   before_action :require_user!
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..d455227eb
--- /dev/null
+++ b/app/controllers/api/v1/timelines/direct_controller.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+class Api::V1::Timelines::DirectController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read }, 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.paginate_by_max_id(
+      limit_param(DEFAULT_STATUSES_LIMIT),
+      params[:max_id],
+      params[:since_id]
+    )
+  end
+
+  def direct_timeline_statuses
+    Status.as_direct_timeline(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/application_controller.rb b/app/controllers/application_controller.rb
index b38a68467..7534b5375 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,7 +12,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_pam?
 
@@ -54,6 +55,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 forbidden
@@ -88,9 +158,14 @@ class ApplicationController < ActionController::Base
     @current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id'])
   end
 
-  def current_theme
-    return Setting.default_settings['theme'] unless Themes.instance.names.include? current_user&.setting_theme
-    current_user.setting_theme
+  def current_flavour
+    return Setting.default_settings['flavour'] unless Themes.instance.flavours.include? current_user&.setting_flavour
+    current_user.setting_flavour
+  end
+
+  def current_skin
+    return 'default' unless Themes.instance.skins_for(current_flavour).include? current_user&.setting_skin
+    current_user.setting_skin
   end
 
   def cache_collection(raw, klass)
@@ -122,6 +197,7 @@ class ApplicationController < ActionController::Base
       format.any  { head code }
       format.html do
         set_locale
+        use_pack 'error'
         render "errors/#{code}", layout: 'error', status: code
       end
     end
diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb
index a240425cd..f3e0ae257 100644
--- a/app/controllers/auth/confirmations_controller.rb
+++ b/app/controllers/auth/confirmations_controller.rb
@@ -4,6 +4,13 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
   layout 'auth'
 
   before_action :set_user, only: [:finish_signup]
+  before_action :set_pack
+
+  private
+
+  def set_pack
+    use_pack 'auth'
+  end
 
   # GET/PATCH /users/:id/finish_signup
   def finish_signup
diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb
index 171b997dc..e0400aa3d 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
 
   layout 'auth'
 
@@ -17,4 +18,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 417e2b63b..9b3ea4f27 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -5,6 +5,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 
   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]
 
@@ -64,6 +65,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 4fc41b378..ce9cf98d7 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 :check_suspension, only: [:destroy]
   prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
+  prepend_before_action :set_pack
   before_action :set_instance_presenter, only: [:new]
 
   def create
@@ -89,6 +90,10 @@ class Auth::SessionsController < Devise::SessionsController
 
   private
 
+  def set_pack
+    use_pack 'auth'
+  end
+
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
diff --git a/app/controllers/authorize_follows_controller.rb b/app/controllers/authorize_follows_controller.rb
index 775d5f23f..95052df7c 100644
--- a/app/controllers/authorize_follows_controller.rb
+++ b/app/controllers/authorize_follows_controller.rb
@@ -4,6 +4,7 @@ class AuthorizeFollowsController < ApplicationController
   layout 'modal'
 
   before_action :authenticate_user!
+  before_action :set_pack
   before_action :set_body_classes
 
   def show
@@ -24,6 +25,10 @@ class AuthorizeFollowsController < ApplicationController
 
   private
 
+  def set_pack
+    use_pack 'modal'
+  end
+
   def follow_attempt
     FollowService.new.call(current_account, acct_without_prefix)
   end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 399e79665..080cbde11 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -7,7 +7,9 @@ class FollowerAccountsController < ApplicationController
     @follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
 
     respond_to do |format|
-      format.html
+      format.html do
+        use_pack 'public'
+      end
 
       format.json do
         render json: collection_presenter,
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index 1e73d4bd4..74e83ad81 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -7,7 +7,9 @@ class FollowingAccountsController < ApplicationController
     @follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
 
     respond_to do |format|
-      format.html
+      format.html do
+        use_pack 'public'
+      end
 
       format.json do
         render json: collection_presenter,
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 21dde20ce..7437a647e 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -2,6 +2,7 @@
 
 class HomeController < ApplicationController
   before_action :authenticate_user!
+  before_action :set_pack
   before_action :set_initial_state_json
 
   def index
@@ -37,6 +38,10 @@ class HomeController < ApplicationController
     redirect_to(default_redirect_path)
   end
 
+  def set_pack
+    use_pack 'home'
+  end
+
   def set_initial_state_json
     serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
     @initial_state_json   = serializable_resource.to_json
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 38d6c8d73..189e4072e 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
 
   def index
     authorize :invite, :create?
@@ -37,6 +38,10 @@ class InvitesController < ApplicationController
 
   private
 
+  def set_pack
+    use_pack 'settings'
+  end
+
   def resource_params
     params.require(:invite).permit(:max_uses, :expires_in)
   end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index e9cdf9fa8..eb977510b 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
 
@@ -13,4 +14,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
   def store_current_location
     store_location_for(:user, request.url)
   end
+
+  def set_pack
+    use_pack 'auth'
+  end
 end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index 395fbc51b..f95d672ec 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
 
   include Localized
 
@@ -13,4 +14,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/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb
index 3b988e08d..41c021781 100644
--- a/app/controllers/remote_follow_controller.rb
+++ b/app/controllers/remote_follow_controller.rb
@@ -4,6 +4,7 @@ class RemoteFollowController < ApplicationController
   layout 'modal'
 
   before_action :set_account
+  before_action :set_pack
   before_action :gone, if: :suspended_account?
 
   def new
@@ -31,6 +32,10 @@ class RemoteFollowController < ApplicationController
     { acct: session[:remote_follow] }
   end
 
+  def set_pack
+    use_pack 'modal'
+  end
+
   def set_account
     @account = Account.find_local!(params[:account_username])
   end
diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb
index 8fc9a0fa9..35a6f7f9e 100644
--- a/app/controllers/settings/applications_controller.rb
+++ b/app/controllers/settings/applications_controller.rb
@@ -1,9 +1,7 @@
 # frozen_string_literal: true
 
-class Settings::ApplicationsController < ApplicationController
-  layout 'admin'
+class Settings::ApplicationsController < Settings::BaseController
 
-  before_action :authenticate_user!
   before_action :set_application, only: [:show, :update, :destroy, :regenerate]
   before_action :prepare_scopes, only: [:create, :update]
 
diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb
new file mode 100644
index 000000000..7322d461b
--- /dev/null
+++ b/app/controllers/settings/base_controller.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class Settings::BaseController < ApplicationController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_pack
+
+  def set_pack
+    use_pack 'settings'
+  end
+end
diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb
index 80002b995..4c1121471 100644
--- a/app/controllers/settings/deletes_controller.rb
+++ b/app/controllers/settings/deletes_controller.rb
@@ -1,10 +1,8 @@
 # frozen_string_literal: true
 
-class Settings::DeletesController < ApplicationController
-  layout 'admin'
+class Settings::DeletesController < Settings::BaseController
 
-  before_action :check_enabled_deletion
-  before_action :authenticate_user!
+  prepend_before_action :check_enabled_deletion
 
   def show
     @confirmation = Form::DeleteConfirmation.new
diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index ae62f00c1..9c03ece86 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -1,10 +1,6 @@
 # frozen_string_literal: true
 
-class Settings::ExportsController < ApplicationController
-  layout 'admin'
-
-  before_action :authenticate_user!
-
+class Settings::ExportsController < Settings::BaseController
   def show
     @export = Export.new(current_account)
   end
diff --git a/app/controllers/settings/flavours_controller.rb b/app/controllers/settings/flavours_controller.rb
new file mode 100644
index 000000000..634387715
--- /dev/null
+++ b/app/controllers/settings/flavours_controller.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class Settings::FlavoursController < Settings::BaseController
+  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/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb
index 9968504e5..141b2270d 100644
--- a/app/controllers/settings/follower_domains_controller.rb
+++ b/app/controllers/settings/follower_domains_controller.rb
@@ -2,11 +2,7 @@
 
 require 'sidekiq-bulk'
 
-class Settings::FollowerDomainsController < ApplicationController
-  layout 'admin'
-
-  before_action :authenticate_user!
-
+class Settings::FollowerDomainsController < Settings::BaseController
   def show
     @account = current_account
     @domains = current_account.followers.reorder('MIN(follows.id) DESC').group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10)
diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb
index 0db13d1ca..dbd136ebe 100644
--- a/app/controllers/settings/imports_controller.rb
+++ b/app/controllers/settings/imports_controller.rb
@@ -1,9 +1,6 @@
 # frozen_string_literal: true
 
-class Settings::ImportsController < ApplicationController
-  layout 'admin'
-
-  before_action :authenticate_user!
+class Settings::ImportsController < Settings::BaseController
   before_action :set_account
 
   def show
diff --git a/app/controllers/settings/keyword_mutes_controller.rb b/app/controllers/settings/keyword_mutes_controller.rb
new file mode 100644
index 000000000..699b8a3ef
--- /dev/null
+++ b/app/controllers/settings/keyword_mutes_controller.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+class Settings::KeywordMutesController < Settings::BaseController
+  before_action :load_keyword_mute, only: [:edit, :update, :destroy]
+
+  def index
+    @keyword_mutes = paginated_keyword_mutes_for_account
+  end
+
+  def new
+    @keyword_mute = keyword_mutes_for_account.build
+  end
+
+  def create
+    @keyword_mute = keyword_mutes_for_account.create(keyword_mute_params)
+
+    if @keyword_mute.persisted?
+      redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
+    else
+      render :new
+    end
+  end
+
+  def update
+    if @keyword_mute.update(keyword_mute_params)
+      redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
+    else
+      render :edit
+    end
+  end
+
+  def destroy
+    @keyword_mute.destroy!
+
+    redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
+  end
+
+  def destroy_all
+    keyword_mutes_for_account.delete_all
+
+    redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
+  end
+
+  private
+
+  def keyword_mutes_for_account
+    Glitch::KeywordMute.where(account: current_account)
+  end
+
+  def load_keyword_mute
+    @keyword_mute = keyword_mutes_for_account.find(params[:id])
+  end
+
+  def keyword_mute_params
+    params.require(:keyword_mute).permit(:keyword, :whole_word)
+  end
+
+  def paginated_keyword_mutes_for_account
+    keyword_mutes_for_account.order(:keyword).page params[:page]
+  end
+end
diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb
index ce2530c54..6286e3ebf 100644
--- a/app/controllers/settings/notifications_controller.rb
+++ b/app/controllers/settings/notifications_controller.rb
@@ -1,10 +1,6 @@
 # frozen_string_literal: true
 
-class Settings::NotificationsController < ApplicationController
-  layout 'admin'
-
-  before_action :authenticate_user!
-
+class Settings::NotificationsController < Settings::BaseController
   def show; end
 
   def update
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index 839763138..c853b5ab7 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -1,10 +1,6 @@
 # frozen_string_literal: true
 
-class Settings::PreferencesController < ApplicationController
-  layout 'admin'
-
-  before_action :authenticate_user!
-
+class Settings::PreferencesController < Settings::BaseController
   def show; end
 
   def update
@@ -37,13 +33,13 @@ class Settings::PreferencesController < ApplicationController
       :setting_default_sensitive,
       :setting_unfollow_modal,
       :setting_boost_modal,
+      :setting_favourite_modal,
       :setting_delete_modal,
       :setting_auto_play_gif,
       :setting_display_sensitive_media,
       :setting_reduce_motion,
       :setting_system_font_ui,
       :setting_noindex,
-      :setting_theme,
       notification_emails: %i(follow follow_request reblog favourite mention digest),
       interactions: %i(must_be_follower must_be_following)
     )
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index 28f78a4fb..dadc3d911 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -1,11 +1,8 @@
 # frozen_string_literal: true
 
-class Settings::ProfilesController < ApplicationController
+class Settings::ProfilesController < Settings::BaseController
   include ObfuscateFilename
 
-  layout 'admin'
-
-  before_action :authenticate_user!
   before_action :set_account
 
   obfuscate_filename [:account, :avatar]
diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb
index 0da1b027b..780ea64b4 100644
--- a/app/controllers/settings/sessions_controller.rb
+++ b/app/controllers/settings/sessions_controller.rb
@@ -1,5 +1,6 @@
 # frozen_string_literal: true
 
+#  Intentionally does not inherit from BaseController
 class Settings::SessionsController < ApplicationController
   before_action :set_session, only: :destroy
 
diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
index 8d534960d..8518c61ee 100644
--- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
@@ -2,10 +2,7 @@
 
 module Settings
   module TwoFactorAuthentication
-    class ConfirmationsController < ApplicationController
-      layout 'admin'
-
-      before_action :authenticate_user!
+    class ConfirmationsController < BaseController
       before_action :ensure_otp_secret
 
       def new
diff --git a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
index e591e9502..94d1567f3 100644
--- a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
@@ -2,11 +2,7 @@
 
 module Settings
   module TwoFactorAuthentication
-    class RecoveryCodesController < ApplicationController
-      layout 'admin'
-
-      before_action :authenticate_user!
-
+    class RecoveryCodesController < BaseController
       def create
         @recovery_codes = current_user.generate_otp_backup_codes!
         current_user.save!
diff --git a/app/controllers/settings/two_factor_authentications_controller.rb b/app/controllers/settings/two_factor_authentications_controller.rb
index 863cc7351..8c7737e9d 100644
--- a/app/controllers/settings/two_factor_authentications_controller.rb
+++ b/app/controllers/settings/two_factor_authentications_controller.rb
@@ -1,10 +1,7 @@
 # frozen_string_literal: true
 
 module Settings
-  class TwoFactorAuthenticationsController < ApplicationController
-    layout 'admin'
-
-    before_action :authenticate_user!
+  class TwoFactorAuthenticationsController < BaseController
     before_action :verify_otp_required, only: [:create]
 
     def show
diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb
index 3ec831a72..3cbaccb35 100644
--- a/app/controllers/shares_controller.rb
+++ b/app/controllers/shares_controller.rb
@@ -4,6 +4,7 @@ class SharesController < ApplicationController
   layout 'modal'
 
   before_action :authenticate_user!
+  before_action :set_pack
   before_action :set_body_classes
 
   def show
@@ -25,6 +26,10 @@ class SharesController < ApplicationController
     }
   end
 
+  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 45226c8d2..61ffb97d9 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -16,6 +16,7 @@ class StatusesController < ApplicationController
   def show
     respond_to do |format|
       format.html do
+        use_pack 'public'
         @ancestors   = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : []
         @descendants = cache_collection(@status.descendants(current_account), Status)
 
@@ -41,6 +42,7 @@ class StatusesController < ApplicationController
   end
 
   def embed
+    use_pack 'embed'
     response.headers['X-Frame-Options'] = 'ALLOWALL'
     render 'stream_entries/embed', layout: 'embedded'
   end
diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb
index f81856cc6..e2ea45c83 100644
--- a/app/controllers/stream_entries_controller.rb
+++ b/app/controllers/stream_entries_controller.rb
@@ -15,6 +15,7 @@ class StreamEntriesController < ApplicationController
   def show
     respond_to do |format|
       format.html do
+        use_pack 'public'
         @ancestors   = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : []
         @descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status)
       end
@@ -53,7 +54,7 @@ class StreamEntriesController < ApplicationController
     @type         = @stream_entry.activity_type.downcase
 
     raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil?
-    authorize @stream_entry.activity, :show? if @stream_entry.hidden?
+    authorize @stream_entry.activity, :show? if @stream_entry.hidden? || @stream_entry.local_only?
   rescue Mastodon::NotPermittedError
     # Reraise in order to get a 404
     raise ActiveRecord::RecordNotFound
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 9f3090e37..5d11a8139 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -9,6 +9,7 @@ class TagsController < ApplicationController
 
     respond_to do |format|
       format.html do
+        use_pack 'about'
         serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
         @initial_state_json   = serializable_resource.to_json
       end