diff --git a/app/assets/javascripts/discourse/lib/search.js.es6 b/app/assets/javascripts/discourse/lib/search.js.es6 index a3f47f7a12e..32c226228d4 100644 --- a/app/assets/javascripts/discourse/lib/search.js.es6 +++ b/app/assets/javascripts/discourse/lib/search.js.es6 @@ -16,6 +16,7 @@ export function translateResults(results, opts) { results.posts = results.posts || []; results.categories = results.categories || []; results.tags = results.tags || []; + results.groups = results.groups || []; const topicMap = {}; results.topics = results.topics.map(function(topic) { @@ -43,6 +44,17 @@ export function translateResults(results, opts) { }) .compact(); + results.groups = results.groups + .map(group => { + const groupName = Handlebars.Utils.escapeExpression(group.name); + return { + id: group.id, + name: groupName, + url: Discourse.getURL(`/g/${groupName}`) + }; + }) + .compact(); + results.tags = results.tags .map(function(tag) { const tagName = Handlebars.Utils.escapeExpression(tag.name); @@ -62,7 +74,8 @@ export function translateResults(results, opts) { ["topic", "posts"], ["category", "categories"], ["tag", "tags"], - ["user", "users"] + ["user", "users"], + ["group", "groups"] ].forEach(function(pair) { const type = pair[0]; const name = pair[1]; diff --git a/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6 b/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6 index 19d642057df..69ecf7ace58 100644 --- a/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6 +++ b/app/assets/javascripts/discourse/widgets/search-menu-results.js.es6 @@ -5,6 +5,7 @@ import { createWidget } from "discourse/widgets/widget"; import { h } from "virtual-dom"; import highlightText from "discourse/lib/highlight-text"; import { escapeExpression, formatUsername } from "discourse/lib/utilities"; +import { iconNode } from "discourse-common/lib/icon-library"; class Highlighted extends RawHtml { constructor(html, term) { @@ -26,15 +27,15 @@ function createSearchResult({ type, linkField, builder }) { let searchResultId; if (type === "topic") { - searchResultId = r.get("topic_id"); + searchResultId = r.topic_id; } else { - searchResultId = r.get("id"); + searchResultId = r.id; } return h( "li.item", this.attach("link", { - href: r.get(linkField), + href: r[linkField], contents: () => builder.call(this, r, attrs.term), className: "search-link", searchResultId, @@ -68,12 +69,11 @@ createSearchResult({ type: "tag", linkField: "url", builder(t) { - const tag = escapeExpression(t.get("id")); + const tag = escapeExpression(t.id); return h( - "a", + "span", { - attributes: { href: t.get("url") }, - className: `widget-link search-link tag-${tag} discourse-tag ${ + className: `tag-${tag} discourse-tag ${ Discourse.SiteSettings.tag_style }` }, @@ -112,6 +112,21 @@ createSearchResult({ } }); +createSearchResult({ + type: "group", + linkField: "url", + builder(group) { + const groupName = escapeExpression(group.name); + return h( + "span", + { + className: `group-${groupName} discourse-group` + }, + [iconNode("users"), h("span", groupName)] + ); + } +}); + createSearchResult({ type: "topic", linkField: "url", diff --git a/app/assets/stylesheets/common/base/search-menu.scss b/app/assets/stylesheets/common/base/search-menu.scss index e008e56dc1e..a38e5b168ec 100644 --- a/app/assets/stylesheets/common/base/search-menu.scss +++ b/app/assets/stylesheets/common/base/search-menu.scss @@ -38,6 +38,8 @@ flex-direction: row; .list { + min-width: 100px; + .item { .blurb { // https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ @@ -97,6 +99,25 @@ } } + .search-result-group { + .search-link { + color: $primary-high; + + &:hover { + color: $primary; + } + } + + .discourse-group { + display: inline-block; + word-break: break-all; + + .d-icon { + margin-right: s(1); + } + } + } + .search-result-user { .user-result { display: flex; diff --git a/app/serializers/grouped_search_result_serializer.rb b/app/serializers/grouped_search_result_serializer.rb index b9dcb5b62ce..b6dac302143 100644 --- a/app/serializers/grouped_search_result_serializer.rb +++ b/app/serializers/grouped_search_result_serializer.rb @@ -3,6 +3,7 @@ class GroupedSearchResultSerializer < ApplicationSerializer has_many :users, serializer: SearchResultUserSerializer has_many :categories, serializer: BasicCategorySerializer has_many :tags, serializer: TagSerializer + has_many :groups, serializer: BasicGroupSerializer attributes :more_posts, :more_users, :more_categories, :term, :search_log_id, :more_full_page_results, :can_create_topic def search_log_id diff --git a/lib/search.rb b/lib/search.rb index 9ec30068410..56ebc31a431 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -598,6 +598,7 @@ class Search user_search if @term.present? category_search if @term.present? tags_search if @term.present? + groups_search if @term.present? end topic_search end @@ -683,6 +684,14 @@ class Search end end + def groups_search + groups = Group + .visible_groups(@guardian.user, "name ASC", include_everyone: false) + .where("groups.name ILIKE ?", "%#{@term}%") + + groups.each { |group| @results.add(group) } + end + def tags_search return unless SiteSetting.tagging_enabled diff --git a/lib/search/grouped_search_results.rb b/lib/search/grouped_search_results.rb index 1f81e43e2c9..68c257f3635 100644 --- a/lib/search/grouped_search_results.rb +++ b/lib/search/grouped_search_results.rb @@ -15,6 +15,7 @@ class Search :categories, :users, :tags, + :groups, :more_posts, :more_categories, :more_users, @@ -36,6 +37,7 @@ class Search @categories = [] @users = [] @tags = [] + @groups = [] end def find_user_data(guardian) diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 159ba501533..993a2597196 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -430,6 +430,38 @@ describe Search do end + context 'groups' do + def search(user = Fabricate(:user)) + Search.execute(group.name, guardian: Guardian.new(user)) + end + + let!(:group) { Group[:trust_level_0] } + + it 'shows group' do + expect(search.groups.map(&:name)).to eq([group.name]) + end + + context 'group visibility' do + let!(:group) { Fabricate(:group) } + + before do + group.update!(visibility_level: 3) + end + + context 'staff logged in' do + it 'shows group' do + expect(search(Fabricate(:admin)).groups.map(&:name)).to eq([group.name]) + end + end + + context 'non staff logged in' do + it 'shows doesn’t show group' do + expect(search.groups.map(&:name)).to be_empty + end + end + end + end + context 'tags' do def search Search.execute(tag.name)