diff --git a/app/assets/javascripts/discourse/app/raw-views/topic-list-header-column.js b/app/assets/javascripts/discourse/app/raw-views/topic-list-header-column.js index 1e118dc5e5d..ba7980f0699 100644 --- a/app/assets/javascripts/discourse/app/raw-views/topic-list-header-column.js +++ b/app/assets/javascripts/discourse/app/raw-views/topic-list-header-column.js @@ -18,13 +18,18 @@ export default EmberObject.extend({ @discourseComputed sortIcon() { - const asc = this.parent.ascending ? "up" : "down"; + const isAscending = this.parent.ascending || this.parent.context?.ascending; + const asc = isAscending ? "up" : "down"; return `chevron-${asc}`; }, @discourseComputed isSorting() { - return this.sortable && this.parent.order === this.order; + return ( + this.sortable && + (this.parent.order === this.order || + this.parent.context?.order === this.order) + ); }, @discourseComputed diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 77bba44dbd7..5a05f102d7b 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -664,9 +664,21 @@ class TopicQuery end def apply_ordering(result, options = {}) - sort_column = SORTABLE_MAPPING[options[:order]] || "default" + order_option = options[:order] sort_dir = (options[:ascending] == "true") ? "ASC" : "DESC" + new_result = + DiscoursePluginRegistry.apply_modifier( + :topic_query_apply_ordering_result, + result, + order_option, + sort_dir, + options, + self, + ) + return new_result if !new_result.nil? && new_result != result + sort_column = SORTABLE_MAPPING[order_option] || "default" + # If we are sorting in the default order desc, we should consider including pinned # topics. Otherwise, just use bumped_at. if sort_column == "default" diff --git a/spec/lib/topic_query_spec.rb b/spec/lib/topic_query_spec.rb index 29560ca8ac8..1a8e473b022 100644 --- a/spec/lib/topic_query_spec.rb +++ b/spec/lib/topic_query_spec.rb @@ -2091,6 +2091,40 @@ RSpec.describe TopicQuery do end end + describe "#apply_ordering" do + fab!(:topic1) { Fabricate(:topic, spam_count: 3, bumped_at: 3.hours.ago) } + fab!(:topic2) { Fabricate(:topic, spam_count: 2, bumped_at: 3.minutes.ago) } + fab!(:topic3) { Fabricate(:topic, spam_count: 3, bumped_at: 1.hour.ago) } + + let(:modifier_block) do + Proc.new do |result, sort_column, sort_dir, options, topic_query| + if sort_column == "spam" + sort_column = "spam_count" + result.order("topics.#{sort_column} #{sort_dir}, bumped_at DESC") + end + end + end + + it "returns the result of topic_query_apply_ordering_result modifier" do + plugin_instance = Plugin::Instance.new + plugin_instance.register_modifier(:topic_query_apply_ordering_result, &modifier_block) + + topics = TopicQuery.new(nil, order: "spam", ascending: "false").list_latest.topics + expect(topics.map(&:id)).to eq([topic3.id, topic1.id, topic2.id]) + ensure + DiscoursePluginRegistry.unregister_modifier( + plugin_instance, + :topic_query_apply_ordering_result, + &modifier_block + ) + end + + it "ignores the result of topic_query_apply_ordering_result if modifier not registered" do + topics = TopicQuery.new(nil, order: "spam", ascending: "false").list_latest.topics + expect(topics.map(&:id)).to eq([topic2.id, topic3.id, topic1.id]) + end + end + describe "show_category_definitions_in_topic_lists setting" do fab!(:category) { Fabricate(:category_with_definition) } fab!(:subcategory) { Fabricate(:category_with_definition, parent_category: category) }