summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/mail_chat_backend.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/mail/static/src/js/mail_chat_backend.js')
-rw-r--r--addons/mail/static/src/js/mail_chat_backend.js1177
1 files changed, 954 insertions, 223 deletions
diff --git a/addons/mail/static/src/js/mail_chat_backend.js b/addons/mail/static/src/js/mail_chat_backend.js
index cd1cc00fdf2..9353afb68ab 100644
--- a/addons/mail/static/src/js/mail_chat_backend.js
+++ b/addons/mail/static/src/js/mail_chat_backend.js
@@ -1,301 +1,1032 @@
-odoo.define('im_chat.im_chat', function (require) {
+odoo.define('mail.chat_backend', function (require) {
"use strict";
-// to do: make this work in website
-
var bus = require('bus.bus');
var core = require('web.core');
var data = require('web.data');
-var Model = require('web.DataModel');
-var session = require('web.session');
+var Model = require('web.Model');
+var pyeval = require('web.pyeval');
var SystrayMenu = require('web.SystrayMenu');
+var Widget = require('web.Widget');
+var Dialog = require('web.Dialog');
+var ControlPanelMixin = require('web.ControlPanelMixin');
+var SearchView = require('web.SearchView');
+var WebClient = require('web.WebClient');
+
+var session = require('web.session');
var utils = require('web.utils');
var web_client = require('web.web_client');
-var Widget = require('web.Widget');
-var im_chat_common = require('im_chat.im_chat_common');
+
+var mail_utils = require('mail.utils');
+var mail_chat_common = require('mail.chat_common');
+var mail_thread = require('mail.thread');
var _t = core._t;
var QWeb = core.qweb;
-
-var USERS_LIMIT = 20;
+var internal_bus = core.bus;
-// ########## CONVERSATION extentions ###############
-
-im_chat_common.ConversationManager.include({
- _message_receive: function(message){
+/**
+ * Widget handeling the channels, in the backend
+ *
+ * Responsible to listen the bus and apply action with the received message. Add layer to coordinate the
+ * folded conversation and trigger event for the InstantMessagingView client action (using internal
+ * comminication bus). It is a component of the WebClient.
+ **/
+var ConversationManagerBackend = mail_chat_common.ConversationManager.extend({
+ _setup: function(init_data){
var self = this;
- var session_id = message.to_id[0];
- var conv = this.sessions[message.to_id[1]];
- if(!conv){
- // fetch the session, and init it with the message
- var def_session = new Model("im_chat.session").call("session_info", [], {"ids" : [session_id]}).then(function(session){
- conv = self.session_apply(session, {'force_open': true});
- conv.message_receive(message);
- });
- }else{
- this._super(message);
+ this._super.apply(this, arguments);
+ _.each(init_data['notifications'], function(n){
+ self.on_notification(n);
+ });
+ },
+ // window title
+ window_title_change: function() {
+ this._super.apply(this, arguments);
+ var title = undefined;
+ if (this.get("waiting_messages") !== 0) {
+ title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages"));
}
+ web_client.set_title_part("im_messages", title);
},
-});
-
-im_chat_common.Conversation.include({
- // user actions
- prepare_action_menu: function(){
- this._super();
- this._add_action(_t('Shortcuts'), 'im_chat_option_shortcut', 'fa fa-info-circle', this.action_shorcode);
- this._add_action(_t('Quit discussion'), 'im_chat_option_quit', 'fa fa-minus-square', this.action_quit_session);
+ // sessions and messages
+ session_apply: function(active_session, options){
+ // for chat windows
+ if(active_session.is_minimized || (!active_session.is_minimized && active_session.state === 'closed')){
+ this._super.apply(this, arguments);
+ }
+ // for client action
+ if(!active_session.is_minimized){ // if not minimized,
+ internal_bus.trigger('mail_session_receive', active_session);
+ }
},
- action_shorcode: function(e){
- return web_client.action_manager.do_action({
- type: 'ir.actions.act_window',
- name : _t('Shortcode'),
- res_model: 'im_chat.shortcode',
- view_mode: 'tree,form',
- view_type: 'tree',
- views: [[false, 'list'], [false, 'form']],
- target: "new",
- limit: 80,
- flags: {
- action_buttons: true,
- pager: true,
- }
+ message_receive: function(message) {
+ var actual_channel_ids = _.map(_.keys(this.sessions), function(item){
+ return parseInt(item);
});
+ var message_channel_ids = message.channel_ids;
+ if(_.intersection(actual_channel_ids, message_channel_ids).length){
+ this._super.apply(this, arguments);
+ }
+ // broadcast the message to the NotificationButton and the InstantMessagingView
+ internal_bus.trigger('mail_message_receive', message);
+ // increment the needaction top counter
+ if(message.needaction_partner_ids && _.contains(message.needaction_partner_ids, session.partner_id)){
+ internal_bus.trigger('mail_needaction_new', 1);
+ }
},
- action_quit_session: function(e){
+});
+
+
+/**
+ * Widget Minimized Conversation
+ *
+ * Add layer of WebClient integration, and user fold state handling (comminication with server)
+ **/
+mail_chat_common.Conversation.include({
+ session_fold: function(state){
var self = this;
- var Session = new Model("im_chat.session");
- return Session.call("quit_user", [this.get("session").uuid]).then(function(res) {
- if(! res){
- self.do_warn(_t("Warning"), _t("You are only 2 identified users. Just close the conversation to leave."));
- }
+ var args = arguments;
+ var super_call = this._super;
+ // broadcast the state changing
+ return new Model("mail.channel").call("channel_fold", [], {"uuid" : this.get("session").uuid, "state" : state}).then(function(){
+ super_call.apply(self, args);
});
},
- // session
- session_update_state: function(state){
+});
+
+/**
+ * Widget : Patch for WebClient
+ *
+ * Create the conversation manager, and attach it to the web_client.
+ **/
+WebClient.include({
+ show_application: function(){
var self = this;
var args = arguments;
var super_call = this._super;
- // broadcast the state changing
- return new Model("im_chat.session").call("update_state", [], {"uuid" : this.get("session").uuid, "state" : state}).then(function(){
+ this.mail_conversation_manager = new ConversationManagerBackend(this);
+ this.mail_conversation_manager.start().then(function(){
super_call.apply(self, args);
+ self.mail_conversation_manager.bus.start_polling();
});
},
- // window title
- window_title_change: function() {
- this.super();
- var title = undefined;
- if (this.get("waiting_messages") !== 0) {
- title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages"))
- }
- web_client.set_title_part("im_messages", title);
+});
+
+
+/**
+ * Widget Top Menu Notification Counter
+ *
+ * Counter of notification in the Systray Menu. Need to know if InstantMessagingView is displayed to
+ * increment (or not) the counter. On click, should redirect to the client action.
+ **/
+var NotificationTopButton = Widget.extend({
+ template:'mail.chat.NotificationTopButton',
+ events: {
+ "click": "on_click",
},
- // TODO : change this way
- add_user: function(user){
- return new Model("im_chat.session").call("add_user", [this.get("session").uuid , user.id]);
+ init: function(parent){
+ this._super.apply(this, arguments);
+ this.set('counter', 0);
+ },
+ willStart: function(){
+ var self = this;
+ return $.when(session.rpc('/mail/needaction'), this._super()).then(function(needaction_count){
+ self.set('counter', needaction_count);
+ });
+ },
+ start: function() {
+ var self = this;
+ this.on("change:counter", this, this.on_change_counter);
+ // events
+ internal_bus.on('mail_needaction_new', this, this.counter_increment);
+ internal_bus.on('mail_needaction_done', this, this.counter_decrement);
+ return this._super();
+ },
+ counter_increment: function(inc){
+ this.set('counter', this.get('counter')+inc);
+ },
+ counter_decrement: function(dec){
+ this.set('counter', this.get('counter')-dec);
+ },
+ on_change_counter: function() {
+ this.$('.fa-comment').html(this.get('counter') || '');
+ },
+ on_click: function(e){
+ e.preventDefault();
+ this.do_action({
+ type: 'ir.actions.client',
+ tag: 'mail.chat.instant_messaging',
+ params: {
+ 'default_active_id': 'channel_inbox',
+ },
+ }, {
+ clear_breadcrumbs: true,
+ });
},
});
+SystrayMenu.Items.push(NotificationTopButton);
-// ###### BACKEND : contact panel, top menu button #########
-
-var UserWidget = Widget.extend({
- template: "im_chat.UserWidget",
+/**
+ * Abstract Class to 'Add More/Search' Widget
+ *
+ * Inputbox using jQueryUI autocomplete to fetch selection, like a Many2One field (on form view)
+ * Used to create or pin a mail.channel or a res.partner on the InstantMessagingView
+ **/
+var AbstractAddMoreSearch = Widget.extend({
+ template: 'mail.chat.AbstractAddMoreSearch',
events: {
- "click": "activate_user",
+ "click .o_mail_chat_add_more_text": "on_click_text",
+ "blur .o_mail_chat_search_input": "_toggle_elements",
},
- init: function(parent, user) {
- this._super(parent);
- this.set("id", user.id);
- this.set("name", user.name);
- this.set("im_status", user.im_status);
- this.set("image_url", user.image_url);
+ init: function(parent, options){
+ this._super.apply(this, arguments);
+ options = _.defaults(options || {}, {
+ 'can_create': false,
+ 'label': _t('+ Add More'),
+ });
+ this.limit = 10;
+ this.can_create = options.can_create;
+ this.label = options.label;
},
- start: function() {
- this.$el.data("user", {id:this.get("id"), name:this.get("name")});
- this.$el.draggable({helper: "clone"});
- this.on("change:im_status", this, this.update_status);
- this.update_status();
+ start: function(){
+ var self = this;
+ this.last_search_val = false;
+ this.$input = this.$('.o_mail_chat_search_input');
+ this._bind_events();
+ return this._super();
},
- update_status: function(){
- this.$(".oe_im_user_online").toggle(this.get('im_status') !== 'offline');
- var img_src = (this.get('im_status') == 'away' ? '/im_chat/static/src/img/yellow.png' : '/im_chat/static/src/img/green.png');
- this.$(".oe_im_user_online").attr('src', img_src);
+ _bind_events: function(){
+ // autocomplete
+ var self = this;
+ this.$input.autocomplete({
+ source: function(request, response) {
+ self.last_search_val = request.term;
+ self.do_search(request.term).done(function(result){
+ if(self.can_create){
+ result.push({
+ 'label': _.str.sprintf('<strong>'+_t("Create %s")+'</strong>', '<em>"'+self.last_search_val+'"</em>'),
+ 'value': '_create',
+ });
+ }
+ response(result);
+ });
+ },
+ select: function(event, ui) {
+ self.on_click_item(ui.item);
+ },
+ focus: function(event, ui) {
+ event.preventDefault();
+ },
+ html: true,
+ });
+ },
+ // ui
+ _toggle_elements: function(){
+ this.$('.o_mail_chat_add_more_text').toggle();
+ this.$('.o_mail_chat_add_more_search_bar').toggle();
+ this.$input.val('');
+ this.$input.focus();
},
- activate_user: function() {
- this.trigger("user_clicked", this.get("id"));
+ on_click_text: function(event){
+ event.preventDefault();
+ this._toggle_elements();
+ },
+ // to be redefined
+ do_search: function(search_val){
+ return $.when();
+ },
+ on_click_item: function(item){
+ if(item.value === '_create'){
+ if(this.last_search_val){
+ this.trigger('item_create', this.last_search_val);
+ }
+ }else{
+ this.trigger('item_clicked', item);
+ }
},
});
-var InstantMessaging = Widget.extend({
- template: "im_chat.InstantMessaging",
- events: {
- "keydown .oe_im_searchbox": "input_change",
- "keyup .oe_im_searchbox": "input_change",
- "change .oe_im_searchbox": "input_change",
- },
- init: function(parent) {
- this._super(parent);
- this.shown = false;
- this.set("right_offset", 0);
- this.set("current_search", "");
- this.users = [];
- this.widgets = {};
-
- // listen bus
- this.bus = bus.bus;
- this.bus.on("notification", this, this.on_notification);
+var PartnerAddMoreSeach = AbstractAddMoreSearch.extend({
+ /**
+ * Do the search call
+ * @override
+ */
+ do_search: function(search_val){
+ var self = this;
+ var Partner = new Model("res.partner");
+ return Partner.call('im_search', [search_val, this.limit]).then(function(result){
+ var values = [];
+ _.each(result, function(user){
+ values.push(_.extend(user, {
+ 'value': user.name,
+ 'label': user.name,
+ }));
+ });
+ return values;
+ });
},
- start: function() {
+});
+
+var ChannelAddMoreSearch = AbstractAddMoreSearch.extend({
+ /**
+ * Do the search call
+ * @override
+ */
+ do_search: function(search_val){
var self = this;
- // ui
- this.conv_manager = new im_chat_common.ConversationManager(this);
- this.on("change:right_offset", this.conv_manager, _.bind(function() {
- this.conv_manager.set("right_offset", this.get("right_offset"));
- }, this));
- $(window).scroll(_.bind(this.position_compute, this));
- $(window).resize(_.bind(this.position_compute, this));
- this.$el.css("right", -this.$el.outerWidth());
- this.position_compute();
-
- // business
- this.on("change:current_search", this, this.user_search);
-
- // add a drag & drop listener
- self.conv_manager.on("im_session_activated", self, function(conv) {
- conv.$el.droppable({
- drop: function(event, ui) {
- conv.add_user(ui.draggable.data("user"));
+ var Channel = new Model("mail.channel");
+ return Channel.call('channel_search_to_join', [search_val]).then(function(result){
+ var values = [];
+ _.each(result, function(channel){
+ values.push(_.extend(channel, {
+ 'value': channel.name,
+ 'label': channel.name,
+ }));
+ });
+ return values;
+ });
+ },
+});
+
+var PrivateGroupAddMoreSearch = AbstractAddMoreSearch.extend({
+ _bind_events: function(){
+ // don't call the super to avoid autocomplete
+ this.$input.on('keyup', this, this.on_keydown);
+ },
+ on_keydown: function(event){
+ if(event.which === $.ui.keyCode.ENTER && this.$input.val()){
+ this.trigger('item_create', this.$input.val());
+ }
+ },
+});
+
+
+/**
+ * Widget : Invite People to Channel Dialog
+ *
+ * Popup containing a 'many2many_tags' custom input to select multiple partners.
+ * Search user according to the input, and trigger event when selection is validated.
+ **/
+var PartnerInviteDialog = Dialog.extend({
+ dialog_title: _t('Invite people'),
+ template: "mail.chat.PartnerInviteDialog",
+ init: function(parent, options){
+ options = _.defaults(options || {}, {
+ buttons: [{
+ text: _t("Invite"),
+ close: true,
+ classes: "btn-primary",
+ click: _.bind(this.on_click_add, this),
+ }],
+ channel: undefined,
+ });
+ this._super.apply(this, arguments);
+ this.set("partners", []);
+ this.channel = options.channel;
+ this.PartnersModel = new Model('res.partner');
+ this.ChannelModel = new Model('mail.channel');
+ this.limit = 20;
+ },
+ start: function(){
+ var self = this;
+ this.$('.o_mail_chat_partner_invite_input').select2({
+ width: '100%',
+ allowClear: true,
+ multiple: true,
+ formatResult: function(item){
+ if(item.im_status === 'online'){
+ return '<span class="fa fa-circle"> ' + item.text + '</span>';
}
+ return '<span class="fa fa-circle-o"> ' + item.text + '</span>';
+ },
+ query: function (query) {
+ self.PartnersModel.call('im_search', [query.term, self.limit]).then(function(result){
+ var data = [];
+ _.each(result, function(partner){
+ partner['text'] = partner.name;
+ data.push(partner);
+ });
+ query.callback({results: data});
+ });
+ }
+ });
+ return this._super.apply(this, arguments);
+ },
+ on_click_add: function(){
+ var self = this;
+ var data = this.$('.o_mail_chat_partner_invite_input').select2('data');
+ if(data.length >= 1){
+ this.ChannelModel.call('channel_invite', [], {"ids" : [this.channel.id], 'partner_ids': _.pluck(data, 'id')}).then(function(){
+ var names = _.pluck(data, 'text').join(', ');
+ self.do_notify(_t('New people'), _.str.sprintf(_t('You added <b>%s</b> to the conversation.'), names));
+ self.close();
});
+ }else{
+ self.close();
+ }
+ },
+});
+
+/**
+ * Client Action : Instant Messaging View, inspired by Slack.com
+ *
+ * Action replacing the Inbox, and the list of group (mailing list, multiple conversation, rooms, ...)
+ * Includes real time messages (received and sent), creating group, channel, chat conversation, ...
+ **/
+var ChatMailThread = Widget.extend(mail_thread.MailThreadMixin, ControlPanelMixin, {
+ template: 'mail.chat.ChatMailThread',
+ events: {
+ // events from MailThreadMixin
+ "click .o_mail_redirect": "on_click_redirect",
+ "click .o_mail_thread_message_star": "on_message_star",
+ // events specific for ChatMailThread
+ "click .o_mail_chat_channel_item": "on_click_channel",
+ "click .o_mail_chat_partner_item": "on_click_partner",
+ "click .o_mail_chat_partner_unpin": "on_click_partner_unpin",
+ "click .o_mail_thread_message_needaction": "on_message_needaction",
+ },
+ init: function (parent, action) {
+ this._super.apply(this, arguments);
+ // attributes
+ this.action_manager = parent;
+ this.help_message = action.help || '';
+ this.context = action.context;
+ this.action = action;
+ this.loading_history = false;
+ this.chatter_needaction_auto = false;
+ this.options = this.action.params;
+ // mail thread mixin
+ mail_thread.MailThreadMixin.init.call(this);
+ // components : conversation manager and search widget (channel_type + '_search_widget')
+ this.conv_manager = web_client.mail_conversation_manager;
+ this.channel_search_widget = new ChannelAddMoreSearch(this, {'label': _t('+ Subscribe'), 'can_create': true});
+ this.group_search_widget = new PrivateGroupAddMoreSearch(this, {'label': _t('+ New private group'), 'can_create': true});
+ this.partner_search_widget = new PartnerAddMoreSeach(this);
+ // options (update the default of MailThreadMixin)
+ this.options = _.extend(this.options, {
+ 'display_document_link': true,
+ 'display_needaction_button': true,
+ 'emoji_list': this.conv_manager.emoji_list,
});
+ this.emoji_set_substitution(this.conv_manager.emoji_list);
+ // channel business
+ this.channels = {};
+ this.mapping = {}; // mapping partner_id/channel_id for 'direct message' channel
+ this.set('current_channel_id', false);
+ this.set('needaction_inbox_counter', 0);
+ this.search_domain = [];
+ // channel slots
+ this.set('channel_channel', []);
+ this.set('channel_direct_message', []);
+ this.set('channel_private_group', []);
+ this.set('partners', []);
+ // models
+ this.ChannelModel = new Model('mail.channel', this.context);
+ // control panel items
+ this.control_elements = {};
+ },
+ willStart: function(){
+ var self = this;
+ return session.rpc('/mail/client_action').then(function(result){
+ self.chatter_needaction_auto = result['chatter_needaction_auto'];
+ self.set('needaction_inbox_counter', result['needaction_inbox_counter']);
+ self.set('partners', result['channel_slots']['partners']);
+ self.mapping = result['channel_slots']['mapping'];
+ self._channel_slot(_.omit(result['channel_slots'], 'partners', 'mapping'));
+ });
+ },
+ start: function(){
+ var self = this;
+ this._super.apply(this, arguments);
+ mail_thread.MailThreadMixin.start.call(this);
+ // channel business events
+ this.on("change:current_channel_id", this, this.channel_change);
+ this.on("change:channel_channel", this, function(){
+ self.channel_render('channel_channel');
+ });
+ this.on("change:channel_private_group", this, function(){
+ self.channel_render('channel_private_group');
+ });
+ this.on("change:partners", this, this.partner_render);
- // fetch the unread message and the recent activity (e.i. to re-init in case of refreshing page)
- return session.rpc("/im_chat/init", {}).then(function(notifications) {
- _.each(notifications, function(notif){
- self.conv_manager.on_notification(notif, {'load_history': true});
+ // search widget for channel
+ this.channel_search_widget.insertAfter(this.$('.o_mail_chat_channel_slot_channel_channel'));
+ this.channel_search_widget.on('item_create', this, function(name){
+ self.channel_create(name, 'public');
+ });
+ this.channel_search_widget.on('item_clicked', this, function(item){
+ self.channel_join_and_get_info(item.id).then(function(channel){
+ self.channel_apply(channel);
+ });
+ });
+ // search widget for direct message
+ this.partner_search_widget.insertAfter(this.$('.o_mail_chat_channel_slot_partners'));
+ this.partner_search_widget.on('item_clicked', this, function(item){
+ self.channel_get([item.id]);
+ self.partner_add(item);
+ });
+ // search widget for private group
+ this.group_search_widget.insertAfter(this.$('.o_mail_chat_channel_slot_channel_private_group'));
+ this.group_search_widget.on('item_create', this, function(name){
+ self.channel_create(name, 'private');
+ });
+
+ // bind event to load history when scrolling near top
+ this.$('.o_mail_chat_messages').scroll(function() {
+ if ($(this).scrollTop() <= 50 && self.loading_history) { // at 50px of the top, load history
+ self.message_load_history().then(function(messages){
+ if(messages.length < mail_thread.LIMIT_MESSAGE){
+ self.loading_history = false;
+ }
+ });
+ }
+ });
+
+ // needaction inbox counter
+ this.on('change:needaction_inbox_counter', this, this.needaction_inbox_change);
+
+ return $.when(this._super.apply(this, arguments), this.cp_render_searchview()).then(function(res){
+ // update control panel
+ self.cp_render_buttons();
+ // apply default channel
+ var channel_id = self.context.active_id || self.action.params['default_active_id'] || 'channel_inbox';
+ if(!_.isString(channel_id)){
+ if(_.contains(_.keys(self.channels), channel_id)){
+ self.set('current_channel_id', channel_id);
+ }else{
+ self.channel_info(channel_id).then(function(channel){
+ self.channel_apply(channel);
+ });
+ }
+ }else{
+ self.set('current_channel_id', channel_id);
+ }
+ // internal communication (bind here to avoid receving message from bus when client action still not totally ready)
+ internal_bus.on('mail_message_receive', self, self.message_receive);
+ internal_bus.on('mail_session_receive', self, self.channel_receive);
+ // needaction : the inbox has the same counter as the needaction top counter
+ internal_bus.on('mail_needaction_new', this, function(inc){
+ self.set('needaction_inbox_counter' , self.get('needaction_inbox_counter') + inc);
+ });
+ internal_bus.on('mail_needaction_done', this, function(dec){
+ self.set('needaction_inbox_counter' , self.get('needaction_inbox_counter') - dec);
});
- // start polling
- self.bus.start_polling();
});
},
- on_notification: function(notification){
- var channel = notification[0];
- var message = notification[1];
- // user status notification
- if(channel[1] === 'im_chat.presence'){
- if(message.im_status){
- this.user_update_status([message]);
+ // event actions
+ on_click_channel: function(event){
+ event.preventDefault();
+ var channel_id = this.$(event.currentTarget).data('channel-id');
+ this.set('current_channel_id', channel_id);
+ },
+ on_click_partner: function(event){
+ if(!this.$(event.target).hasClass('o_mail_chat_channel_unpin')){
+ event.preventDefault();
+ var partner_id = this.$(event.currentTarget).data('partner-id');
+ if(this.mapping[partner_id]){ // don't fetch if channel already in local
+ this.set('current_channel_id', this.mapping[partner_id]);
+ }else{
+ this.channel_get([partner_id]);
}
}
},
- // ui
- position_compute: function() {
- var top = 48;
- this.$el.css("top", top);
- this.$el.css("bottom", 0);
- },
- input_change: function() {
- this.set("current_search", this.$(".oe_im_searchbox").val());
- },
- switch_display: function() {
- this.position_compute();
- var fct = _.bind(function(place) {
- this.set("right_offset", place + this.$el.outerWidth());
- this.$(".oe_im_searchbox").focus();
- }, this);
- var opt = {
- step: fct,
- };
- if (this.shown) {
- this.$el.animate({
- right: -this.$el.outerWidth(),
- }, opt);
- } else {
- if (! bus.bus.activated) {
- this.do_warn("Instant Messaging is not activated on this server. Try later.", "");
- return;
+ on_click_partner_unpin: function(event){
+ event.preventDefault();
+ var self = this;
+ var $source = this.$(event.currentTarget);
+ var partner_id = $source.data('partner-id');
+ var channel_id = this.mapping[partner_id];
+ var channel = this.channels[channel_id];
+ this.channel_pin(channel.uuid, false).then(function(){
+ self.set('partners', _.filter(self.get('partners'), function(p){ return p.id !== partner_id; }));
+ self.channel_remove(channel_id);
+ delete self.mapping[partner_id];
+ // if unpin current channel, switch to inbox
+ if(self.get('current_channel_id') === channel_id){
+ self.set('current_channel_id', 'channel_inbox');
}
- // update the list of user status when show the IM
- this.user_search();
- this.$el.animate({
- right: 0,
- }, opt);
+ });
+ },
+ on_click_button_minimize: function(event){
+ event.preventDefault();
+ var current_channel = this.channels[this.get('current_channel_id')];
+ if(current_channel){
+ var channel_uuid = current_channel.uuid;
+ return this.ChannelModel.call("channel_minimize", [channel_uuid, true]);
}
- this.shown = ! this.shown;
+ return $.Deferred.reject();
},
- // user methods
- user_search: function(e) {
+ on_click_button_invite: function(event){
+ var dialog = new PartnerInviteDialog(this, {
+ title: _.str.sprintf(_t('Invite people to %s.'), this.get_current_channel_name()),
+ channel: this.channels[this.get('current_channel_id')],
+ }).open();
+ },
+ on_click_button_unsubscribe: function(event){
var self = this;
- var user_model = new Model("res.users");
- return user_model.call('im_search', [this.get("current_search"), USERS_LIMIT]).then(function(result){
- self.$(".oe_im_input").val("");
- var old_widgets = self.widgets;
- self.widgets = {};
- self.users = [];
- _.each(result, function(user) {
- user.image_url = session.url('/web/image', {model:'res.users', field: 'image_small', id: user.id});
- var widget = new UserWidget(self, user);
- widget.appendTo(self.$(".oe_im_users"));
- widget.on("user_clicked", self, self.user_clicked);
- self.widgets[user.id] = widget;
- self.users.push(user);
- });
- _.each(old_widgets, function(w) {
- w.destroy();
- });
+ this.ChannelModel.call('action_unfollow', [[this.get('current_channel_id')]]).then(function(){
+ var channel = self.channels[self.get('current_channel_id')]
+ var slot = self.get_channel_slot(channel);
+ self.set(slot, _.filter(self.get(slot), function(c){ return c.id !== channel.id; }));
+ self.do_notify(_t('Unsubscribe'), _.str.sprintf(_t('You unsubscribe from <b>%s</b>.'), self.get_current_channel_name()));
+ self.set('current_channel_id', 'channel_inbox'); // jump to inbox
});
},
- user_clicked: function(user_id) {
+ on_click_button_settings: function(event){
+ this.do_action({
+ type: 'ir.actions.act_window',
+ res_model: "mail.channel",
+ res_id: this.get('current_channel_id'),
+ views: [[false, 'form']],
+ target: 'current'
+ }, {
+ clear_breadcrumbs: true
+ });
+ },
+ on_message_needaction: function(event){
var self = this;
- var sessions = new Model("im_chat.session");
- return sessions.call("session_get", [user_id]).then(function(session) {
- self.conv_manager.session_apply(session, {'focus': true, 'force_open': true});
+ mail_thread.MailThreadMixin.on_message_needaction.call(this, event).then(function(message_id){
+ // decrement the needaction top counter
+ internal_bus.trigger('mail_needaction_done', 1);
+ // decrement the channel labels
+ var treated_messages = _.filter(self.get('messages'), function(m){ return m.id == message_id; });
+ if(treated_messages.length){
+ self.needaction_decrement(treated_messages[0].channel_ids || []);
+ }
});
},
- user_update_status: function(users_list){
+ // control panel
+ cp_update: function(){
var self = this;
- _.each(users_list, function(el) {
- if (self.widgets[el.id]) {
- self.widgets[el.id].set("im_status", el.im_status);
+ var current_channel_id = this.get('current_channel_id');
+ var current_channel_name = this.get_current_channel_name();
+ // toggle cp buttons
+ if(_.isString(current_channel_id)){
+ self.control_elements.$buttons.find('button').hide();
+ }else{
+ self.control_elements.$buttons.find('button').show();
+ var current_channel = this.channels[current_channel_id];
+ if(current_channel.channel_type == 'chat'){
+ self.control_elements.$buttons.find('.o_mail_chat_button_invite, .o_mail_chat_button_more').hide();
}
+ }
+ // update control panel
+ var status = {
+ breadcrumbs: this.action_manager.get_breadcrumbs().concat([{'title': current_channel_name, 'action': _.clone(this)}]),
+ cp_content: {
+ $buttons: self.control_elements.$buttons,
+ $searchview: self.control_elements.$searchview,
+ $searchview_buttons: self.control_elements.$searchview_buttons,
+ $switch_buttons: false,
+ $pager: false,
+ },
+ searchview: self.searchview,
+ };
+ this.update_control_panel(status, {clear: false});
+ },
+ cp_render_buttons: function() {
+ var self = this;
+ this.control_elements.$buttons = $(QWeb.render("mail.chat.ControlButtons", {}));
+ this.control_elements.$buttons.on('click', '.o_mail_chat_button_minimize', _.bind(this.on_click_button_minimize, this));
+ this.control_elements.$buttons.on('click', '.o_mail_chat_button_invite', _.bind(this.on_click_button_invite, this));
+ this.control_elements.$buttons.on('click', '.o_mail_chat_button_unsubscribe', _.bind(this.on_click_button_unsubscribe, this));
+ this.control_elements.$buttons.on('click', '.o_mail_chat_button_settings', _.bind(this.on_click_button_settings, this));
+ },
+ cp_render_searchview: function(){
+ var self = this;
+ var options = {
+ $buttons: $("<div>"),
+ action: this.action,
+ };
+ var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false;
+ this.searchview = new SearchView(this, this.MessageDatasetSearch, view_id, {}, options);
+
+ this.searchview.on('search_data', this, this.on_search);
+ return $.when(this.searchview.appendTo($("<div>"))).done(function() {
+ self.control_elements.$searchview = self.searchview.$el;
+ self.control_elements.$searchview_buttons = self.searchview.$buttons.contents();
+ // hack to hide 'group by' button, since it is not relevant
+ self.searchview.$buttons.find('.oe-groupby-menu').hide();
+ });
+ },
+ /**
+ * Method call when action if done after redirecting (using 'on_click_redirect')
+ * @override
+ */
+ on_reverse_breadcrumb: function(){
+ var current_channel_id = this.get('current_channel_id');
+ this.cp_update(); // do not reload the client action, just display it, but a refresh of the control panel is needed.
+ // push state
+ this.action_manager.do_push_state({
+ action: this.action.id,
+ active_id: current_channel_id,
+ });
+ },
+ // messages search methods
+ on_search: function(domains, contexts, groupbys){
+ var self = this;
+ return pyeval.eval_domains_and_contexts({
+ domains: [[]].concat(domains || []),
+ contexts: [this.context].concat(contexts || []),
+ group_by_seq: groupbys || []
+ }).done(function(results){
+ if (results.error) {
+ throw new Error(_.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s", JSON.stringify(results.error)));
+ }
+ // modify the search domain and do search
+ self.search_domain = results.domain;
+ return self.message_load_new();
+ });
+ },
+ // channels
+ /**
+ * Set the channel slot
+ * @param {Object[]} fetch_result : should contains only the slot name (as key) and the list of channel header as value.
+ */
+ _channel_slot: function(fetch_result){
+ var self = this;
+ var channel_slots = _.keys(fetch_result);
+ _.each(channel_slots, function(slot){
+ // update the channel slot
+ self.set(slot, fetch_result[slot]);
+ // flatten the result : update the complete channel list
+ _.each(fetch_result[slot], function(channel){
+ self.channels[channel.id] = channel;
+ });
+ });
+ },
+ /**
+ * Apply a channel means adding it, and swith to it
+ * @param {Object} channel : channel header
+ */
+ channel_apply: function(channel){
+ this.channel_add(channel);
+ this.set('current_channel_id', channel.id);
+ },
+ /**
+ * Add the given channel, or update it if already exists and loaded
+ * @param {Object} channel : channel header to add
+ */
+ channel_add: function(channel){
+ var channel_slot = this.get_channel_slot(channel);
+ var existing = this.get(channel_slot);
+ if(_.contains(_.pluck(existing, 'id'), channel.id)){
+ // update the old channel
+ var filtered_channels = _.filter(existing, function(item){ return item.id != channel.id; });
+ this.set(channel_slot, filtered_channels.concat([channel]));
+ }else{
+ // simply add the reveiced channel
+ this.set(channel_slot, existing.concat([channel]));
+ }
+ // also update the flatten list
+ this.channels[channel.id] = channel;
+
+ // update the mapping for 'direct message' channel, and the partner list
+ if(channel_slot === 'channel_direct_message'){
+ var partner = channel.direct_partner[0];
+ this.mapping[partner.id] = channel.id;
+ this.partner_add(partner);
+ }
+ },
+ channel_remove: function(channel_id){
+ var channel = this.channels[channel_id];
+ var slot = this.get_channel_slot(channel);
+ this.set(slot, _.filter(this.get(slot), function(c){ return c.id !== channel_id; }));
+ delete this.channels[channel_id];
+ },
+ /**
+ * Get the channel the current user has with the given partner, and get the channel header
+ * @param {Number[]} partner_ids : list of res.partner identifier
+ */
+ channel_get: function(partner_ids){
+ var self = this;
+ return this.ChannelModel.call('channel_get', [partner_ids]).then(function(channel){
+ self.channel_apply(channel);
+ });
+ },
+ /**
+ * Create a channel with the given name and type, and apply it
+ * @param {String} channel_name : the name of the channel
+ * @param {String} privacy : the privacy of the channel (groups, public, ...)
+ */
+ channel_create: function(channel_name, privacy){
+ var self = this;
+ return this.ChannelModel.call('channel_create', [channel_name, privacy]).then(function(channel){
+ self.channel_apply(channel);
+ });
+ },
+ channel_info: function(channel_id){
+ var self = this;
+ return this.ChannelModel.call('channel_info', [[channel_id]]).then(function(channels){
+ return channels[0];
+ });
+ },
+ channel_pin: function(uuid, pinned){
+ return this.ChannelModel.call('channel_pin', [uuid, pinned]);
+ },
+ channel_join_and_get_info: function(channel_id){
+ return this.ChannelModel.call('channel_join_and_get_info', [[channel_id]]);
+ },
+ channel_change: function(){
+ var self = this;
+ var current_channel_id = this.get('current_channel_id');
+ var current_channel = this.channels[current_channel_id];
+ // mail chat compose message (destroy and replace it)
+ this.mail_chat_compose_message && this.mail_chat_compose_message.destroy();
+ this.mail_chat_compose_message = new mail_thread.MailComposeMessage(this, new data.DataSetSearch(this, 'mail.channel', this.context), {
+ 'emoji_list': this.options.emoji_list,
+ 'context': _.extend(this.context, {
+ 'default_res_id': current_channel_id,
+ }),
+ 'display_mode': 'chat',
+ });
+ if(_.isString(current_channel_id)){
+ this.$('.o_mail_chat_composer_wrapper').hide();
+ }else{
+ this.$('.o_mail_chat_composer_wrapper').show();
+ }
+ this.mail_chat_compose_message.appendTo(this.$('.o_mail_chat_composer_wrapper'));
+ this.mail_chat_compose_message.focus();
+
+ // push state (the action is referred by action_manager, and no reloaded when jumping
+ // channel, so update context is requried)
+ this.action.context['active_id'] = current_channel_id;
+ this.action.context['active_ids'] = [current_channel_id];
+ web_client.action_manager.do_push_state({
+ action: this.action.id,
+ active_id: current_channel_id,
});
- }
-});
+ // update control panel
+ this.cp_update();
-var ImTopButton = Widget.extend({
- template:'im_chat.ImTopButton',
- events: {
- "click": "clicked",
+ // fetch the messages
+ return this.message_load_new().then(function(raw_messages){
+ // allow loading history
+ self.loading_history = true;
+ // if normal channel, update last message id, and unbold
+ var def = $.Deferred().resolve();
+ if(_.isNumber(current_channel_id)){
+ def = self.ChannelModel.call('channel_seen', [[current_channel_id]]);
+ }else{
+ // auto treat needaction from inbox
+ if(current_channel_id === 'channel_inbox' && self.chatter_needaction_auto){
+ def = self.MessageDatasetSearch._model.query(['id', 'channel_ids']).filter([['needaction', '=', true]]).all().then(function(messages){
+ // get needaction message ids
+ var message_ids = _.pluck(messages, 'id');
+ if(message_ids){
+ // call to unlink
+ self.MessageDatasetSearch._model.call('set_message_done',[message_ids]).then(function(){
+ // decrement channel items in batch
+ var flatten_channel_ids = _.flatten(_.pluck(messages, 'channel_ids'));
+ var channel_count = _.countBy(flatten_channel_ids, function(cid) {
+ return cid;
+ });
+ _.each(_.keys(channel_count), function(cid){
+ self.needaction_decrement([cid], channel_count[cid]);
+ });
+ // decrement the needaction top counter
+ internal_bus.trigger('mail_needaction_done', message_ids.length);
+ });
+ }
+ });
+ }
+ }
+ def.then(function(){
+ self._toggle_unread_message(current_channel_id, false, true);
+ self._scroll();
+ });
+ });
},
- start: function() {
- // Create the InstantMessaging widget and put it in the DOM
- var im = new InstantMessaging(this);
- web_client.im_messaging = im;
- im.appendTo(web_client.$el);
- // Bind the click action to the ImTopButton
- this.on("clicked", im, im.switch_display);
- return this._super();
+ channel_render: function(channel_slot){
+ this.$('.o_mail_chat_channel_slot_' + channel_slot).replaceWith(QWeb.render("mail.chat.ChatMailThread.channels", {'widget': this, 'channel_slot': channel_slot}));
+ },
+ // partners
+ partner_add: function(partner){
+ var partners = _.filter(this.get('partners'), function(p){ return p.id != partner.id; });
+ this.set('partners', partners.concat([partner]));
+ },
+ partner_render: function(){
+ this.$('.o_mail_chat_channel_slot_partners').replaceWith(QWeb.render("mail.chat.ChatMailThread.partners", {'widget': this}));
+ },
+ // needaction
+ needaction_increment: function(channel_ids, inc){
+ var self = this;
+ _.each(channel_ids, function(channel_id){
+ if(self.channels[channel_id]){
+ var count = (self.channels[channel_id].message_needaction_counter || 0) + (inc || 1);
+ self.channels[channel_id].message_needaction_counter = count;
+ self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').html(count);
+ self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').show();
+ }
+ });
},
- clicked: function(ev) {
- ev.preventDefault();
- this.trigger("clicked");
+ needaction_decrement: function(channel_ids, dec){
+ var self = this;
+ _.each(channel_ids, function(channel_id){
+ if(self.channels[channel_id]){
+ var count = (self.channels[channel_id].message_needaction_counter || 0) - (dec || 1);
+ self.channels[channel_id].message_needaction_counter = count;
+ self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').html(count);
+ if(count <= 0){
+ self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').show();
+ }
+ }
+ });
+ },
+ needaction_inbox_change: function(inc){
+ var count = this.get('needaction_inbox_counter');
+ var $elem = this.$('.o_mail_chat_needaction[data-channel-id="channel_inbox"]');
+ $elem.html(count);
+ if(count > 0){
+ $elem.show();
+ }else{
+ $elem.hide();
+ }
+ },
+ // from bus
+ channel_receive: function(channel){
+ this.channel_add(channel);
+ },
+ message_receive: function(message){
+ var self = this;
+ // if current channel should reveice message, give it to it
+ if(_.contains(message['channel_ids'], this.get('current_channel_id'))){
+ this.message_insert([message]);
+ }
+ // for other message channel, get the channel if not loaded yet, and bolded them
+ var other_message_channel_ids = _.without(message['channel_ids'], this.get('current_channel_id'));
+ var active_channel_ids = _.map(_.keys(this.channels), function(cid){
+ return parseInt(cid);
+ }); // integer as key of a dict is cast as string in javascript
+ var channel_to_fetch = _.difference(other_message_channel_ids, active_channel_ids);
+ // fetch unloaded channels and add it
+ var def = $.Deferred();
+ if(channel_to_fetch.length >= 1){
+ def = this.ChannelModel.call("channel_info", [], {"ids" : channel_to_fetch}).then(function(channels){
+ _.each(channels, function(channel){
+ self.channel_add(channel);
+ });
+ });
+ }else{
+ def.resolve([]);
+ }
+ // bold the channel to indicate unread messages
+ def.then(function(){
+ // bold channel having unread messages
+ _.each(other_message_channel_ids, function(channel_id){
+ self._toggle_unread_message(channel_id, true);
+ });
+ // auto scroll to bottom
+ self._scroll();
+ });
+ // if needaction, then increment the label
+ if(message.needaction_partner_ids && _.contains(message.needaction_partner_ids, session.partner_id)){
+ this.needaction_increment(message.channel_ids || []);
+ }
+ },
+ // utils function
+ get_channel_slot: function(channel){
+ if(channel.channel_type === 'channel'){
+ if(channel.public === 'private'){
+ return 'channel_private_group';
+ }
+ return 'channel_channel';
+ }
+ if(channel.channel_type === 'chat'){
+ return 'channel_direct_message';
+ }
+ },
+ get_current_channel_name: function(){
+ var current_channel_id = this.get('current_channel_id');
+ var current_channel = this.channels[current_channel_id];
+ var current_channel_name = current_channel && current_channel.name || _t('Unknown');
+ // virtual channel id (for inbox, or starred channel)
+ if(_.isString(current_channel_id)){
+ if(current_channel_id == 'channel_inbox'){
+ current_channel_name = _t('Inbox');
+ }
+ if(current_channel_id == 'channel_starred'){
+ current_channel_name = _t('Starred');
+ }
+ }
+ return current_channel_name;
+ },
+ _toggle_unread_message: function(channel_id, add_or_remove, set_as_active){
+ var partner_id = false;
+ var inverse_mapping = _.invert(this.mapping);
+ // toggle bold channel/partner item
+ this.$('.o_mail_chat_sidebar .o_mail_chat_channel_item[data-channel-id="'+channel_id+'"]').toggleClass('o_mail_chat_channel_unread', add_or_remove);
+ if(_.contains(_.keys(inverse_mapping), channel_id.toString())){
+ partner_id = parseInt(inverse_mapping[channel_id]);
+ this.$('.o_mail_chat_sidebar .o_mail_chat_partner_item[data-partner-id="'+partner_id+'"]').toggleClass('o_mail_chat_channel_unread', add_or_remove);
+ }
+ // set the channel/partner item as the active one
+ if(set_as_active){
+ this.$('.o_mail_chat_sidebar .o_mail_chat_channel_item, .o_mail_chat_sidebar .o_mail_chat_partner_item').find('a').removeClass('active');
+ this.$('.o_mail_chat_sidebar .o_mail_chat_channel_item[data-channel-id="'+channel_id+'"]').find('a').addClass('active');
+ if(partner_id){
+ this.$('.o_mail_chat_sidebar .o_mail_chat_partner_item[data-partner-id="'+partner_id+'"]').find('a').addClass('active');
+ }
+ }
+ },
+ _scroll: function(){
+ var current_channel_id = this.get('current_channel_id');
+ var current_channel = this.channels[current_channel_id];
+ if(_.isNumber(current_channel_id)){
+ if(current_channel.seen_message_id){
+ if(this.$(".o_mail_thread_message[data-message-id="+current_channel.seen_message_id+"]")){
+ this.$('.o_mail_chat_messages').scrollTop(this.$(".o_mail_thread_message[data-message-id="+current_channel.seen_message_id+"]").offset().top);
+ }else{
+ this.$('.o_mail_chat_messages').scrollTop(0);
+ }
+ }else{
+ this.$('.o_mail_chat_messages').scrollTop(this.$('.o_mail_chat_messages')[0].scrollHeight);
+ }
+ current_channel.seen_message_id = false;
+ }else{
+ this.$('.o_mail_chat_messages').scrollTop(this.$('.o_mail_chat_messages')[0].scrollHeight);
+ }
+ },
+ /**
+ * Render the messages
+ * @override
+ */
+ message_render: function(){
+ this.$('.o_mail_chat_messages_content').html(QWeb.render('mail.chat.ChatMailThread.content', {'widget': this}));
+ },
+ /**
+ * Return the message domain for the current channel
+ * @override
+ */
+ get_message_domain: function(){
+ // default channel domain
+ var current_channel_id = this.get('current_channel_id');
+ var domain = [['channel_ids', 'in', current_channel_id]];
+ // virtual channel id (for inbox, or starred channel)
+ if(_.isString(current_channel_id)){
+ if(current_channel_id == 'channel_inbox'){
+ domain = [['needaction', '=', true]];
+ }
+ if(current_channel_id == 'channel_starred'){
+ domain = [['starred', '=', true]];
+ }
+ }
+ // add search domain
+ domain = domain.concat(this.search_domain);
+ return domain;
},
});
-// Put the ImTopButton widget in the systray menu if the user is an employee
-var Users = new Model('res.users');
-Users.call('has_group', ['base.group_user']).done(function(is_employee) {
- if (is_employee) {
- SystrayMenu.Items.push(ImTopButton);
- }
-});
+core.action_registry.add('mail.chat.instant_messaging', ChatMailThread);
+
return {
- InstantMessaging: InstantMessaging,
- UserWidget: UserWidget,
+ ChatMailThread: ChatMailThread,
};
});