FEATURE: Setting for topic list pills to link to posts (#39)

FEATURE: Setting for topic list pills to link to posts (#39)

diff --git a/assets/javascripts/discourse/connectors/topic-list-after-title/category-experts-indicator.hbr b/assets/javascripts/discourse/connectors/topic-list-after-title/category-experts-indicator.hbr
index afd28c2..63ef1af 100644
--- a/assets/javascripts/discourse/connectors/topic-list-after-title/category-experts-indicator.hbr
+++ b/assets/javascripts/discourse/connectors/topic-list-after-title/category-experts-indicator.hbr
@@ -1,21 +1 @@
-{{#if context.topic.expert_post_group_names}}
-  {{#each context.topic.expert_post_group_names as |groupName|}}
-    <span class="topic-list-category-expert-tags">
-      <a href="/search?q=with:category_expert_response" class={{groupName}}>
-        {{i18n "category_experts.topic_list.response_by_group" groupName=groupName}}
-      </a>
-    </span>
-  {{/each}}
-{{/if}}
-
-{{#if context.currentUser.staff}}
-  {{#if context.topic.needs_category_expert_post_approval}}
-    <a href="/search?q=with:unapproved_ce_posts" class="topic-list-category-expert-needs-approval">
-      {{i18n "category_experts.topic_list.needs_approval"}}
-    </a>
-  {{/if}}
-{{/if}}
-
-{{#if context.topic.is_category_expert_question}}
-  {{category-expert-question-indicator context.topic context.currentUser}}
-{{/if}}
+{{category-expert-topic-list-indicators context}}
diff --git a/assets/javascripts/discourse/helpers/category-expert-topic-list-indicators.js b/assets/javascripts/discourse/helpers/category-expert-topic-list-indicators.js
new file mode 100644
index 0000000..a9e897e
--- /dev/null
+++ b/assets/javascripts/discourse/helpers/category-expert-topic-list-indicators.js
@@ -0,0 +1,83 @@
+import I18n from "I18n";
+import { registerUnbound } from "discourse-common/lib/helpers";
+import { htmlSafe } from "@ember/template";
+
+export function categoryExpertTopicListIndicators(context) {
+  let html = "";
+
+  html += addApprovedPills(context.topic, context.siteSettings);
+  html += addNeedsApprovalPill(
+    context.topic,
+    context.currentUser,
+    context.siteSettings
+  );
+  html += addIsQuestionPill(
+    context.topic,
+    context.currentUser,
+    context.siteSettings
+  );
+
+  return htmlSafe(html);
+}
+
+const addApprovedPills = (topic, siteSettings) => {
+  let html = "";
+  (topic.expert_post_group_names || []).forEach((groupName) => {
+    const href = siteSettings.category_experts_topic_list_link_to_posts
+      ? `${topic.url}/${topic.first_expert_post_id}`
+      : "/search?q=with:category_expert_response";
+
+    html += `<span class='topic-list-category-expert-tags'>
+    <a href=${href} class=${groupName}>
+    ${I18n.t("category_experts.topic_list.response_by_group", { groupName })}
+    </a>
+    </span>
+    `;
+  });
+
+  return html;
+};
+
+const addNeedsApprovalPill = (topic, currentUser, siteSettings) => {
+  if (
+    currentUser &&
+    currentUser.staff &&
+    topic.needs_category_expert_post_approval
+  ) {
+    const href = siteSettings.category_experts_topic_list_link_to_posts
+      ? `${topic.url}/${topic.needs_category_expert_post_approval}`
+      : "/search?q=with:unapproved_ce_posts";
+    return `
+      <a href=${href} class="topic-list-category-expert-needs-approval">
+      ${I18n.t("category_experts.topic_list.needs_approval")}
+      </a>
+      `;
+  } else {
+    return "";
+  }
+};
+
+const addIsQuestionPill = (topic, currentUser, siteSettings) => {
+  if (
+    topic.is_category_expert_question &&
+    currentUser &&
+    (currentUser.staff ||
+      (topic.creator && topic.creator.id === currentUser.id) ||
+      currentUser.expert_for_category_ids.includes(topic.category_id))
+  ) {
+    const href = siteSettings.category_experts_topic_list_link_to_posts
+      ? topic.url
+      : "/search?q=is:category_expert_question";
+
+    return `<a href=${href} class='topic-list-category-expert-question'>${I18n.t(
+      "category_experts.topic_list.question"
+    )}</a>`;
+  } else {
+    return "";
+  }
+};
+
+registerUnbound(
+  "category-expert-topic-list-indicators",
+  categoryExpertTopicListIndicators
+);
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 5472c90..c27f274 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -7,6 +7,7 @@ en:
     send_admin_category_experts_posts_reminder_pm: "Send reminder PMs to staff, to review unapproved category expert posts."
     approve_past_posts_on_becoming_category_expert: "When a user becomes an expert, all their past posts in the category are automatically approved. When a user is removed as an expert, all their posts are unapproved."
     max_category_expert_endorsements_per_day: "The maximum number of endorsements a user is allowed in a day"
+    category_experts_topic_list_link_to_posts: "Topic list pills link to posts instead of search"
     tl2_additional_category_expert_endorsements_per_day_multiplier: "Increase limit of endorsements per day for tl2 (member) by multiplying with this number"
     tl3_additional_category_expert_endorsements_per_day_multiplier: "Increase limit of endorsements per day for tl3 (regular) by multiplying with this number"
     tl4_additional_category_expert_endorsements_per_day_multiplier: "Increase limit of endorsements per day for tl4 (leader) by multiplying with this number"
diff --git a/config/settings.yml b/config/settings.yml
index 43e9f54..666c8bb 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -14,6 +14,9 @@ plugins:
     default: false
   approve_past_posts_on_becoming_category_expert:
     default: false
+  category_experts_topic_list_link_to_posts:
+    default: false
+    client: true
   max_category_expert_endorsements_per_day:
     default: 1
     min: 1
diff --git a/db/post_migrate/20210614050646_change_boolean_custom_fields_to_ints.rb b/db/post_migrate/20210614050646_change_boolean_custom_fields_to_ints.rb
new file mode 100644
index 0000000..df0d954
--- /dev/null
+++ b/db/post_migrate/20210614050646_change_boolean_custom_fields_to_ints.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+class ChangeBooleanCustomFieldsToInts < ActiveRecord::Migration[6.1]
+  def up
+    set_topic_first_approved_post_ids
+    set_topic_needs_approval_to_int
+  end
+
+  def set_topic_needs_approval_to_int
+    topic_needs_approval_custom_field_ids = execute("SELECT topic_id FROM topic_custom_fields WHERE name='category_expert_topic_post_needs_approval' AND value = 't'").values.flatten
+
+    topic_needs_approval_custom_field_ids.each do |topic_id|
+      post_number = execute(<<~SQL
+        SELECT p.post_number
+        FROM posts AS p
+        INNER JOIN post_custom_fields AS pcf ON pcf.post_id = p.id AND p.topic_id = #{topic_id}
+        WHERE pcf.name='category_expert_post_pending' AND value = 't'
+        ORDER BY pcf.id ASC
+        LIMIT 1
+                        SQL
+                       )
+
+      if post_number.count > 0
+        # We have a custom field, now update the 't' to be the post_id.
+        execute(<<~SQL
+                UPDATE topic_custom_fields
+                SET value = #{post_number.getvalue(0, 0)}
+                WHERE topic_id = #{topic_id}
+                AND name = 'category_expert_topic_post_needs_approval'
+                SQL
+               )
+      end
+
+    end
+    # Now set all 'f' values to integer 0
+    execute(<<~SQL
+              UPDATE topic_custom_fields
+              SET value = 0
+              WHERE value = 'f'
+              AND name = 'category_expert_topic_post_needs_approval'
+            SQL
+           )
+  end
+
+  def set_topic_first_approved_post_ids
+    topic_approved_custom_field_ids = execute("SELECT topic_id FROM topic_custom_fields WHERE name='category_expert_topic_approved_group_names' AND value IS NOT NULL")
+
+    topic_approved_custom_field_ids.values.flatten.each do |topic_id|
+      post_number = execute(<<~SQL
+        SELECT p.post_number
+        FROM posts AS p

[... diff too long, it was truncated ...]

GitHub sha: 67200af52aca9102ad2563d67747dfa1bb287e50

This commit appears in #39 which was approved by davidtaylorhq. It was merged by markvanlan.