2018-06-22 12:09:09 +02:00
|
|
|
// Exported for unit testing
|
|
|
|
exports.is_using_input_method = false;
|
2018-06-09 11:10:56 +02:00
|
|
|
|
2013-07-30 20:29:31 +02:00
|
|
|
function narrow_or_search_for_term(search_string) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const search_query_box = $("#search_query");
|
2018-06-22 12:09:09 +02:00
|
|
|
if (exports.is_using_input_method) {
|
|
|
|
// Neither narrow nor search when using input tools as
|
|
|
|
// `updater` is also triggered when 'enter' is triggered
|
|
|
|
// while using input tool
|
2018-06-09 11:10:56 +02:00
|
|
|
return search_query_box.val();
|
|
|
|
}
|
2017-03-18 21:35:35 +01:00
|
|
|
ui_util.change_tab_to('#home');
|
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) {
|
|
|
|
// search_string only contains the suggestion selected
|
|
|
|
// from the typeahead. base_query stores the 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(
|
2018-07-23 02:20:57 +02:00
|
|
|
search_pill_widget.widget);
|
2019-11-02 00:06:25 +01:00
|
|
|
const base_operators = Filter.parse(base_query);
|
|
|
|
const suggestion_operator = Filter.parse(search_string);
|
2018-07-14 16:10:00 +02:00
|
|
|
operators = base_operators.concat(suggestion_operator);
|
|
|
|
} else {
|
|
|
|
operators = Filter.parse(search_string);
|
|
|
|
}
|
2018-04-23 06:02:11 +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.
|
|
|
|
search_query_box.blur();
|
|
|
|
return search_query_box.val();
|
2012-11-14 22:12:21 +01:00
|
|
|
}
|
|
|
|
|
2013-01-02 19:58:55 +01:00
|
|
|
function update_buttons_with_focus(focused) {
|
2019-11-02 00:06:25 +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.
|
|
|
|
if (focused
|
2018-06-22 11:55:21 +02:00
|
|
|
|| search_query_box.val()
|
2017-04-25 15:25:31 +02:00
|
|
|
|| narrow_state.active()) {
|
2017-06-30 00:57:46 +02:00
|
|
|
$('.search_button').prop('disabled', false);
|
2012-12-18 23:38:55 +01:00
|
|
|
} else {
|
2018-06-22 11:55:21 +02:00
|
|
|
$('.search_button').prop('disabled', true);
|
2012-12-18 23:38:55 +01:00
|
|
|
}
|
2013-01-02 19:58:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
exports.update_button_visibility = function () {
|
|
|
|
update_buttons_with_focus($('#search_query').is(':focus'));
|
2012-12-18 23:38:55 +01:00
|
|
|
};
|
|
|
|
|
2012-11-14 22:12:21 +01:00
|
|
|
exports.initialize = function () {
|
2019-11-02 00:06:25 +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.
|
|
|
|
// This maps a search string to an object with a "description" field.
|
|
|
|
// (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.)
|
2019-11-02 00:06:25 +01:00
|
|
|
let search_object = {};
|
2013-07-30 19:41:23 +02:00
|
|
|
|
2018-06-22 11:59:47 +02:00
|
|
|
search_query_box.typeahead({
|
2013-07-30 19:41:23 +02:00
|
|
|
source: function (query) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let suggestions;
|
2018-07-14 13:01:21 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const base_query = search_pill.get_search_string_for_current_filter(
|
2018-07-23 02:20:57 +02:00
|
|
|
search_pill_widget.widget);
|
2018-07-02 22:30:18 +02:00
|
|
|
suggestions = search_suggestion.get_suggestions(base_query, query);
|
2018-07-14 13:01:21 +02:00
|
|
|
} else {
|
|
|
|
suggestions = search_suggestion.get_suggestions_legacy(query);
|
|
|
|
}
|
2013-07-30 19:41:23 +02:00
|
|
|
// Update our global search_object hash
|
|
|
|
search_object = suggestions.lookup_table;
|
|
|
|
return suggestions.strings;
|
|
|
|
},
|
2013-08-21 17:37:54 +02:00
|
|
|
fixed: true,
|
2017-04-03 10:15:09 +02:00
|
|
|
items: 12,
|
2018-12-04 22:55:55 +01:00
|
|
|
helpOnEmptyStrings: true,
|
2013-07-23 03:05:06 +02:00
|
|
|
naturalSearch: true,
|
2012-11-18 19:19:52 +01:00
|
|
|
highlighter: function (item) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const obj = search_object[item];
|
2013-07-16 01:03:13 +02:00
|
|
|
return obj.description;
|
2012-11-18 19:19:52 +01:00
|
|
|
},
|
2016-12-02 14:06:06 +01:00
|
|
|
matcher: function () {
|
2013-07-15 22:43:25 +02:00
|
|
|
return true;
|
2012-11-15 03:44:50 +01:00
|
|
|
},
|
2018-07-14 16:10:00 +02:00
|
|
|
updater: function (search_string) {
|
|
|
|
// Order is important here. narrow_or_search_for_term
|
|
|
|
// gets a search string from existing pills and obtains
|
|
|
|
// existing operators. Newly selected suggestion is added
|
|
|
|
// to those operators. If narrow_or_search_for_term was
|
|
|
|
// called after append_search_string, the existing search
|
|
|
|
// pills at the time for calling that function would also
|
|
|
|
// have the newly selected suggestion, and appending it again
|
|
|
|
// would cause duplication.
|
2019-11-02 00:06:25 +01:00
|
|
|
const result = narrow_or_search_for_term(search_string);
|
2018-07-14 16:10:00 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
|
|
|
search_pill.append_search_string(search_string,
|
2018-07-23 02:20:57 +02:00
|
|
|
search_pill_widget.widget);
|
2018-07-14 16:10:00 +02:00
|
|
|
$("#search_query").focus();
|
|
|
|
} else {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
},
|
2013-07-15 22:57:52 +02:00
|
|
|
sorter: function (items) {
|
|
|
|
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],
|
2012-11-14 22:12:21 +01:00
|
|
|
});
|
|
|
|
|
2018-06-22 11:59:47 +02:00
|
|
|
searchbox_form.on('compositionend', function () {
|
2018-06-09 11:10:56 +02:00
|
|
|
// Set `is_using_input_method` to true if enter is pressed to exit
|
|
|
|
// the input tool popover and get the text in the search bar. Then
|
|
|
|
// we suppress searching triggered by this enter key by checking
|
|
|
|
// `is_using_input_method` before searching.
|
|
|
|
// More details in the commit message that added this line.
|
2018-06-22 12:09:09 +02:00
|
|
|
exports.is_using_input_method = true;
|
2018-06-09 11:10:56 +02:00
|
|
|
});
|
|
|
|
|
2018-06-22 11:59:47 +02:00
|
|
|
searchbox_form.keydown(function (e) {
|
2012-12-18 23:38:55 +01:00
|
|
|
exports.update_button_visibility();
|
2019-11-02 00:06:25 +01:00
|
|
|
const code = e.which;
|
2012-11-15 20:22:20 +01:00
|
|
|
if (code === 13 && search_query_box.is(":focus")) {
|
|
|
|
// 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.
|
2012-11-14 22:12:21 +01:00
|
|
|
return false;
|
|
|
|
}
|
2012-12-18 22:58:20 +01:00
|
|
|
}).keyup(function (e) {
|
2018-06-22 12:09:09 +02:00
|
|
|
if (exports.is_using_input_method) {
|
|
|
|
exports.is_using_input_method = false;
|
2018-06-09 11:10:56 +02:00
|
|
|
return;
|
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const code = e.which;
|
2012-11-15 20:22:20 +01:00
|
|
|
if (code === 13 && search_query_box.is(":focus")) {
|
2013-01-31 23:50:39 +01:00
|
|
|
// We just pressed enter and the box had focus, which
|
|
|
|
// 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)
|
2018-04-23 06:02:11 +02:00
|
|
|
narrow.activate(Filter.parse(search_query_box.val()), {trigger: 'search'});
|
2013-01-31 23:50:39 +01:00
|
|
|
search_query_box.blur();
|
|
|
|
update_buttons_with_focus(false);
|
2012-11-15 20:22:20 +01:00
|
|
|
}
|
|
|
|
});
|
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.
|
Simplify narrow/search interactions.
Before this change, if you hit ESC, then hotkey
code would call search.clear_search, which would
call narrow.deactivate(), which would then use
`$('#search_query')` to clear a value, but then
let search.clear_search blur the input and
disable the exit button. It was all confusing.
Things are a bit more organized now.
Now the code works like this:
hotkey.process_escape_key
Just call narrow.deactivate.
$('#search_exit').on('click', ...):
Just call narrow.deactivate.
narrow.deactivate:
Just call search.clear_search_form
search.clear_search_form:
Just do simple jquery stuff. Don't
change the entire user's narrow, not
even indirectly!
There's still a two-way interaction between
the narrow.js module and the search.js module,
but in each direction it's a one-liner.
The guiding principle here is that we only
want one top-level API, which is narrow.deactivate,
and that does the whole "kitchen sink" of
clearing searches, closing popovers, switching
in views, etc. And then all the functions it
calls out to tend to have much smaller jobs to
do.
This commit can mostly be considered a refactoring, but the
order of operations changes slightly. Basically, as
soon as you hit ESC or click on the search "X", we
clear the search widget. Most users won't notice
any difference, because we don't have to hit the
server to populate the home view. And it's arguably
an improvement to give more immediate feedback.
2018-09-10 19:36:58 +02:00
|
|
|
$('#search_exit').on('click', narrow.deactivate);
|
2013-02-27 21:00:06 +01:00
|
|
|
|
2018-06-22 11:59:47 +02:00
|
|
|
search_query_box.on('focus', exports.focus_search);
|
2018-12-07 21:21:39 +01:00
|
|
|
search_query_box.on('blur', function () {
|
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
|
|
|
//
|
|
|
|
// The workaround is to check 100ms later -- long
|
|
|
|
// 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).
|
|
|
|
|
|
|
|
setTimeout(function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const search_string = narrow_state.search_string();
|
2018-06-22 11:59:47 +02:00
|
|
|
search_query_box.val(search_string);
|
2013-02-27 21:00:06 +01:00
|
|
|
exports.update_button_visibility();
|
|
|
|
}, 100);
|
|
|
|
});
|
2018-07-23 00:25:21 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
searchbox.on('focusin', function () {
|
|
|
|
searchbox.css({"box-shadow": "inset 0px 0px 0px 2px hsl(204, 20%, 74%)"});
|
|
|
|
});
|
|
|
|
searchbox.on('focusout', function () {
|
|
|
|
searchbox.css({"box-shadow": "unset"});
|
|
|
|
});
|
|
|
|
}
|
2012-11-14 22:12:21 +01:00
|
|
|
};
|
|
|
|
|
2012-12-18 22:42:13 +01:00
|
|
|
exports.focus_search = function () {
|
2013-01-02 19:58:55 +01:00
|
|
|
// The search bar is not focused yet, but will be.
|
|
|
|
update_buttons_with_focus(true);
|
2012-11-14 20:52:53 +01:00
|
|
|
};
|
2012-11-01 19:20:51 +01:00
|
|
|
|
2012-11-14 20:52:53 +01:00
|
|
|
exports.initiate_search = function () {
|
2018-07-23 17:14:53 +02:00
|
|
|
if (page_params.search_pills_enabled) {
|
|
|
|
$('#search_query').focus();
|
|
|
|
} else {
|
|
|
|
$('#search_query').select();
|
|
|
|
}
|
2012-11-14 20:52:53 +01:00
|
|
|
};
|
2012-11-01 17:14:33 +01:00
|
|
|
|
Simplify narrow/search interactions.
Before this change, if you hit ESC, then hotkey
code would call search.clear_search, which would
call narrow.deactivate(), which would then use
`$('#search_query')` to clear a value, but then
let search.clear_search blur the input and
disable the exit button. It was all confusing.
Things are a bit more organized now.
Now the code works like this:
hotkey.process_escape_key
Just call narrow.deactivate.
$('#search_exit').on('click', ...):
Just call narrow.deactivate.
narrow.deactivate:
Just call search.clear_search_form
search.clear_search_form:
Just do simple jquery stuff. Don't
change the entire user's narrow, not
even indirectly!
There's still a two-way interaction between
the narrow.js module and the search.js module,
but in each direction it's a one-liner.
The guiding principle here is that we only
want one top-level API, which is narrow.deactivate,
and that does the whole "kitchen sink" of
clearing searches, closing popovers, switching
in views, etc. And then all the functions it
calls out to tend to have much smaller jobs to
do.
This commit can mostly be considered a refactoring, but the
order of operations changes slightly. Basically, as
soon as you hit ESC or click on the search "X", we
clear the search widget. Most users won't notice
any difference, because we don't have to hit the
server to populate the home view. And it's arguably
an improvement to give more immediate feedback.
2018-09-10 19:36:58 +02:00
|
|
|
exports.clear_search_form = function () {
|
|
|
|
$('#search_query').val('');
|
2013-02-27 21:00:06 +01:00
|
|
|
$('#search_query').blur();
|
Simplify narrow/search interactions.
Before this change, if you hit ESC, then hotkey
code would call search.clear_search, which would
call narrow.deactivate(), which would then use
`$('#search_query')` to clear a value, but then
let search.clear_search blur the input and
disable the exit button. It was all confusing.
Things are a bit more organized now.
Now the code works like this:
hotkey.process_escape_key
Just call narrow.deactivate.
$('#search_exit').on('click', ...):
Just call narrow.deactivate.
narrow.deactivate:
Just call search.clear_search_form
search.clear_search_form:
Just do simple jquery stuff. Don't
change the entire user's narrow, not
even indirectly!
There's still a two-way interaction between
the narrow.js module and the search.js module,
but in each direction it's a one-liner.
The guiding principle here is that we only
want one top-level API, which is narrow.deactivate,
and that does the whole "kitchen sink" of
clearing searches, closing popovers, switching
in views, etc. And then all the functions it
calls out to tend to have much smaller jobs to
do.
This commit can mostly be considered a refactoring, but the
order of operations changes slightly. Basically, as
soon as you hit ESC or click on the search "X", we
clear the search widget. Most users won't notice
any difference, because we don't have to hit the
server to populate the home view. And it's arguably
an improvement to give more immediate feedback.
2018-09-10 19:36:58 +02:00
|
|
|
$('.search_button').prop('disabled', true);
|
2012-12-07 20:18:01 +01:00
|
|
|
};
|
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
window.search = exports;
|