mirror of https://github.com/zulip/zulip.git
search: Create a template for search descriptions.
This commit is contained in:
parent
6346110b56
commit
8f5305a4ce
|
@ -1,7 +1,7 @@
|
||||||
import Handlebars from "handlebars/runtime";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
import * as resolved_topic from "../shared/src/resolved_topic";
|
import * as resolved_topic from "../shared/src/resolved_topic";
|
||||||
|
import render_search_description from "../templates/search_description.hbs";
|
||||||
|
|
||||||
import * as hash_util from "./hash_util";
|
import * as hash_util from "./hash_util";
|
||||||
import {$t} from "./i18n";
|
import {$t} from "./i18n";
|
||||||
|
@ -1030,34 +1030,14 @@ export class Filter {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static describe_is_operator(operator) {
|
|
||||||
const verb = operator.negated ? "exclude " : "";
|
|
||||||
const operand = operator.operand;
|
|
||||||
|
|
||||||
switch (operand) {
|
|
||||||
case "starred":
|
|
||||||
case "alerted":
|
|
||||||
case "unread":
|
|
||||||
return verb + operand + " messages";
|
|
||||||
case "mentioned":
|
|
||||||
return verb + "@-mentions";
|
|
||||||
case "dm":
|
|
||||||
case "private":
|
|
||||||
return verb + "direct messages";
|
|
||||||
case "resolved":
|
|
||||||
return verb + "topics marked as resolved";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "invalid " + operand + " operand for is operator";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a list of operators to a human-readable description.
|
// Convert a list of operators to a human-readable description.
|
||||||
static describe_unescaped(operators) {
|
static parts_for_describe(operators) {
|
||||||
if (operators.length === 0) {
|
const parts = [];
|
||||||
return "all messages";
|
|
||||||
}
|
|
||||||
|
|
||||||
let parts = [];
|
if (operators.length === 0) {
|
||||||
|
parts.push({type: "plain_text", content: "all messages"});
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
if (operators.length >= 2) {
|
if (operators.length >= 2) {
|
||||||
const is = (term, expected) => term.operator === expected && !term.negated;
|
const is = (term, expected) => term.operator === expected && !term.negated;
|
||||||
|
@ -1065,8 +1045,11 @@ export class Filter {
|
||||||
if (is(operators[0], "stream") && is(operators[1], "topic")) {
|
if (is(operators[0], "stream") && is(operators[1], "topic")) {
|
||||||
const stream = operators[0].operand;
|
const stream = operators[0].operand;
|
||||||
const topic = operators[1].operand;
|
const topic = operators[1].operand;
|
||||||
const part = "stream " + stream + " > " + topic;
|
parts.push({
|
||||||
parts = [part];
|
type: "stream_topic",
|
||||||
|
stream,
|
||||||
|
topic,
|
||||||
|
});
|
||||||
operators = operators.slice(2);
|
operators = operators.slice(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1075,7 +1058,12 @@ export class Filter {
|
||||||
const operand = elem.operand;
|
const operand = elem.operand;
|
||||||
const canonicalized_operator = Filter.canonicalize_operator(elem.operator);
|
const canonicalized_operator = Filter.canonicalize_operator(elem.operator);
|
||||||
if (canonicalized_operator === "is") {
|
if (canonicalized_operator === "is") {
|
||||||
return Filter.describe_is_operator(elem);
|
const verb = elem.negated ? "exclude " : "";
|
||||||
|
return {
|
||||||
|
type: "is_operator",
|
||||||
|
verb,
|
||||||
|
operand,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (canonicalized_operator === "has") {
|
if (canonicalized_operator === "has") {
|
||||||
// search_suggestion.get_suggestions takes care that this message will
|
// search_suggestion.get_suggestions takes care that this message will
|
||||||
|
@ -1089,7 +1077,10 @@ export class Filter {
|
||||||
"attachments",
|
"attachments",
|
||||||
];
|
];
|
||||||
if (!valid_has_operands.includes(operand)) {
|
if (!valid_has_operands.includes(operand)) {
|
||||||
return "invalid " + operand + " operand for has operator";
|
return {
|
||||||
|
type: "invalid_has",
|
||||||
|
operand,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const prefix_for_operator = Filter.operator_to_prefix(
|
const prefix_for_operator = Filter.operator_to_prefix(
|
||||||
|
@ -1097,15 +1088,24 @@ export class Filter {
|
||||||
elem.negated,
|
elem.negated,
|
||||||
);
|
);
|
||||||
if (prefix_for_operator !== "") {
|
if (prefix_for_operator !== "") {
|
||||||
return prefix_for_operator + " " + operand;
|
return {
|
||||||
|
type: "prefix_for_operator",
|
||||||
|
prefix_for_operator,
|
||||||
|
operand,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return "unknown operator";
|
return {
|
||||||
|
type: "plain_text",
|
||||||
|
content: "unknown operator",
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return [...parts, ...more_parts].join(", ");
|
return [...parts, ...more_parts];
|
||||||
}
|
}
|
||||||
|
|
||||||
static search_description_as_html(operators) {
|
static search_description_as_html(operators) {
|
||||||
return Handlebars.Utils.escapeExpression(Filter.describe_unescaped(operators));
|
return render_search_description({
|
||||||
|
parts: Filter.parts_for_describe(operators),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static is_spectator_compatible(ops) {
|
static is_spectator_compatible(ops) {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
{{~#each parts ~}}
|
||||||
|
|
||||||
|
{{#if (eq this.type "plain_text")~}}
|
||||||
|
{{~this.content~}}
|
||||||
|
{{else if (eq this.type "stream_topic")}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
stream {{this.stream}} > {{this.topic}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{else if (eq this.type "invalid_has")}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
invalid {{this.operand}} operand for has operator
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{else if (eq this.type "prefix_for_operator")}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{this.prefix_for_operator}} {{this.operand}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{else if (eq this.type "is_operator")}}
|
||||||
|
{{#if (eq this.operand "mentioned")}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{this.verb}}@-mentions
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{else if (or (eq this.operand "starred") (eq this.operand "alerted") (eq this.operand "unread"))}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{this.verb}}{{this.operand}} messages
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{else if (or (eq this.operand "dm") (eq this.operand "private"))}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{this.verb}}direct messages
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{else if (eq this.operand "resolved")}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{this.verb}}topics marked as resolved
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{else}}
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
invalid {{this.operand}} operand for is operator
|
||||||
|
{{~!-- squash whitespace --~}}
|
||||||
|
{{~/if~}}
|
||||||
|
{{~/if~}}
|
||||||
|
{{~#if (not @last)~}}, {{/if~}}
|
||||||
|
|
||||||
|
{{~/each~}}
|
|
@ -1156,9 +1156,10 @@ test("unparse", () => {
|
||||||
assert.deepEqual(Filter.unparse(operators), string);
|
assert.deepEqual(Filter.unparse(operators), string);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("describe", () => {
|
test("describe", ({mock_template}) => {
|
||||||
let narrow;
|
let narrow;
|
||||||
let string;
|
let string;
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
narrow = [{operator: "streams", operand: "public"}];
|
narrow = [{operator: "streams", operand: "public"}];
|
||||||
string = "streams public";
|
string = "streams public";
|
||||||
|
@ -1186,7 +1187,7 @@ test("describe", () => {
|
||||||
{operator: "stream", operand: "devel"},
|
{operator: "stream", operand: "devel"},
|
||||||
{operator: "topic", operand: "JS"},
|
{operator: "topic", operand: "JS"},
|
||||||
];
|
];
|
||||||
string = "stream devel > JS";
|
string = "stream devel > JS";
|
||||||
assert.equal(Filter.search_description_as_html(narrow), string);
|
assert.equal(Filter.search_description_as_html(narrow), string);
|
||||||
|
|
||||||
narrow = [
|
narrow = [
|
||||||
|
|
|
@ -79,7 +79,9 @@ function test(label, f) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test("basic_get_suggestions", ({override}) => {
|
test("basic_get_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (data, html) => html);
|
||||||
|
|
||||||
const query = "fred";
|
const query = "fred";
|
||||||
|
|
||||||
override(narrow_state, "stream", () => "office");
|
override(narrow_state, "stream", () => "office");
|
||||||
|
@ -99,7 +101,9 @@ test("basic_get_suggestions_for_spectator", () => {
|
||||||
page_params.is_spectator = false;
|
page_params.is_spectator = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("subset_suggestions", () => {
|
test("subset_suggestions", ({mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
const query = "stream:Denmark topic:Hamlet shakespeare";
|
const query = "stream:Denmark topic:Hamlet shakespeare";
|
||||||
|
|
||||||
const suggestions = get_suggestions(query);
|
const suggestions = get_suggestions(query);
|
||||||
|
@ -113,7 +117,9 @@ test("subset_suggestions", () => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("dm_suggestions", ({override}) => {
|
test("dm_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
let query = "is:dm";
|
let query = "is:dm";
|
||||||
let suggestions = get_suggestions(query);
|
let suggestions = get_suggestions(query);
|
||||||
let expected = [
|
let expected = [
|
||||||
|
@ -248,7 +254,9 @@ test("dm_suggestions", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("group_suggestions", () => {
|
test("group_suggestions", ({mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
// Entering a comma in a "dm:" query should immediately
|
// Entering a comma in a "dm:" query should immediately
|
||||||
// generate suggestions for the next person.
|
// generate suggestions for the next person.
|
||||||
let query = "dm:bob@zulip.com,";
|
let query = "dm:bob@zulip.com,";
|
||||||
|
@ -446,7 +454,9 @@ test("empty_query_suggestions", () => {
|
||||||
assert.equal(describe("has:attachment"), "Messages that contain attachments");
|
assert.equal(describe("has:attachment"), "Messages that contain attachments");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("has_suggestions", ({override}) => {
|
test("has_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
// Checks that category wise suggestions are displayed instead of a single
|
// Checks that category wise suggestions are displayed instead of a single
|
||||||
// default suggestion when suggesting `has` operator.
|
// default suggestion when suggesting `has` operator.
|
||||||
let query = "h";
|
let query = "h";
|
||||||
|
@ -506,7 +516,9 @@ test("has_suggestions", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("check_is_suggestions", ({override}) => {
|
test("check_is_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true});
|
stream_data.add_sub({stream_id: 44, name: "devel", subscribed: true});
|
||||||
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
|
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
|
||||||
override(narrow_state, "stream", () => {});
|
override(narrow_state, "stream", () => {});
|
||||||
|
@ -587,7 +599,9 @@ test("check_is_suggestions", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sent_by_me_suggestions", ({override}) => {
|
test("sent_by_me_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
override(narrow_state, "stream", () => {});
|
override(narrow_state, "stream", () => {});
|
||||||
|
|
||||||
let query = "";
|
let query = "";
|
||||||
|
@ -659,7 +673,8 @@ test("sent_by_me_suggestions", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("topic_suggestions", ({override}) => {
|
test("topic_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
let suggestions;
|
let suggestions;
|
||||||
let expected;
|
let expected;
|
||||||
|
|
||||||
|
@ -703,7 +718,7 @@ test("topic_suggestions", ({override}) => {
|
||||||
return suggestions.lookup_table.get(q).description_html;
|
return suggestions.lookup_table.get(q).description_html;
|
||||||
}
|
}
|
||||||
assert.equal(describe("te"), "Search for te");
|
assert.equal(describe("te"), "Search for te");
|
||||||
assert.equal(describe("stream:office topic:team"), "Stream office > team");
|
assert.equal(describe("stream:office topic:team"), "Stream office > team");
|
||||||
|
|
||||||
suggestions = get_suggestions("topic:staplers stream:office");
|
suggestions = get_suggestions("topic:staplers stream:office");
|
||||||
expected = ["topic:staplers stream:office", "topic:staplers"];
|
expected = ["topic:staplers stream:office", "topic:staplers"];
|
||||||
|
@ -783,7 +798,9 @@ test("topic_suggestions (limits)", () => {
|
||||||
assert_result("z", []);
|
assert_result("z", []);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("whitespace_glitch", ({override}) => {
|
test("whitespace_glitch", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
const query = "stream:office "; // note trailing space
|
const query = "stream:office "; // note trailing space
|
||||||
|
|
||||||
override(stream_topic_history_util, "get_server_history", () => {});
|
override(stream_topic_history_util, "get_server_history", () => {});
|
||||||
|
@ -796,7 +813,9 @@ test("whitespace_glitch", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("stream_completion", ({override}) => {
|
test("stream_completion", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
|
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
|
||||||
stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});
|
stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});
|
||||||
|
|
||||||
|
@ -818,7 +837,9 @@ test("stream_completion", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("people_suggestions", ({override}) => {
|
test("people_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
let query = "te";
|
let query = "te";
|
||||||
|
|
||||||
override(narrow_state, "stream", () => {});
|
override(narrow_state, "stream", () => {});
|
||||||
|
@ -920,7 +941,9 @@ test("people_suggestions", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("operator_suggestions", ({override}) => {
|
test("operator_suggestions", ({override, mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
override(narrow_state, "stream", () => undefined);
|
override(narrow_state, "stream", () => undefined);
|
||||||
|
|
||||||
// Completed operator should return nothing
|
// Completed operator should return nothing
|
||||||
|
@ -951,7 +974,9 @@ test("operator_suggestions", ({override}) => {
|
||||||
assert.deepEqual(suggestions.strings, expected);
|
assert.deepEqual(suggestions.strings, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("queries_with_spaces", () => {
|
test("queries_with_spaces", ({mock_template}) => {
|
||||||
|
mock_template("search_description.hbs", true, (_data, html) => html);
|
||||||
|
|
||||||
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
|
stream_data.add_sub({stream_id: 77, name: "office", subscribed: true});
|
||||||
stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});
|
stream_data.add_sub({stream_id: 88, name: "dev help", subscribed: true});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue