diff --git a/app/controllers/admin/rules_controller.rb b/app/controllers/admin/rules_controller.rb
index 837eb91489..b9331e7e98 100644
--- a/app/controllers/admin/rules_controller.rb
+++ b/app/controllers/admin/rules_controller.rb
@@ -50,6 +50,22 @@ module Admin
redirect_to admin_rules_path
end
+ def move_up
+ authorize @rule, :update?
+
+ @rule.move!(-1)
+
+ redirect_to admin_rules_path
+ end
+
+ def move_down
+ authorize @rule, :update?
+
+ @rule.move!(+1)
+
+ redirect_to admin_rules_path
+ end
+
private
def set_rule
diff --git a/app/javascript/material-icons/400-24px/arrow_downward-fill.svg b/app/javascript/material-icons/400-24px/arrow_downward-fill.svg
new file mode 100644
index 0000000000..a9157219ca
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/arrow_downward-fill.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/arrow_downward.svg b/app/javascript/material-icons/400-24px/arrow_downward.svg
new file mode 100644
index 0000000000..a9157219ca
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/arrow_downward.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/arrow_upward-fill.svg b/app/javascript/material-icons/400-24px/arrow_upward-fill.svg
new file mode 100644
index 0000000000..1ec54b0702
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/arrow_upward-fill.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/arrow_upward.svg b/app/javascript/material-icons/400-24px/arrow_upward.svg
new file mode 100644
index 0000000000..1ec54b0702
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/arrow_upward.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 4c75231b5c..962fd2d20e 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -1127,6 +1127,15 @@ a.name-tag,
}
}
+.rule-actions {
+ display: flex;
+ flex-direction: column;
+
+ a.table-action-link {
+ padding-inline-start: 0;
+ }
+}
+
.dashboard__counters.admin-account-counters {
margin-top: 10px;
}
diff --git a/app/models/rule.rb b/app/models/rule.rb
index 99a36397aa..3033a2b03d 100644
--- a/app/models/rule.rb
+++ b/app/models/rule.rb
@@ -22,4 +22,18 @@ class Rule < ApplicationRecord
validates :text, presence: true, length: { maximum: TEXT_SIZE_LIMIT }
scope :ordered, -> { kept.order(priority: :asc, id: :asc) }
+
+ def move!(offset)
+ rules = Rule.ordered.to_a
+ position = rules.index(self)
+
+ rules.delete_at(position)
+ rules.insert(position + offset, self)
+
+ transaction do
+ rules.each.with_index do |rule, index|
+ rule.update!(priority: index)
+ end
+ end
+ end
end
diff --git a/app/views/admin/rules/_rule.html.haml b/app/views/admin/rules/_rule.html.haml
index eb97eefb3c..7d84534d59 100644
--- a/app/views/admin/rules/_rule.html.haml
+++ b/app/views/admin/rules/_rule.html.haml
@@ -7,5 +7,7 @@
.announcements-list__item__meta
= rule.hint
- %div
+ .rule-actions
+ = table_link_to 'arrow_upward', t('admin.rules.move_up'), move_up_admin_rule_path(rule), method: :post if can?(:update, rule) && !rule_iteration.first?
= table_link_to 'delete', t('admin.rules.delete'), admin_rule_path(rule), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, rule)
+ = table_link_to 'arrow_downward', t('admin.rules.move_down'), move_down_admin_rule_path(rule), method: :post if can?(:update, rule) && !rule_iteration.last?
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 29b4f642a1..dad46d9d51 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -790,6 +790,8 @@ en:
description_html: While most claim to have read and agree to the terms of service, usually people do not read through until after a problem arises. Make it easier to see your server's rules at a glance by providing them in a flat bullet point list. Try to keep individual rules short and simple, but try not to split them up into many separate items either.
edit: Edit rule
empty: No server rules have been defined yet.
+ move_down: Move down
+ move_up: Move up
title: Server rules
settings:
about:
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 2f7b86162b..e7439f66b7 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -93,7 +93,12 @@ namespace :admin do
end
end
- resources :rules, only: [:index, :new, :create, :edit, :update, :destroy]
+ resources :rules, only: [:index, :new, :create, :edit, :update, :destroy] do
+ member do
+ post :move_up
+ post :move_down
+ end
+ end
resources :webhooks do
member do
diff --git a/spec/models/rule_spec.rb b/spec/models/rule_spec.rb
index 375483e0db..a7fc5ef693 100644
--- a/spec/models/rule_spec.rb
+++ b/spec/models/rule_spec.rb
@@ -16,4 +16,24 @@ RSpec.describe Rule do
end
end
end
+
+ describe '#move!' do
+ let!(:first_rule) { Fabricate(:rule, text: 'foo') }
+ let!(:second_rule) { Fabricate(:rule, text: 'bar') }
+ let!(:third_rule) { Fabricate(:rule, text: 'baz') }
+
+ it 'moves the rules as expected' do
+ expect { first_rule.move!(+1) }
+ .to change { described_class.ordered.pluck(:text) }.from(%w(foo bar baz)).to(%w(bar foo baz))
+
+ expect { first_rule.move!(-1) }
+ .to change { described_class.ordered.pluck(:text) }.from(%w(bar foo baz)).to(%w(foo bar baz))
+
+ expect { third_rule.move!(-1) }
+ .to change { described_class.ordered.pluck(:text) }.from(%w(foo bar baz)).to(%w(foo baz bar))
+
+ expect { second_rule.move!(-1) }
+ .to change { described_class.ordered.pluck(:text) }.from(%w(foo baz bar)).to(%w(foo bar baz))
+ end
+ end
end
diff --git a/spec/system/admin/rules_spec.rb b/spec/system/admin/rules_spec.rb
index afe9e87a52..dca6323f6c 100644
--- a/spec/system/admin/rules_spec.rb
+++ b/spec/system/admin/rules_spec.rb
@@ -48,6 +48,29 @@ RSpec.describe 'Admin Rules' do
end
end
+ describe 'Moving down an existing rule' do
+ let!(:first_rule) { Fabricate(:rule, text: 'This is another rule') }
+ let!(:second_rule) { Fabricate(:rule, text: 'This is a rule') }
+
+ it 'moves the rule down' do
+ visit admin_rules_path
+
+ expect(page)
+ .to have_content(I18n.t('admin.rules.title'))
+
+ expect(Rule.ordered.pluck(:text)).to eq ['This is another rule', 'This is a rule']
+
+ click_on(I18n.t('admin.rules.move_down'))
+
+ expect(page)
+ .to have_content(I18n.t('admin.rules.title'))
+ .and have_content(first_rule.text)
+ .and have_content(second_rule.text)
+
+ expect(Rule.ordered.pluck(:text)).to eq ['This is a rule', 'This is another rule']
+ end
+ end
+
describe 'Editing an existing rule' do
let!(:rule) { Fabricate :rule, text: 'Rule text' }