mirror of https://github.com/zulip/zulip.git
search_suggestion: Convert module to TypeScript.
This commit is contained in:
parent
611b28e308
commit
15db564246
|
@ -411,7 +411,7 @@ export class Filter {
|
||||||
// If any search query was present and it is followed by some other filters
|
// If any search query was present and it is followed by some other filters
|
||||||
// then we must add that search filter in its current position in the
|
// then we must add that search filter in its current position in the
|
||||||
// terms list. This is done so that the last active filter is correctly
|
// terms list. This is done so that the last active filter is correctly
|
||||||
// detected by the `get_search_result` function (in search_suggestions.js).
|
// detected by the `get_search_result` function (in search_suggestions.ts).
|
||||||
maybe_add_search_terms();
|
maybe_add_search_terms();
|
||||||
term = {negated, operator, operand};
|
term = {negated, operator, operand};
|
||||||
terms.push(term);
|
terms.push(term);
|
||||||
|
|
|
@ -1,30 +1,51 @@
|
||||||
import Handlebars from "handlebars/runtime";
|
import Handlebars from "handlebars/runtime";
|
||||||
|
import assert from "minimalistic-assert";
|
||||||
|
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
import {Filter} from "./filter";
|
import {Filter} from "./filter";
|
||||||
import * as huddle_data from "./huddle_data";
|
import * as huddle_data from "./huddle_data";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
|
import type {User} from "./people";
|
||||||
|
import type {NarrowTerm} from "./state_data";
|
||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
import * as stream_topic_history from "./stream_topic_history";
|
import * as stream_topic_history from "./stream_topic_history";
|
||||||
import * as stream_topic_history_util from "./stream_topic_history_util";
|
import * as stream_topic_history_util from "./stream_topic_history_util";
|
||||||
import * as typeahead_helper from "./typeahead_helper";
|
import * as typeahead_helper from "./typeahead_helper";
|
||||||
|
|
||||||
|
type UserPillItem = {
|
||||||
|
id: number;
|
||||||
|
display_value: Handlebars.SafeString;
|
||||||
|
has_image: boolean;
|
||||||
|
img_src: string;
|
||||||
|
should_add_guest_user_indicator: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TermPattern = Omit<NarrowTerm, "operand"> & Partial<Pick<NarrowTerm, "operand">>;
|
||||||
|
|
||||||
|
type Suggestion = {
|
||||||
|
description_html: string;
|
||||||
|
search_string: string;
|
||||||
|
is_person?: boolean;
|
||||||
|
user_pill_context?: UserPillItem;
|
||||||
|
};
|
||||||
|
|
||||||
export const max_num_of_search_results = 12;
|
export const max_num_of_search_results = 12;
|
||||||
|
|
||||||
function stream_matches_query(stream_name, q) {
|
function stream_matches_query(stream_name: string, q: string): boolean {
|
||||||
return common.phrase_match(q, stream_name);
|
return common.phrase_match(q, stream_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function make_person_highlighter(query) {
|
function make_person_highlighter(query: string): (person: User) => string {
|
||||||
const highlight_query = typeahead_helper.make_query_highlighter(query);
|
const highlight_query = typeahead_helper.make_query_highlighter(query);
|
||||||
|
|
||||||
return function (person) {
|
return function (person: User): string {
|
||||||
return highlight_query(person.full_name);
|
return highlight_query(person.full_name);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlight_person(person, highlighter) {
|
function highlight_person(person: User, highlighter: (person: User) => string): UserPillItem {
|
||||||
const avatar_url = people.small_avatar_url_for_person(person);
|
const avatar_url = people.small_avatar_url_for_person(person);
|
||||||
const highlighted_name = highlighter(person);
|
const highlighted_name = highlighter(person);
|
||||||
|
|
||||||
|
@ -37,17 +58,22 @@ function highlight_person(person, highlighter) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function match_criteria(terms, criteria) {
|
function match_criteria(terms: NarrowTerm[], criteria: TermPattern[]): boolean {
|
||||||
const filter = new Filter(terms);
|
const filter = new Filter(terms);
|
||||||
return criteria.some((cr) => {
|
return criteria.some((cr) => {
|
||||||
if (Object.hasOwn(cr, "operand")) {
|
if (cr.operand !== undefined) {
|
||||||
return filter.has_operand(cr.operator, cr.operand);
|
return filter.has_operand(cr.operator, cr.operand);
|
||||||
}
|
}
|
||||||
return filter.has_operator(cr.operator);
|
return filter.has_operator(cr.operator);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_validity(last, terms, valid, incompatible_patterns) {
|
function check_validity(
|
||||||
|
last: NarrowTerm,
|
||||||
|
terms: NarrowTerm[],
|
||||||
|
valid: string[],
|
||||||
|
incompatible_patterns: TermPattern[],
|
||||||
|
): boolean {
|
||||||
// valid: list of strings valid for the last operator
|
// valid: list of strings valid for the last operator
|
||||||
// incompatible_patterns: list of terms incompatible for any previous terms except last.
|
// incompatible_patterns: list of terms incompatible for any previous terms except last.
|
||||||
if (!valid.includes(last.operator)) {
|
if (!valid.includes(last.operator)) {
|
||||||
|
@ -59,33 +85,31 @@ function check_validity(last, terms, valid, incompatible_patterns) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_as_suggestion(terms) {
|
function format_as_suggestion(terms: NarrowTerm[]): Suggestion {
|
||||||
return {
|
return {
|
||||||
description_html: Filter.search_description_as_html(terms),
|
description_html: Filter.search_description_as_html(terms),
|
||||||
search_string: Filter.unparse(terms),
|
search_string: Filter.unparse(terms),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function compare_by_huddle(huddle_emails) {
|
function compare_by_huddle(huddle_emails: string[]): (person1: User, person2: User) => number {
|
||||||
huddle_emails = huddle_emails.slice(0, -1).map((person) => {
|
const user_ids = huddle_emails.slice(0, -1).flatMap((person) => {
|
||||||
person = people.get_by_email(person);
|
const user = people.get_by_email(person);
|
||||||
return person && person.user_id;
|
return user?.user_id ?? [];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Construct dict for all huddles, so we can look up each's recency
|
// Construct dict for all huddles, so we can look up each's recency
|
||||||
const huddles = huddle_data.get_huddles();
|
const huddles = huddle_data.get_huddles();
|
||||||
const huddle_dict = new Map();
|
const huddle_dict = new Map<string, number>();
|
||||||
for (const [i, huddle] of huddles.entries()) {
|
for (const [i, huddle] of huddles.entries()) {
|
||||||
huddle_dict.set(huddle, i + 1);
|
huddle_dict.set(huddle, i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (person1, person2) {
|
return function (person1: User, person2: User): number {
|
||||||
const huddle1 = people.concat_huddle(huddle_emails, person1.user_id);
|
const huddle1 = people.concat_huddle(user_ids, person1.user_id);
|
||||||
const huddle2 = people.concat_huddle(huddle_emails, person2.user_id);
|
const huddle2 = people.concat_huddle(user_ids, person2.user_id);
|
||||||
|
|
||||||
// If not in the dict, assign an arbitrarily high index
|
// If not in the dict, assign an arbitrarily high index
|
||||||
const score1 = huddle_dict.get(huddle1) || huddles.length + 1;
|
const score1 = huddle_dict.get(huddle1) ?? huddles.length + 1;
|
||||||
const score2 = huddle_dict.get(huddle2) || huddles.length + 1;
|
const score2 = huddle_dict.get(huddle2) ?? huddles.length + 1;
|
||||||
const diff = score1 - score2;
|
const diff = score1 - score2;
|
||||||
|
|
||||||
if (diff !== 0) {
|
if (diff !== 0) {
|
||||||
|
@ -95,7 +119,7 @@ function compare_by_huddle(huddle_emails) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_stream_suggestions(last, terms) {
|
function get_stream_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestion[] {
|
||||||
const valid = ["stream", "search", ""];
|
const valid = ["stream", "search", ""];
|
||||||
const incompatible_patterns = [
|
const incompatible_patterns = [
|
||||||
{operator: "stream"},
|
{operator: "stream"},
|
||||||
|
@ -135,7 +159,7 @@ function get_stream_suggestions(last, terms) {
|
||||||
return objs;
|
return objs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_group_suggestions(last, terms) {
|
function get_group_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestion[] {
|
||||||
// For users with "pm-with" in their muscle memory, still
|
// For users with "pm-with" in their muscle memory, still
|
||||||
// have group direct message suggestions with "dm:" operator.
|
// have group direct message suggestions with "dm:" operator.
|
||||||
if (!check_validity(last, terms, ["dm", "pm-with"], [{operator: "stream"}])) {
|
if (!check_validity(last, terms, ["dm", "pm-with"], [{operator: "stream"}])) {
|
||||||
|
@ -194,7 +218,7 @@ function get_group_suggestions(last, terms) {
|
||||||
const description_html =
|
const description_html =
|
||||||
prefix + Handlebars.Utils.escapeExpression(" " + all_but_last_part + ",");
|
prefix + Handlebars.Utils.escapeExpression(" " + all_but_last_part + ",");
|
||||||
|
|
||||||
let terms = [term];
|
let terms: NarrowTerm[] = [term];
|
||||||
if (negated) {
|
if (negated) {
|
||||||
terms = [{operator: "is", operand: "dm"}, term];
|
terms = [{operator: "is", operand: "dm"}, term];
|
||||||
}
|
}
|
||||||
|
@ -210,19 +234,19 @@ function get_group_suggestions(last, terms) {
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function make_people_getter(last) {
|
function make_people_getter(last: NarrowTerm): () => User[] {
|
||||||
let persons;
|
let persons: User[];
|
||||||
|
|
||||||
/* The next function will be called between 0 and 4
|
/* The next function will be called between 0 and 4
|
||||||
times for each keystroke in a search, but we will
|
times for each keystroke in a search, but we will
|
||||||
only do real work one time.
|
only do real work one time.
|
||||||
*/
|
*/
|
||||||
return function () {
|
return function (): User[] {
|
||||||
if (persons !== undefined) {
|
if (persons !== undefined) {
|
||||||
return persons;
|
return persons;
|
||||||
}
|
}
|
||||||
|
|
||||||
let query;
|
let query: string;
|
||||||
|
|
||||||
// This next block is designed to match the behavior
|
// This next block is designed to match the behavior
|
||||||
// of the "is:dm" block in get_person_suggestions.
|
// of the "is:dm" block in get_person_suggestions.
|
||||||
|
@ -239,7 +263,12 @@ function make_people_getter(last) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Possible args for autocomplete_operator: dm, pm-with, sender, from, dm-including
|
// Possible args for autocomplete_operator: dm, pm-with, sender, from, dm-including
|
||||||
function get_person_suggestions(people_getter, last, terms, autocomplete_operator) {
|
function get_person_suggestions(
|
||||||
|
people_getter: () => User[],
|
||||||
|
last: NarrowTerm,
|
||||||
|
terms: NarrowTerm[],
|
||||||
|
autocomplete_operator: string,
|
||||||
|
): Suggestion[] {
|
||||||
if ((last.operator === "is" && last.operand === "dm") || last.operator === "pm-with") {
|
if ((last.operator === "is" && last.operand === "dm") || last.operator === "pm-with") {
|
||||||
// Interpret "is:dm" or "pm-with:" operator as equivalent to "dm:".
|
// Interpret "is:dm" or "pm-with:" operator as equivalent to "dm:".
|
||||||
last = {operator: "dm", operand: "", negated: false};
|
last = {operator: "dm", operand: "", negated: false};
|
||||||
|
@ -253,7 +282,7 @@ function get_person_suggestions(people_getter, last, terms, autocomplete_operato
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = ["search", autocomplete_operator];
|
const valid = ["search", autocomplete_operator];
|
||||||
let incompatible_patterns;
|
let incompatible_patterns: TermPattern[] = [];
|
||||||
|
|
||||||
switch (autocomplete_operator) {
|
switch (autocomplete_operator) {
|
||||||
case "dm-including":
|
case "dm-including":
|
||||||
|
@ -285,7 +314,7 @@ function get_person_suggestions(people_getter, last, terms, autocomplete_operato
|
||||||
const person_highlighter = make_person_highlighter(query);
|
const person_highlighter = make_person_highlighter(query);
|
||||||
|
|
||||||
const objs = persons.map((person) => {
|
const objs = persons.map((person) => {
|
||||||
const terms = [
|
const terms: NarrowTerm[] = [
|
||||||
{
|
{
|
||||||
operator: autocomplete_operator,
|
operator: autocomplete_operator,
|
||||||
operand: person.email,
|
operand: person.email,
|
||||||
|
@ -314,7 +343,7 @@ function get_person_suggestions(people_getter, last, terms, autocomplete_operato
|
||||||
return objs;
|
return objs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_default_suggestion(terms) {
|
function get_default_suggestion(terms: NarrowTerm[]): Suggestion {
|
||||||
// Here we return the canonical suggestion for the query that the
|
// Here we return the canonical suggestion for the query that the
|
||||||
// user typed. (The caller passes us the parsed query as "terms".)
|
// user typed. (The caller passes us the parsed query as "terms".)
|
||||||
if (terms.length === 0) {
|
if (terms.length === 0) {
|
||||||
|
@ -323,7 +352,13 @@ function get_default_suggestion(terms) {
|
||||||
return format_as_suggestion(terms);
|
return format_as_suggestion(terms);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_topic_suggestions_from_candidates({candidate_topics, guess}) {
|
export function get_topic_suggestions_from_candidates({
|
||||||
|
candidate_topics,
|
||||||
|
guess,
|
||||||
|
}: {
|
||||||
|
candidate_topics: string[];
|
||||||
|
guess: string;
|
||||||
|
}): string[] {
|
||||||
// This function is exported for unit testing purposes.
|
// This function is exported for unit testing purposes.
|
||||||
const max_num_topics = 10;
|
const max_num_topics = 10;
|
||||||
|
|
||||||
|
@ -342,7 +377,7 @@ export function get_topic_suggestions_from_candidates({candidate_topics, guess})
|
||||||
// The following loop can be expensive if you have lots
|
// The following loop can be expensive if you have lots
|
||||||
// of topics in a stream, so we try to exit the loop as
|
// of topics in a stream, so we try to exit the loop as
|
||||||
// soon as we find enough matches.
|
// soon as we find enough matches.
|
||||||
const topics = [];
|
const topics: string[] = [];
|
||||||
for (const topic of candidate_topics) {
|
for (const topic of candidate_topics) {
|
||||||
if (common.phrase_match(guess, topic)) {
|
if (common.phrase_match(guess, topic)) {
|
||||||
topics.push(topic);
|
topics.push(topic);
|
||||||
|
@ -355,7 +390,7 @@ export function get_topic_suggestions_from_candidates({candidate_topics, guess})
|
||||||
return topics;
|
return topics;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_topic_suggestions(last, terms) {
|
function get_topic_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestion[] {
|
||||||
const incompatible_patterns = [
|
const incompatible_patterns = [
|
||||||
{operator: "dm"},
|
{operator: "dm"},
|
||||||
{operator: "is", operand: "dm"},
|
{operator: "is", operand: "dm"},
|
||||||
|
@ -369,10 +404,10 @@ function get_topic_suggestions(last, terms) {
|
||||||
const operator = Filter.canonicalize_operator(last.operator);
|
const operator = Filter.canonicalize_operator(last.operator);
|
||||||
const operand = last.operand;
|
const operand = last.operand;
|
||||||
const negated = operator === "topic" && last.negated;
|
const negated = operator === "topic" && last.negated;
|
||||||
let stream;
|
let stream: string | undefined;
|
||||||
let guess;
|
let guess: string | undefined;
|
||||||
const filter = new Filter(terms);
|
const filter = new Filter(terms);
|
||||||
const suggest_terms = [];
|
const suggest_terms: NarrowTerm[] = [];
|
||||||
|
|
||||||
// stream:Rome -> show all Rome topics
|
// stream:Rome -> show all Rome topics
|
||||||
// stream:Rome topic: -> show all Rome topics
|
// stream:Rome topic: -> show all Rome topics
|
||||||
|
@ -402,8 +437,10 @@ function get_topic_suggestions(last, terms) {
|
||||||
stream = filter.operands("stream")[0];
|
stream = filter.operands("stream")[0];
|
||||||
} else {
|
} else {
|
||||||
stream = narrow_state.stream_name();
|
stream = narrow_state.stream_name();
|
||||||
|
if (stream) {
|
||||||
suggest_terms.push({operator: "stream", operand: stream});
|
suggest_terms.push({operator: "stream", operand: stream});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,6 +454,7 @@ function get_topic_suggestions(last, terms) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream_data.can_access_topic_history(stream_sub)) {
|
if (stream_data.can_access_topic_history(stream_sub)) {
|
||||||
|
stream_topic_history_util.get_server_history(stream_sub.stream_id, () => {
|
||||||
// Fetch topic history from the server, in case we will need it.
|
// Fetch topic history from the server, in case we will need it.
|
||||||
// Note that we won't actually use the results from the server here
|
// Note that we won't actually use the results from the server here
|
||||||
// for this particular keystroke from the user, because we want to
|
// for this particular keystroke from the user, because we want to
|
||||||
|
@ -424,15 +462,16 @@ function get_topic_suggestions(last, terms) {
|
||||||
// as the user makes their search more specific, subsequent calls to
|
// as the user makes their search more specific, subsequent calls to
|
||||||
// this function will get more candidates from calling
|
// this function will get more candidates from calling
|
||||||
// stream_topic_history.get_recent_topic_names.
|
// stream_topic_history.get_recent_topic_names.
|
||||||
stream_topic_history_util.get_server_history(stream_sub.stream_id, () => {});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const candidate_topics = stream_topic_history.get_recent_topic_names(stream_sub.stream_id);
|
const candidate_topics = stream_topic_history.get_recent_topic_names(stream_sub.stream_id);
|
||||||
|
|
||||||
if (!candidate_topics || !candidate_topics.length) {
|
if (!candidate_topics?.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(guess !== undefined);
|
||||||
const topics = get_topic_suggestions_from_candidates({candidate_topics, guess});
|
const topics = get_topic_suggestions_from_candidates({candidate_topics, guess});
|
||||||
|
|
||||||
// Just use alphabetical order. While recency and read/unreadness of
|
// Just use alphabetical order. While recency and read/unreadness of
|
||||||
|
@ -448,7 +487,7 @@ function get_topic_suggestions(last, terms) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_term_subset_suggestions(terms) {
|
function get_term_subset_suggestions(terms: NarrowTerm[]): Suggestion[] {
|
||||||
// For stream:a topic:b search:c, suggest:
|
// For stream:a topic:b search:c, suggest:
|
||||||
// stream:a topic:b
|
// stream:a topic:b
|
||||||
// stream:a
|
// stream:a
|
||||||
|
@ -456,10 +495,9 @@ function get_term_subset_suggestions(terms) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let i;
|
const suggestions: Suggestion[] = [];
|
||||||
const suggestions = [];
|
|
||||||
|
|
||||||
for (i = terms.length - 1; i >= 1; i -= 1) {
|
for (let i = terms.length - 1; i >= 1; i -= 1) {
|
||||||
const subset = terms.slice(0, i);
|
const subset = terms.slice(0, i);
|
||||||
suggestions.push(format_as_suggestion(subset));
|
suggestions.push(format_as_suggestion(subset));
|
||||||
}
|
}
|
||||||
|
@ -467,11 +505,15 @@ function get_term_subset_suggestions(terms) {
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_special_filter_suggestions(last, terms, suggestions) {
|
function get_special_filter_suggestions(
|
||||||
const is_search_operand_negated = last.operator === "search" && last.operand[0] === "-";
|
last: NarrowTerm,
|
||||||
|
terms: NarrowTerm[],
|
||||||
|
suggestions: (Suggestion & {incompatible_patterns: TermPattern[]})[],
|
||||||
|
): Suggestion[] {
|
||||||
|
const is_search_operand_negated = last.operator === "search" && last.operand.startsWith("-");
|
||||||
// Negating suggestions on is_search_operand_negated is required for
|
// Negating suggestions on is_search_operand_negated is required for
|
||||||
// suggesting negated terms.
|
// suggesting negated terms.
|
||||||
if (last.negated || is_search_operand_negated) {
|
if (last.negated === true || is_search_operand_negated) {
|
||||||
suggestions = suggestions.map((suggestion) => ({
|
suggestions = suggestions.map((suggestion) => ({
|
||||||
search_string: "-" + suggestion.search_string,
|
search_string: "-" + suggestion.search_string,
|
||||||
description_html: "exclude " + suggestion.description_html,
|
description_html: "exclude " + suggestion.description_html,
|
||||||
|
@ -499,10 +541,12 @@ function get_special_filter_suggestions(last, terms, suggestions) {
|
||||||
s.description_html.toLowerCase().startsWith(last_string)
|
s.description_html.toLowerCase().startsWith(last_string)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return suggestions;
|
const filtered_suggestions = suggestions.map(({incompatible_patterns, ...s}) => s);
|
||||||
|
|
||||||
|
return filtered_suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_streams_filter_suggestions(last, terms) {
|
function get_streams_filter_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestion[] {
|
||||||
const suggestions = [
|
const suggestions = [
|
||||||
{
|
{
|
||||||
search_string: "streams:public",
|
search_string: "streams:public",
|
||||||
|
@ -519,7 +563,7 @@ function get_streams_filter_suggestions(last, terms) {
|
||||||
];
|
];
|
||||||
return get_special_filter_suggestions(last, terms, suggestions);
|
return get_special_filter_suggestions(last, terms, suggestions);
|
||||||
}
|
}
|
||||||
function get_is_filter_suggestions(last, terms) {
|
function get_is_filter_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestion[] {
|
||||||
const suggestions = [
|
const suggestions = [
|
||||||
{
|
{
|
||||||
search_string: "is:dm",
|
search_string: "is:dm",
|
||||||
|
@ -566,7 +610,7 @@ function get_is_filter_suggestions(last, terms) {
|
||||||
return get_special_filter_suggestions(last, terms, suggestions);
|
return get_special_filter_suggestions(last, terms, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_has_filter_suggestions(last, terms) {
|
function get_has_filter_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestion[] {
|
||||||
const suggestions = [
|
const suggestions = [
|
||||||
{
|
{
|
||||||
search_string: "has:link",
|
search_string: "has:link",
|
||||||
|
@ -587,9 +631,10 @@ function get_has_filter_suggestions(last, terms) {
|
||||||
return get_special_filter_suggestions(last, terms, suggestions);
|
return get_special_filter_suggestions(last, terms, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_sent_by_me_suggestions(last, terms) {
|
function get_sent_by_me_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggestion[] {
|
||||||
const last_string = Filter.unparse([last]).toLowerCase();
|
const last_string = Filter.unparse([last]).toLowerCase();
|
||||||
const negated = last.negated || (last.operator === "search" && last.operand[0] === "-");
|
const negated =
|
||||||
|
last.negated === true || (last.operator === "search" && last.operand.startsWith("-"));
|
||||||
const negated_symbol = negated ? "-" : "";
|
const negated_symbol = negated ? "-" : "";
|
||||||
const verb = negated ? "exclude " : "";
|
const verb = negated ? "exclude " : "";
|
||||||
|
|
||||||
|
@ -629,7 +674,7 @@ function get_sent_by_me_suggestions(last, terms) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_operator_suggestions(last) {
|
function get_operator_suggestions(last: NarrowTerm): Suggestion[] {
|
||||||
// Suggest "is:dm" to anyone with "is:private" in their muscle memory
|
// Suggest "is:dm" to anyone with "is:private" in their muscle memory
|
||||||
if (last.operator === "is" && common.phrase_match(last.operand, "private")) {
|
if (last.operator === "is" && common.phrase_match(last.operand, "private")) {
|
||||||
const is_dm = format_as_suggestion([
|
const is_dm = format_as_suggestion([
|
||||||
|
@ -664,14 +709,15 @@ function get_operator_suggestions(last) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Attacher {
|
class Attacher {
|
||||||
result = [];
|
result: Suggestion[] = [];
|
||||||
prev = new Set();
|
prev = new Set<string>();
|
||||||
|
base: Suggestion;
|
||||||
|
|
||||||
constructor(base) {
|
constructor(base: Suggestion) {
|
||||||
this.base = base;
|
this.base = base;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepend_base(suggestion) {
|
prepend_base(suggestion: Suggestion): void {
|
||||||
if (this.base && this.base.description_html.length > 0) {
|
if (this.base && this.base.description_html.length > 0) {
|
||||||
suggestion.search_string = this.base.search_string + " " + suggestion.search_string;
|
suggestion.search_string = this.base.search_string + " " + suggestion.search_string;
|
||||||
suggestion.description_html =
|
suggestion.description_html =
|
||||||
|
@ -679,20 +725,20 @@ class Attacher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
push(suggestion) {
|
push(suggestion: Suggestion): void {
|
||||||
if (!this.prev.has(suggestion.search_string)) {
|
if (!this.prev.has(suggestion.search_string)) {
|
||||||
this.prev.add(suggestion.search_string);
|
this.prev.add(suggestion.search_string);
|
||||||
this.result.push(suggestion);
|
this.result.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
push_many(suggestions) {
|
push_many(suggestions: Suggestion[]): void {
|
||||||
for (const suggestion of suggestions) {
|
for (const suggestion of suggestions) {
|
||||||
this.push(suggestion);
|
this.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attach_many(suggestions) {
|
attach_many(suggestions: Suggestion[]): void {
|
||||||
for (const suggestion of suggestions) {
|
for (const suggestion of suggestions) {
|
||||||
this.prepend_base(suggestion);
|
this.prepend_base(suggestion);
|
||||||
this.push(suggestion);
|
this.push(suggestion);
|
||||||
|
@ -700,16 +746,17 @@ class Attacher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_search_result(query) {
|
export function get_search_result(query: string): Suggestion[] {
|
||||||
let suggestion;
|
let suggestion: Suggestion;
|
||||||
|
|
||||||
// search_terms correspond to the terms for the query in the input.
|
// search_terms correspond to the terms for the query in the input.
|
||||||
// This includes the entire query entered in the searchbox.
|
// This includes the entire query entered in the searchbox.
|
||||||
// terms correspond to the terms for the entire query entered in the searchbox.
|
// terms correspond to the terms for the entire query entered in the searchbox.
|
||||||
const search_terms = Filter.parse(query);
|
const search_terms = Filter.parse(query);
|
||||||
let last = {operator: "", operand: "", negated: false};
|
|
||||||
|
let last: NarrowTerm = {operator: "", operand: "", negated: false};
|
||||||
if (search_terms.length > 0) {
|
if (search_terms.length > 0) {
|
||||||
last = search_terms.at(-1);
|
last = search_terms.at(-1)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const person_suggestion_ops = ["sender", "dm", "dm-including", "from", "pm-with"];
|
const person_suggestion_ops = ["sender", "dm", "dm-including", "from", "pm-with"];
|
||||||
|
@ -723,9 +770,9 @@ export function get_search_result(query) {
|
||||||
if (
|
if (
|
||||||
search_terms.length > 1 &&
|
search_terms.length > 1 &&
|
||||||
last.operator === "search" &&
|
last.operator === "search" &&
|
||||||
person_suggestion_ops.includes(search_terms.at(-2).operator)
|
person_suggestion_ops.includes(search_terms.at(-2)!.operator)
|
||||||
) {
|
) {
|
||||||
const person_op = search_terms.at(-2);
|
const person_op = search_terms.at(-2)!;
|
||||||
if (!people.reply_to_to_user_ids_string(person_op.operand)) {
|
if (!people.reply_to_to_user_ids_string(person_op.operand)) {
|
||||||
last = {
|
last = {
|
||||||
operator: person_op.operator,
|
operator: person_op.operator,
|
||||||
|
@ -761,8 +808,10 @@ export function get_search_result(query) {
|
||||||
// only make one people_getter to avoid duplicate work
|
// only make one people_getter to avoid duplicate work
|
||||||
const people_getter = make_people_getter(last);
|
const people_getter = make_people_getter(last);
|
||||||
|
|
||||||
function get_people(flavor) {
|
function get_people(
|
||||||
return function (last, base_terms) {
|
flavor: string,
|
||||||
|
): (last: NarrowTerm, base_terms: NarrowTerm[]) => Suggestion[] {
|
||||||
|
return function (last: NarrowTerm, base_terms: NarrowTerm[]): Suggestion[] {
|
||||||
return get_person_suggestions(people_getter, last, base_terms, flavor);
|
return get_person_suggestions(people_getter, last, base_terms, flavor);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -812,12 +861,18 @@ export function get_search_result(query) {
|
||||||
return attacher.result.slice(0, max_items);
|
return attacher.result.slice(0, max_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_suggestions(query) {
|
export function get_suggestions(query: string): {
|
||||||
|
strings: string[];
|
||||||
|
lookup_table: Map<string, Suggestion>;
|
||||||
|
} {
|
||||||
const result = get_search_result(query);
|
const result = get_search_result(query);
|
||||||
return finalize_search_result(result);
|
return finalize_search_result(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function finalize_search_result(result) {
|
export function finalize_search_result(result: Suggestion[]): {
|
||||||
|
strings: string[];
|
||||||
|
lookup_table: Map<string, Suggestion>;
|
||||||
|
} {
|
||||||
for (const sug of result) {
|
for (const sug of result) {
|
||||||
const first = sug.description_html.charAt(0).toUpperCase();
|
const first = sug.description_html.charAt(0).toUpperCase();
|
||||||
sug.description_html = first + sug.description_html.slice(1);
|
sug.description_html = first + sug.description_html.slice(1);
|
||||||
|
@ -825,13 +880,13 @@ export function finalize_search_result(result) {
|
||||||
|
|
||||||
// Typeahead expects us to give it strings, not objects,
|
// Typeahead expects us to give it strings, not objects,
|
||||||
// so we maintain our own hash back to our objects
|
// so we maintain our own hash back to our objects
|
||||||
const lookup_table = new Map();
|
const lookup_table = new Map<string, Suggestion>();
|
||||||
|
|
||||||
for (const obj of result) {
|
for (const obj of result) {
|
||||||
lookup_table.set(obj.search_string, obj);
|
lookup_table.set(obj.search_string, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
const strings = result.map((obj) => obj.search_string);
|
const strings = result.map((obj: Suggestion) => obj.search_string);
|
||||||
return {
|
return {
|
||||||
strings,
|
strings,
|
||||||
lookup_table,
|
lookup_table,
|
Loading…
Reference in New Issue