about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2022-10-22 11:44:41 +0200
committerGitHub <noreply@github.com>2022-10-22 11:44:41 +0200
commit7c152acb2cc545a87610de349a94e14f45fbed5d (patch)
tree1f698c5ffb954b000cb0779de5a2bf25884779c0
parentabf6c87ee8b57e09dca5f5b1fe1839a314e1aa46 (diff)
Change settings area to be separated into categories in admin UI (#19407)
And update all descriptions
-rw-r--r--app/controllers/admin/settings/about_controller.rb9
-rw-r--r--app/controllers/admin/settings/appearance_controller.rb9
-rw-r--r--app/controllers/admin/settings/branding_controller.rb9
-rw-r--r--app/controllers/admin/settings/content_retention_controller.rb9
-rw-r--r--app/controllers/admin/settings/discovery_controller.rb9
-rw-r--r--app/controllers/admin/settings/registrations_controller.rb9
-rw-r--r--app/controllers/admin/settings_controller.rb10
-rw-r--r--app/controllers/admin/site_uploads_controller.rb2
-rw-r--r--app/helpers/admin/settings_helper.rb7
-rw-r--r--app/javascript/styles/mastodon/admin.scss82
-rw-r--r--app/javascript/styles/mastodon/components.scss5
-rw-r--r--app/javascript/styles/mastodon/forms.scss16
-rw-r--r--app/models/form/admin_settings.rb57
-rw-r--r--app/serializers/rest/extended_description_serializer.rb12
-rw-r--r--app/views/admin/settings/about/show.html.haml33
-rw-r--r--app/views/admin/settings/appearance/show.html.haml34
-rw-r--r--app/views/admin/settings/branding/show.html.haml39
-rw-r--r--app/views/admin/settings/content_retention/show.html.haml22
-rw-r--r--app/views/admin/settings/discovery/show.html.haml40
-rw-r--r--app/views/admin/settings/edit.html.haml102
-rw-r--r--app/views/admin/settings/registrations/show.html.haml27
-rw-r--r--app/views/admin/settings/shared/_links.html.haml8
-rw-r--r--app/views/layouts/admin.html.haml11
-rw-r--r--config/locales/en.yml87
-rw-r--r--config/locales/simple_form.en.yml37
-rw-r--r--config/navigation.rb2
-rw-r--r--config/routes.rb13
-rw-r--r--spec/controllers/admin/settings/branding_controller_spec.rb53
-rw-r--r--spec/controllers/admin/settings_controller_spec.rb71
29 files changed, 528 insertions, 296 deletions
diff --git a/app/controllers/admin/settings/about_controller.rb b/app/controllers/admin/settings/about_controller.rb
new file mode 100644
index 000000000..86327fe39
--- /dev/null
+++ b/app/controllers/admin/settings/about_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::AboutController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_about_path
+  end
+end
diff --git a/app/controllers/admin/settings/appearance_controller.rb b/app/controllers/admin/settings/appearance_controller.rb
new file mode 100644
index 000000000..39b2448d8
--- /dev/null
+++ b/app/controllers/admin/settings/appearance_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::AppearanceController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_appearance_path
+  end
+end
diff --git a/app/controllers/admin/settings/branding_controller.rb b/app/controllers/admin/settings/branding_controller.rb
new file mode 100644
index 000000000..4a4d76f49
--- /dev/null
+++ b/app/controllers/admin/settings/branding_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::BrandingController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_branding_path
+  end
+end
diff --git a/app/controllers/admin/settings/content_retention_controller.rb b/app/controllers/admin/settings/content_retention_controller.rb
new file mode 100644
index 000000000..b88336a2c
--- /dev/null
+++ b/app/controllers/admin/settings/content_retention_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::ContentRetentionController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_content_retention_path
+  end
+end
diff --git a/app/controllers/admin/settings/discovery_controller.rb b/app/controllers/admin/settings/discovery_controller.rb
new file mode 100644
index 000000000..be4b57f79
--- /dev/null
+++ b/app/controllers/admin/settings/discovery_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::DiscoveryController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_discovery_path
+  end
+end
diff --git a/app/controllers/admin/settings/registrations_controller.rb b/app/controllers/admin/settings/registrations_controller.rb
new file mode 100644
index 000000000..b4a74349c
--- /dev/null
+++ b/app/controllers/admin/settings/registrations_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::RegistrationsController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_registrations_path
+  end
+end
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index dc1c79b7f..338a3638c 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -2,7 +2,7 @@
 
 module Admin
   class SettingsController < BaseController
-    def edit
+    def show
       authorize :settings, :show?
 
       @admin_settings = Form::AdminSettings.new
@@ -15,14 +15,18 @@ module Admin
 
       if @admin_settings.save
         flash[:notice] = I18n.t('generic.changes_saved_msg')
-        redirect_to edit_admin_settings_path
+        redirect_to after_update_redirect_path
       else
-        render :edit
+        render :show
       end
     end
 
     private
 
+    def after_update_redirect_path
+      raise NotImplementedError
+    end
+
     def settings_params
       params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end
diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb
index cacecedb0..a5d2cf41c 100644
--- a/app/controllers/admin/site_uploads_controller.rb
+++ b/app/controllers/admin/site_uploads_controller.rb
@@ -9,7 +9,7 @@ module Admin
 
       @site_upload.destroy!
 
-      redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
+      redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
     end
 
     private
diff --git a/app/helpers/admin/settings_helper.rb b/app/helpers/admin/settings_helper.rb
index baf14ab25..a133b4e7d 100644
--- a/app/helpers/admin/settings_helper.rb
+++ b/app/helpers/admin/settings_helper.rb
@@ -1,11 +1,4 @@
 # frozen_string_literal: true
 
 module Admin::SettingsHelper
-  def site_upload_delete_hint(hint, var)
-    upload = SiteUpload.find_by(var: var.to_s)
-    return hint unless upload
-
-    link = link_to t('admin.site_uploads.delete'), admin_site_upload_path(upload), data: { method: :delete }
-    safe_join([hint, link], '<br/>'.html_safe)
-  end
 end
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 1c5494cde..affe1c79c 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -188,21 +188,70 @@ $content-width: 840px;
       padding-top: 30px;
     }
 
-    &-heading {
-      display: flex;
+    &__heading {
       padding-bottom: 36px;
       border-bottom: 1px solid lighten($ui-base-color, 8%);
-      margin: -15px -15px 40px 0;
-      flex-wrap: wrap;
-      align-items: center;
-      justify-content: space-between;
+      margin-bottom: 40px;
 
-      & > * {
-        margin-top: 15px;
-        margin-right: 15px;
+      &__row {
+        display: flex;
+        flex-wrap: wrap;
+        align-items: center;
+        justify-content: space-between;
+        margin: -15px -15px 0 0;
+
+        & > * {
+          margin-top: 15px;
+          margin-right: 15px;
+        }
       }
 
-      &-actions {
+      &__tabs {
+        margin-top: 30px;
+        margin-bottom: -31px;
+
+        & > div {
+          display: flex;
+          gap: 10px;
+        }
+
+        a {
+          font-size: 14px;
+          display: inline-flex;
+          align-items: center;
+          padding: 7px 15px;
+          border-radius: 4px;
+          color: $darker-text-color;
+          text-decoration: none;
+          position: relative;
+          font-weight: 500;
+          gap: 5px;
+          white-space: nowrap;
+
+          &.selected {
+            font-weight: 700;
+            color: $primary-text-color;
+
+            &::after {
+              content: "";
+              display: block;
+              width: 100%;
+              border-bottom: 1px solid $ui-highlight-color;
+              position: absolute;
+              bottom: -5px;
+              left: 0;
+            }
+          }
+
+          &:hover,
+          &:focus,
+          &:active {
+            background: lighten($ui-base-color, 4%);
+          }
+        }
+      }
+
+      &__actions {
         display: inline-flex;
 
         & > :not(:first-child) {
@@ -228,11 +277,7 @@ $content-width: 840px;
       color: $secondary-text-color;
       font-size: 24px;
       line-height: 36px;
-      font-weight: 400;
-
-      @media screen and (max-width: $no-columns-breakpoint) {
-        font-weight: 700;
-      }
+      font-weight: 700;
     }
 
     h3 {
@@ -437,6 +482,11 @@ body,
       }
     }
 
+    & > div {
+      display: flex;
+      gap: 5px;
+    }
+
     strong {
       font-weight: 500;
       text-transform: uppercase;
@@ -1143,7 +1193,7 @@ a.name-tag,
 
     path:first-child {
       fill: rgba($highlight-text-color, 0.25) !important;
-      fill-opacity: 1 !important;
+      fill-opacity: 100% !important;
     }
 
     path:last-child {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 587eba663..5d0ff8536 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -29,6 +29,11 @@
   background: transparent;
   padding: 0;
   cursor: pointer;
+  text-decoration: none;
+
+  &--destructive {
+    color: $error-value-color;
+  }
 
   &:hover,
   &:active {
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 69a0b22d6..25c9c9fe5 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -254,7 +254,7 @@ code {
 
     & > label {
       font-family: inherit;
-      font-size: 16px;
+      font-size: 14px;
       color: $primary-text-color;
       display: block;
       font-weight: 500;
@@ -291,6 +291,20 @@ code {
     .input:last-child {
       margin-bottom: 0;
     }
+
+    &__thumbnail {
+      display: block;
+      margin: 0;
+      margin-bottom: 10px;
+      max-width: 100%;
+      height: auto;
+      border-radius: 4px;
+      background: url("images/void.png");
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
   }
 
   .fields-row {
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index b6bb3d795..957a32b7c 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -8,7 +8,6 @@ class Form::AdminSettings
     site_contact_email
     site_title
     site_short_description
-    site_description
     site_extended_description
     site_terms
     registrations_mode
@@ -53,45 +52,55 @@ class Form::AdminSettings
 
   attr_accessor(*KEYS)
 
-  validates :site_short_description, :site_description, html: { wrap_with: :p }
-  validates :site_extended_description, :site_terms, :closed_registrations_message, html: true
-  validates :registrations_mode, inclusion: { in: %w(open approved none) }
-  validates :site_contact_email, :site_contact_username, presence: true
-  validates :site_contact_username, existing_username: true
-  validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
-  validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }
-  validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }
-  validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true
+  validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) }
+  validates :site_contact_email, :site_contact_username, presence: true, if: -> { defined?(@site_contact_username) || defined?(@site_contact_email) }
+  validates :site_contact_username, existing_username: true, if: -> { defined?(@site_contact_username) }
+  validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) }
+  validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) }
+  validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) }
+  validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) }
+  validates :site_short_description, length: { maximum: 200 }, if: -> { defined?(@site_short_description) }
 
-  def initialize(_attributes = {})
-    super
-    initialize_attributes
+  KEYS.each do |key|
+    define_method(key) do
+      return instance_variable_get("@#{key}") if instance_variable_defined?("@#{key}")
+
+      stored_value = begin
+        if UPLOAD_KEYS.include?(key)
+          SiteUpload.where(var: key).first_or_initialize(var: key)
+        else
+          Setting.public_send(key)
+        end
+      end
+
+      instance_variable_set("@#{key}", stored_value)
+    end
+  end
+
+  UPLOAD_KEYS.each do |key|
+    define_method("#{key}=") do |file|
+      value = public_send(key)
+      value.file = file
+    end
   end
 
   def save
     return false unless valid?
 
     KEYS.each do |key|
-      value = instance_variable_get("@#{key}")
+      next unless instance_variable_defined?("@#{key}")
 
-      if UPLOAD_KEYS.include?(key) && !value.nil?
-        upload = SiteUpload.where(var: key).first_or_initialize(var: key)
-        upload.update(file: value)
+      if UPLOAD_KEYS.include?(key)
+        public_send(key).save
       else
         setting = Setting.where(var: key).first_or_initialize(var: key)
-        setting.update(value: typecast_value(key, value))
+        setting.update(value: typecast_value(key, instance_variable_get("@#{key}")))
       end
     end
   end
 
   private
 
-  def initialize_attributes
-    KEYS.each do |key|
-      instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil?
-    end
-  end
-
   def typecast_value(key, value)
     if BOOLEAN_KEYS.include?(key)
       value == '1'
diff --git a/app/serializers/rest/extended_description_serializer.rb b/app/serializers/rest/extended_description_serializer.rb
index 0c3649033..c0fa3450d 100644
--- a/app/serializers/rest/extended_description_serializer.rb
+++ b/app/serializers/rest/extended_description_serializer.rb
@@ -8,6 +8,16 @@ class REST::ExtendedDescriptionSerializer < ActiveModel::Serializer
   end
 
   def content
-    object.text
+    if object.text.present?
+      markdown.render(object.text)
+    else
+      ''
+    end
+  end
+
+  private
+
+  def markdown
+    @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML)
   end
 end
diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml
new file mode 100644
index 000000000..366d213f6
--- /dev/null
+++ b/app/views/admin/settings/about/show.html.haml
@@ -0,0 +1,33 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.settings.about.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_about_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.about.preamble')
+
+  .fields-group
+    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+    %p.hint
+      = t 'admin.settings.about.rules_hint'
+      = link_to t('admin.settings.about.manage_rules'), admin_rules_path
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+
+  .fields-group
+    = f.input :site_terms, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml
new file mode 100644
index 000000000..d321c4b04
--- /dev/null
+++ b/app/views/admin/settings/appearance/show.html.haml
@@ -0,0 +1,34 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.settings.appearance.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_appearance_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.appearance.preamble')
+
+  .fields-group
+    = f.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false
+
+  .fields-group
+    = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :mascot, as: :file, wrapper: :with_block_label
+
+    .fields-row__column.fields-row__column-6.fields-group
+      - if @admin_settings.mascot.persisted?
+        = image_tag @admin_settings.mascot.file.url, class: 'fields-group__thumbnail'
+        = link_to admin_site_upload_path(@admin_settings.mascot), data: { method: :delete }, class: 'link-button link-button--destructive' do
+          = fa_icon 'trash fw'
+          = t('admin.site_uploads.delete')
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml
new file mode 100644
index 000000000..74a6fadf9
--- /dev/null
+++ b/app/views/admin/settings/branding/show.html.haml
@@ -0,0 +1,39 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.settings.branding.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.branding.preamble')
+
+  .fields-group
+    = f.input :site_title, wrapper: :with_label
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :site_contact_username, wrapper: :with_label
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :site_contact_email, wrapper: :with_label
+
+  .fields-group
+    = f.input :site_short_description, wrapper: :with_block_label, as: :text, input_html: { rows: 2, maxlength: 200 }
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :thumbnail, as: :file, wrapper: :with_block_label
+    .fields-row__column.fields-row__column-6.fields-group
+      - if @admin_settings.thumbnail.persisted?
+        = image_tag @admin_settings.thumbnail.file.url(:'@1x'), class: 'fields-group__thumbnail'
+        = link_to admin_site_upload_path(@admin_settings.thumbnail), data: { method: :delete }, class: 'link-button link-button--destructive' do
+          = fa_icon 'trash fw'
+          = t('admin.site_uploads.delete')
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml
new file mode 100644
index 000000000..36856127f
--- /dev/null
+++ b/app/views/admin/settings/content_retention/show.html.haml
@@ -0,0 +1,22 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.settings.content_retention.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_content_retention_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.content_retention.preamble')
+
+  .fields-group
+    = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+    = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+    = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/discovery/show.html.haml b/app/views/admin/settings/discovery/show.html.haml
new file mode 100644
index 000000000..e63c853fb
--- /dev/null
+++ b/app/views/admin/settings/discovery/show.html.haml
@@ -0,0 +1,40 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.settings.discovery.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_discovery_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.discovery.preamble')
+
+  %h4= t('admin.settings.discovery.trends')
+
+  .fields-group
+    = f.input :trends, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :trendable_by_default, as: :boolean, wrapper: :with_label
+
+  %h4= t('admin.settings.discovery.public_timelines')
+
+  .fields-group
+    = f.input :timeline_preview, as: :boolean, wrapper: :with_label
+
+  %h4= t('admin.settings.discovery.follow_recommendations')
+
+  .fields-group
+    = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label
+
+  %h4= t('admin.settings.discovery.profile_directory')
+
+  .fields-group
+    = f.input :profile_directory, as: :boolean, wrapper: :with_label
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
deleted file mode 100644
index 15b1a2498..000000000
--- a/app/views/admin/settings/edit.html.haml
+++ /dev/null
@@ -1,102 +0,0 @@
-- content_for :header_tags do
-  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
-
-- content_for :page_title do
-  = t('admin.settings.title')
-
-  - content_for :heading_actions do
-    = button_tag t('generic.save_changes'), class: 'button', form: 'edit_admin'
-
-= simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch, id: 'edit_admin' } do |f|
-  = render 'shared/error_messages', object: @admin_settings
-
-  .fields-group
-    = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :theme, collection: Themes.instance.names, label: t('simple_form.labels.defaults.setting_theme'), label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, label: t('admin.settings.registrations_mode.title'), include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :site_contact_username, wrapper: :with_label, label: t('admin.settings.contact_information.username')
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email')
-
-  .fields-group
-    = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
-
-  .fields-group
-    = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 2 }
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: site_upload_delete_hint(t('admin.settings.thumbnail.desc_html'), :thumbnail)
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :mascot, as: :file, wrapper: :with_block_label, label: t('admin.settings.mascot.title'), hint: site_upload_delete_hint(t('admin.settings.mascot.desc_html'), :mascot)
-
-  %hr.spacer/
-
-  .fields-group
-    = f.input :require_invite_text, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.require_invite_text.title'), hint: t('admin.settings.registrations.require_invite_text.desc_html'), disabled: !approved_registrations?
-
-  %hr.spacer/
-
-  .fields-group
-    = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html')
-
-  %hr.spacer/
-
-  - unless whitelist_mode?
-    .fields-group
-      = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
-
-  - unless whitelist_mode?
-    .fields-group
-      = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html'), recommended: true
-
-    .fields-group
-      = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html'), recommended: true
-
-    .fields-group
-      = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
-
-    .fields-group
-      = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
-
-    .fields-group
-      = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
-
-    .fields-group
-      = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html'), recommended: :not_recommended
-
-    .fields-group
-      = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
-
-  %hr.spacer/
-
-  .fields-row
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
-    .fields-row__column.fields-row__column-6.fields-group
-      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
-
-  .fields-group
-    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?
-    = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
-    = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
-    = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
-
-  %hr.spacer/
-
-  .fields-group
-    = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
-    = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
-    = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
-
-  .actions
-    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml
new file mode 100644
index 000000000..0129332d7
--- /dev/null
+++ b/app/views/admin/settings/registrations/show.html.haml
@@ -0,0 +1,27 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.settings.registrations.title')
+
+- content_for :heading do
+  %h2= t('admin.settings.title')
+  = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
+
+  %p.lead= t('admin.settings.registrations.preamble')
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
+
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations?
+
+  .fields-group
+    = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, input_html: { rows: 2 }
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/shared/_links.html.haml b/app/views/admin/settings/shared/_links.html.haml
new file mode 100644
index 000000000..1294c26ce
--- /dev/null
+++ b/app/views/admin/settings/shared/_links.html.haml
@@ -0,0 +1,8 @@
+.content__heading__tabs
+  = render_navigation renderer: :links do |primary|
+    - primary.item :branding, safe_join([fa_icon('pencil fw'), t('admin.settings.branding.title')]), admin_settings_branding_path
+    - primary.item :about, safe_join([fa_icon('file-text fw'), t('admin.settings.about.title')]), admin_settings_about_path
+    - primary.item :registrations, safe_join([fa_icon('users fw'), t('admin.settings.registrations.title')]), admin_settings_registrations_path
+    - primary.item :discovery, safe_join([fa_icon('search fw'), t('admin.settings.discovery.title')]), admin_settings_discovery_path
+    - primary.item :content_retention, safe_join([fa_icon('history fw'), t('admin.settings.content_retention.title')]), admin_settings_content_retention_path
+    - primary.item :appearance, safe_join([fa_icon('desktop fw'), t('admin.settings.appearance.title')]), admin_settings_appearance_path
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index e577b9803..59021ad88 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -22,15 +22,16 @@
 
     .content-wrapper
       .content
-        .content-heading
+        .content__heading
           - if content_for?(:heading)
             = yield :heading
           - else
-            %h2= yield :page_title
+            .content__heading__row
+              %h2= yield :page_title
 
-          - if :heading_actions
-            .content-heading-actions
-              = yield :heading_actions
+              - if content_for?(:heading_actions)
+                .content__heading__actions
+                  = yield :heading_actions
 
         = render 'application/flashes'
 
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 412178ca3..70850d478 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -667,79 +667,40 @@ en:
       empty: No server rules have been defined yet.
       title: Server rules
     settings:
-      activity_api_enabled:
-        desc_html: Counts of locally published posts, active users, and new registrations in weekly buckets
-        title: Publish aggregate statistics about user activity in the API
-      bootstrap_timeline_accounts:
-        desc_html: Separate multiple usernames by comma. These accounts will be guaranteed to be shown in follow recommendations
-        title: Recommend these accounts to new users
-      contact_information:
-        email: Business e-mail
-        username: Contact username
-      custom_css:
-        desc_html: Modify the look with CSS loaded on every page
-        title: Custom CSS
-      default_noindex:
-        desc_html: Affects all users who have not changed this setting themselves
-        title: Opt users out of search engine indexing by default
+      about:
+        manage_rules: Manage server rules
+        preamble: Provide in-depth information about how the server is operated, moderated, funded.
+        rules_hint: There is a dedicated area for rules that your users are expected to adhere to.
+        title: About
+      appearance:
+        preamble: Customize Mastodon's web interface.
+        title: Appearance
+      branding:
+        preamble: Your server's branding differentiates it from other servers in the network. This information may be displayed across a variety of environments, such as Mastodon's web interface, native applications, in link previews on other websites and within messaging apps, and so on. For this reason, it is best to keep this information clear, short and concise.
+        title: Branding
+      content_retention:
+        preamble: Control how user-generated content is stored in Mastodon.
+        title: Content retention
+      discovery:
+        follow_recommendations: Follow recommendations
+        preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server.
+        profile_directory: Profile directory
+        public_timelines: Public timelines
+        title: Discovery
+        trends: Trends
       domain_blocks:
         all: To everyone
         disabled: To no one
-        title: Show domain blocks
         users: To logged-in local users
-      domain_blocks_rationale:
-        title: Show rationale
-      mascot:
-        desc_html: Displayed on multiple pages. At least 293×205px recommended. When not set, falls back to default mascot
-        title: Mascot image
-      peers_api_enabled:
-        desc_html: Domain names this server has encountered in the fediverse
-        title: Publish list of discovered servers in the API
-      preview_sensitive_media:
-        desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive
-        title: Show sensitive media in OpenGraph previews
-      profile_directory:
-        desc_html: Allow users to be discoverable
-        title: Enable profile directory
       registrations:
-        closed_message:
-          desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags
-          title: Closed registration message
-        require_invite_text:
-          desc_html: When registrations require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
-          title: Require new users to enter a reason to join
+        preamble: Control who can create an account on your server.
+        title: Registrations
       registrations_mode:
         modes:
           approved: Approval required for sign up
           none: Nobody can sign up
           open: Anyone can sign up
-        title: Registrations mode
-      site_description:
-        desc_html: Introductory paragraph on the API. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>.
-        title: Server description
-      site_description_extended:
-        desc_html: A good place for your code of conduct, rules, guidelines and other things that set your server apart. You can use HTML tags
-        title: Custom extended information
-      site_short_description:
-        desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph.
-        title: Short server description
-      site_terms:
-        desc_html: You can write your own privacy policy. You can use HTML tags
-        title: Custom privacy policy
-      site_title: Server name
-      thumbnail:
-        desc_html: Used for previews via OpenGraph and API. 1200x630px recommended
-        title: Server thumbnail
-      timeline_preview:
-        desc_html: Display link to public timeline on landing page and allow API access to the public timeline without authentication
-        title: Allow unauthenticated access to public timeline
-      title: Site settings
-      trendable_by_default:
-        desc_html: Specific trending content can still be explicitly disallowed
-        title: Allow trends without prior review
-      trends:
-        desc_html: Publicly display previously reviewed content that is currently trending
-        title: Trends
+      title: Server Settings
     site_uploads:
       delete: Delete uploaded file
       destroyed_msg: Site upload successfully deleted!
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index db5b45e41..64281d7b7 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -75,8 +75,25 @@ en:
           warn: Hide the filtered content behind a warning mentioning the filter's title
       form_admin_settings:
         backups_retention_period: Keep generated user archives for the specified number of days.
+        bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations.
+        closed_registrations_message: Displayed when sign-ups are closed
         content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible.
+        custom_css: You can apply custom styles on the web version of Mastodon.
+        mascot: Overrides the illustration in the advanced web interface.
         media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand.
+        profile_directory: The profile directory lists all users who have opted-in to be discoverable.
+        require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
+        site_contact_email: How people can reach you for legal or support inquiries.
+        site_contact_username: How people can reach you on Mastodon.
+        site_extended_description: Any additional information that may be useful to visitors and your users. Can be structured with Markdown syntax.
+        site_short_description: A short description to help uniquely identify your server. Who is running it, who is it for?
+        site_terms: Use your own privacy policy or leave blank to use the default. Can be structured with Markdown syntax.
+        site_title: How people may refer to your server besides its domain name.
+        theme: Theme that logged out visitors and new users see.
+        thumbnail: A roughly 2:1 image displayed alongside your server information.
+        timeline_preview: Logged out visitors will be able to browse the most recent public posts available on the server.
+        trendable_by_default: Skip manual review of trending content. Individual items can still be removed from trends after the fact.
+        trends: Trends show which posts, hashtags and news stories are gaining traction on your server.
       form_challenge:
         current_password: You are entering a secure area
       imports:
@@ -213,8 +230,28 @@ en:
           warn: Hide with a warning
       form_admin_settings:
         backups_retention_period: User archive retention period
+        bootstrap_timeline_accounts: Always recommend these accounts to new users
+        closed_registrations_message: Custom message when sign-ups are not available
         content_cache_retention_period: Content cache retention period
+        custom_css: Custom CSS
+        mascot: Custom mascot (legacy)
         media_cache_retention_period: Media cache retention period
+        profile_directory: Enable profile directory
+        registrations_mode: Who can sign-up
+        require_invite_text: Require a reason to join
+        show_domain_blocks: Show domain blocks
+        show_domain_blocks_rationale: Show why domains were blocked
+        site_contact_email: Contact e-mail
+        site_contact_username: Contact username
+        site_extended_description: Extended description
+        site_short_description: Server description
+        site_terms: Privacy Policy
+        site_title: Server name
+        theme: Default theme
+        thumbnail: Server thumbnail
+        timeline_preview: Allow unauthenticated access to public timelines
+        trendable_by_default: Allow trends without prior review
+        trends: Enable trends
       interactions:
         must_be_follower: Block notifications from non-followers
         must_be_following: Block notifications from people you don't follow
diff --git a/config/navigation.rb b/config/navigation.rb
index 706de0471..e901fb932 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -52,7 +52,7 @@ SimpleNavigation::Configuration.run do |navigation|
 
     n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), nil, if: -> { current_user.can?(:view_dashboard, :manage_settings, :manage_rules, :manage_announcements, :manage_custom_emojis, :manage_webhooks, :manage_federation) } do |s|
       s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_path, if: -> { current_user.can?(:view_dashboard) }
-      s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings}
+      s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings}
       s.item :rules, safe_join([fa_icon('gavel fw'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules}, if: -> { current_user.can?(:manage_rules) }
       s.item :roles, safe_join([fa_icon('vcard fw'), t('admin.roles.title')]), admin_roles_path, highlights_on: %r{/admin/roles}, if: -> { current_user.can?(:manage_roles) }
       s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) }
diff --git a/config/routes.rb b/config/routes.rb
index 1ed585f19..b44479e77 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -241,7 +241,18 @@ Rails.application.routes.draw do
       end
     end
 
-    resource :settings, only: [:edit, :update]
+    get '/settings', to: redirect('/admin/settings/branding')
+    get '/settings/edit', to: redirect('/admin/settings/branding')
+
+    namespace :settings do
+      resource :branding, only: [:show, :update], controller: 'branding'
+      resource :registrations, only: [:show, :update], controller: 'registrations'
+      resource :content_retention, only: [:show, :update], controller: 'content_retention'
+      resource :about, only: [:show, :update], controller: 'about'
+      resource :appearance, only: [:show, :update], controller: 'appearance'
+      resource :discovery, only: [:show, :update], controller: 'discovery'
+    end
+
     resources :site_uploads, only: [:destroy]
 
     resources :invites, only: [:index, :create, :destroy] do
diff --git a/spec/controllers/admin/settings/branding_controller_spec.rb b/spec/controllers/admin/settings/branding_controller_spec.rb
new file mode 100644
index 000000000..ee1c441bc
--- /dev/null
+++ b/spec/controllers/admin/settings/branding_controller_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Settings::BrandingController, type: :controller do
+  render_views
+
+  describe 'When signed in as an admin' do
+    before do
+      sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
+    end
+
+    describe 'GET #show' do
+      it 'returns http success' do
+        get :show
+
+        expect(response).to have_http_status(200)
+      end
+    end
+
+    describe 'PUT #update' do
+      before do
+        allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
+      end
+
+      around do |example|
+        before = Setting.site_short_description
+        Setting.site_short_description = nil
+        example.run
+        Setting.site_short_description = before
+        Setting.new_setting_key = nil
+      end
+
+      it 'cannot create a setting value for a non-admin key' do
+        expect(Setting.new_setting_key).to be_blank
+
+        patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } }
+
+        expect(response).to redirect_to(admin_settings_branding_path)
+        expect(Setting.new_setting_key).to be_nil
+      end
+
+      it 'creates a settings value that didnt exist before for eligible key' do
+        expect(Setting.site_short_description).to be_blank
+
+        patch :update, params: { form_admin_settings: { site_short_description: 'New key value' } }
+
+        expect(response).to redirect_to(admin_settings_branding_path)
+        expect(Setting.site_short_description).to eq 'New key value'
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb
deleted file mode 100644
index 46749f76c..000000000
--- a/spec/controllers/admin/settings_controller_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Admin::SettingsController, type: :controller do
-  render_views
-
-  describe 'When signed in as an admin' do
-    before do
-      sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
-    end
-
-    describe 'GET #edit' do
-      it 'returns http success' do
-        get :edit
-
-        expect(response).to have_http_status(200)
-      end
-    end
-
-    describe 'PUT #update' do
-      before do
-        allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
-      end
-
-      describe 'for a record that doesnt exist' do
-        around do |example|
-          before = Setting.site_extended_description
-          Setting.site_extended_description = nil
-          example.run
-          Setting.site_extended_description = before
-          Setting.new_setting_key = nil
-        end
-
-        it 'cannot create a setting value for a non-admin key' do
-          expect(Setting.new_setting_key).to be_blank
-
-          patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } }
-
-          expect(response).to redirect_to(edit_admin_settings_path)
-          expect(Setting.new_setting_key).to be_nil
-        end
-
-        it 'creates a settings value that didnt exist before for eligible key' do
-          expect(Setting.site_extended_description).to be_blank
-
-          patch :update, params: { form_admin_settings: { site_extended_description: 'New key value' } }
-
-          expect(response).to redirect_to(edit_admin_settings_path)
-          expect(Setting.site_extended_description).to eq 'New key value'
-        end
-      end
-
-      context do
-        around do |example|
-          site_title = Setting.site_title
-          example.run
-          Setting.site_title = site_title
-        end
-
-        it 'updates a settings value' do
-          Setting.site_title = 'Original'
-          patch :update, params: { form_admin_settings: { site_title: 'New title' } }
-
-          expect(response).to redirect_to(edit_admin_settings_path)
-          expect(Setting.site_title).to eq 'New title'
-        end
-      end
-    end
-  end
-end