2021-03-11 05:43:45 +01:00
|
|
|
import $ from "jquery";
|
|
|
|
|
2021-12-06 23:44:26 +01:00
|
|
|
import render_search_list_item from "../templates/search_list_item.hbs";
|
|
|
|
|
2021-02-28 01:04:58 +01:00
|
|
|
import {Filter} from "./filter";
|
2022-09-28 08:27:24 +02:00
|
|
|
import * as keydown_util from "./keydown_util";
|
2021-02-28 01:24:04 +01:00
|
|
|
import * as message_view_header from "./message_view_header";
|
2021-02-28 21:31:57 +01:00
|
|
|
import * as narrow from "./narrow";
|
2021-02-28 01:04:58 +01:00
|
|
|
import * as narrow_state from "./narrow_state";
|
2021-03-25 22:35:45 +01:00
|
|
|
import {page_params} from "./page_params";
|
2021-02-28 01:04:58 +01:00
|
|
|
import * as search_pill from "./search_pill";
|
2021-02-28 01:28:13 +01:00
|
|
|
import * as search_pill_widget from "./search_pill_widget";
|
2021-02-28 01:04:58 +01:00
|
|
|
import * as search_suggestion from "./search_suggestion";
|
|
|
|
import * as ui_util from "./ui_util";
|
2021-02-10 17:09:55 +01:00
|
|
|
|
2018-06-22 12:09:09 +02:00
|
|
|
// Exported for unit testing
|
2021-02-28 01:04:58 +01:00
|
|
|
export let is_using_input_method = false;
|
2018-06-09 11:10:56 +02:00
|
|
|
|
2021-02-28 01:04:58 +01:00
|
|
|
export function narrow_or_search_for_term(search_string) {
|
2022-01-25 11:36:19 +01:00
|
|
|
const $search_query_box = $("#search_query");
|
2021-02-28 01:04:58 +01:00
|
|
|
if (is_using_input_method) {
|
2018-06-22 12:09:09 +02:00
|
|
|
// Neither narrow nor search when using input tools as
|
|
|
|
// `updater` is also triggered when 'enter' is triggered
|
|
|
|
// while using input tool
|
2022-01-25 11:36:19 +01:00
|
|
|
return $search_query_box.val();
|
2018-06-09 11:10:56 +02:00
|
|
|
}
|
2018-07-14 16:10:00 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let operators;
|
2018-07-14 16:10:00 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
2020-05-10 03:58:57 +02:00
|
|
|
// We have to take care to append the new pill before calling this
|
|
|
|
// function, so that the base_query includes the suggestion selected
|
|
|
|
// along with query corresponding to the existing pills.
|
2019-11-02 00:06:25 +01:00
|
|
|
const base_query = search_pill.get_search_string_for_current_filter(
|
2020-07-15 00:34:28 +02:00
|
|
|
search_pill_widget.widget,
|
|
|
|
);
|
2020-05-10 03:58:57 +02:00
|
|
|
operators = Filter.parse(base_query);
|
2018-07-14 16:10:00 +02:00
|
|
|
} else {
|
|
|
|
operators = Filter.parse(search_string);
|
|
|
|
}
|
2020-07-15 01:29:15 +02:00
|
|
|
narrow.activate(operators, {trigger: "search"});
|
2012-12-17 19:47:09 +01:00
|
|
|
|
2013-07-16 04:55:46 +02:00
|
|
|
// It's sort of annoying that this is not in a position to
|
|
|
|
// blur the search box, because it means that Esc won't
|
|
|
|
// unnarrow, it'll leave the searchbox.
|
2013-02-28 22:10:22 +01:00
|
|
|
|
2013-07-16 04:55:46 +02:00
|
|
|
// Narrowing will have already put some operators in the search box,
|
|
|
|
// so leave the current text in.
|
2020-05-10 03:58:57 +02:00
|
|
|
if (!page_params.search_pills_enabled) {
|
2022-01-25 11:36:19 +01:00
|
|
|
$search_query_box.trigger("blur");
|
2020-05-10 03:58:57 +02:00
|
|
|
}
|
2022-01-25 11:36:19 +01:00
|
|
|
return $search_query_box.val();
|
2021-02-28 01:04:58 +01:00
|
|
|
}
|
2012-11-14 22:12:21 +01:00
|
|
|
|
2013-01-02 19:58:55 +01:00
|
|
|
function update_buttons_with_focus(focused) {
|
2022-01-25 11:36:19 +01:00
|
|
|
const $search_query_box = $("#search_query");
|
2012-12-18 23:38:55 +01:00
|
|
|
|
|
|
|
// Show buttons iff the search input is focused, or has non-empty contents,
|
|
|
|
// or we are narrowed.
|
2022-01-25 11:36:19 +01:00
|
|
|
if (focused || $search_query_box.val() || narrow_state.active()) {
|
2020-07-15 01:29:15 +02:00
|
|
|
$(".search_button").prop("disabled", false);
|
2012-12-18 23:38:55 +01:00
|
|
|
}
|
2013-01-02 19:58:55 +01:00
|
|
|
}
|
|
|
|
|
2021-02-28 01:04:58 +01:00
|
|
|
export function update_button_visibility() {
|
2020-07-15 01:29:15 +02:00
|
|
|
update_buttons_with_focus($("#search_query").is(":focus"));
|
2021-02-28 01:04:58 +01:00
|
|
|
}
|
2012-12-18 23:38:55 +01:00
|
|
|
|
2021-02-28 01:04:58 +01:00
|
|
|
export function initialize() {
|
2022-01-25 11:36:19 +01:00
|
|
|
const $search_query_box = $("#search_query");
|
|
|
|
const $searchbox_form = $("#searchbox_form");
|
|
|
|
const $searchbox = $("#searchbox");
|
2013-07-30 19:41:23 +02:00
|
|
|
|
|
|
|
// Data storage for the typeahead.
|
2022-06-19 07:17:16 +02:00
|
|
|
// This maps a search string to an object with a "description_html" field.
|
2013-07-30 19:41:23 +02:00
|
|
|
// (It's a bit of legacy that we have an object with only one important
|
|
|
|
// field. There's also a "search_string" field on each element that actually
|
|
|
|
// just represents the key of the hash, so it's redundant.)
|
2020-02-12 06:58:20 +01:00
|
|
|
let search_map = new Map();
|
2013-07-30 19:41:23 +02:00
|
|
|
|
2022-01-25 11:36:19 +01:00
|
|
|
$search_query_box.typeahead({
|
2020-07-20 22:18:43 +02:00
|
|
|
source(query) {
|
2020-07-15 01:29:15 +02:00
|
|
|
let base_query = "";
|
2018-07-14 13:01:21 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
2020-06-01 15:00:42 +02:00
|
|
|
base_query = search_pill.get_search_string_for_current_filter(
|
2020-07-15 00:34:28 +02:00
|
|
|
search_pill_widget.widget,
|
|
|
|
);
|
2018-07-14 13:01:21 +02:00
|
|
|
}
|
2020-06-01 15:00:42 +02:00
|
|
|
const suggestions = search_suggestion.get_suggestions(base_query, query);
|
2020-02-12 06:58:20 +01:00
|
|
|
// Update our global search_map hash
|
|
|
|
search_map = suggestions.lookup_table;
|
2013-07-30 19:41:23 +02:00
|
|
|
return suggestions.strings;
|
|
|
|
},
|
2013-08-21 17:37:54 +02:00
|
|
|
fixed: true,
|
2019-12-25 16:58:11 +01:00
|
|
|
items: search_suggestion.max_num_of_search_results,
|
2018-12-04 22:55:55 +01:00
|
|
|
helpOnEmptyStrings: true,
|
2013-07-23 03:05:06 +02:00
|
|
|
naturalSearch: true,
|
2020-07-20 22:18:43 +02:00
|
|
|
highlighter(item) {
|
2020-02-12 06:58:20 +01:00
|
|
|
const obj = search_map.get(item);
|
2021-12-06 23:44:26 +01:00
|
|
|
return render_search_list_item(obj);
|
2012-11-18 19:19:52 +01:00
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
matcher() {
|
2013-07-15 22:43:25 +02:00
|
|
|
return true;
|
2012-11-15 03:44:50 +01:00
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
updater(search_string) {
|
2018-07-14 16:10:00 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
2020-07-15 00:34:28 +02:00
|
|
|
search_pill.append_search_string(search_string, search_pill_widget.widget);
|
2022-01-25 11:36:19 +01:00
|
|
|
return $search_query_box.val();
|
2018-07-14 16:10:00 +02:00
|
|
|
}
|
2021-02-28 01:04:58 +01:00
|
|
|
return narrow_or_search_for_term(search_string);
|
2018-07-14 16:10:00 +02:00
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
sorter(items) {
|
2013-07-15 22:57:52 +02:00
|
|
|
return items;
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2018-07-14 16:10:00 +02:00
|
|
|
stopAdvance: page_params.search_pills_enabled,
|
2018-07-28 07:33:24 +02:00
|
|
|
advanceKeyCodes: [8],
|
2020-05-06 21:44:50 +02:00
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
on_move() {
|
2020-06-09 12:59:12 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
2022-01-25 11:36:19 +01:00
|
|
|
ui_util.place_caret_at_end($search_query_box[0]);
|
2020-06-09 12:59:12 +02:00
|
|
|
}
|
|
|
|
},
|
2020-05-06 21:44:50 +02:00
|
|
|
// Use our custom typeahead `on_escape` hook to exit
|
|
|
|
// the search bar as soon as the user hits Esc.
|
2020-07-08 23:44:01 +02:00
|
|
|
on_escape: message_view_header.exit_search,
|
2012-11-14 22:12:21 +01:00
|
|
|
});
|
|
|
|
|
2022-01-25 11:36:19 +01:00
|
|
|
$searchbox_form.on("compositionend", () => {
|
2020-08-11 02:09:14 +02:00
|
|
|
// Set `is_using_input_method` to true if Enter is pressed to exit
|
2018-06-09 11:10:56 +02:00
|
|
|
// the input tool popover and get the text in the search bar. Then
|
2020-08-11 02:09:14 +02:00
|
|
|
// we suppress searching triggered by this Enter key by checking
|
2018-06-09 11:10:56 +02:00
|
|
|
// `is_using_input_method` before searching.
|
|
|
|
// More details in the commit message that added this line.
|
2021-02-28 01:04:58 +01:00
|
|
|
is_using_input_method = true;
|
2018-06-09 11:10:56 +02:00
|
|
|
});
|
|
|
|
|
2022-01-25 11:36:19 +01:00
|
|
|
$searchbox_form
|
2020-07-20 21:26:58 +02:00
|
|
|
.on("keydown", (e) => {
|
2021-02-28 01:04:58 +01:00
|
|
|
update_button_visibility();
|
2022-09-28 08:27:24 +02:00
|
|
|
if (keydown_util.is_enter_event(e) && $search_query_box.is(":focus")) {
|
2020-07-15 00:34:28 +02:00
|
|
|
// Don't submit the form so that the typeahead can instead
|
|
|
|
// handle our Enter keypress. Any searching that needs
|
|
|
|
// to be done will be handled in the keyup.
|
2020-09-24 07:50:36 +02:00
|
|
|
e.preventDefault();
|
2020-07-15 00:34:28 +02:00
|
|
|
}
|
|
|
|
})
|
2020-07-20 21:26:58 +02:00
|
|
|
.on("keyup", (e) => {
|
2021-02-28 01:04:58 +01:00
|
|
|
if (is_using_input_method) {
|
|
|
|
is_using_input_method = false;
|
2020-07-15 00:34:28 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-05-31 18:38:57 +02:00
|
|
|
|
2022-09-28 08:27:24 +02:00
|
|
|
if (keydown_util.is_enter_event(e) && $search_query_box.is(":focus")) {
|
2020-08-11 02:09:14 +02:00
|
|
|
// We just pressed Enter and the box had focus, which
|
2020-07-15 00:34:28 +02:00
|
|
|
// means we didn't use the typeahead at all. In that
|
|
|
|
// case, we should act as though we're searching by
|
|
|
|
// operators. (The reason the other actions don't call
|
|
|
|
// this codepath is that they first all blur the box to
|
|
|
|
// indicate that they've done what they need to do)
|
|
|
|
|
|
|
|
// Pill is already added during keydown event of input pills.
|
2022-01-25 11:36:19 +01:00
|
|
|
narrow_or_search_for_term($search_query_box.val());
|
|
|
|
$search_query_box.trigger("blur");
|
2020-07-15 00:34:28 +02:00
|
|
|
update_buttons_with_focus(false);
|
|
|
|
}
|
|
|
|
});
|
2013-02-27 20:29:25 +01:00
|
|
|
|
|
|
|
// Some of these functions don't actually need to be exported,
|
|
|
|
// but the code was moved here from elsewhere, and it would be
|
|
|
|
// more work to re-order everything and make them private.
|
2013-02-27 21:00:06 +01:00
|
|
|
|
2022-01-25 11:36:19 +01:00
|
|
|
$search_query_box.on("focus", focus_search);
|
|
|
|
$search_query_box.on("blur", (e) => {
|
2013-02-27 21:00:06 +01:00
|
|
|
// The search query box is a visual cue as to
|
|
|
|
// whether search or narrowing is active. If
|
2013-07-23 03:41:21 +02:00
|
|
|
// the user blurs the search box, then we should
|
2018-06-08 21:14:55 +02:00
|
|
|
// update the search string to reflect the current
|
2013-07-23 03:41:21 +02:00
|
|
|
// narrow (or lack of narrow).
|
2013-02-27 21:00:06 +01:00
|
|
|
//
|
|
|
|
// But we can't do this right away, because
|
|
|
|
// selecting something in the typeahead menu causes
|
2013-07-23 03:41:21 +02:00
|
|
|
// the box to lose focus a moment before.
|
2013-02-27 21:00:06 +01:00
|
|
|
//
|
2020-07-14 21:26:28 +02:00
|
|
|
// The workaround is to check 100ms later -- long
|
2013-02-27 21:00:06 +01:00
|
|
|
// enough for the search to have gone through, but
|
|
|
|
// short enough that the user won't notice (though
|
|
|
|
// really it would be OK if they did).
|
|
|
|
|
2020-06-18 17:17:11 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
2022-07-11 04:27:00 +02:00
|
|
|
const $element = $(e.relatedTarget).closest(".pill");
|
|
|
|
const search_pill = search_pill_widget.widget.getByElement($element[0]);
|
2020-06-18 17:17:11 +02:00
|
|
|
if (search_pill) {
|
|
|
|
// The searchbox loses focus while the search
|
|
|
|
// pill element gains focus.
|
|
|
|
// We do not consider the searchbox to actually
|
|
|
|
// lose focus when a pill inside it gets selected
|
|
|
|
// or deleted by a click.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2020-07-02 01:45:54 +02:00
|
|
|
setTimeout(() => {
|
2021-02-28 01:04:58 +01:00
|
|
|
update_button_visibility();
|
2020-07-14 21:26:28 +02:00
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (page_params.search_pills_enabled) {
|
|
|
|
// Uses jquery instead of pure css as the `:focus` event occurs on `#search_query`,
|
|
|
|
// while we want to add box-shadow to `#searchbox`. This could have been done
|
|
|
|
// with `:focus-within` CSS selector, but it is not supported in IE or Opera.
|
2022-01-25 11:36:19 +01:00
|
|
|
$searchbox.on("focusout", () => {
|
2020-07-08 23:44:01 +02:00
|
|
|
message_view_header.close_search_bar_and_open_narrow_description();
|
2022-01-25 11:36:19 +01:00
|
|
|
$searchbox.css({"box-shadow": "unset"});
|
2020-07-14 21:26:28 +02:00
|
|
|
});
|
|
|
|
}
|
2021-02-28 01:04:58 +01:00
|
|
|
}
|
2012-11-14 22:12:21 +01:00
|
|
|
|
2021-02-28 01:04:58 +01:00
|
|
|
export function focus_search() {
|
2013-01-02 19:58:55 +01:00
|
|
|
// The search bar is not focused yet, but will be.
|
|
|
|
update_buttons_with_focus(true);
|
2021-02-28 01:04:58 +01:00
|
|
|
}
|
2012-11-01 19:20:51 +01:00
|
|
|
|
2021-02-28 01:04:58 +01:00
|
|
|
export function initiate_search() {
|
2020-07-08 23:44:01 +02:00
|
|
|
message_view_header.open_search_bar_and_close_narrow_description();
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#searchbox").css({"box-shadow": "inset 0px 0px 0px 2px hsl(204, 20%, 74%)"});
|
2020-07-22 04:50:11 +02:00
|
|
|
$("#search_query").typeahead("lookup").trigger("select");
|
2018-07-23 17:14:53 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
2020-07-20 21:24:26 +02:00
|
|
|
$("#search_query").trigger("focus");
|
2020-07-15 01:29:15 +02:00
|
|
|
ui_util.place_caret_at_end($("#search_query")[0]);
|
2018-07-23 17:14:53 +02:00
|
|
|
}
|
2021-02-28 01:04:58 +01:00
|
|
|
}
|
2012-11-01 17:14:33 +01:00
|
|
|
|
2021-02-28 01:04:58 +01:00
|
|
|
export function clear_search_form() {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#search_query").val("");
|
2020-07-20 21:24:26 +02:00
|
|
|
$("#search_query").trigger("blur");
|
2020-07-15 01:29:15 +02:00
|
|
|
$(".search_button").prop("disabled", true);
|
2021-02-28 01:04:58 +01:00
|
|
|
}
|