FEATURE: Show dropdown to filter topic lists by solved status

approved

#1

FEATURE: Show dropdown to filter topic lists by solved status

diff --git a/assets/javascripts/discourse/connectors/bread-crumbs-right/topic-status-filter.hbs b/assets/javascripts/discourse/connectors/bread-crumbs-right/topic-status-filter.hbs
new file mode 100644
index 0000000..e85eadc
--- /dev/null
+++ b/assets/javascripts/discourse/connectors/bread-crumbs-right/topic-status-filter.hbs
@@ -0,0 +1 @@
+{{combo-box content=statuses value=status valueAttribute="value" onSelect=(action "changeStatus")}}
diff --git a/assets/javascripts/discourse/connectors/bread-crumbs-right/topic-status-filter.js.es6 b/assets/javascripts/discourse/connectors/bread-crumbs-right/topic-status-filter.js.es6
new file mode 100644
index 0000000..3319e23
--- /dev/null
+++ b/assets/javascripts/discourse/connectors/bread-crumbs-right/topic-status-filter.js.es6
@@ -0,0 +1,60 @@
+import DiscourseUrl from "discourse/lib/url";
+
+export default {
+  shouldRender(args, component) {
+    if (!component.siteSettings.show_filter_by_solved_status) {
+      return false;
+    } else if (component.siteSettings.allow_solved_on_all_topics) {
+      return true;
+    } else {
+      const controller = Discourse.__container__.lookup(
+        "controller:navigation/category"
+      );
+
+      return controller && controller.get("category.enable_accepted_answers");
+    }
+  },
+
+  setupComponent(args, component) {
+    const statuses = ["all", "solved", "unsolved"].map(status => {
+      return {
+        name: I18n.t(`solved.topic_status_filter.${status}`),
+        value: status
+      };
+    });
+    component.set("statuses", statuses);
+
+    const queryStrings = window.location.search;
+    if (queryStrings.match(/solved=yes/)) {
+      component.set("status", "solved");
+    } else if (queryStrings.match(/solved=no/)) {
+      component.set("status", "unsolved");
+    } else {
+      component.set("status", "all");
+    }
+  },
+
+  actions: {
+    changeStatus(newStatus) {
+      let location = window.location;
+      let queryStrings = location.search;
+      let params = queryStrings.startsWith("?")
+        ? queryStrings.substr(1).split("&")
+        : [];
+
+      params = params.filter(param => {
+        return !param.startsWith("solved=");
+      });
+
+      if (newStatus && newStatus !== "all") {
+        newStatus = newStatus === "solved" ? "yes" : "no";
+        params.push(`solved=${newStatus}`);
+      }
+
+      queryStrings = params.length > 0 ? `?${params.join("&")}` : "";
+      DiscourseUrl.routeTo(
+        `${location.pathname}${queryStrings}${location.hash}`
+      );
+    }
+  }
+};
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 16abec3..e1ab2d9 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -18,6 +18,10 @@ en:
         other: "solutions"
       accepted_html: "{{icon}} Solved <span class='by'>by <a href data-user-card='{{username_lower}}'>{{username}}</a></span> in <a href='{{post_path}}' class='back'>post #{{post_number}}</a>"
       accepted_notification: "<p><span>{{username}}</span> {{description}}</p>"
+      topic_status_filter:
+        all: "all"
+        solved: "solved"
+        unsolved: "unsolved"
 
     topic_statuses:
       solved:
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 25ccf27..752ec08 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -6,6 +6,7 @@ en:
     empty_box_on_unsolved: "Display an empty box next to unsolved topics"
     solved_quote_length: "Number of characters to quote when displaying the solution under the first post"
     solved_topics_auto_close_hours: "Auto close topic (n) hours after the last reply once the topic has been marked as solved"
+    show_filter_by_solved_status: "Show a dropdown to filter a topic list by solved status."
   reports:
     accepted_solutions:
       title: "Accepted solutions"
diff --git a/config/settings.yml b/config/settings.yml
index da6d43e..5bbd6c6 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -16,3 +16,6 @@ plugins:
     client: false
   solved_topics_auto_close_hours:
     default: 0
+  show_filter_by_solved_status:
+    default: false
+    client: true
diff --git a/plugin.rb b/plugin.rb
index 68af667..3de7396 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -324,6 +324,19 @@ SQL
     end
   end
 
+  require_dependency 'basic_category_serializer'
+  class ::BasicCategorySerializer
+    attributes :custom_fields
+
+    def custom_fields
+      object.custom_fields.slice("enable_accepted_answers")
+    end
+
+    def include_custom_fields?
+      custom_fields.present?
+    end
+  end
+
   require_dependency 'topic_view_serializer'
   class ::TopicViewSerializer
     attributes :accepted_answer
diff --git a/spec/models/site_spec.rb b/spec/models/site_spec.rb
new file mode 100644
index 0000000..09cfac3
--- /dev/null
+++ b/spec/models/site_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+require_dependency 'site'
+
+describe Site do
+  let(:category) { Fabricate(:category) }
+  let(:guardian) { Guardian.new }
+
+  it "includes `enable_accepted_answers` custom field for categories" do
+    category.custom_fields["enable_accepted_answers"] = true
+    category.save_custom_fields
+
+    json = Site.json_for(guardian)
+
+    expect(json).to include("enable_accepted_answers")
+  end
+end

GitHub sha: 5ddd644e


Follow Up #2

#7

#9

It’s done.


Approved #10