diff options
| author | Aaron Bohy <aab@odoo.com> | 2015-10-26 16:46:09 +0100 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2015-11-03 16:19:35 +0100 |
| commit | 388f7a7db9cd42142d86a768d1438af5af2f3b73 (patch) | |
| tree | 1b1f7963d4ddcb253580b81d1893f0bd100f5798 /addons/mail/static/src/js | |
| parent | b61c0d24993761b936766665823869f831fbb737 (diff) | |
[IMP] mail: composer: mention channels feature
Generalizes the mention mechanism to allow mentionning partners (@) and
channels (#).
When a user clicks on a mentionned channel, it subscribes to it and joins it.
If the mention is in a chatter, the user is redirected to the Discuss client
action.
Moves the RPCs to get the menu and action id of the Discuss client action from
systray.js to chat_managaer.js as they are now needed in the chatter as well.
Also fixes the on_channel_clicked redirection for notification message
(created/joined/left #channel). Those three messages are now of type
notification, and correctly redirect on click.
Diffstat (limited to 'addons/mail/static/src/js')
| -rw-r--r-- | addons/mail/static/src/js/chat_manager.js | 15 | ||||
| -rw-r--r-- | addons/mail/static/src/js/chatter.js | 49 | ||||
| -rw-r--r-- | addons/mail/static/src/js/client_action.js | 13 | ||||
| -rw-r--r-- | addons/mail/static/src/js/composer.js | 566 | ||||
| -rw-r--r-- | addons/mail/static/src/js/systray.js | 17 | ||||
| -rw-r--r-- | addons/mail/static/src/js/thread.js | 2 |
6 files changed, 390 insertions, 272 deletions
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js index 9764bdd1018..42f2056e011 100644 --- a/addons/mail/static/src/js/chat_manager.js +++ b/addons/mail/static/src/js/chat_manager.js @@ -22,6 +22,7 @@ var channels = []; var emojis = []; var emoji_substitutions = {}; var needaction_counter = 0; +var discuss_ids = {}; // Message and channel manipulation helpers //---------------------------------------------------------------------------------- @@ -376,6 +377,10 @@ var chat_manager = { return needaction_counter; }, + get_discuss_ids: function () { + return discuss_ids; + }, + detach_channel: function (channel) { return ChannelModel.call("channel_minimize", [channel.uuid, true]); }, @@ -454,6 +459,10 @@ function init () { }); }); + var ir_model = new Model("ir.model.data"); + var load_menu_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"]); + var load_action_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"]); + bus.on('notification', null, function (notification) { var model = notification[0][1]; if (model === 'ir.needaction') { @@ -498,7 +507,11 @@ function init () { } }); - return $.when(load_channels, load_emojis).then(function () { + return $.when(load_menu_id, load_action_id, load_channels, load_emojis).then(function (menu_id, action_id) { + discuss_ids = { + menu_id: menu_id, + action_id: action_id, + }; bus.start_polling(); return chat_manager; }); diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index 2aaf856cd46..0d261ea9053 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -105,7 +105,7 @@ var Followers = form_common.AbstractField.extend({ // event: click on 'edit_subtype(pencil)' button to edit subscription this.$el.on('click', '.o_edit_subtype', self.on_edit_subtype); this.$el.on('click', '.o_remove_follower', self.on_remove_follower); - this.$el.on('click', 'a[data-partner]', self.on_follower_clicked); + this.$el.on('click', '.o_mail_redirect', self.on_click_redirect); }, on_edit_subtype: function (event) { @@ -167,24 +167,11 @@ var Followers = form_common.AbstractField.extend({ } }, - on_follower_clicked: function (event) { + on_click_redirect: function (event) { event.preventDefault(); - var partner_id = $(event.target).data('partner'); - var state = { - 'model': 'res.partner', - 'id': partner_id, - 'title': this.record_name - }; - web_client.action_manager.do_push_state(state); - var action = { - type:'ir.actions.act_window', - view_type: 'form', - view_mode: 'form', - res_model: 'res.partner', - views: [[false, 'form']], - res_id: partner_id, - }; - this.do_action(action); + var res_id = $(event.target).data('oe-id'); + var res_model = $(event.target).data('oe-model'); + this.trigger('redirect', res_model, res_id); }, read_value: function () { @@ -697,7 +684,6 @@ var Chatter = form_common.AbstractField.extend({ template: 'mail.Chatter', events: { - "click .o_mail_redirect": "on_click_redirect", "click .o_chatter_button_new_message": "on_open_composer_new_message", "click .o_chatter_button_log_note": "on_open_composer_log_note", }, @@ -717,6 +703,7 @@ var Chatter = form_common.AbstractField.extend({ this.followers = this.field_manager.fields.message_follower_ids; if (this.followers) { this.$('.o_chatter_topbar').append(this.followers.$el); + this.followers.on('redirect', this, this.on_redirect); } this.thread = new ChatThread(this, { @@ -728,6 +715,8 @@ var Chatter = form_common.AbstractField.extend({ this.thread.on('toggle_star_status', this, function (message_id) { chat_manager.toggle_star_status(message_id); }); + this.thread.on('redirect', this, this.on_redirect); + this.thread.on('redirect_to_channel', this, this.on_channel_redirect); var def1 = this._super.apply(this, arguments); var def2 = this.thread.appendTo(this.$el); @@ -778,15 +767,31 @@ var Chatter = form_common.AbstractField.extend({ } }, - on_click_redirect: function (event) { + on_channel_redirect: function (channel_id) { + event.preventDefault(); + var self = this; + var def; + var channel = chat_manager.get_channel(channel_id); + // If not registered to 'channel' yet, do it + if (channel.id !== channel_id) { + def = chat_manager.join_channel(channel_id); + } + $.when(def).then(function () { + // Execute Discuss client action with 'channel' as default channel + var discuss_ids = chat_manager.get_discuss_ids(); + self.do_action(discuss_ids.action_id, {active_id: channel_id}); + }); + }, + + on_redirect: function (res_model, res_id) { event.preventDefault(); this.do_action({ type:'ir.actions.act_window', view_type: 'form', view_mode: 'form', - res_model: $(event.target).data('oe-model'), + res_model: res_model, views: [[false, 'form']], - res_id: $(event.target).data('oe-id'), + res_id: res_id, }); }, diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index 4f161f3d93c..5113ebff5d6 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -118,11 +118,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop(); }, - init: function(parent, action) { + init: function(parent, action, options) { this._super.apply(this, arguments); this.action_manager = parent; this.domain = []; this.action = action; + this.options = options || {}; this.channels_scrolltop = {}; }, @@ -137,7 +138,8 @@ var ChatAction = Widget.extend(ControlPanelMixin, { }; var dataset = new data.DataSetSearch(this, 'mail.message'); var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; - var default_channel_id = this.action.context.active_id || + var default_channel_id = this.options.active_id || + this.action.context.active_id || this.action.params.default_active_id || 'channel_inbox'; this.searchview = new SearchView(this, dataset, view_id, {}, options); @@ -164,7 +166,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.thread.on('redirect', this, this.on_redirect); this.thread.on('redirect_to_channel', this, function (channel_id) { - this.set_channel(chat_manager.get_channel(channel_id)); + var channel = chat_manager.get_channel(channel_id); + if (channel.id === channel_id) { + this.set_channel(channel); + } else { + chat_manager.join_channel(channel_id); + } }); this.thread.on('load_more_messages', this, this.load_more_messages); this.thread.on('mark_as_read', this, function (message_id) { diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index 7ed8318594a..b53221dac35 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -12,6 +12,296 @@ var Widget = require('web.Widget'); var QWeb = core.qweb; var _t = core._t; +// 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 +// suggestion clicked, it updates the selection for the corresponding listener. +var MentionManager = Widget.extend({ + className: 'dropup o_composer_mention_dropdown', + + events: { + "mouseover .o_mention_proposition": "on_hover_mention_proposition", + "click .o_mention_proposition": "on_click_mention_item", + }, + + init: function (parent, options) { + this._super.apply(this, arguments); + + this.composer = parent; + this.options = _.extend({}, options, { + min_length: 0, + typing_speed: 200, + }); + + this.open = false; + this.listeners = []; + this.set('mention_suggestions', []); + this.on('change:mention_suggestions', this, this._render_suggestions); + }, + + // Events + on_hover_mention_proposition: function (event) { + var $elem = $(event.currentTarget); + this.$('.o_mention_proposition').removeClass('active'); + $elem.addClass('active'); + }, + on_click_mention_item: function (event) { + event.preventDefault(); + + var text_input = this.composer.$input.val(); + var id = $(event.currentTarget).data('id'); + var selected_suggestion = _.find(_.flatten(this.get('mention_suggestions')), function (s) { + return s.id === id; + }); + var get_mention_index = function (matches, cursor_position) { + for (var i=0; i<matches.length; i++) { + if (cursor_position <= matches[i].index) { + return i; + } + } + return i; + }; + + // add the selected suggestion to the list + var selection = this.active_listener.selection; + if (selection.length && this.active_listener.sort_selection) { + // 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); + } else { + selection = selection.concat([selected_suggestion]); + } + this._update_listener_selection(this.active_listener, selection); + + // update input text, and reset dropdown + var cursor_position = this._get_selection_positions().start; + var text_left = text_input.substring(0, cursor_position-(this.mention_word.length+1)); + var text_right = text_input.substring(cursor_position, text_input.length); + var text_input_new = text_left + this.active_listener.delimiter + selected_suggestion.name + ' ' + text_right; + this.composer.$input.val(text_input_new); + this._set_cursor_position(text_left.length+selected_suggestion.name.length+2); + this.set('mention_suggestions', []); + }, + + // Public API + /** + * Registers a new listener, described by an object containing the following keys + * @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, { + selection: [], + })); + }, + + /** + * Returns true if the mention suggestions dropdown is open, false otherwise + */ + is_open: function () { + return this.open; + }, + + proposition_navigation: function (keycode) { + var $active = this.$('.o_mention_proposition.active'); + if (keycode === $.ui.keyCode.ENTER) { // selecting proposition + $active.click(); + } else { // navigation in propositions + var $to; + if (keycode === $.ui.keyCode.DOWN) { + $to = $active.nextAll('.o_mention_proposition').first(); + } else { + $to = $active.prevAll('.o_mention_proposition').first(); + } + if ($to.length) { + $active.removeClass('active'); + $to.addClass('active'); + } + } + }, + + /** + * Detects if the user is currently typing a mention word + * @return the search string if it is, false otherwise + */ + 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); + function validate_keyword (delimiter) { + var search_str = text_val.substring(left_string.lastIndexOf(delimiter) - 1, cursor_position); + 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) { + search_str = search_str.replace(pattern, ''); + return search_str.indexOf(' ') < 0 && !/[\r\n]/.test(search_str) ? search_str.replace(delimiter, '') : false; + } + return false; + } + + this.active_listener = undefined; + for (var i=0; i<this.listeners.length; i++) { + var listener = this.listeners[i]; + this.mention_word = validate_keyword(listener.delimiter); + + if (this.mention_word !== false) { + this.active_listener = listener; + break; + } + } + + 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) { + self.set('mention_suggestions', suggestions); + }); + }, this.options.typing_speed); + } else { + this.set('mention_suggestions', []); // close the dropdown + } + }, + + /** + * Checks if a listener's selection should be updated after DELETE or BACKSPACE keypress + */ + check_remove: function () { + var self = this; + var to_remove = []; + var selection = this._get_selection_positions(); + var deleted_binf = selection.start; + var deleted_bsup = selection.end; + + _.each(this.listeners, function (listener) { + var mention_selection = listener.selection; + var matches = self._get_match(self.composer.$input.val(), listener); + for (var i=0; i<matches.length; i++) { + var m = matches[i]; + var m1 = m.index; + var m2 = m.index + m[0].length; + if (deleted_binf <= m2 && m1 < deleted_bsup) { + to_remove.push(mention_selection[i]); + } + } + if (to_remove.length) { + self._update_listener_selection(listener, _.difference(mention_selection, to_remove)); + } + }); + }, + + /** + * Replaces mentions appearing in the string 's' by html links with proper redirection + */ + generate_links: function (s) { + var self = this; + var mention_link = "<a href='#' class='%s' data-oe-id='%s' data-oe-model='%s'>%s%s</a>"; + _.each(this.listeners, function (listener) { + var selection = listener.selection; + if (selection.length) { + var matches = self._get_match(s, listener); + var substrings = []; + var start_index = 0; + for (var i=0; i<matches.length; i++) { + var match = matches[i]; + var end_index = match.index + match[0].length; + var match_name = match[0].substring(1); + var processed_text = _.str.sprintf(mention_link, listener.redirect_classname, selection[i].id, listener.model, listener.delimiter, match_name); + var subtext = s.substring(start_index, end_index).replace(match[0], processed_text); + substrings.push(subtext); + start_index = end_index; + } + substrings.push(s.substring(start_index, s.length)); + s = substrings.join(''); + } + }); + return s; + }, + + reset_selections: function () { + var self = this; + _.each(this.listeners, function (listener) { + self._update_listener_selection(listener, []); + }); + }, + + // Private functions + /** + * Returns the matches (as RexExp.exec does) for the mention in the input text + * @param {String} input_text: the text to search matches + * @param {Object} listener: the listener for which we want to find a match + * @returns {Object[]} matches in the same format as RexExp.exec() + */ + _get_match: function (input_text, listener) { + // create the regex of all mention's names + var names = _.pluck(listener.selection, 'name'); + var escaped_names = _.map(names, function (str) { + return "("+_.str.escapeRegExp(listener.delimiter+str)+")"; + }); + var regex_str = escaped_names.join('|'); + // extract matches + var result = []; + if (regex_str.length) { + var myRegexp = new RegExp(regex_str, 'g'); + var match = myRegexp.exec(input_text); + while (match !== null) { + result.push(match); + match = myRegexp.exec(input_text); + } + } + return result; + }, + _render_suggestions: function () { + if (_.flatten(this.get('mention_suggestions')).length) { + this.$el.html(QWeb.render(this.active_listener.suggestions_template, { + suggestions: this.get('mention_suggestions'), + })); + this.$el + .addClass('open') + .find('.o_mention_proposition').first().addClass('active'); + this.open = true; + } else { + this.$el.removeClass('open'); + this.$el.empty(); + 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 () { + var el = this.composer.$input.get(0); + return el ? {start: el.selectionStart, end: el.selectionEnd} : {start: 0, end: 0}; + }, + _set_cursor_position: function (pos) { + this.composer.$input.each(function (index, elem) { + if (elem.setSelectionRange){ + elem.setSelectionRange(pos, pos); + } + else if (elem.createTextRange){ + elem.createTextRange().collapse(true).moveEnd('character', pos).moveStart('character', pos).select(); + } + }); + }, + +}); + var Composer = Widget.extend({ template: "mail.ChatComposer", @@ -22,8 +312,6 @@ var Composer = Widget.extend({ "click .o_composer_button_send": "send_message", "click .o_composer_button_add_attachment": "on_click_add_attachment", "click .o_attachment_delete": "on_attachment_delete", - "click .o_mention_proposition": "on_click_mention_item", - "mouseover .o_mention_proposition": "on_hover_mention_proposition", }, init: function (parent, options) { @@ -33,9 +321,6 @@ var Composer = Widget.extend({ input_baseline: 18, input_max_height: 150, input_min_height: 28, - mention_delimiter: '@', - mention_min_length: 0, - mention_typing_speed: 200, mention_fetch_limit: 8, get_channel_info: function () {}, }); @@ -48,10 +333,26 @@ var Composer = Widget.extend({ this.set('attachment_ids', []); // Mention + this.mention_manager = new MentionManager(this); + this.mention_manager.register({ + 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.mention_dropdown_open = false; - this.set('mention_partners', []); // proposition of not-mention partner matching the mention_word - this.set('mention_selected_partners', []); // contains the mention partners sorted as they appear in the input text + this.ChannelModel = new Model('mail.channel'); }, start: function () { @@ -60,7 +361,6 @@ 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.$mention_dropdown = this.$('.o_composer_mention_dropdown'); this.$input = this.$('.o_composer_input'); this.resize_input(); @@ -86,8 +386,8 @@ var Composer = Widget.extend({ }); // Mention - this.on('change:mention_partners', this, this.render_mention_partners); - this.on('change:mention_selected_partners', this, this.render_mention_selected_partners); + this.mention_manager.prependTo(this.$('.o_composer')); + return this._super(); }, @@ -96,7 +396,7 @@ var Composer = Widget.extend({ // behavior for the chatter composer var value = this.$input.val().replace(/\n|\r/g, '<br/>'); return $.when({ - content: this.mention_preprocess_message(value), + content: this.mention_manager.generate_links(value), attachment_ids: _.pluck(this.get('attachment_ids'), 'id'), partner_ids: _.pluck(this.get('mention_selected_partners'), 'id'), }); @@ -114,7 +414,7 @@ var Composer = Widget.extend({ // Empty input, selected partners and attachments self.$input.val(''); self.resize_input(); - self.set('mention_selected_partners', []); + self.mention_manager.reset_selections(); self.set('attachment_ids', []); self.$input.focus(); @@ -157,18 +457,18 @@ var Composer = Widget.extend({ // UP, DOWN: prevent moving cursor if navigation in mention propositions case $.ui.keyCode.UP: case $.ui.keyCode.DOWN: - if (this.mention_dropdown_open) { + if (this.mention_manager.is_open()) { event.preventDefault(); } break; // BACKSPACE, DELETE: check if need to remove a mention case $.ui.keyCode.BACKSPACE: case $.ui.keyCode.DELETE: - this.mention_check_remove(); + this.mention_manager.check_remove(); break; // ENTER: submit the message only if the dropdown mention proposition is not displayed case $.ui.keyCode.ENTER: - if (this.mention_dropdown_open) { + if (this.mention_manager.is_open()) { event.preventDefault(); } else if (!this.prevent_send(event)) { event.preventDefault(); @@ -195,16 +495,11 @@ var Composer = Widget.extend({ case $.ui.keyCode.ENTER: case $.ui.keyCode.UP: case $.ui.keyCode.DOWN: - this.mention_proposition_navigation(event.which); + this.mention_manager.proposition_navigation(event.which); break; // Otherwise, check if a mention is typed default: - this.mention_word = this.mention_detect_delimiter(); - if (this.mention_word !== false) { - this.mention_word_changed(); - } else { - this.set('mention_partners', []); // close the dropdown - } + this.mention_manager.detect_delimiter(); this.resize_input(); } }, @@ -294,226 +589,35 @@ var Composer = Widget.extend({ }, // Mention - on_click_mention_item: function (event) { - event.preventDefault(); - - var text_input = this.$input.val(); - var partner_id = $(event.currentTarget).data('partner-id'); - var selected_partner = _.filter(_.flatten(this.get('mention_partners')), function (p) { - return p.id === partner_id; - })[0]; - - // add the mention partner to the list - var mention_selected_partners = this.get('mention_selected_partners'); - if (mention_selected_partners.length) { // there are already mention partners - // get mention matches (ordered by index in the text) - var matches = this.mention_get_match(text_input); - var index = this.mention_get_index(matches, this.get_selection_positions().start); - mention_selected_partners.splice(index, 0, selected_partner); - mention_selected_partners = _.clone(mention_selected_partners); - } else { // this is the first mentionned partner - mention_selected_partners = mention_selected_partners.concat([selected_partner]); - } - this.set('mention_selected_partners', mention_selected_partners); - - // update input text, and reset dropdown - var cursor_position = this.get_selection_positions().start; - var text_left = text_input.substring(0, cursor_position-(this.mention_word.length+1)); - var text_right = text_input.substring(cursor_position, text_input.length); - var text_input_new = text_left + this.options.mention_delimiter + selected_partner.name + ' ' + text_right; - this.$input.val(text_input_new); - this.set_cursor_position(text_left.length+selected_partner.name.length+2); - this.set('mention_partners', []); - }, - - on_hover_mention_proposition: function (event) { - var $elem = $(event.currentTarget); - this.$('.o_mention_proposition').removeClass('active'); - $elem.addClass('active'); - }, - - mention_proposition_navigation: function (keycode) { - var $active = this.$('.o_mention_proposition.active'); - if (keycode === $.ui.keyCode.ENTER) { // selecting proposition - $active.click(); - } else { // navigation in propositions - var $to; - if (keycode === $.ui.keyCode.DOWN) { - $to = $active.nextAll('.o_mention_proposition').first(); - } else { - $to = $active.prevAll('.o_mention_proposition').first(); - } - if ($to.length) { - $active.removeClass('active'); - $to.addClass('active'); - } - } - }, - - /** - * Return the text attached to the mention delimiter - * @returns {String|false}: the text right after the delimiter or false - */ - mention_detect_delimiter: function () { - var options = this.options; - var delimiter = options.mention_delimiter; - var text_val = this.$input.val(); - var cursor_position = this.get_selection_positions().start; - var left_string = text_val.substring(0, cursor_position); - var search_str = text_val.substring(left_string.lastIndexOf(delimiter) - 1, cursor_position); - - return validate_keyword(search_str); - - function validate_keyword (search_str) { - 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.mention_min_length) { - search_str = search_str.replace(pattern, ''); - return search_str.indexOf(' ') < 0 && !/[\r\n]/.test(search_str) ? search_str.replace(delimiter, '') : false; - } - return false; - } - }, - - mention_word_changed: function () { - var self = this; - // start a timeout to fetch partner with the current 'mention word'. The timer avoid to start - // an RPC for each pushed key when the user is typing the partner name. - // The 'mention_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 () { - self.mention_fetch_partner(self.mention_word); - }, this.options.mention_typing_speed); + mention_fetch_channels: function (search) { + var kwargs = { + limit: this.options.mention_fetch_limit, + search: search, + }; + return this.ChannelModel + .call('get_mention_suggestions', kwargs) + .then(function (suggestions) { + return _.partition(suggestions, function (suggestion) { + return _.contains(['public', 'groups'], suggestion.public); + }); + }); }, - - mention_fetch_partner: function (search) { - var self = this; + mention_fetch_partners: function (search, listener) { var kwargs = { channel: this.get_channel_info(), - exclude: _.pluck(this.get('mention_selected_partners'), 'id'), + exclude: listener ? _.pluck(listener.selection, 'id') : [], limit: this.options.mention_fetch_limit, search: search, }; - this.PartnerModel.call('get_mention_suggestions', kwargs).then(function (suggestions) { - self.set('mention_partners', suggestions); - }); - }, - - mention_check_remove: function () { - var mention_selected_partners = this.get('mention_selected_partners'); - var partners_to_remove = []; - var selection = this.get_selection_positions(); - var deleted_binf = selection.start; - var deleted_bsup = selection.end; - - var matches = this.mention_get_match(this.$input.val()); - for (var i=0; i<matches.length; i++) { - var m = matches[i]; - var m1 = m.index; - var m2 = m.index + m[0].length; - if (deleted_binf <= m2 && m1 < deleted_bsup) { - partners_to_remove.push(mention_selected_partners[i]); - } - } - this.set('mention_selected_partners', _.difference(mention_selected_partners, partners_to_remove)); + return this.PartnerModel.call('get_mention_suggestions', kwargs); }, - - mention_preprocess_message: function (message) { - var partners = this.get('mention_selected_partners'); - if (partners.length) { - var matches = this.mention_get_match(message); - var substrings = []; - var start_index = 0; - for (var i=0; i<matches.length; i++) { - var match = matches[i]; - var end_index = match.index + match[0].length; - var partner_name = match[0].substring(1); - var processed_text = _.str.sprintf("<a href='#' class='o_mail_redirect' data-oe-model='res.partner' data-oe-id='%s'>@%s</a>", partners[i].id, partner_name); - var subtext = message.substring(start_index, end_index).replace(match[0], processed_text); - substrings.push(subtext); - start_index = end_index; - } - substrings.push(message.substring(start_index, message.length)); - return substrings.join(''); - } - return message; - }, - - render_mention_partners: function () { - if (_.flatten(this.get('mention_partners')).length) { - this.$mention_dropdown.html(QWeb.render('mail.ChatComposer.MentionMenu', { - suggestions: this.get('mention_partners'), - })); - this.$mention_dropdown - .addClass('open') - .find('.o_mention_proposition').first().addClass('active'); - this.mention_dropdown_open = true; - } else { - this.$mention_dropdown.removeClass('open'); - this.$mention_dropdown.empty(); - this.mention_dropdown_open = false; - } - }, - - render_mention_selected_partners: function () { + mention_render_selection: function (listener) { this.$mention_partner_tags.html(QWeb.render('mail.ChatComposer.MentionTags', { - partners: this.get('mention_selected_partners'), + partners: listener.selection, })); }, - /** - * Return the matches (as RexExp.exec does) for the partner mention in the input text - * @param {String} input_text: the text to search matches - * @returns {Object[]} matches in the same format as RexExp.exec() - */ - mention_get_match: function (input_text) { - var self = this; - // create the regex of all mention partner name - var partner_names = _.pluck(this.get('mention_selected_partners'), 'name'); - var escaped_partner_names = _.map(partner_names, function (str) { - return "("+_.str.escapeRegExp(self.options.mention_delimiter+str)+")"; - }); - var regex_str = escaped_partner_names.join('|'); - // extract matches - var result = []; - if(regex_str.length){ - var myRegexp = new RegExp(regex_str, 'g'); - var match = myRegexp.exec(input_text); - while (match !== null) { - result.push(match); - match = myRegexp.exec(input_text); - } - } - return result; - }, - - mention_get_index: function (matches, cursor_position) { - for (var i=0; i<matches.length; i++) { - if (cursor_position <= matches[i].index) { - return i; - } - } - return i; - }, - // Others - get_selection_positions: function () { - var el = this.$input.get(0); - return el ? {start: el.selectionStart, end: el.selectionEnd} : {start: 0, end: 0}; - }, - - set_cursor_position: function (pos) { - this.$input.each(function (index, elem) { - if (elem.setSelectionRange){ - elem.setSelectionRange(pos, pos); - } - else if (elem.createTextRange){ - elem.createTextRange().collapse(true).moveEnd('character', pos).moveStart('character', pos).select(); - } - }); - }, - is_empty: function () { return !this.$input.val().trim() && !this.$('.o_attachments').children().length; }, diff --git a/addons/mail/static/src/js/systray.js b/addons/mail/static/src/js/systray.js index 83ac8cb3dd7..c5d34f179bc 100644 --- a/addons/mail/static/src/js/systray.js +++ b/addons/mail/static/src/js/systray.js @@ -2,7 +2,6 @@ odoo.define('mail.systray', function (require) { "use strict"; var core = require('web.core'); -var Model = require('web.Model'); var SystrayMenu = require('web.SystrayMenu'); var web_client = require('web.web_client'); var Widget = require('web.Widget'); @@ -20,16 +19,6 @@ var NotificationTopButton = Widget.extend({ events: { "click": "on_click", }, - willStart: function () { - var self = this; - var ir_model = new Model("ir.model.data"); - var def1 = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"]); - var def2 = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"]); - return $.when(def1, def2, this._super()).then(function (menu_id, action_id) { - self.discuss_menu_id = menu_id; - self.client_action_id = action_id; - }); - }, start: function () { chat_manager.bus.on("update_needaction", this, this.update_counter); this.update_counter(chat_manager.get_needaction_counter()); @@ -39,10 +28,10 @@ var NotificationTopButton = Widget.extend({ this.$('.o_notification_counter').html(counter); }, on_click: function (event) { - var self = this; event.preventDefault(); - this.do_action(this.client_action_id, {clear_breadcrumbs: true}).then(function () { - core.bus.trigger('change_menu_section', self.discuss_menu_id); + var discuss_ids = chat_manager.get_discuss_ids(); + this.do_action(discuss_ids.action_id, {clear_breadcrumbs: true}).then(function () { + core.bus.trigger('change_menu_section', discuss_ids.menu_id); }); }, }); diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js index 32444085936..76d10ed3cdd 100644 --- a/addons/mail/static/src/js/thread.js +++ b/addons/mail/static/src/js/thread.js @@ -83,7 +83,7 @@ var Thread = Widget.extend({ on_channel_redirect: function (event) { event.preventDefault(); - var channel_id = $(event.target).data('channel-id'); + var channel_id = $(event.target).data('oe-id'); this.trigger('redirect_to_channel', channel_id); }, |
