diff options
| author | Aaron Bohy <aab@odoo.com> | 2015-11-24 15:30:25 +0100 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2015-11-27 11:47:34 +0100 |
| commit | db8203c27a21acdbcad2cf1c394b6fea3cf13688 (patch) | |
| tree | 32b886c20afe00b22c05192ae6387edc5c8a1a6b /addons/mail/static/src/js | |
| parent | 9ef3a9296d02ca27e728de1bb2582821269f1b26 (diff) | |
[FIX] base,hr,mail: prefetch mention suggestions
The purpose of this commit is to improve the overall performance of the mention
mechanism of the client action and chatter composer, by pre-fetching some data.
First, the employees are fetched at chat initialization (basically, at web
client initialization), if hr is installed.
Second, when focusing on the composer of a channel, the members of this channel
are fetched. In the case of a chatter, the members are already known as they
are the followers of the document.
When the user types a mention, we first display suggestions from the pre-fetched
partners, and we only perform an RPC when there are no more result matching the
search string. This RPC searches among user, and then partners if there aren't
enough matches.
A few side changes occurred:
- auto_join set to True on user_ids field of res.partner to boost the query
that searches partners that are users
- the '/mail/read_followers' route now returns follower's email as well, as
we need it for the mention;
- a partner can now be mentionned several times in the same message;
- mentionned partner previsualization has been removed, mainly because of
the previous point, but also because it simplifies the code;
Diffstat (limited to 'addons/mail/static/src/js')
| -rw-r--r-- | addons/mail/static/src/js/chat_manager.js | 23 | ||||
| -rw-r--r-- | addons/mail/static/src/js/chatter.js | 39 | ||||
| -rw-r--r-- | addons/mail/static/src/js/client_action.js | 11 | ||||
| -rw-r--r-- | addons/mail/static/src/js/composer.js | 124 |
4 files changed, 136 insertions, 61 deletions
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js index 331fd6772a5..9b89cb88995 100644 --- a/addons/mail/static/src/js/chat_manager.js +++ b/addons/mail/static/src/js/chat_manager.js @@ -24,6 +24,7 @@ var channel_defs = {}; var emojis = []; var emoji_substitutions = {}; var needaction_counter = 0; +var mention_partner_suggestions = []; var discuss_ids = {}; // Message and channel manipulation helpers @@ -584,6 +585,27 @@ var chat_manager = { return get_channel_cache(channel, domain).all_history_loaded; }, + get_mention_partner_suggestions: function (channel) { + if (!channel) { + return mention_partner_suggestions; + } + if (!channel.members_deferred) { + channel.members_deferred = ChannelModel + .call("channel_fetch_listeners", [channel.uuid]) + .then(function (members) { + var suggestions = []; + _.each(mention_partner_suggestions, function (partners) { + suggestions.push(_.filter(partners, function (partner) { + return !_.findWhere(members, { id: partner.id }); + })); + }); + + return [members].concat(suggestions); + }); + } + return channel.members_deferred; + }, + get_emojis: function() { return emojis; }, @@ -671,6 +693,7 @@ function init () { _.each(channels, add_channel); }); needaction_counter = result.needaction_inbox_counter; + mention_partner_suggestions = result.mention_partner_suggestions; }); var load_emojis = session.rpc("/mail/chat_init").then(function (result) { diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index f8c9f1e032e..d29312cf6f6 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -37,6 +37,7 @@ var Followers = form_common.AbstractField.extend({ this.value = []; this.followers = []; + this.followers_fetched = $.Deferred(); this.data_subtype = {}; this.view_is_editable = this.__parentedParent.is_action_enabled('edit'); @@ -188,9 +189,14 @@ var Followers = form_common.AbstractField.extend({ }, fetch_followers: function (value_) { + var self = this; this.value = value_ || []; return ajax.jsonRpc('/mail/read_followers', 'call', {'follower_ids': this.value}) - .then(this.proxy('display_followers'), this.proxy('fetch_generic')) + .then(function (followers) { + self.trigger('followers_update', followers); + self.display_followers(followers); + self.fetch_generic(); + }) .then(this.proxy('display_buttons')) .then(this.proxy('fetch_subtypes')); }, @@ -706,6 +712,7 @@ var Chatter = form_common.AbstractField.extend({ if (this.followers) { this.$('.o_chatter_topbar').append(this.followers.$el); this.followers.on('redirect', this, this.on_redirect); + this.followers.on('followers_update', this, this.on_followers_update); } this.thread = new ChatThread(this, { @@ -798,6 +805,30 @@ var Chatter = form_common.AbstractField.extend({ }); }, + on_followers_update: function (followers) { + this.mention_suggestions = []; + var self = this; + var prefetched_partners = chat_manager.get_mention_partner_suggestions(); + var follower_suggestions = []; + _.each(followers, function (follower) { + if (follower.res_model === 'res.partner') { + follower_suggestions.push({ + id: follower.res_id, + name: follower.name, + email: follower.email, + }); + } + }); + if (follower_suggestions.length) { + this.mention_suggestions.push(follower_suggestions); + } + _.each(prefetched_partners, function (partners) { + self.mention_suggestions.push(_.filter(partners, function (partner) { + return !_.findWhere(follower_suggestions, { id: partner.id }); + })); + }); + }, + load_more_messages: function () { this.fetch_and_render_thread(this.msg_ids, {force_fetch: true}); }, @@ -854,9 +885,9 @@ var Chatter = form_common.AbstractField.extend({ internal_subtypes: this.options.internal_subtypes, is_log: options && options.is_log, record_name: this.record_name, - get_channel_info: function () { - return { res_id: self.res_id, res_model: self.model }; - }, + }); + this.composer.on('input_focused', this, function () { + this.composer.mention_set_prefetched_partners(this.mention_suggestions || []); }); this.composer.insertBefore(this.$('.o_mail_thread')).then(function () { // destroy existing composer diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index 8acbb679b2b..89afd003126 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -160,11 +160,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.searchview = new SearchView(this, dataset, view_id, {}, options); this.searchview.on('search_data', this, this.on_search); - this.composer = new ChatComposer(this, { - get_channel_info: function () { - return { channel_id: self.channel.id }; - }, - }); + this.composer = new ChatComposer(this); this.thread = new ChatThread(this, { display_help: true }); @@ -200,6 +196,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { chat_manager.toggle_star_status(message_id); }); this.composer.on('post_message', this, this.on_post_message); + this.composer.on('input_focused', this, this.on_composer_input_focused); var def1 = this.thread.prependTo(this.$('.o_mail_chat_content')); var def2 = this.composer.appendTo(this.$('.o_mail_chat_content')); @@ -581,6 +578,10 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.set_channel(channel); } }, + on_composer_input_focused: function () { + var suggestions = chat_manager.get_mention_partner_suggestions(this.channel); + this.composer.mention_set_prefetched_partners(suggestions); + }, on_click_button_invite: function () { var title = _.str.sprintf(_t('Invite people to %s'), this.channel.name); diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index bfbf3618a98..24257d1a72a 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -12,6 +12,19 @@ var Widget = require('web.Widget'); var QWeb = core.qweb; var _t = core._t; +var accented_letters_mapping = { + 'a': '[àáâãäå]', + 'ae': 'æ', + 'c': 'ç', + 'e': '[èéêë]', + 'i': '[ìíîï]', + 'n': 'ñ', + 'o': '[òóôõö]', + 'oe': 'œ', + 'u': '[ùúûűü]', + 'y': '[ýÿ]', +}; + // The MentionManager allows the Composer to register listeners. For each // listener, it detects if the user is currently typing a mention (starting by a // given delimiter). If so, if fetches mention suggestions and renders them. On @@ -63,16 +76,14 @@ var MentionManager = Widget.extend({ }; // add the selected suggestion to the list - var selection = this.active_listener.selection; - if (selection.length && this.active_listener.sort_selection) { + if (this.active_listener.selection.length) { // get mention matches (ordered by index in the text) var matches = this._get_match(text_input, this.active_listener); var index = get_mention_index(matches, this._get_selection_positions().start); - selection.splice(index, 0, selected_suggestion); + this.active_listener.selection.splice(index, 0, selected_suggestion); } else { - selection = selection.concat([selected_suggestion]); + this.active_listener.selection.push(selected_suggestion); } - this._update_listener_selection(this.active_listener, selection); // update input text, and reset dropdown var cursor_position = this._get_selection_positions().start; @@ -90,10 +101,7 @@ var MentionManager = Widget.extend({ * @param {char} [delimiter] the mention delimiter * @param {function} [fetch_callback] the callback to fetch mention suggestions * @param {string} [model] the model used for redirection - * @param {boolean} [notify_selection_changed] true to trigger an event when the selection changes * @param {string} [redirect_classname] the classname of the <a> wrapping the mention - * @param {boolean} [sort_selection] true if the selection must be sorted - * @param {string} [suggestions_template] the QWeb template to render suggestions */ register: function (listener) { this.listeners.push(_.extend(listener, { @@ -137,7 +145,6 @@ var MentionManager = Widget.extend({ */ detect_delimiter: function () { var self = this; - var options = this.options; var text_val = this.composer.$input.val(); var cursor_position = this._get_selection_positions().start; var left_string = text_val.substring(0, cursor_position); @@ -146,7 +153,7 @@ var MentionManager = Widget.extend({ var pattern = "(^"+delimiter+"|(^\\s"+delimiter+"))"; var regex_start = new RegExp(pattern, "g"); search_str = search_str.replace(/^\s\s*|^[\n\r]/g, ''); - if (regex_start.test(search_str) && search_str.length > options.min_length) { + if (regex_start.test(search_str) && search_str.length > self.options.min_length) { search_str = search_str.replace(pattern, ''); return search_str.indexOf(' ') < 0 && !/[\r\n]/.test(search_str) ? search_str.replace(delimiter, '') : false; } @@ -164,19 +171,19 @@ var MentionManager = Widget.extend({ } } - if (this.active_listener) { - // start a timeout to fetch data with the current 'mention word'. The timer avoid to start - // an RPC for each pushed key when the user is still typing. - // The 'typing_speed' option should approach the time for a human to type a letter. - clearTimeout(this.mention_fetch_timer); - this.mention_fetch_timer = setTimeout(function () { - $.when(listener.fetch_callback(self.mention_word, self.active_listener)).then(function (suggestions) { + // start a timeout to fetch data with the current 'mention word'. The timer avoid to start + // an RPC for each pushed key when the user is still typing. + // The 'typing_speed' option should approach the time for a human to type a letter. + clearTimeout(this.mention_fetch_timer); + this.mention_fetch_timer = setTimeout(function () { + if (self.active_listener) { + $.when(listener.fetch_callback(self.mention_word)).then(function (suggestions) { self.set('mention_suggestions', suggestions); }); - }, this.options.typing_speed); - } else { - this.set('mention_suggestions', []); // close the dropdown - } + } else { + self.set('mention_suggestions', []); // close the dropdown + } + }, this.options.typing_speed); }, /** @@ -201,7 +208,7 @@ var MentionManager = Widget.extend({ } } if (to_remove.length) { - self._update_listener_selection(listener, _.difference(mention_selection, to_remove)); + listener.selection = _.difference(mention_selection, to_remove); } }); }, @@ -235,9 +242,8 @@ var MentionManager = Widget.extend({ }, reset_selections: function () { - var self = this; _.each(this.listeners, function (listener) { - self._update_listener_selection(listener, []); + listener.selection = []; }); }, @@ -269,7 +275,7 @@ var MentionManager = Widget.extend({ }, _render_suggestions: function () { if (_.flatten(this.get('mention_suggestions')).length) { - this.$el.html(QWeb.render(this.active_listener.suggestions_template, { + this.$el.html(QWeb.render('mail.ChatComposer.MentionSuggestions', { suggestions: this.get('mention_suggestions'), })); this.$el @@ -282,12 +288,6 @@ var MentionManager = Widget.extend({ this.open = false; } }, - _update_listener_selection: function (listener, selection) { - listener.selection = selection; - if (listener.notify_selection_changed) { - this.trigger('selection_changed', listener); - } - }, // Cursor position and selection utils _get_selection_positions: function () { @@ -327,10 +327,8 @@ var Composer = Widget.extend({ input_max_height: 150, input_min_height: 28, mention_fetch_limit: 8, - get_channel_info: function () {}, }); this.context = this.options.context; - this.get_channel_info = this.options.get_channel_info; // Attachments this.AttachmentDataSet = new data.DataSetSearch(this, 'ir.attachment', this.context); @@ -343,19 +341,14 @@ var Composer = Widget.extend({ delimiter: '@', fetch_callback: this.mention_fetch_partners.bind(this), model: 'res.partner', - notify_selection_changed: true, redirect_classname: 'o_mail_redirect', - sort_selection: true, - suggestions_template: 'mail.ChatComposer.MentionPartnerSuggestions', }); this.mention_manager.register({ delimiter: '#', fetch_callback: this.mention_fetch_channels.bind(this), model: 'mail.channel', redirect_classname: 'o_channel_redirect', - suggestions_template: 'mail.ChatComposer.MentionChannelSuggestions' }); - this.mention_manager.on('selection_changed', this, this.mention_render_selection); this.PartnerModel = new Model('res.partner'); this.ChannelModel = new Model('mail.channel'); }, @@ -365,8 +358,10 @@ var Composer = Widget.extend({ this.$attachment_button = this.$(".o_composer_button_add_attachment"); this.$attachments_list = this.$('.o_composer_attachments_list'); - this.$mention_partner_tags = this.$('.o_composer_mentioned_partners'); this.$input = this.$('.o_composer_input'); + this.$input.focus(function () { + self.trigger('input_focused'); + }); this.resize_input(); // Attachments @@ -403,7 +398,7 @@ var Composer = Widget.extend({ return $.when({ content: this.mention_manager.generate_links(value), attachment_ids: _.pluck(this.get('attachment_ids'), 'id'), - partner_ids: _.pluck(this.mention_manager.get_listener_selection('@'), 'id'), + partner_ids: _.uniq(_.pluck(this.mention_manager.get_listener_selection('@'), 'id')), }); }, @@ -607,19 +602,38 @@ var Composer = Widget.extend({ }); }); }, - mention_fetch_partners: function (search, listener) { - var kwargs = { - channel: this.get_channel_info(), - exclude: listener ? _.pluck(listener.selection, 'id') : [], - limit: this.options.mention_fetch_limit, - search: search, - }; - return this.PartnerModel.call('get_mention_suggestions', kwargs); + mention_fetch_partners: function (search) { + var self = this; + return $.when(this.mention_prefetched_partners).then(function (prefetched_partners) { + // filter prefetched partners with the given search string + var suggestions = []; + var limit = self.options.mention_fetch_limit; + var search_regexp = new RegExp(self.unaccent(search), 'i'); + _.each(prefetched_partners, function (partners) { + if (limit > 0) { + var filtered_partners = _.filter(partners, function (partner) { + return partner.email && partner.email.search(search_regexp) !== -1 || + partner.name && self.unaccent(partner.name).search(search_regexp) !== -1; + }); + if (filtered_partners.length) { + suggestions.push(filtered_partners.slice(0, limit)); + limit -= filtered_partners.length; + } + } + }); + if (!suggestions.length) { + // no result found among prefetched partners, fetch other suggestions + var kwargs = { + limit: limit, + search: search, + }; + suggestions = self.PartnerModel.call('get_mention_suggestions', kwargs); + } + return suggestions; + }); }, - mention_render_selection: function (listener) { - this.$mention_partner_tags.html(QWeb.render('mail.ChatComposer.MentionTags', { - partners: listener.selection, - })); + mention_set_prefetched_partners: function (prefetched_partners) { + this.mention_prefetched_partners = prefetched_partners; }, // Others @@ -629,6 +643,12 @@ var Composer = Widget.extend({ focus: function () { this.$input.focus(); }, + unaccent: function (str) { + _.each(accented_letters_mapping, function (value, key) { + str = str.replace(new RegExp(value, 'g'), key); + }); + return str; + }, }); return Composer; |
