2012-11-14 20:52:53 +01:00
|
|
|
var search = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2012-10-26 16:59:38 +02:00
|
|
|
var cached_term = "";
|
|
|
|
var cached_matches = [];
|
|
|
|
var cached_index;
|
|
|
|
var cached_table = $('table.focused_table');
|
2013-03-04 23:24:36 +01:00
|
|
|
var current_search_term;
|
2012-10-26 16:59:38 +02:00
|
|
|
|
2012-11-15 03:44:50 +01:00
|
|
|
// Data storage for the typeahead -- to go from object to string representation and vice versa.
|
2013-07-16 02:18:36 +02:00
|
|
|
var search_object = {};
|
2012-11-15 03:44:50 +01:00
|
|
|
|
2013-02-28 22:20:49 +01:00
|
|
|
function get_query(obj) {
|
|
|
|
return obj.query;
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_person(obj) {
|
|
|
|
return typeahead_helper.render_person(obj.query);
|
|
|
|
}
|
|
|
|
|
2013-05-06 20:43:55 +02:00
|
|
|
function phrase_match(phrase, q) {
|
2013-05-06 19:29:02 +02:00
|
|
|
// match "tes" to "test" and "stream test" but not "hostess"
|
|
|
|
var i;
|
2013-05-06 20:43:55 +02:00
|
|
|
q = q.toLowerCase();
|
|
|
|
|
|
|
|
var parts = phrase.split(' ');
|
2013-05-06 19:29:02 +02:00
|
|
|
for (i = 0; i < parts.length; i++) {
|
2013-05-06 20:43:55 +02:00
|
|
|
if (parts[i].toLowerCase().indexOf(q) === 0) {
|
2013-05-06 19:29:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2013-05-06 18:35:21 +02:00
|
|
|
}
|
|
|
|
|
2013-05-06 20:43:55 +02:00
|
|
|
function person_matches_query(person, q) {
|
|
|
|
return phrase_match(person.full_name, q) || phrase_match(person.email, q);
|
|
|
|
}
|
|
|
|
|
|
|
|
function stream_matches_query(stream_name, q) {
|
|
|
|
return phrase_match(stream_name, q);
|
|
|
|
}
|
|
|
|
|
2013-07-15 19:25:47 +02:00
|
|
|
|
|
|
|
// Convert a list of operators to a human-readable description.
|
|
|
|
function describe(operators) {
|
|
|
|
return $.map(operators, function (elem) {
|
|
|
|
var operand = elem[1];
|
2013-07-16 22:52:42 +02:00
|
|
|
switch (narrow.canonicalize_operator(elem[0])) {
|
2013-07-15 19:25:47 +02:00
|
|
|
case 'is':
|
|
|
|
if (operand === 'private') {
|
|
|
|
return 'Narrow to all private messages';
|
|
|
|
} else if (operand === 'starred') {
|
|
|
|
return 'Narrow to starred messages';
|
|
|
|
} else if (operand === 'mentioned') {
|
|
|
|
return 'Narrow to mentioned messages';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'stream':
|
|
|
|
return 'Narrow to stream ' + operand;
|
|
|
|
|
2013-07-16 22:52:02 +02:00
|
|
|
case 'topic':
|
|
|
|
return 'Narrow to topic ' + operand;
|
2013-07-15 19:25:47 +02:00
|
|
|
|
|
|
|
case 'sender':
|
|
|
|
return 'Narrow to sender ' + operand;
|
|
|
|
|
|
|
|
case 'pm-with':
|
|
|
|
return 'Narrow to private messages with ' + operand;
|
|
|
|
|
|
|
|
case 'search':
|
|
|
|
return 'Search for ' + operand;
|
|
|
|
|
|
|
|
case 'in':
|
|
|
|
return 'Narrow to messages in ' + operand;
|
|
|
|
}
|
|
|
|
return 'Narrow to (unknown operator)';
|
|
|
|
}).join(', ');
|
|
|
|
}
|
|
|
|
|
2013-07-16 04:07:23 +02:00
|
|
|
function get_search_string(obj) {
|
2013-07-15 19:51:08 +02:00
|
|
|
switch (obj.action) {
|
|
|
|
case 'stream':
|
|
|
|
return 'stream:' + obj.query;
|
|
|
|
|
|
|
|
case 'private_message':
|
|
|
|
return 'pm-with:' + obj.query.email;
|
|
|
|
|
|
|
|
case 'sender':
|
|
|
|
return 'sender:' + obj.query.email;
|
|
|
|
|
|
|
|
case 'operators':
|
|
|
|
return obj.query;
|
|
|
|
}
|
2012-11-19 01:31:23 +01:00
|
|
|
}
|
|
|
|
|
2012-11-14 22:12:21 +01:00
|
|
|
function narrow_or_search_for_term(item) {
|
2012-12-12 19:36:18 +01:00
|
|
|
var search_query_box = $("#search_query");
|
2013-07-16 02:18:36 +02:00
|
|
|
var obj = search_object[item];
|
2013-01-22 00:30:27 +01:00
|
|
|
ui.change_tab_to('#home');
|
2013-07-16 04:55:46 +02:00
|
|
|
var operators = narrow.parse(obj.search_string);
|
|
|
|
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) {
|
2012-12-18 23:38:55 +01:00
|
|
|
var search_query = $('#search_query');
|
|
|
|
|
|
|
|
// Show buttons iff the search input is focused, or has non-empty contents,
|
|
|
|
// or we are narrowed.
|
|
|
|
if (focused
|
|
|
|
|| search_query.val()
|
|
|
|
|| narrow.active()) {
|
2013-03-06 20:46:33 +01:00
|
|
|
$('.search_button').removeAttr('disabled');
|
2012-12-18 23:38:55 +01:00
|
|
|
} else {
|
2013-03-06 20:46:33 +01:00
|
|
|
$('.search_button').attr('disabled', 'disabled');
|
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
|
|
|
};
|
|
|
|
|
2013-05-07 15:50:46 +02:00
|
|
|
function highlight_person(query, person) {
|
|
|
|
var hilite = typeahead_helper.highlight_query_in_phrase;
|
2013-05-07 21:32:57 +02:00
|
|
|
return hilite(query, person.full_name) + " <" + hilite(query, person.email) + ">";
|
2013-05-07 15:50:46 +02:00
|
|
|
}
|
|
|
|
|
2013-07-15 23:20:23 +02:00
|
|
|
function get_stream_suggestions(query) {
|
2013-07-16 03:40:30 +02:00
|
|
|
var streams = subs.subscribed_streams();
|
|
|
|
|
|
|
|
streams = $.grep(streams, function (stream) {
|
|
|
|
return stream_matches_query(stream, query);
|
2013-07-15 23:20:23 +02:00
|
|
|
});
|
2013-07-16 03:40:30 +02:00
|
|
|
|
|
|
|
var objs = $.map(streams, function (stream) {
|
|
|
|
return {action: 'stream', query: stream};
|
2013-07-15 23:20:23 +02:00
|
|
|
});
|
|
|
|
|
2013-07-16 01:03:13 +02:00
|
|
|
$.each(objs, function (idx, obj) {
|
|
|
|
var prefix = 'Narrow to stream';
|
|
|
|
var stream = obj.query;
|
|
|
|
stream = typeahead_helper.highlight_query_in_phrase(query, stream);
|
|
|
|
obj.description = prefix + ' ' + stream;
|
2013-07-16 04:07:23 +02:00
|
|
|
obj.search_string = get_search_string(obj);
|
2013-07-16 01:03:13 +02:00
|
|
|
});
|
|
|
|
|
2013-07-15 23:20:23 +02:00
|
|
|
objs = typeahead_helper.sorter(query, objs, get_query);
|
|
|
|
|
2013-07-16 03:40:30 +02:00
|
|
|
return objs;
|
2013-07-15 23:20:23 +02:00
|
|
|
}
|
|
|
|
|
2013-07-16 02:58:17 +02:00
|
|
|
function get_person_suggestions(all_people, query, action) {
|
|
|
|
var people = $.grep(all_people, function (person) {
|
|
|
|
return person_matches_query(person, query);
|
2013-07-15 23:34:19 +02:00
|
|
|
});
|
2013-07-16 02:58:17 +02:00
|
|
|
|
|
|
|
var objs = $.map(people, function (person) {
|
|
|
|
return {action: action, query: person};
|
2013-07-15 23:34:19 +02:00
|
|
|
});
|
|
|
|
|
2013-07-16 01:03:13 +02:00
|
|
|
$.each(objs, function (idx, obj) {
|
|
|
|
var prefix;
|
|
|
|
var person;
|
|
|
|
var name;
|
|
|
|
|
|
|
|
if (action === 'private_message') {
|
|
|
|
prefix = 'Narrow to private messages with';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (action === 'sender') {
|
|
|
|
prefix = 'Narrow to messages sent by';
|
|
|
|
}
|
|
|
|
|
|
|
|
person = obj.query;
|
|
|
|
name = highlight_person(query, person);
|
|
|
|
obj.description = prefix + ' ' + name;
|
2013-07-16 04:07:23 +02:00
|
|
|
obj.search_string = get_search_string(obj);
|
2013-07-16 01:03:13 +02:00
|
|
|
});
|
|
|
|
|
2013-07-15 23:34:19 +02:00
|
|
|
|
|
|
|
objs.sort(function (x, y) {
|
|
|
|
return typeahead_helper.compare_by_pms(get_query(x), get_query(y));
|
|
|
|
});
|
|
|
|
|
2013-07-16 02:18:36 +02:00
|
|
|
return objs;
|
2013-07-15 23:34:19 +02:00
|
|
|
}
|
|
|
|
|
2012-11-14 22:12:21 +01:00
|
|
|
exports.initialize = function () {
|
|
|
|
$( "#search_query" ).typeahead({
|
|
|
|
source: function (query, process) {
|
2013-07-15 23:59:47 +02:00
|
|
|
var result = [];
|
|
|
|
|
2012-12-18 00:28:31 +01:00
|
|
|
// Add an entry for narrow by operators.
|
|
|
|
var operators = narrow.parse(query);
|
2013-05-30 23:47:32 +02:00
|
|
|
if (operators.length !== 0) {
|
2013-07-16 04:55:46 +02:00
|
|
|
var obj = {action: 'operators', query: query};
|
2013-07-16 04:07:23 +02:00
|
|
|
obj.search_string = get_search_string(obj);
|
2013-07-16 01:03:13 +02:00
|
|
|
var description = describe(operators);
|
|
|
|
obj.description = Handlebars.Utils.escapeExpression(description);
|
2013-07-16 02:18:36 +02:00
|
|
|
result = [obj];
|
2013-05-30 23:47:32 +02:00
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
2013-07-15 22:43:25 +02:00
|
|
|
|
2013-07-15 23:20:23 +02:00
|
|
|
var stream_suggestions = get_stream_suggestions(query).slice(0,4);
|
|
|
|
result = result.concat(stream_suggestions);
|
|
|
|
|
2013-07-16 02:58:17 +02:00
|
|
|
var people = page_params.people_list;
|
2013-07-15 23:34:19 +02:00
|
|
|
var person_suggestions;
|
|
|
|
|
2013-07-16 02:58:17 +02:00
|
|
|
person_suggestions = get_person_suggestions(people, query, 'private_message').slice(0, 4);
|
2013-07-15 23:34:19 +02:00
|
|
|
result = result.concat(person_suggestions);
|
|
|
|
|
2013-07-16 02:58:17 +02:00
|
|
|
person_suggestions = get_person_suggestions(people, query, 'sender').slice(0, 4);
|
2013-07-15 23:34:19 +02:00
|
|
|
result = result.concat(person_suggestions);
|
|
|
|
|
2013-07-16 04:07:23 +02:00
|
|
|
// We can't send typeahead objects, only strings.
|
2013-07-16 02:18:36 +02:00
|
|
|
search_object = {};
|
|
|
|
$.each(result, function (idx, obj) {
|
2013-07-16 04:07:23 +02:00
|
|
|
search_object[obj.search_string] = obj;
|
2013-07-16 02:18:36 +02:00
|
|
|
});
|
|
|
|
return $.map(result, function (obj) {
|
2013-07-16 04:07:23 +02:00
|
|
|
return obj.search_string;
|
2013-07-16 02:18:36 +02:00
|
|
|
});
|
2012-11-14 22:12:21 +01:00
|
|
|
},
|
2013-05-06 18:35:21 +02:00
|
|
|
items: 20,
|
2012-11-18 19:19:52 +01:00
|
|
|
highlighter: function (item) {
|
2013-07-16 02:18:36 +02:00
|
|
|
var obj = search_object[item];
|
2013-07-16 01:03:13 +02:00
|
|
|
return obj.description;
|
2012-11-18 19:19:52 +01:00
|
|
|
},
|
2012-11-15 03:44:50 +01:00
|
|
|
matcher: function (item) {
|
2013-07-15 22:43:25 +02:00
|
|
|
return true;
|
2012-11-15 03:44:50 +01:00
|
|
|
},
|
2012-11-26 21:39:15 +01:00
|
|
|
updater: narrow_or_search_for_term,
|
2013-07-15 22:57:52 +02:00
|
|
|
sorter: function (items) {
|
|
|
|
return items;
|
|
|
|
}
|
2012-11-14 22:12:21 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
$("#searchbox_form").keydown(function (e) {
|
2012-12-18 23:38:55 +01:00
|
|
|
exports.update_button_visibility();
|
2012-11-14 22:12:21 +01:00
|
|
|
var code = e.which;
|
2012-11-15 20:22:20 +01:00
|
|
|
var search_query_box = $("#search_query");
|
|
|
|
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
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
2012-12-18 22:58:20 +01:00
|
|
|
}).keyup(function (e) {
|
2012-11-15 20:22:20 +01:00
|
|
|
var code = e.which;
|
|
|
|
var search_query_box = $("#search_query");
|
|
|
|
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)
|
2013-05-30 23:47:32 +02:00
|
|
|
if (search_query_box.val().trim()) {
|
2013-01-31 23:50:39 +01:00
|
|
|
narrow.activate(narrow.parse(search_query_box.val()));
|
2012-11-15 20:22:20 +01:00
|
|
|
}
|
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.
|
|
|
|
$('#search_exit' ).on('click', exports.clear_search);
|
2013-02-27 21:00:06 +01:00
|
|
|
|
|
|
|
var query = $('#search_query');
|
|
|
|
query.on('focus', exports.focus_search)
|
|
|
|
.on('blur' , function () {
|
|
|
|
|
|
|
|
// The search query box is a visual cue as to
|
|
|
|
// whether search or narrowing is active. If
|
|
|
|
// neither is active, we should clear the box on
|
|
|
|
// blur.
|
|
|
|
//
|
|
|
|
// But we can't do this right away, because
|
|
|
|
// selecting something in the typeahead menu causes
|
|
|
|
// the box to lose focus a moment before. We would
|
|
|
|
// clear the thing we're about to search for.
|
|
|
|
//
|
|
|
|
// 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 () {
|
2013-03-04 23:28:04 +01:00
|
|
|
if (!(narrow.active())) {
|
2013-02-27 21:00:06 +01:00
|
|
|
query.val('');
|
|
|
|
}
|
|
|
|
exports.update_button_visibility();
|
|
|
|
}, 100);
|
|
|
|
});
|
2012-11-14 22:12:21 +01:00
|
|
|
};
|
|
|
|
|
2012-10-26 16:59:38 +02:00
|
|
|
function match_on_visible_text(row, search_term) {
|
|
|
|
// You can't select on :visible, since that includes hidden elements that
|
|
|
|
// take up space.
|
2013-03-04 23:24:36 +01:00
|
|
|
return row.find(".message_content, .message_header")
|
2012-10-27 04:14:17 +02:00
|
|
|
.text().toLowerCase().indexOf(search_term) !== -1;
|
2012-10-26 16:59:38 +02: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 () {
|
2013-01-29 20:54:11 +01:00
|
|
|
$('#search_query').select();
|
2012-11-14 20:52:53 +01:00
|
|
|
};
|
2012-11-01 17:14:33 +01:00
|
|
|
|
2012-11-14 20:52:53 +01:00
|
|
|
exports.clear_search = function () {
|
2012-12-18 21:45:48 +01:00
|
|
|
narrow.deactivate();
|
2013-02-27 21:00:06 +01:00
|
|
|
|
2012-10-26 16:59:38 +02:00
|
|
|
$('table tr').removeHighlight();
|
2013-02-27 21:00:06 +01:00
|
|
|
$('#search_query').blur();
|
2012-12-18 23:38:55 +01:00
|
|
|
exports.update_button_visibility();
|
2012-12-07 20:18:01 +01:00
|
|
|
};
|
|
|
|
|
2012-11-14 20:52:53 +01:00
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|