FEATURE: Add solved icon for topic titles in search results and other topic lists

approved

#1

FEATURE: Add solved icon for topic titles in search results and other topic lists

diff --git a/app/serializers/concerns/topic_answer_mixin.rb b/app/serializers/concerns/topic_answer_mixin.rb
new file mode 100644
index 0000000..23c8a22
--- /dev/null
+++ b/app/serializers/concerns/topic_answer_mixin.rb
@@ -0,0 +1,19 @@
+module TopicAnswerMixin
+  def self.included(klass)
+    klass.attributes :has_accepted_answer, :can_have_answer
+  end
+
+  def has_accepted_answer
+    object.custom_fields["accepted_answer_post_id"] ? true : false
+  end
+
+  def can_have_answer
+    return true if SiteSetting.allow_solved_on_all_topics
+    return false if object.closed || object.archived
+    return scope.allow_accepted_answers_on_category?(object.category_id)
+  end
+
+  def include_can_have_answer?
+    SiteSetting.empty_box_on_unsolved
+  end
+end
diff --git a/assets/javascripts/discourse/initializers/extend-for-solved-button.js.es6 b/assets/javascripts/discourse/initializers/extend-for-solved-button.js.es6
index 1aa1fe2..ae8391d 100644
--- a/assets/javascripts/discourse/initializers/extend-for-solved-button.js.es6
+++ b/assets/javascripts/discourse/initializers/extend-for-solved-button.js.es6
@@ -1,6 +1,7 @@
 import Topic from "discourse/models/topic";
 import User from "discourse/models/user";
 import TopicStatus from "discourse/raw-views/topic-status";
+import TopicStatusIcons from "discourse/helpers/topic-status-icons";
 import { popupAjaxError } from "discourse/lib/ajax-error";
 import { withPluginApi } from "discourse/lib/plugin-api";
 import { ajax } from "discourse/lib/ajax";
@@ -64,6 +65,12 @@ function acceptPost(post) {
 function initializeWithApi(api) {
   const currentUser = api.getCurrentUser();
 
+  TopicStatusIcons.addObject([
+    "has_accepted_answer",
+    "check-square-o",
+    "solved"
+  ]);
+
   api.includePostAttributes(
     "can_accept_answer",
     "can_unaccept_answer",
@@ -219,7 +226,7 @@ export default {
           results.push({
             openTag: "span",
             closeTag: "span",
-            title: I18n.t("solved.has_accepted_answer"),
+            title: I18n.t("topic_statuses.solved.help"),
             icon: "check-square-o"
           });
         } else if (
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 4157acf..b8ec771 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -19,3 +19,7 @@ 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_statuses:
+      solved:
+        help: "This topic has a solution"
diff --git a/plugin.rb b/plugin.rb
index 0514e45..68af667 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -18,6 +18,10 @@ register_asset 'stylesheets/solutions.scss'
 
 after_initialize do
 
+  [
+    '../app/serializers/concerns/topic_answer_mixin.rb'
+  ].each { |path| load File.expand_path(path, __FILE__) }
+
   # we got to do a one time upgrade
   if defined?(UserAction::SOLVED)
     unless $redis.get('solved_already_upgraded')
@@ -479,42 +483,29 @@ SQL
   end
 
   require_dependency 'topic_list_item_serializer'
-  require_dependency 'listable_topic_serializer'
+  require_dependency 'search_topic_list_item_serializer'
+  require_dependency 'suggested_topic_serializer'
+  require_dependency 'category_detailed_serializer'
+  require_dependency 'user_summary_serializer'
 
   class ::TopicListItemSerializer
-    attributes :has_accepted_answer, :can_have_answer
-
-    def has_accepted_answer
-      object.custom_fields["accepted_answer_post_id"] ? true : false
-    end
-
-    def can_have_answer
-      return true if SiteSetting.allow_solved_on_all_topics
-      return false if object.closed || object.archived
-      return scope.allow_accepted_answers_on_category?(object.category_id)
-    end
-
-    def include_can_have_answer?
-      SiteSetting.empty_box_on_unsolved
-    end
+    include TopicAnswerMixin
   end
 
-  class ::ListableTopicSerializer
-    attributes :has_accepted_answer, :can_have_answer
+  class ::SearchTopicListItemSerializer
+    include TopicAnswerMixin
+  end
 
-    def has_accepted_answer
-      object.custom_fields["accepted_answer_post_id"] ? true : false
-    end
+  class ::SuggestedTopicSerializer
+    include TopicAnswerMixin
+  end
 
-    def can_have_answer
-      return true if SiteSetting.allow_solved_on_all_topics
-      return false if object.closed || object.archived
-      return scope.allow_accepted_answers_on_category?(object.category_id)
-    end
+  class ::CategoryDetailedSerializer
+    include TopicAnswerMixin
+  end
 
-    def include_can_have_answer?
-      SiteSetting.empty_box_on_unsolved
-    end
+  class ::UserSummarySerializer::TopicSerializer
+    include TopicAnswerMixin
   end
 
   TopicList.preloaded_custom_fields << "accepted_answer_post_id" if TopicList.respond_to? :preloaded_custom_fields

GitHub sha: e9be43be


#2

@vinothkannans I had to remove the mixin from CategoryDetailedSerializer in FIX: Remove topic answer mixin from category detailed serializer. · discourse/discourse-solved@818bff2 · GitHub. The serializers should probably be tested to ensure that the mixin works with each serializer.


Follow Up #3

#5

@vinothkannans

This is causing N+1 queries to /search. Can you have a look at this as well? Thank you :grinning:


#6

Both are done by commits


Approved #7