FIX: Include tags in quick search suggestions (#14080)

FIX: Include tags in quick search suggestions (#14080)

Followup to https://github.com/discourse/discourse/commit/438a7629561397830fa9622f517ecec46e003e82

diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu-results.js b/app/assets/javascripts/discourse/app/widgets/search-menu-results.js
index 6da7139..ca01d7e 100644
--- a/app/assets/javascripts/discourse/app/widgets/search-menu-results.js
+++ b/app/assets/javascripts/discourse/app/widgets/search-menu-results.js
@@ -5,6 +5,7 @@ import { avatarImg } from "discourse/widgets/post";
 import { createWidget } from "discourse/widgets/widget";
 import { dateNode } from "discourse/helpers/node";
 import { emojiUnescape } from "discourse/lib/text";
+import getURL from "discourse-common/lib/get-url";
 import { h } from "virtual-dom";
 import highlightSearch from "discourse/lib/highlight-search";
 import { iconNode } from "discourse-common/lib/icon-library";
@@ -369,37 +370,45 @@ createWidget("search-menu-assistant", {
 
     const content = [];
     const { fullTerm, suggestionKeyword } = attrs;
-    const prefix = fullTerm.split(suggestionKeyword)[0].trim() || null;
+    let prefix = fullTerm.split(suggestionKeyword)[0].trim() || "";
+
+    if (prefix.length) {
+      prefix = `${prefix} `;
+    }
 
     switch (suggestionKeyword) {
       case "#":
-        attrs.results.forEach((category) => {
-          const fullSlug = category.parentCategory
-            ? `#${category.parentCategory.slug}:${category.slug}`
-            : `#${category.slug}`;
-
-          const slug = prefix ? `${prefix} ${fullSlug} ` : `${fullSlug} `;
-
-          content.push(
-            this.attach("search-menu-assistant-item", {
-              prefix: prefix,
-              category,
-              slug,
-            })
-          );
+        attrs.results.forEach((item) => {
+          if (item.model) {
+            const fullSlug = item.model.parentCategory
+              ? `#${item.model.parentCategory.slug}:${item.model.slug}`
+              : `#${item.model.slug}`;
+
+            content.push(
+              this.attach("search-menu-assistant-item", {
+                prefix,
+                category: item.model,
+                slug: `${prefix}${fullSlug} `,
+              })
+            );
+          } else {
+            content.push(
+              this.attach("search-menu-assistant-item", {
+                prefix,
+                tag: item.name,
+                slug: `${prefix}#${item.name} `,
+              })
+            );
+          }
         });
         break;
       case "@":
         attrs.results.forEach((user) => {
-          const slug = prefix
-            ? `${prefix} @${user.username} `
-            : `@${user.username} `;
-
           content.push(
             this.attach("search-menu-assistant-item", {
-              prefix: prefix,
+              prefix,
               user,
-              slug,
+              slug: `${prefix}@${user.username} `,
             })
           );
         });
@@ -407,8 +416,11 @@ createWidget("search-menu-assistant", {
       default:
         suggestionShortcuts.forEach((item) => {
           if (item.includes(suggestionKeyword)) {
-            const slug = prefix ? `${prefix} ${item} ` : `${item} `;
-            content.push(this.attach("search-menu-assistant-item", { slug }));
+            content.push(
+              this.attach("search-menu-assistant-item", {
+                slug: `${prefix}${item} `,
+              })
+            );
           }
         });
         break;
@@ -422,6 +434,7 @@ createWidget("search-menu-assistant-item", {
   tagName: "li.search-menu-assistant-item",
 
   html(attrs) {
+    const prefix = attrs.prefix?.trim();
     if (attrs.category) {
       return h(
         "a.widget-link.search-link",
@@ -431,7 +444,7 @@ createWidget("search-menu-assistant-item", {
           },
         },
         [
-          h("span.search-item-prefix", attrs.prefix),
+          h("span.search-item-prefix", prefix),
           this.attach("category-link", {
             category: attrs.category,
             allowUncategorized: true,
@@ -439,6 +452,20 @@ createWidget("search-menu-assistant-item", {
           }),
         ]
       );
+    } else if (attrs.tag) {
+      return h(
+        "a.widget-link.search-link",
+        {
+          attributes: {
+            href: getURL(`/tag/${attrs.tag}`),
+          },
+        },
+        [
+          h("span.search-item-prefix", prefix),
+          iconNode("tag"),
+          h("span.search-item-tag", attrs.tag),
+        ]
+      );
     } else if (attrs.user) {
       const userResult = [
         avatarImg("small", {
@@ -456,7 +483,7 @@ createWidget("search-menu-assistant-item", {
           },
         },
         [
-          h("span.search-item-prefix", attrs.prefix),
+          h("span.search-item-prefix", prefix),
           h("span.search-item-user", userResult),
         ]
       );
diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu.js b/app/assets/javascripts/discourse/app/widgets/search-menu.js
index f34668e..4dee355 100644
--- a/app/assets/javascripts/discourse/app/widgets/search-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/search-menu.js
@@ -1,5 +1,4 @@
 import { isValidSearchTerm, searchForTerm } from "discourse/lib/search";
-import Category from "discourse/models/category";
 import DiscourseURL from "discourse/lib/url";
 import { createWidget } from "discourse/widgets/widget";
 import discourseDebounce from "discourse-common/lib/debounce";
@@ -7,6 +6,8 @@ import { get } from "@ember/object";
 import getURL from "discourse-common/lib/get-url";
 import { h } from "virtual-dom";
 import { popupAjaxError } from "discourse/lib/ajax-error";
+import { Promise } from "rsvp";
+import { search as searchCategoryTag } from "discourse/lib/category-tag-search";
 import userSearch from "discourse/lib/user-search";
 
 const CATEGORY_SLUG_REGEXP = /(\#[a-zA-Z0-9\-:]*)$/gi;
@@ -59,9 +60,15 @@ const SearchHelper = {
           ""
         );
 
-        searchData.suggestionResults = Category.search(categorySearchTerm);
-        searchData.suggestionKeyword = "#";
-        widget.scheduleRerender();
+        const categoryTagSearch = searchCategoryTag(
+          categorySearchTerm,
+          widget.siteSettings
+        );
+        Promise.resolve(categoryTagSearch).then((results) => {
+          searchData.suggestionResults = results;
+          searchData.suggestionKeyword = "#";
+          widget.scheduleRerender();
+        });
       } else if (matchSuggestions.type === "username") {
         const userSearchTerm = matchSuggestions.usernamesMatch[0].replace(
           "@",
diff --git a/app/assets/javascripts/discourse/tests/acceptance/search-test.js b/app/assets/javascripts/discourse/tests/acceptance/search-test.js
index 982749a..19b333b 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/search-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/search-test.js
@@ -263,6 +263,22 @@ acceptance("Search - with tagging enabled", function (needs) {
 
     assert.equal(tags, "dev slow");
   });
+
+  test("displays tag shortcuts", async function (assert) {
+    await visit("/");
+
+    await click("#search-button");
+
+    await fillIn("#search-term", "dude #monk");
+    await triggerKeyEvent("#search-term", "keyup", 51);
+
+    const firstItem =
+      ".search-menu .results ul.search-menu-assistant .search-link";
+    assert.ok(exists(query(firstItem)));
+
+    const firstTag = query(`${firstItem} .search-item-tag`).innerText.trim();
+    assert.equal(firstTag, "monkey");
+  });
 });
 
 acceptance("Search - assistant", function (needs) {
diff --git a/app/assets/stylesheets/common/base/search-menu.scss b/app/assets/stylesheets/common/base/search-menu.scss
index b6c6926..2c19163 100644
--- a/app/assets/stylesheets/common/base/search-menu.scss
+++ b/app/assets/stylesheets/common/base/search-menu.scss
@@ -267,6 +267,18 @@
       .search-item-prefix {
         margin-right: 0.5em;
       }
+
+      .search-item-tag {
+        color: var(--primary-high);

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

GitHub sha: 052c78381be2cd4783dd1b9159ea6316768864b7

This commit appears in #14080 which was approved by eviltrout. It was merged by pmusaraj.