search_suggestion: Show profile pictures in autocomplete suggestions.

Because the typeahead.js list items are currently just text, a user's
full name and avatar should be displayed in `input_pill`. To use
`input_pill`, a separate Handlebars partial view was created to
provide a mandatory container (`<div class="pill-container">`) for
`input_pill` and a flex container (`<div class="search_list_item">`)
for vertically aligning the text.

The description of each suggestion (i.e `description_html`) is
rendered as raw HTML, so every special character (e.g. whitespace)
should be HTML-escaped. This enables highlighting the substring in
each search suggestion that matches the query.

Fixes: #20267
This commit is contained in:
Oliver Pham
2021-12-06 17:44:26 -05:00
committed by Tim Abbott
parent 9cc8a2bc80
commit 2ed650f596
9 changed files with 335 additions and 134 deletions

View File

@@ -30,9 +30,9 @@ const search_pill = zrequire("search_pill");
const {Filter} = zrequire("../js/filter");
function test(label, f) {
run_test(label, ({override}) => {
run_test(label, ({override, mock_template}) => {
page_params.search_pills_enabled = true;
f({override});
f({override, mock_template});
});
}
@@ -81,12 +81,23 @@ test("update_button_visibility", () => {
assert.ok(!$search_button.prop("disabled"));
});
test("initialize", () => {
test("initialize", ({mock_template}) => {
const $search_query_box = $("#search_query");
const $searchbox_form = $("#searchbox_form");
const $search_button = $(".search_button");
const $searchbox = $("#searchbox");
mock_template("search_list_item.hbs", true, (data, html) => {
assert.equal(typeof data.description_html, "string");
if (data.is_person) {
assert.equal(typeof data.user_pill_context.id, "number");
assert.equal(typeof data.user_pill_context.display_value, "string");
assert.equal(typeof data.user_pill_context.has_image, "boolean");
assert.equal(typeof data.user_pill_context.img_src, "string");
}
return html;
});
$search_query_box[0] = "stub";
search_pill.get_search_string_for_current_filter = () => "is:starred";
@@ -106,7 +117,7 @@ test("initialize", () => {
[
"stream:Verona",
{
description_html: "Stream <strong>Ver</strong>ona",
description_html: "Stream&nbsp;<strong>Ver</strong>ona",
search_string: "stream:Verona",
},
],
@@ -128,16 +139,98 @@ test("initialize", () => {
assert.equal(source, expected_source_value);
/* Test highlighter */
let expected_value = "Search for ver";
let expected_value = `<div class="search_list_item">\n Search for ver\n</div>\n`;
assert.equal(opts.highlighter(source[0]), expected_value);
expected_value = "Stream <strong>Ver</strong>ona";
expected_value = `<div class="search_list_item">\n Stream&nbsp;<strong>Ver</strong>ona\n</div>\n`;
assert.equal(opts.highlighter(source[1]), expected_value);
/* Test sorter */
assert.equal(opts.sorter(search_suggestions.strings), search_suggestions.strings);
}
{
const search_suggestions = {
lookup_table: new Map([
[
"group-pm-with:zo",
{
description_html: "group private messages including",
is_person: true,
search_string: "group-pm-with:user7@zulipdev.com",
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1&s=50",
},
},
],
[
"pm-with:zo",
{
description_html: "private messages with",
is_person: true,
search_string: "pm-with:user7@zulipdev.com",
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1&s=50",
},
},
],
[
"sender:zo",
{
description_html: "sent by",
is_person: true,
search_string: "sender:user7@zulipdev.com",
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1&s=50",
},
},
],
[
"zo",
{
description_html: "Search for zo",
search_string: "zo",
},
],
]),
strings: ["zo", "sender:zo", "pm-with:zo", "group-pm-with:zo"],
};
/* Test source */
search_suggestion.get_suggestions = () => search_suggestions;
const expected_source_value = search_suggestions.strings;
const source = opts.source("zo");
assert.equal(source, expected_source_value);
/* Test highlighter */
let expected_value = `<div class="search_list_item">\n Search for zo\n</div>\n`;
assert.equal(opts.highlighter(source[0]), expected_value);
expected_value = `<div class="search_list_item">\n sent by\n <span class="pill-container pill-container-btn">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1&amp;s&#x3D;50" />\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</span>\n <div class="exit">\n <span aria-hidden="true">&times;</span>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter(source[1]), expected_value);
expected_value = `<div class="search_list_item">\n private messages with\n <span class="pill-container pill-container-btn">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1&amp;s&#x3D;50" />\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</span>\n <div class="exit">\n <span aria-hidden="true">&times;</span>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter(source[2]), expected_value);
expected_value = `<div class="search_list_item">\n group private messages including\n <span class="pill-container pill-container-btn">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1&amp;s&#x3D;50" />\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</span>\n <div class="exit">\n <span aria-hidden="true">&times;</span>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter(source[3]), expected_value);
/* Test sorter */
assert.equal(opts.sorter(search_suggestions.strings), search_suggestions.strings);
}
{
let operators;
let is_blurred;

View File

@@ -73,11 +73,22 @@ run_test("update_button_visibility", () => {
assert.ok(!$search_button.prop("disabled"));
});
run_test("initialize", () => {
run_test("initialize", ({mock_template}) => {
const $search_query_box = $("#search_query");
const $searchbox_form = $("#searchbox_form");
const $search_button = $(".search_button");
mock_template("search_list_item.hbs", true, (data, html) => {
assert.equal(typeof data.description_html, "string");
if (data.is_person) {
assert.equal(typeof data.user_pill_context.id, "number");
assert.equal(typeof data.user_pill_context.display_value, "string");
assert.equal(typeof data.user_pill_context.has_image, "boolean");
assert.equal(typeof data.user_pill_context.img_src, "string");
}
return html;
});
search_suggestion.max_num_of_search_results = 999;
$search_query_box.typeahead = (opts) => {
assert.equal(opts.fixed, true);
@@ -92,7 +103,7 @@ run_test("initialize", () => {
[
"stream:Verona",
{
description_html: "Stream <strong>Ver</strong>ona",
description_html: "Stream&nbsp;<strong>Ver</strong>ona",
search_string: "stream:Verona",
},
],
@@ -114,16 +125,98 @@ run_test("initialize", () => {
assert.equal(source, expected_source_value);
/* Test highlighter */
let expected_value = "Search for ver";
let expected_value = `<div class="search_list_item">\n Search for ver\n</div>\n`;
assert.equal(opts.highlighter(source[0]), expected_value);
expected_value = "Stream <strong>Ver</strong>ona";
expected_value = `<div class="search_list_item">\n Stream&nbsp;<strong>Ver</strong>ona\n</div>\n`;
assert.equal(opts.highlighter(source[1]), expected_value);
/* Test sorter */
assert.equal(opts.sorter(search_suggestions.strings), search_suggestions.strings);
}
{
const search_suggestions = {
lookup_table: new Map([
[
"group-pm-with:zo",
{
description_html: "group private messages including",
is_person: true,
search_string: "group-pm-with:user7@zulipdev.com",
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1&s=50",
},
},
],
[
"pm-with:zo",
{
description_html: "private messages with",
is_person: true,
search_string: "pm-with:user7@zulipdev.com",
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1&s=50",
},
},
],
[
"sender:zo",
{
description_html: "sent by",
is_person: true,
search_string: "sender:user7@zulipdev.com",
user_pill_context: {
display_value: "<strong>Zo</strong>e",
has_image: true,
id: 7,
img_src:
"https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d=identicon&version=1&s=50",
},
},
],
[
"zo",
{
description_html: "Search for zo",
search_string: "zo",
},
],
]),
strings: ["zo", "sender:zo", "pm-with:zo", "group-pm-with:zo"],
};
/* Test source */
search_suggestion.get_suggestions = () => search_suggestions;
const expected_source_value = search_suggestions.strings;
const source = opts.source("zo");
assert.equal(source, expected_source_value);
/* Test highlighter */
let expected_value = `<div class="search_list_item">\n Search for zo\n</div>\n`;
assert.equal(opts.highlighter(source[0]), expected_value);
expected_value = `<div class="search_list_item">\n sent by\n <span class="pill-container pill-container-btn">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1&amp;s&#x3D;50" />\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</span>\n <div class="exit">\n <span aria-hidden="true">&times;</span>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter(source[1]), expected_value);
expected_value = `<div class="search_list_item">\n private messages with\n <span class="pill-container pill-container-btn">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1&amp;s&#x3D;50" />\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</span>\n <div class="exit">\n <span aria-hidden="true">&times;</span>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter(source[2]), expected_value);
expected_value = `<div class="search_list_item">\n group private messages including\n <span class="pill-container pill-container-btn">\n <div class='pill ' tabindex=0>\n <img class="pill-image" src="https://secure.gravatar.com/avatar/0f030c97ab51312c7bbffd3966198ced?d&#x3D;identicon&amp;version&#x3D;1&amp;s&#x3D;50" />\n <span class="pill-value">&lt;strong&gt;Zo&lt;/strong&gt;e</span>\n <div class="exit">\n <span aria-hidden="true">&times;</span>\n </div>\n</div>\n </span>\n</div>\n`;
assert.equal(opts.highlighter(source[3]), expected_value);
/* Test sorter */
assert.equal(opts.sorter(search_suggestions.strings), search_suggestions.strings);
}
{
let operators;
let is_blurred;

View File

@@ -2,7 +2,7 @@
const {strict: assert} = require("assert");
const {mock_esm, with_overrides, zrequire} = require("../zjsunit/namespace");
const {mock_esm, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const {page_params} = require("../zjsunit/zpage_params");
@@ -51,6 +51,7 @@ const jeff = {
};
const noop = () => {};
const example_avatar_url = "http://example.com/example.png";
function init() {
page_params.is_admin = true;
@@ -831,6 +832,7 @@ function people_suggestion_setup() {
email: "bob@zulip.com",
user_id: 202,
full_name: "Bob Térry",
avatar_url: example_avatar_url,
};
people.add_active_user(bob);
@@ -860,16 +862,35 @@ test("people_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
const describe = (q) => suggestions.lookup_table.get(q).description_html;
const is_person = (q) => suggestions.lookup_table.get(q).is_person;
assert.equal(is_person("pm-with:ted@zulip.com"), true);
assert.equal(is_person("sender:ted@zulip.com"), true);
assert.equal(is_person("group-pm-with:ted@zulip.com"), true);
assert.equal(
describe("pm-with:ted@zulip.com"),
"Private messages with <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;",
);
assert.equal(
describe("sender:ted@zulip.com"),
"Sent by <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;",
);
const has_image = (q) => suggestions.lookup_table.get(q).user_pill_context.has_image;
assert.equal(has_image("pm-with:bob@zulip.com"), true);
assert.equal(has_image("sender:bob@zulip.com"), true);
assert.equal(has_image("group-pm-with:bob@zulip.com"), true);
const describe = (q) => suggestions.lookup_table.get(q).description_html;
assert.equal(describe("pm-with:ted@zulip.com"), "Private messages with");
assert.equal(describe("sender:ted@zulip.com"), "Sent by");
assert.equal(describe("group-pm-with:ted@zulip.com"), "Group private messages including");
let expectedString = "<strong>Te</strong>d Smith";
const get_full_name = (q) =>
suggestions.lookup_table.get(q).user_pill_context.display_value.string;
assert.equal(get_full_name("sender:ted@zulip.com"), expectedString);
assert.equal(get_full_name("pm-with:ted@zulip.com"), expectedString);
assert.equal(get_full_name("group-pm-with:ted@zulip.com"), expectedString);
expectedString = `${example_avatar_url}?s=50`;
const get_avatar_url = (q) => suggestions.lookup_table.get(q).user_pill_context.img_src;
assert.equal(get_avatar_url("pm-with:bob@zulip.com"), expectedString);
assert.equal(get_avatar_url("sender:bob@zulip.com"), expectedString);
assert.equal(get_avatar_url("group-pm-with:bob@zulip.com"), expectedString);
suggestions = get_suggestions("", "Ted "); // note space
expected = [
@@ -906,39 +927,6 @@ test("people_suggestions", ({override}) => {
assert.deepEqual(suggestions.strings, expected);
});
test("people_suggestion (Admin only email visibility)", ({override}) => {
/* Suggestions when realm_email_address_visibility is set to admin
only */
override(narrow_state, "stream", noop);
people_suggestion_setup();
const query = "te";
const suggestions = with_overrides(({override}) => {
override(page_params, "is_admin", false);
return get_suggestions("", query);
});
const expected = [
"te",
"sender:bob@zulip.com",
"sender:ted@zulip.com",
"pm-with:bob@zulip.com", // bob térry
"pm-with:ted@zulip.com",
"group-pm-with:bob@zulip.com",
"group-pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
const describe = (q) => suggestions.lookup_table.get(q).description_html;
assert.equal(
describe("pm-with:ted@zulip.com"),
"Private messages with <strong>Te</strong>d Smith",
);
assert.equal(describe("sender:ted@zulip.com"), "Sent by <strong>Te</strong>d Smith");
});
test("operator_suggestions", ({override}) => {
override(narrow_state, "stream", () => undefined);

View File

@@ -2,7 +2,7 @@
const {strict: assert} = require("assert");
const {mock_esm, with_overrides, zrequire} = require("../zjsunit/namespace");
const {mock_esm, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const {page_params} = require("../zjsunit/zpage_params");
@@ -50,6 +50,8 @@ const jeff = {
full_name: "Jeff Zoolipson",
};
const example_avatar_url = "http://example.com/example.png";
function init() {
page_params.is_admin = true;
page_params.search_pills_enabled = false;
@@ -805,6 +807,7 @@ test("people_suggestions", ({override}) => {
email: "bob@zulip.com",
user_id: 202,
full_name: "Bob Térry",
avatar_url: example_avatar_url,
};
const alice = {
@@ -829,17 +832,45 @@ test("people_suggestions", ({override}) => {
];
assert.deepEqual(suggestions.strings, expected);
function is_person(q) {
return suggestions.lookup_table.get(q).is_person;
}
assert.equal(is_person("pm-with:ted@zulip.com"), true);
assert.equal(is_person("sender:ted@zulip.com"), true);
assert.equal(is_person("group-pm-with:ted@zulip.com"), true);
function has_image(q) {
return suggestions.lookup_table.get(q).user_pill_context.has_image;
}
assert.equal(has_image("pm-with:bob@zulip.com"), true);
assert.equal(has_image("sender:bob@zulip.com"), true);
assert.equal(has_image("group-pm-with:bob@zulip.com"), true);
function describe(q) {
return suggestions.lookup_table.get(q).description_html;
}
assert.equal(
describe("pm-with:ted@zulip.com"),
"Private messages with <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;",
);
assert.equal(
describe("sender:ted@zulip.com"),
"Sent by <strong>Te</strong>d Smith &lt;<strong>te</strong>d@zulip.com&gt;",
);
assert.equal(describe("pm-with:ted@zulip.com"), "Private messages with");
assert.equal(describe("sender:ted@zulip.com"), "Sent by");
assert.equal(describe("group-pm-with:ted@zulip.com"), "Group private messages including");
let expectedString = "<strong>Te</strong>d Smith";
function get_full_name(q) {
return suggestions.lookup_table.get(q).user_pill_context.display_value.string;
}
assert.equal(get_full_name("sender:ted@zulip.com"), expectedString);
assert.equal(get_full_name("pm-with:ted@zulip.com"), expectedString);
assert.equal(get_full_name("group-pm-with:ted@zulip.com"), expectedString);
expectedString = example_avatar_url + "?s=50";
function get_avatar_url(q) {
return suggestions.lookup_table.get(q).user_pill_context.img_src;
}
assert.equal(get_avatar_url("pm-with:bob@zulip.com"), expectedString);
assert.equal(get_avatar_url("sender:bob@zulip.com"), expectedString);
assert.equal(get_avatar_url("group-pm-with:bob@zulip.com"), expectedString);
suggestions = get_suggestions("", "Ted "); // note space
@@ -926,59 +957,3 @@ test("queries_with_spaces", () => {
expected = ["stream:offi", "stream:office"];
assert.deepEqual(suggestions.strings, expected);
});
function people_suggestion_setup() {
const ted = {
email: "ted@zulip.com",
user_id: 201,
full_name: "Ted Smith",
};
people.add_active_user(ted);
const bob = {
email: "bob@zulip.com",
user_id: 202,
full_name: "Bob Térry",
};
people.add_active_user(bob);
const alice = {
email: "alice@zulip.com",
user_id: 203,
full_name: "Alice Ignore",
};
people.add_active_user(alice);
}
test("people_suggestion (Admin only email visibility)", ({override}) => {
/* Suggestions when realm_email_address_visibility is set to admin
only */
override(narrow_state, "stream", () => {});
people_suggestion_setup();
const query = "te";
const suggestions = with_overrides(({override}) => {
override(page_params, "is_admin", false);
return get_suggestions("", query);
});
const expected = [
"te",
"sender:bob@zulip.com",
"sender:ted@zulip.com",
"pm-with:bob@zulip.com", // bob térry
"pm-with:ted@zulip.com",
"group-pm-with:bob@zulip.com",
"group-pm-with:ted@zulip.com",
];
assert.deepEqual(suggestions.strings, expected);
const describe = (q) => suggestions.lookup_table.get(q).description_html;
assert.equal(
describe("pm-with:ted@zulip.com"),
"Private messages with <strong>Te</strong>d Smith",
);
assert.equal(describe("sender:ted@zulip.com"), "Sent by <strong>Te</strong>d Smith");
});

View File

@@ -1,5 +1,7 @@
import $ from "jquery";
import render_search_list_item from "../templates/search_list_item.hbs";
import {Filter} from "./filter";
import * as message_view_header from "./message_view_header";
import * as narrow from "./narrow";
@@ -94,7 +96,7 @@ export function initialize() {
naturalSearch: true,
highlighter(item) {
const obj = search_map.get(item);
return obj.description_html;
return render_search_list_item(obj);
},
matcher() {
return true;

View File

@@ -6,7 +6,6 @@ import * as huddle_data from "./huddle_data";
import * as narrow_state from "./narrow_state";
import {page_params} from "./page_params";
import * as people from "./people";
import * as settings_data from "./settings_data";
import * as stream_data from "./stream_data";
import * as stream_topic_history from "./stream_topic_history";
import * as stream_topic_history_util from "./stream_topic_history_util";
@@ -22,15 +21,22 @@ function make_person_highlighter(query) {
const highlight_query = typeahead_helper.make_query_highlighter(query);
return function (person) {
if (settings_data.show_email()) {
return (
highlight_query(person.full_name) + " &lt;" + highlight_query(person.email) + "&gt;"
);
}
return highlight_query(person.full_name);
};
}
function highlight_person(person, highlighter) {
const avatar_url = people.small_avatar_url_for_person(person);
const highlighted_name = highlighter(person);
return {
id: person.user_id,
display_value: new Handlebars.SafeString(highlighted_name),
has_image: true,
img_src: avatar_url,
};
}
function match_criteria(operators, criteria) {
const filter = new Filter(operators);
return criteria.some((cr) => {
@@ -115,7 +121,7 @@ function get_stream_suggestions(last, operators) {
const prefix = "stream";
const highlighted_stream = highlight_query(regex, stream);
const verb = last.negated ? "exclude " : "";
const description_html = verb + prefix + " " + highlighted_stream;
const description_html = verb + prefix + "&nbsp;" + highlighted_stream;
const term = {
operator: "stream",
operand: stream,
@@ -178,15 +184,24 @@ function get_group_suggestions(last, operators) {
operand: all_but_last_part + "," + person.email,
negated,
};
const name = person_highlighter(person);
// Note that description_html won't contain the user's
// identity; that instead will be rendered in the separate
// user pill.
const description_html =
prefix + " " + Handlebars.Utils.escapeExpression(all_but_last_part) + "," + name;
prefix + Handlebars.Utils.escapeExpression(" " + all_but_last_part + ",");
let terms = [term];
if (negated) {
terms = [{operator: "is", operand: "private"}, term];
}
const search_string = Filter.unparse(terms);
return {description_html, search_string};
return {
description_html,
search_string: Filter.unparse(terms),
is_person: true,
user_pill_context: highlight_person(person, person_highlighter),
};
});
return suggestions;
@@ -254,8 +269,6 @@ function get_person_suggestions(people_getter, last, operators, autocomplete_ope
const person_highlighter = make_person_highlighter(query);
const objs = persons.map((person) => {
const name = person_highlighter(person);
const description_html = prefix + " " + name;
const terms = [
{
operator: autocomplete_operator,
@@ -268,8 +281,13 @@ function get_person_suggestions(people_getter, last, operators, autocomplete_ope
// because we assume the user still wants to narrow to PMs
terms.unshift({operator: "is", operand: "private"});
}
const search_string = Filter.unparse(terms);
return {description_html, search_string};
return {
description_html: prefix,
search_string: Filter.unparse(terms),
is_person: true,
user_pill_context: highlight_person(person, person_highlighter),
};
});
return objs;

View File

@@ -73,6 +73,20 @@
}
}
&.pill-container-btn {
cursor: pointer;
padding: 0;
.pill {
margin: 0;
border: none;
.exit {
display: none;
}
}
}
.input {
display: inline-block;
padding: 2px 4px;

View File

@@ -751,6 +751,16 @@ strong {
hsl(200, 100%, 35%)
);
}
.search_list_item {
display: flex;
align-items: center;
}
.search_list_item .pill-container {
margin-left: 5px;
}
/* styles defined for user_circle here only deal with positioning of user_presence_circle
in typeahead list in order to ensure they are rendered correctly in in all screen sizes.
Most of the style rules related to color, gradient etc. which are generally common throughout

View File

@@ -0,0 +1,8 @@
<div class="search_list_item">
{{{ description_html }}}
{{#if is_person}}
<span class="pill-container pill-container-btn">
{{> input_pill user_pill_context}}
</span>
{{/if}}
</div>