From 9322e63d32af4625728eaf1aad24bab4544340ed Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Tue, 18 Jun 2013 15:09:29 -0400 Subject: [PATCH] Refactor typeahead for user names (PM and @reply). Get data directly from the main user list, rather than maintaining a separate list just for autocomplete. Fixes trac #1362 -- Does not depend on historical messages, so will do the correct autocomplete after a single reload. (imported from commit 6b35a709dba3384530082e8cfacf0151f9e0eb26) --- zephyr/static/js/composebox_typeahead.js | 44 ++++++++++++++---------- zephyr/static/js/search.js | 2 +- zephyr/static/js/typeahead_helper.js | 29 ++++------------ zephyr/static/js/ui.js | 1 - zephyr/static/js/zephyr.js | 26 +++++++------- 5 files changed, 46 insertions(+), 56 deletions(-) diff --git a/zephyr/static/js/composebox_typeahead.js b/zephyr/static/js/composebox_typeahead.js index 57956db93e..27befa1262 100644 --- a/zephyr/static/js/composebox_typeahead.js +++ b/zephyr/static/js/composebox_typeahead.js @@ -34,14 +34,16 @@ function get_last_recipient_in_pm(query_string) { } function composebox_typeahead_highlighter(item) { - var query = this.query; - if ($(this.$element).attr('id') === 'private_message_recipient') { - // There could be multiple recipients in a private message, - // we want to decide what to highlight based only on the most - // recent one we're entering. - query = get_last_recipient_in_pm(this.query); - } - return typeahead_helper.highlight_with_escaping(query, item); + return typeahead_helper.highlight_with_escaping(this.query, item); +} + +function query_matches_person (query, person) { + // Case-insensitive. + query = query.toLowerCase(); + + return ( person.email .toLowerCase().indexOf(query) !== -1 + || person.full_name.toLowerCase().indexOf(query) !== -1); + } // nextFocus is set on a keydown event to indicate where we should focus on keyup. @@ -206,10 +208,14 @@ exports.initialize = function () { }); $( "#private_message_recipient" ).typeahead({ - source: typeahead_helper.private_message_typeahead_list, + source: page_params.people_list, items: 5, dropup: true, - highlighter: composebox_typeahead_highlighter, + highlighter: function (item) { + var query = get_last_recipient_in_pm(this.query); + var item_formatted = typeahead_helper.render_person(item); + return typeahead_helper.highlight_with_escaping(query, item_formatted); + }, matcher: function (item) { var current_recipient = get_last_recipient_in_pm(this.query); // If the name is only whitespace (does not contain any non-whitespace), @@ -218,8 +224,7 @@ exports.initialize = function () { return false; } - // Case-insensitive. - return (item.toLowerCase().indexOf(current_recipient.toLowerCase()) !== -1); + return query_matches_person(current_recipient, item); }, sorter: typeahead_helper.sort_recipientbox_typeahead, updater: function (item) { @@ -229,15 +234,18 @@ exports.initialize = function () { if (previous_recipients.length !== 0) { previous_recipients += ", "; } - return previous_recipients + typeahead_helper.private_message_mapped[item].email + ", "; + return previous_recipients + item.email + ", "; }, stopAdvance: true // Do not advance to the next field on a tab or enter }); $( "#new_message_content" ).typeahead({ - source: typeahead_helper.private_message_typeahead_list, + source: page_params.people_list, items: 5, - highlighter: composebox_typeahead_highlighter, + highlighter: function (item) { + var item_formatted = typeahead_helper.render_person(item); + return typeahead_helper.highlight_with_escaping(this.query, item_formatted); + }, dropup: true, matcher: function (item) { var query = exports.split_at_cursor(this.query)[0]; @@ -250,10 +258,8 @@ exports.initialize = function () { if (current_recipient.length < 2 || current_recipient.charAt(0) !== "@") { return false; } - current_recipient = current_recipient.substring(1); - // Case-insensitive. - return (item.toLowerCase().indexOf(current_recipient.toLowerCase()) !== -1); + return query_matches_person(current_recipient.substring(1), item); }, sorter: typeahead_helper.sort_textbox_typeahead, updater: function (item) { @@ -261,7 +267,7 @@ exports.initialize = function () { var beginning = pieces[0]; var rest = pieces[1]; - beginning = beginning.replace(/@\S+$/, "") + "@**" + typeahead_helper.private_message_mapped[item].full_name + "**"; + beginning = beginning.replace(/@\S+$/, "") + "@**" + item.full_name + "**"; // Keep the cursor after the newly inserted name, as Bootstrap will call textbox.change() to overwrite the text // in the textbox. setTimeout(function () { diff --git a/zephyr/static/js/search.js b/zephyr/static/js/search.js index ead5435b60..03a2d36344 100644 --- a/zephyr/static/js/search.js +++ b/zephyr/static/js/search.js @@ -159,7 +159,7 @@ function searchbox_sorter(items) { // Get the first object in sorted order. if (action === 'private_message' || action === 'sender') { objs.sort(function (x, y) { - return typeahead_helper.compare_by_pms(get_person(x), get_person(y)); + return typeahead_helper.compare_by_pms(get_query(x), get_query(y)); }); } else if (action !== 'stream') { // streams are already sorted diff --git a/zephyr/static/js/typeahead_helper.js b/zephyr/static/js/typeahead_helper.js index 12734b61d9..33862041d4 100644 --- a/zephyr/static/js/typeahead_helper.js +++ b/zephyr/static/js/typeahead_helper.js @@ -145,24 +145,16 @@ exports.sorter = function (query, objs, get_item) { return results.matches.concat(results.rest); }; -exports.compare_by_pms = function(user_a, user_b) { - var x_count = 0, y_count = 0; - if (typeahead_helper.private_message_mapped[user_a]) { - x_count = typeahead_helper.private_message_mapped[user_a].count; - } - if (typeahead_helper.private_message_mapped[user_b]) { - y_count = typeahead_helper.private_message_mapped[user_b].count; - } - - if (x_count > y_count) { +exports.compare_by_pms = function (user_a, user_b) { + if (user_a.pm_recipient_count > user_b.pm_recipient_count) { return -1; - } else if (x_count < y_count) { + } else if (user_a.pm_recipient_count < user_b.pm_recipient_count) { return 1; } // We use alpha sort as a tiebreaker, which might be helpful for // new users. - if (user_a < user_b) + if (user_a.full_name < user_b.full_name) return -1; else if (user_a === user_b) return 0; @@ -171,10 +163,7 @@ exports.compare_by_pms = function(user_a, user_b) { }; exports.sort_by_pms = function(objs) { - objs.sort(function (x, y) { - return exports.compare_by_pms(x, y); - }); - + objs.sort(exports.compare_by_pms); return objs; }; @@ -182,17 +171,13 @@ function identity(item) { return item; } -function email_from_identity(identity) { - return exports.private_message_mapped[identity].email; -} - exports.sort_subjects = function (items) { return exports.sorter(this.query, items, identity); }; exports.sort_recipients = function (matches, query) { - var name_results = prefix_sort(query, matches, identity); - var email_results = prefix_sort(query, name_results.rest, email_from_identity); + var name_results = prefix_sort(query, matches, function (x) { return x.full_name; }); + var email_results = prefix_sort(query, name_results.rest, function (x) { return x.email; }); var matches_sorted_by_pms = exports.sort_by_pms(name_results.matches.concat(email_results.matches)); var rest_sorted_by_pms = exports.sort_by_pms(email_results.rest); return matches_sorted_by_pms.concat(rest_sorted_by_pms); diff --git a/zephyr/static/js/ui.js b/zephyr/static/js/ui.js index b037abedc4..d60a44b741 100644 --- a/zephyr/static/js/ui.js +++ b/zephyr/static/js/ui.js @@ -1571,7 +1571,6 @@ $(function () { }); // initialize other stuff - typeahead_helper.update_all_recipients(page_params.people_list); composebox_typeahead.initialize(); search.initialize(); notifications.initialize(); diff --git a/zephyr/static/js/zephyr.js b/zephyr/static/js/zephyr.js index 117dc316da..ee56defd0d 100644 --- a/zephyr/static/js/zephyr.js +++ b/zephyr/static/js/zephyr.js @@ -43,6 +43,7 @@ var pointer_update_in_flight = false; function add_person(person) { page_params.people_list.push(person); people_dict[person.email] = person; + person.pm_recipient_count = 0; } function remove_person(person) { @@ -59,11 +60,15 @@ function remove_person(person) { $(function () { $.each(page_params.people_list, function (idx, person) { people_dict[person.email] = person; + person.pm_recipient_count = 0; }); + // The special account feedback@humbughq.com is used for in-app // feedback and should always show up as an autocomplete option. - typeahead_helper.update_your_recipients([{"email": "feedback@humbughq.com", - "full_name": "Humbug Feedback Bot"}]); + if (people_dict['feedback@humbughq.com'] === undefined){ + add_person({"email": "feedback@humbughq.com", + "full_name": "Humbug Feedback Bot"}); + } $.each(page_params.initial_presences, function (email, presence) { activity.set_user_status(email, presence, page_params.initial_servertime); @@ -577,12 +582,6 @@ function add_message_metadata(message, dummy) { message.display_reply_to = get_private_message_recipient(message, 'full_name'); involved_people = message.display_recipient; - - if (message.sent_by_me) { - typeahead_helper.update_your_recipients(involved_people); - } else { - typeahead_helper.update_all_recipients(involved_people); - } break; } @@ -592,9 +591,12 @@ function add_message_metadata(message, dummy) { // with keys like "hasOwnProperty" if (people_dict[person.email] === undefined) { add_person(person); - if (!typeahead_helper.known_to_typeahead(person)) { - typeahead_helper.autocomplete_needs_update(true); - } + typeahead_helper.autocomplete_needs_update(true); + } + + if (message.type === 'private' && message.sent_by_me) { + // Track the number of PMs we've sent to this person to improve autocomplete + people_dict[person.email].pm_recipient_count += 1; } }); @@ -849,10 +851,8 @@ function get_updates(options) { case 'realm_user': if (event.op === 'add') { add_person(event.person); - typeahead_helper.update_all_recipients([event.person]); } else if (event.op === 'remove') { remove_person(event.person); - typeahead_helper.remove_recipient([event.person]); } typeahead_helper.autocomplete_needs_update(true); break;