From baf7dd1f9ff8833ae2a03277b7fe3d08661e2650 Mon Sep 17 00:00:00 2001
From: MoshiKoi <54333972+MoshiKoi@users.noreply.github.com>
Date: Fri, 21 Aug 2026 05:29:31 +0000
Subject: [PATCH 1/9] Add a user preference for the preferred community order
---
app/assets/javascripts/community_order.js | 19 ++++++
app/assets/stylesheets/users.scss | 24 +++++++
app/views/layouts/_head.html.erb | 1 +
app/views/users/_prefs.html.erb | 79 ++++++++++++++++-------
app/views/users/_prefs_block.erb | 9 +++
app/views/users/_prefs_inline.erb | 9 +++
config/config/preferences.yml | 9 ++-
package-lock.json | 20 ++++++
package.json | 1 +
tsconfig.json | 2 +-
10 files changed, 147 insertions(+), 26 deletions(-)
create mode 100644 app/assets/javascripts/community_order.js
create mode 100644 app/views/users/_prefs_block.erb
create mode 100644 app/views/users/_prefs_inline.erb
diff --git a/app/assets/javascripts/community_order.js b/app/assets/javascripts/community_order.js
new file mode 100644
index 000000000..b2641695e
--- /dev/null
+++ b/app/assets/javascripts/community_order.js
@@ -0,0 +1,19 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const pref = $(".community-sortable");
+ const sortable = pref.find(".sortable");
+
+ const input = pref.find(".js-user-pref");
+
+ sortable.sortable({
+ appendTo: pref,
+ update: () => {
+ const val = sortable.children()
+ .map((_, el) => el.dataset['community'])
+ .toArray()
+ .join(',');
+
+ input.val(val);
+ input.trigger("change");
+ }
+ });
+});
\ No newline at end of file
diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss
index 3835a2878..2fe215ab9 100644
--- a/app/assets/stylesheets/users.scss
+++ b/app/assets/stylesheets/users.scss
@@ -187,6 +187,30 @@ img {
}
}
+.user-pref.user-block-pref {
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.community-sortable {
+ .community-sortable-list-item {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ cursor: grab;
+ img {
+ max-height: 1.5rem;
+ }
+ .img {
+ width: 100px;
+ padding: 10px;
+ }
+ .label {
+ flex-grow: 1;
+ }
+ }
+}
+
$sizes: (16, 32, 40, 48, 64, 128, 256);
@each $size in $sizes {
diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb
index 700655931..3d38a4143 100644
--- a/app/views/layouts/_head.html.erb
+++ b/app/views/layouts/_head.html.erb
@@ -34,6 +34,7 @@
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag "https://cdn.jsdelivr.net/npm/jquery@2.2.2/dist/jquery.min.js" %>
+<%= javascript_include_tag "https://cdn.jsdelivr.net/npm/jquery-ui@1.14.2/dist/jquery-ui.min.js" %>
<%= javascript_include_tag "https://cdn.jsdelivr.net/npm/moment@2.13.0/min/moment.min.js" %>
<%= javascript_include_tag "https://cdn.jsdelivr.net/npm/select2@4.0.12/dist/js/select2.min.js" %>
<% if SiteSetting['DonationsEnabled'] && (SiteSetting['LoadStripeEverywhere'] || controller_name == 'donations') %>
diff --git a/app/views/users/_prefs.html.erb b/app/views/users/_prefs.html.erb
index 7239b2f9a..46201d3cf 100644
--- a/app/views/users/_prefs.html.erb
+++ b/app/views/users/_prefs.html.erb
@@ -1,27 +1,58 @@
<% prefs.each do |name, value| %>
<% pref_config = AppConfig.preferences[name] %>
-
-
- <%= name.humanize %>
- <%= pref_config['description'] %>
-
-
- <% case pref_config['type'] %>
- <% when 'boolean' %>
-
- data-community="<%= community %>" />
- <% when 'choice' %>
- <%= select_tag nil,
- options_for_select(preference_choice(pref_config), selected: value),
- class: 'js-user-pref form-element',
- data: { pref: name, community: community } %>
- <% when 'integer' %>
-
- <% when 'string' %>
-
- <% end %>
-
-
+ <% case pref_config['type'] %>
+ <% when 'boolean' %>
+ <%= render 'prefs_inline', name: name, pref_config: pref_config do %>
+
+ data-community="<%= community %>" />
+ <% end %>
+ <% when 'choice' %>
+ <%= render 'prefs_inline', name: name, pref_config: pref_config do %>
+ <%= select_tag nil,
+ options_for_select(preference_choice(pref_config), selected: value),
+ class: 'js-user-pref form-element',
+ data: { pref: name, community: community } %>
+ <% end %>
+ <% when 'integer' %>
+ <%= render 'prefs_inline', name: name, pref_config: pref_config do %>
+
+ <% end %>
+ <% when 'string' %>
+ <%= render 'prefs_inline', name: name, pref_config: pref_config do %>
+
+ <% end %>
+ <% when 'community_order' %>
+ <%= render 'prefs_block', name: name, pref_config: pref_config do %>
+
+ <% end %>
+ <% end %>
<% end %>
diff --git a/app/views/users/_prefs_block.erb b/app/views/users/_prefs_block.erb
new file mode 100644
index 000000000..cc3c395d6
--- /dev/null
+++ b/app/views/users/_prefs_block.erb
@@ -0,0 +1,9 @@
+
+
+ <%= name.humanize %>
+ <%= pref_config['description'] %>
+
+
+ <%= yield %>
+
+
\ No newline at end of file
diff --git a/app/views/users/_prefs_inline.erb b/app/views/users/_prefs_inline.erb
new file mode 100644
index 000000000..838f296ca
--- /dev/null
+++ b/app/views/users/_prefs_inline.erb
@@ -0,0 +1,9 @@
+
+
+ <%= name.humanize %>
+ <%= pref_config['description'] %>
+
+
+ <%= yield %>
+
+
\ No newline at end of file
diff --git a/config/config/preferences.yml b/config/config/preferences.yml
index 240b901ba..8aa7b4e41 100644
--- a/config/config/preferences.yml
+++ b/config/config/preferences.yml
@@ -2,7 +2,7 @@
# Schema:
#
# preference_name:
-# type: boolean | string | integer | choice
+# type: boolean | string | integer | choice | community_order
# description: Free-form description
# choice:
# - only
@@ -100,3 +100,10 @@ collapse_related_posts:
Collapse the Related Posts widget in the sidebar by default.
default: 'false'
global: true
+
+community_order:
+ type: community_order
+ description: >
+ Order of communities in the dashboard and dropdown
+ default: ''
+ global: true
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 33a5deaed..25f607d60 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
"name": "qpixel",
"devDependencies": {
"@types/jquery": "^3.5.32",
+ "@types/jqueryui": "1.12.24",
"@types/prettier": "2.7.3",
"@types/select2": "^4.0.63",
"dompurify": "3.4.13",
@@ -25,6 +26,16 @@
"@types/sizzle": "*"
}
},
+ "node_modules/@types/jqueryui": {
+ "version": "1.12.24",
+ "resolved": "https://registry.npmjs.org/@types/jqueryui/-/jqueryui-1.12.24.tgz",
+ "integrity": "sha512-E2sGULwzMhg4kAeOV+gYcXjg988RuPkviWCt09jLe6GGK9sHM7dTqS8H7JMuUWoZQBucIBzBAgM5o/ezKUFkeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/jquery": "*"
+ }
+ },
"node_modules/@types/prettier": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
@@ -151,6 +162,15 @@
"@types/sizzle": "*"
}
},
+ "@types/jqueryui": {
+ "version": "1.12.24",
+ "resolved": "https://registry.npmjs.org/@types/jqueryui/-/jqueryui-1.12.24.tgz",
+ "integrity": "sha512-E2sGULwzMhg4kAeOV+gYcXjg988RuPkviWCt09jLe6GGK9sHM7dTqS8H7JMuUWoZQBucIBzBAgM5o/ezKUFkeg==",
+ "dev": true,
+ "requires": {
+ "@types/jquery": "*"
+ }
+ },
"@types/prettier": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
diff --git a/package.json b/package.json
index d61965491..2b0198c14 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
},
"devDependencies": {
"@types/jquery": "^3.5.32",
+ "@types/jqueryui": "1.12.24",
"@types/prettier": "2.7.3",
"@types/select2": "^4.0.63",
"dompurify": "3.4.13",
diff --git a/tsconfig.json b/tsconfig.json
index 04dd419db..1d355ad6f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,7 +8,7 @@
"moduleResolution": "node",
"noEmit": true,
"target": "ES2021",
- "types": ["./global.d.ts", "@types/jquery", "moment", "@types/select2"]
+ "types": ["./global.d.ts", "@types/jquery", "@types/jqueryui", "moment", "@types/select2"]
},
"include": ["./app/assets/javascripts", "./public/assets/community"],
"exclude": [
From 3a556aa0d88d6b1ceda70ff76dff66b99ab2a224 Mon Sep 17 00:00:00 2001
From: MoshiKoi <54333972+MoshiKoi@users.noreply.github.com>
Date: Fri, 21 Aug 2026 06:41:18 +0000
Subject: [PATCH 2/9] Make the dropdown and dashboard follow the user's
preferred order
---
app/controllers/application_controller.rb | 2 +-
app/models/community.rb | 12 ++++++++++++
app/views/layouts/_header.html.erb | 2 +-
app/views/users/_prefs.html.erb | 2 +-
4 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 62372c684..cb94a1de0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -32,7 +32,7 @@ def upload
end
def dashboard
- @communities = Community.all
+ @communities = Community.all.user_preferred_order(current_user)
@edits = Post.unscoped do
SuggestedEdit.unscoped.joins(:post).where(active: true).group(Arel.sql('posts.category_id')).count
end
diff --git a/app/models/community.rb b/app/models/community.rb
index d6eca7ac3..6c0db2fde 100644
--- a/app/models/community.rb
+++ b/app/models/community.rb
@@ -5,4 +5,16 @@ class Community < ApplicationRecord
default_scope { where(is_fake: false) }
validates :host, uniqueness: { case_sensitive: false }
+
+ scope :user_preferred_order, lambda { |user|
+ if user.nil?
+ all
+ else
+ user_order = user.preference('community_order', community: false)
+ .split(',')
+ .map(&:to_i)
+
+ in_order_of(:id, user_order, filter: false)
+ end
+ }
end
diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb
index d228bede2..ed42dc466 100644
--- a/app/views/layouts/_header.html.erb
+++ b/app/views/layouts/_header.html.erb
@@ -135,7 +135,7 @@
Communities