You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1468 lines
60 KiB

[LINT] ************* Module mail_archives mail_archives/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline mail_archives/README.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_to mail_to/static/src/js/mail_to.js:11: [W7903(javascript-lint), ] Missing semicolon. mail_to/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline mail_to/doc/changelog.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_move_message mail_move_message/doc/changelog.rst:1: [E7901(rst-syntax-error), ] Hyperlink target "changelog" is not referenced. mail_move_message/data/mail_move_message_data.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_move_message.mail_move_message_models mail_move_message/mail_move_message_models.py:74: [C8108(method-compute), Wizard] Name of compute method should starts with "_compute_" mail_move_message/mail_move_message_models.py:91: [W8104(api-one-deprecated), Wizard.get_can_move] api.one deprecated mail_move_message/mail_move_message_models.py:141: [W8104(api-one-deprecated), Wizard.check_access] api.one deprecated mail_move_message/mail_move_message_models.py:198: [W8104(api-one-deprecated), Wizard.delete] api.one deprecated mail_move_message/mail_move_message_models.py:233: [W8104(api-one-deprecated), Wizard.read_close] api.one deprecated mail_move_message/mail_move_message_models.py:248: [C8108(method-compute), MailMessage] Name of compute method should starts with "_compute_" mail_move_message/mail_move_message_models.py:251: [W8104(api-one-deprecated), MailMessage._get_all_childs] api.one deprecated mail_move_message/mail_move_message_models.py:274: [W8104(api-one-deprecated), MailMessage.move] api.one deprecated ************* Module mail_base mail_base/static/src/js/base.js:245: [W7903(javascript-lint), ] Misleading line break before '||'; readers may interpret this as an expression boundary. mail_base/static/src/js/base.js:1119: [W7903(javascript-lint), ] Missing semicolon. mail_base/static/src/js/base.js:1121: [W7903(javascript-lint), ] Missing semicolon. mail_base/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_sent mail_sent/doc/changelog.rst:1: [E7901(rst-syntax-error), ] Hyperlink target "changelog" is not referenced. mail_sent/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_sent.models mail_sent/models.py:8: [C8108(method-compute), MailMessage] Name of compute method should starts with "_compute_" mail_sent/models.py:12: [W8104(api-one-deprecated), MailMessage._get_sent] api.one deprecated ************* Module mail_reply mail_reply/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module res_partner_mails_count res_partner_mails_count/README.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module res_partner_mails_count.models res_partner_mails_count/models.py:8: [C8108(method-compute), ResPartner] Name of compute method should starts with "_compute_" res_partner_mails_count/models.py:9: [C8108(method-compute), ResPartner] Name of compute method should starts with "_compute_" res_partner_mails_count/models.py:12: [W8104(api-one-deprecated), ResPartner._mails_to] api.one deprecated res_partner_mails_count/models.py:17: [W8104(api-one-deprecated), ResPartner._mails_from] api.one deprecated
8 years ago
[LINT] ************* Module mail_archives mail_archives/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline mail_archives/README.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_to mail_to/static/src/js/mail_to.js:11: [W7903(javascript-lint), ] Missing semicolon. mail_to/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline mail_to/doc/changelog.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_move_message mail_move_message/doc/changelog.rst:1: [E7901(rst-syntax-error), ] Hyperlink target "changelog" is not referenced. mail_move_message/data/mail_move_message_data.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_move_message.mail_move_message_models mail_move_message/mail_move_message_models.py:74: [C8108(method-compute), Wizard] Name of compute method should starts with "_compute_" mail_move_message/mail_move_message_models.py:91: [W8104(api-one-deprecated), Wizard.get_can_move] api.one deprecated mail_move_message/mail_move_message_models.py:141: [W8104(api-one-deprecated), Wizard.check_access] api.one deprecated mail_move_message/mail_move_message_models.py:198: [W8104(api-one-deprecated), Wizard.delete] api.one deprecated mail_move_message/mail_move_message_models.py:233: [W8104(api-one-deprecated), Wizard.read_close] api.one deprecated mail_move_message/mail_move_message_models.py:248: [C8108(method-compute), MailMessage] Name of compute method should starts with "_compute_" mail_move_message/mail_move_message_models.py:251: [W8104(api-one-deprecated), MailMessage._get_all_childs] api.one deprecated mail_move_message/mail_move_message_models.py:274: [W8104(api-one-deprecated), MailMessage.move] api.one deprecated ************* Module mail_base mail_base/static/src/js/base.js:245: [W7903(javascript-lint), ] Misleading line break before '||'; readers may interpret this as an expression boundary. mail_base/static/src/js/base.js:1119: [W7903(javascript-lint), ] Missing semicolon. mail_base/static/src/js/base.js:1121: [W7903(javascript-lint), ] Missing semicolon. mail_base/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_sent mail_sent/doc/changelog.rst:1: [E7901(rst-syntax-error), ] Hyperlink target "changelog" is not referenced. mail_sent/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_sent.models mail_sent/models.py:8: [C8108(method-compute), MailMessage] Name of compute method should starts with "_compute_" mail_sent/models.py:12: [W8104(api-one-deprecated), MailMessage._get_sent] api.one deprecated ************* Module mail_reply mail_reply/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module res_partner_mails_count res_partner_mails_count/README.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module res_partner_mails_count.models res_partner_mails_count/models.py:8: [C8108(method-compute), ResPartner] Name of compute method should starts with "_compute_" res_partner_mails_count/models.py:9: [C8108(method-compute), ResPartner] Name of compute method should starts with "_compute_" res_partner_mails_count/models.py:12: [W8104(api-one-deprecated), ResPartner._mails_to] api.one deprecated res_partner_mails_count/models.py:17: [W8104(api-one-deprecated), ResPartner._mails_from] api.one deprecated
8 years ago
[LINT] ************* Module mail_archives mail_archives/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline mail_archives/README.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_to mail_to/static/src/js/mail_to.js:11: [W7903(javascript-lint), ] Missing semicolon. mail_to/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline mail_to/doc/changelog.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_move_message mail_move_message/doc/changelog.rst:1: [E7901(rst-syntax-error), ] Hyperlink target "changelog" is not referenced. mail_move_message/data/mail_move_message_data.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_move_message.mail_move_message_models mail_move_message/mail_move_message_models.py:74: [C8108(method-compute), Wizard] Name of compute method should starts with "_compute_" mail_move_message/mail_move_message_models.py:91: [W8104(api-one-deprecated), Wizard.get_can_move] api.one deprecated mail_move_message/mail_move_message_models.py:141: [W8104(api-one-deprecated), Wizard.check_access] api.one deprecated mail_move_message/mail_move_message_models.py:198: [W8104(api-one-deprecated), Wizard.delete] api.one deprecated mail_move_message/mail_move_message_models.py:233: [W8104(api-one-deprecated), Wizard.read_close] api.one deprecated mail_move_message/mail_move_message_models.py:248: [C8108(method-compute), MailMessage] Name of compute method should starts with "_compute_" mail_move_message/mail_move_message_models.py:251: [W8104(api-one-deprecated), MailMessage._get_all_childs] api.one deprecated mail_move_message/mail_move_message_models.py:274: [W8104(api-one-deprecated), MailMessage.move] api.one deprecated ************* Module mail_base mail_base/static/src/js/base.js:245: [W7903(javascript-lint), ] Misleading line break before '||'; readers may interpret this as an expression boundary. mail_base/static/src/js/base.js:1119: [W7903(javascript-lint), ] Missing semicolon. mail_base/static/src/js/base.js:1121: [W7903(javascript-lint), ] Missing semicolon. mail_base/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_sent mail_sent/doc/changelog.rst:1: [E7901(rst-syntax-error), ] Hyperlink target "changelog" is not referenced. mail_sent/views/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module mail_sent.models mail_sent/models.py:8: [C8108(method-compute), MailMessage] Name of compute method should starts with "_compute_" mail_sent/models.py:12: [W8104(api-one-deprecated), MailMessage._get_sent] api.one deprecated ************* Module mail_reply mail_reply/templates.xml:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module res_partner_mails_count res_partner_mails_count/README.rst:1: [W7908(missing-newline-extrafiles), ] Missing newline ************* Module res_partner_mails_count.models res_partner_mails_count/models.py:8: [C8108(method-compute), ResPartner] Name of compute method should starts with "_compute_" res_partner_mails_count/models.py:9: [C8108(method-compute), ResPartner] Name of compute method should starts with "_compute_" res_partner_mails_count/models.py:12: [W8104(api-one-deprecated), ResPartner._mails_to] api.one deprecated res_partner_mails_count/models.py:17: [W8104(api-one-deprecated), ResPartner._mails_from] api.one deprecated
8 years ago
  1. /* Copyright 2017 Artyom Losev <https://github.com/ArtyomLosev>
  2. Copyright 2019 Artem Rafailov <https://github.com/Ommo73>
  3. License LGPL-3.0 (https://www.gnu.org/licenses/lgpl.html). */
  4. odoo.define('mail_base.base', function (require) {
  5. "use strict";
  6. var bus = require('bus.bus').bus;
  7. var utils = require('mail.utils');
  8. var config = require('web.config');
  9. var core = require('web.core');
  10. var data = require('web.data');
  11. var Model = require('web.Model');
  12. var session = require('web.session');
  13. var time = require('web.time');
  14. var web_client = require('web.web_client');
  15. var composer = require('mail.composer');
  16. var config = require('web.config');
  17. var Chatter = require('mail.Chatter');
  18. var form_common = require('web.form_common');
  19. var _t = core._t;
  20. var _lt = core._lt;
  21. var LIMIT = 100;
  22. var preview_msg_max_size = 350; // optimal for native english speakers
  23. var ODOOBOT_ID = "ODOOBOT";
  24. var MessageModel = new Model('mail.message', session.context);
  25. var ChannelModel = new Model('mail.channel', session.context);
  26. var UserModel = new Model('res.users', session.context);
  27. var PartnerModel = new Model('res.partner', session.context);
  28. var chat_manager = require('mail.chat_manager');
  29. // Private model
  30. //----------------------------------------------------------------------------------
  31. var messages = [];
  32. var channels = [];
  33. var channels_preview_def;
  34. var channel_defs = {};
  35. var chat_unread_counter = 0;
  36. var unread_conversation_counter = 0;
  37. var emojis = [];
  38. var emoji_substitutions = {};
  39. var needaction_counter = 0;
  40. var starred_counter = 0;
  41. var mention_partner_suggestions = [];
  42. var canned_responses = [];
  43. var commands = [];
  44. var discuss_menu_id;
  45. var global_unread_counter = 0;
  46. var pinned_dm_partners = []; // partner_ids we have a pinned DM with
  47. var client_action_open = false;
  48. bus.on("window_focus", null, function() {
  49. global_unread_counter = 0;
  50. web_client.set_title_part("_chat");
  51. });
  52. var channel_seen = _.throttle(function (channel) {
  53. return ChannelModel.call('channel_seen', [[channel.id]], {}, {shadow: true});
  54. }, 3000);
  55. var ChatAction = core.action_registry.get('mail.chat.instant_messaging');
  56. ChatAction.include({
  57. init: function(parent, action, options) {
  58. this._super.apply(this, arguments);
  59. this.channels_show_send_button = ['channel_inbox'];
  60. this.channels_display_subject = [];
  61. },
  62. start: function() {
  63. var result = this._super.apply(this, arguments);
  64. var search_defaults = {};
  65. var context = this.action ? this.action.context : [];
  66. _.each(context, function (value, key) {
  67. var match = /^search_default_(.*)$/.exec(key);
  68. if (match) {
  69. search_defaults[match[1]] = value;
  70. }
  71. });
  72. this.searchview.defaults = search_defaults;
  73. var self = this;
  74. return $.when(result).done(function() {
  75. $('.oe_leftbar').toggle(false);
  76. self.searchview.do_search();
  77. });
  78. },
  79. destroy: function() {
  80. var result = this._super.apply(this, arguments);
  81. $('.oe_leftbar .oe_secondary_menu').each(function(){
  82. if ($(this).css('display') == 'block'){
  83. if ($(this).children().length > 0) {
  84. $('.oe_leftbar').toggle(true);
  85. }
  86. return false;
  87. }
  88. });
  89. return result;
  90. },
  91. set_channel: function(channel){
  92. var result = this._super.apply(this, arguments);
  93. var self = this;
  94. return $.when(result).done(function() {
  95. self.$buttons
  96. .find('.o_mail_chat_button_new_message')
  97. .toggle(self.channels_show_send_button.indexOf(channel.id) != -1);
  98. });
  99. },
  100. get_thread_rendering_options: function (messages) {
  101. var options = this._super.apply(this, arguments);
  102. options.display_subject = options.display_subject || this.channels_display_subject.indexOf(this.channel.id) != -1;
  103. return options;
  104. },
  105. update_message_on_current_channel: function(current_channel_id, message){
  106. var starred = current_channel_id === "channel_starred" && !message.is_starred;
  107. var inbox = current_channel_id === "channel_inbox" && !message.is_needaction;
  108. return starred || inbox;
  109. },
  110. on_update_message: function (message) {
  111. var self = this;
  112. var current_channel_id = this.channel.id;
  113. if (this.update_message_on_current_channel(current_channel_id, message)) {
  114. chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function (messages) {
  115. var options = self.get_thread_rendering_options(messages);
  116. self.thread.remove_message_and_render(message.id, messages, options).then(function () {
  117. self.update_button_status(messages.length === 0);
  118. });
  119. });
  120. } else if (_.contains(message.channel_ids, current_channel_id)) {
  121. this.fetch_and_render_thread();
  122. }
  123. }
  124. });
  125. var MailComposer = composer.BasicComposer.extend({
  126. template: 'mail.chatter.ChatComposer',
  127. init: function (parent, dataset, options) {
  128. this._super(parent, options);
  129. this.thread_dataset = dataset;
  130. this.suggested_partners = [];
  131. this.options = _.defaults(this.options, {
  132. display_mode: 'textarea',
  133. record_name: false,
  134. is_log: false,
  135. });
  136. if (this.options.is_log) {
  137. this.options.send_text = _t('Log');
  138. }
  139. this.events = _.extend(this.events, {
  140. 'click .o_composer_button_full_composer': 'on_open_full_composer',
  141. });
  142. },
  143. willStart: function () {
  144. if (this.options.is_log) {
  145. return this._super.apply(this, arguments);
  146. }
  147. return $.when(this._super.apply(this, arguments), this.message_get_suggested_recipients());
  148. },
  149. should_send: function () {
  150. return false;
  151. },
  152. preprocess_message: function () {
  153. var self = this;
  154. var def = $.Deferred();
  155. this._super().then(function (message) {
  156. message = _.extend(message, {
  157. subtype: 'mail.mt_comment',
  158. message_type: 'comment',
  159. content_subtype: 'html',
  160. context: self.context,
  161. });
  162. // Subtype
  163. if (self.options.is_log) {
  164. message.subtype = 'mail.mt_note';
  165. }
  166. // Partner_ids
  167. if (!self.options.is_log) {
  168. var checked_suggested_partners = self.get_checked_suggested_partners();
  169. self.check_suggested_partners(checked_suggested_partners).done(function (partner_ids) {
  170. message.partner_ids = (message.partner_ids || []).concat(partner_ids);
  171. // update context
  172. message.context = _.defaults({}, message.context, {
  173. mail_post_autofollow: true,
  174. });
  175. def.resolve(message);
  176. });
  177. } else {
  178. def.resolve(message);
  179. }
  180. });
  181. return def;
  182. },
  183. /**
  184. * Send the message on SHIFT+ENTER, but go to new line on ENTER
  185. */
  186. prevent_send: function (event) {
  187. return !event.shiftKey;
  188. },
  189. message_get_suggested_recipients: function () {
  190. var self = this;
  191. var email_addresses = _.pluck(this.suggested_partners, 'email_address');
  192. return this.thread_dataset
  193. .call('message_get_suggested_recipients', [[this.context.default_res_id], this.context])
  194. .done(function (suggested_recipients) {
  195. var thread_recipients = suggested_recipients[self.context.default_res_id];
  196. _.each(thread_recipients, function (recipient) {
  197. var parsed_email = utils.parse_email(recipient[1]);
  198. if (_.indexOf(email_addresses, parsed_email[1]) === -1) {
  199. self.suggested_partners.push({
  200. checked: false,
  201. partner_id: recipient[0],
  202. full_name: recipient[1],
  203. name: parsed_email[0],
  204. email_address: parsed_email[1],
  205. reason: recipient[2],
  206. });
  207. }
  208. });
  209. });
  210. },
  211. /**
  212. * Get the list of selected suggested partners
  213. * @returns Array() : list of 'recipient' selected partners (may not be created in db)
  214. **/
  215. get_checked_suggested_partners: function () {
  216. var self = this;
  217. var checked_partners = [];
  218. this.$('.o_composer_suggested_partners input:checked').each(function() {
  219. var full_name = $(this).data('fullname');
  220. checked_partners = checked_partners.concat(_.filter(self.suggested_partners, function(item) {
  221. return full_name === item.full_name;
  222. }));
  223. });
  224. return checked_partners;
  225. },
  226. /**
  227. * Check the additional partners (not necessary registered partners), and open a popup form view
  228. * for the ones who informations is missing.
  229. * @param Array : list of 'recipient' partners to complete informations or validate
  230. * @returns Deferred resolved with the list of checked suggested partners (real partner)
  231. **/
  232. check_suggested_partners: function (checked_suggested_partners) {
  233. var self = this;
  234. var check_done = $.Deferred();
  235. var recipients = _.filter(checked_suggested_partners, function (recipient) { return recipient.checked; });
  236. var recipients_to_find = _.filter(recipients, function (recipient) { return (! recipient.partner_id); });
  237. var names_to_find = _.pluck(recipients_to_find, 'full_name');
  238. var recipients_to_check = _.filter(recipients, function (recipient) { return (recipient.partner_id && ! recipient.email_address); });
  239. var recipient_ids = _.pluck(_.filter(recipients, function (recipient) { return recipient.partner_id && recipient.email_address; }), 'partner_id');
  240. var names_to_remove = [];
  241. var recipient_ids_to_remove = [];
  242. // have unknown names -> call message_get_partner_info_from_emails to try to find partner_id
  243. var find_done = $.Deferred();
  244. if (names_to_find.length > 0) {
  245. find_done = self.thread_dataset.call('message_partner_info_from_emails', [[this.context.default_res_id], names_to_find]);
  246. } else {
  247. find_done.resolve([]);
  248. }
  249. // for unknown names + incomplete partners -> open popup - cancel = remove from recipients
  250. $.when(find_done).pipe(function (result) {
  251. var emails_deferred = [];
  252. var recipient_popups = result.concat(recipients_to_check);
  253. _.each(recipient_popups, function (partner_info) {
  254. var deferred = $.Deferred();
  255. emails_deferred.push(deferred);
  256. var partner_name = partner_info.full_name;
  257. var partner_id = partner_info.partner_id;
  258. var parsed_email = utils.parse_email(partner_name);
  259. var dialog = new form_common.FormViewDialog(self, {
  260. res_model: 'res.partner',
  261. res_id: partner_id,
  262. context: {
  263. force_email: true,
  264. ref: "compound_context",
  265. default_name: parsed_email[0],
  266. default_email: parsed_email[1],
  267. },
  268. title: _t("Please complete partner's informations"),
  269. disable_multiple_selection: true,
  270. }).open();
  271. dialog.on('closed', self, function () {
  272. deferred.resolve();
  273. });
  274. dialog.opened().then(function () {
  275. dialog.view_form.on('on_button_cancel', self, function () {
  276. names_to_remove.push(partner_name);
  277. if (partner_id) {
  278. recipient_ids_to_remove.push(partner_id);
  279. }
  280. });
  281. });
  282. });
  283. $.when.apply($, emails_deferred).then(function () {
  284. var new_names_to_find = _.difference(names_to_find, names_to_remove);
  285. find_done = $.Deferred();
  286. if (new_names_to_find.length > 0) {
  287. find_done = self.thread_dataset.call('message_partner_info_from_emails', [[self.context.default_res_id], new_names_to_find, true]);
  288. } else {
  289. find_done.resolve([]);
  290. }
  291. $.when(find_done).pipe(function (result) {
  292. var recipient_popups = result.concat(recipients_to_check);
  293. _.each(recipient_popups, function (partner_info) {
  294. if (partner_info.partner_id && _.indexOf(partner_info.partner_id, recipient_ids_to_remove) === -1) {
  295. recipient_ids.push(partner_info.partner_id);
  296. }
  297. });
  298. }).pipe(function () {
  299. check_done.resolve(recipient_ids);
  300. });
  301. });
  302. });
  303. return check_done;
  304. },
  305. on_open_full_composer: function() {
  306. if (!this.do_check_attachment_upload()){
  307. return false;
  308. }
  309. var self = this;
  310. var recipient_done = $.Deferred();
  311. if (this.options.is_log) {
  312. recipient_done.resolve([]);
  313. } else {
  314. var checked_suggested_partners = this.get_checked_suggested_partners();
  315. recipient_done = this.check_suggested_partners(checked_suggested_partners);
  316. }
  317. recipient_done.then(function (partner_ids) {
  318. var context = {
  319. default_parent_id: self.id,
  320. default_body: utils.get_text2html(self.$input.val()),
  321. default_attachment_ids: _.pluck(self.get('attachment_ids'), 'id'),
  322. default_partner_ids: partner_ids,
  323. default_is_log: self.options.is_log,
  324. mail_post_autofollow: true,
  325. };
  326. if (self.context.default_model && self.context.default_res_id) {
  327. context.default_model = self.context.default_model;
  328. context.default_res_id = self.context.default_res_id;
  329. }
  330. self.do_action({
  331. type: 'ir.actions.act_window',
  332. res_model: 'mail.compose.message',
  333. view_mode: 'form',
  334. view_type: 'form',
  335. views: [[false, 'form']],
  336. target: 'new',
  337. context: context,
  338. }, {
  339. on_close: function() {
  340. self.trigger('need_refresh');
  341. var parent = self.getParent();
  342. chat_manager.get_messages({model: parent.model, res_id: parent.res_id});
  343. },
  344. }).then(self.trigger.bind(self, 'close_composer'));
  345. });
  346. }
  347. });
  348. Chatter.include({
  349. open_composer: function (options) {
  350. var self = this;
  351. var old_composer = this.composer;
  352. // create the new composer
  353. this.composer = new MailComposer(this, this.thread_dataset, {
  354. commands_enabled: false,
  355. context: this.context,
  356. input_min_height: 50,
  357. input_max_height: Number.MAX_VALUE, // no max_height limit for the chatter
  358. input_baseline: 14,
  359. is_log: options && options.is_log,
  360. record_name: this.record_name,
  361. default_body: old_composer && old_composer.$input && old_composer.$input.val(),
  362. default_mention_selections: old_composer && old_composer.mention_get_listener_selections(),
  363. });
  364. this.composer.on('input_focused', this, function () {
  365. this.composer.mention_set_prefetched_partners(this.mention_suggestions || []);
  366. });
  367. this.composer.insertBefore(this.$('.o_mail_thread')).then(function () {
  368. // destroy existing composer
  369. if (old_composer) {
  370. old_composer.destroy();
  371. }
  372. if (!config.device.touch) {
  373. self.composer.focus();
  374. }
  375. self.composer.on('post_message', self, self.on_post_message);
  376. self.composer.on('need_refresh', self, self.refresh_followers);
  377. self.composer.on('close_composer', null, self.close_composer.bind(self, true));
  378. });
  379. this.mute_new_message_button(true);
  380. },
  381. });
  382. var MailTools = core.Class.extend({
  383. notify_incoming_message: function (msg, options) {
  384. if (bus.is_odoo_focused() && options.is_displayed) {
  385. // no need to notify
  386. return;
  387. }
  388. var title = _t('New message');
  389. if (msg.author_id[1]) {
  390. title = _.escape(msg.author_id[1]);
  391. }
  392. var content = utils.parse_and_transform(msg.body, utils.strip_html).substr(0, preview_msg_max_size);
  393. if (!bus.is_odoo_focused()) {
  394. global_unread_counter++;
  395. var tab_title = _.str.sprintf(_t("%d Messages"), global_unread_counter);
  396. web_client.set_title_part("_chat", tab_title);
  397. }
  398. utils.send_notification(title, content);
  399. },
  400. // Message and channel manipulation helpers
  401. //----------------------------------------------------------------------------------
  402. // options: channel_id, silent
  403. add_message: function (data, options) {
  404. options = options || {};
  405. var msg = _.findWhere(messages, { id: data.id });
  406. if (!msg) {
  407. msg = chat_manager.mail_tools.make_message(data);
  408. // Keep the array ordered by id when inserting the new message
  409. messages.splice(_.sortedIndex(messages, msg, 'id'), 0, msg);
  410. _.each(msg.channel_ids, function (channel_id) {
  411. var channel = chat_manager.get_channel(channel_id);
  412. if (channel) {
  413. chat_manager.mail_tools.add_to_cache(msg, []);
  414. if (options.domain && options.domain !== []) {
  415. chat_manager.mail_tools.add_to_cache(msg, options.domain);
  416. }
  417. if (channel.hidden) {
  418. channel.hidden = false;
  419. chat_manager.bus.trigger('new_channel', channel);
  420. }
  421. if (channel.type !== 'static' && !msg.is_author && !msg.is_system_notification) {
  422. if (options.increment_unread) {
  423. chat_manager.mail_tools.update_channel_unread_counter(channel, channel.unread_counter+1);
  424. }
  425. if (channel.is_chat && options.show_notification) {
  426. if (!client_action_open && config.device.size_class !== config.device.SIZES.XS) {
  427. // automatically open chat window
  428. chat_manager.bus.trigger('open_chat', channel, { passively: true });
  429. }
  430. var query = {is_displayed: false};
  431. chat_manager.bus.trigger('anyone_listening', channel, query);
  432. chat_manager.mail_tools.notify_incoming_message(msg, query);
  433. }
  434. }
  435. }
  436. });
  437. if (!options.silent) {
  438. chat_manager.bus.trigger('new_message', msg);
  439. }
  440. } else if (options.domain && options.domain !== []) {
  441. chat_manager.mail_tools.add_to_cache(msg, options.domain);
  442. }
  443. return msg;
  444. },
  445. property_descr: function (channel, msg, self) {
  446. return {
  447. enumerable: true,
  448. get: function () {
  449. return _.contains(msg.channel_ids, channel);
  450. },
  451. set: function (bool) {
  452. if (bool) {
  453. self.add_channel_to_message(msg, channel);
  454. } else {
  455. msg.channel_ids = _.without(msg.channel_ids, channel);
  456. }
  457. }
  458. };
  459. },
  460. get_properties: function(msg){
  461. return {
  462. is_starred: chat_manager.mail_tools.property_descr("channel_starred", msg, chat_manager.mail_tools),
  463. is_needaction: chat_manager.mail_tools.property_descr("channel_inbox", msg, chat_manager.mail_tools)
  464. };
  465. },
  466. set_channel_flags: function(data, msg){
  467. if (_.contains(data.needaction_partner_ids, session.partner_id)) {
  468. msg.is_needaction = true;
  469. }
  470. if (_.contains(data.starred_partner_ids, session.partner_id)) {
  471. msg.is_starred = true;
  472. }
  473. return msg;
  474. },
  475. get_channel_array: function(msg){
  476. return [ msg.channel_ids, 'channel_inbox', 'channel_starred' ];
  477. },
  478. make_message: function (data) {
  479. var msg = {
  480. id: data.id,
  481. author_id: data.author_id,
  482. body: data.body || "",
  483. date: moment(time.str_to_datetime(data.date)),
  484. message_type: data.message_type,
  485. subtype_description: data.subtype_description,
  486. is_author: data.author_id && data.author_id[0] === session.partner_id,
  487. is_note: data.is_note,
  488. is_system_notification: data.message_type === 'notification' && data.model === 'mail.channel' || data.info === 'transient_message',
  489. attachment_ids: data.attachment_ids || [],
  490. subject: data.subject,
  491. email_from: data.email_from,
  492. customer_email_status: data.customer_email_status,
  493. customer_email_data: data.customer_email_data,
  494. record_name: data.record_name,
  495. tracking_value_ids: data.tracking_value_ids,
  496. channel_ids: data.channel_ids,
  497. model: data.model,
  498. res_id: data.res_id,
  499. url: session.url("/mail/view?message_id=" + data.id)
  500. };
  501. _.each(_.keys(emoji_substitutions), function (key) {
  502. var escaped_key = String(key).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1');
  503. var regexp = new RegExp("(?:^|\\s|<[a-z]*>)(" + escaped_key + ")(?=\\s|$|</[a-z]*>)", "g");
  504. msg.body = msg.body.replace(regexp, ' <span class="o_mail_emoji">'+emoji_substitutions[key]+'</span> ');
  505. });
  506. Object.defineProperties(msg, chat_manager.mail_tools.get_properties(msg));
  507. msg = chat_manager.mail_tools.set_channel_flags(data, msg);
  508. if (msg.model === 'mail.channel') {
  509. var real_channels = _.without(chat_manager.mail_tools.get_channel_array(msg));
  510. var origin = real_channels.length === 1 ? real_channels[0] : undefined;
  511. var channel = origin && chat_manager.get_channel(origin);
  512. if (channel) {
  513. msg.origin_id = origin;
  514. msg.origin_name = channel.name;
  515. }
  516. }
  517. // Compute displayed author name or email
  518. if ((!msg.author_id || !msg.author_id[0]) && msg.email_from) {
  519. msg.mailto = msg.email_from;
  520. } else {
  521. msg.displayed_author = (msg.author_id === ODOOBOT_ID) && "OdooBot" ||
  522. msg.author_id && msg.author_id[1] ||
  523. msg.email_from || _t('Anonymous');
  524. }
  525. // Don't redirect on author clicked of self-posted or OdooBot messages
  526. msg.author_redirect = !msg.is_author && msg.author_id !== ODOOBOT_ID;
  527. // Compute the avatar_url
  528. if (msg.author_id === ODOOBOT_ID) {
  529. msg.avatar_src = "/mail/static/src/img/odoo_o.png";
  530. } else if (msg.author_id && msg.author_id[0]) {
  531. msg.avatar_src = "/web/image/res.partner/" + msg.author_id[0] + "/image_small";
  532. } else if (msg.message_type === 'email') {
  533. msg.avatar_src = "/mail/static/src/img/email_icon.png";
  534. } else {
  535. msg.avatar_src = "/mail/static/src/img/smiley/avatar.jpg";
  536. }
  537. // add anchor tags to urls
  538. msg.body = utils.parse_and_transform(msg.body, utils.add_link);
  539. // Compute url of attachments
  540. _.each(msg.attachment_ids, function(a) {
  541. a.url = '/web/content/' + a.id + '?download=true';
  542. });
  543. return msg;
  544. },
  545. add_channel_to_message: function (message, channel_id) {
  546. message.channel_ids.push(channel_id);
  547. message.channel_ids = _.uniq(message.channel_ids);
  548. },
  549. add_channel: function (data, options) {
  550. options = typeof options === "object" ? options : {};
  551. var channel = chat_manager.get_channel(data.id);
  552. if (channel) {
  553. if (channel.is_folded !== (data.state === "folded")) {
  554. channel.is_folded = (data.state === "folded");
  555. chat_manager.bus.trigger("channel_toggle_fold", channel);
  556. }
  557. } else {
  558. channel = chat_manager.mail_tools.make_channel(data, options);
  559. channels.push(channel);
  560. channels = _.sortBy(channels, function (channel) { return _.isString(channel.name) ? channel.name.toLowerCase() : ''; });
  561. if (!options.silent) {
  562. chat_manager.bus.trigger("new_channel", channel);
  563. }
  564. if (channel.is_detached) {
  565. chat_manager.bus.trigger("open_chat", channel);
  566. }
  567. }
  568. return channel;
  569. },
  570. make_channel: function (data, options) {
  571. var channel = {
  572. id: data.id,
  573. name: data.name,
  574. server_type: data.channel_type,
  575. type: data.type || data.channel_type,
  576. all_history_loaded: false,
  577. uuid: data.uuid,
  578. is_detached: data.is_minimized,
  579. is_folded: data.state === "folded",
  580. autoswitch: 'autoswitch' in options ? options.autoswitch : true,
  581. hidden: options.hidden,
  582. display_needactions: options.display_needactions,
  583. mass_mailing: data.mass_mailing,
  584. needaction_counter: data.message_needaction_counter || 0,
  585. unread_counter: 0,
  586. last_seen_message_id: data.seen_message_id,
  587. cache: {'[]': {
  588. all_history_loaded: false,
  589. loaded: false,
  590. messages: []
  591. }}
  592. };
  593. if (channel.type === "channel" && data.public !== "private") {
  594. channel.type = "public";
  595. } else if (data.public === "private") {
  596. channel.type = "private";
  597. }
  598. if (_.size(data.direct_partner) > 0) {
  599. channel.type = "dm";
  600. channel.name = data.direct_partner[0].name;
  601. channel.direct_partner_id = data.direct_partner[0].id;
  602. channel.status = data.direct_partner[0].im_status;
  603. pinned_dm_partners.push(channel.direct_partner_id);
  604. bus.update_option('bus_presence_partner_ids', pinned_dm_partners);
  605. } else if ('anonymous_name' in data) {
  606. channel.name = data.anonymous_name;
  607. }
  608. if (data.last_message_date) {
  609. channel.last_message_date = moment(time.str_to_datetime(data.last_message_date));
  610. }
  611. channel.is_chat = !channel.type.match(/^(public|private|static)$/);
  612. if (data.message_unread_counter) {
  613. chat_manager.mail_tools.update_channel_unread_counter(channel, data.message_unread_counter);
  614. }
  615. return channel;
  616. },
  617. remove_channel: function (channel) {
  618. if (!channel) { return; }
  619. if (channel.type === 'dm') {
  620. var index = pinned_dm_partners.indexOf(channel.direct_partner_id);
  621. if (index > -1) {
  622. pinned_dm_partners.splice(index, 1);
  623. bus.update_option('bus_presence_partner_ids', pinned_dm_partners);
  624. }
  625. }
  626. channels = _.without(channels, channel);
  627. delete channel_defs[channel.id];
  628. },
  629. get_channel_cache: function (channel, domain) {
  630. var stringified_domain = JSON.stringify(domain || []);
  631. if (!channel.cache[stringified_domain]) {
  632. channel.cache[stringified_domain] = {
  633. all_history_loaded: false,
  634. loaded: false,
  635. messages: []
  636. };
  637. }
  638. return channel.cache[stringified_domain];
  639. },
  640. invalidate_caches: function (channel_ids) {
  641. _.each(channel_ids, function (channel_id) {
  642. var channel = chat_manager.get_channel(channel_id);
  643. if (channel) {
  644. channel.cache = { '[]': channel.cache['[]']};
  645. }
  646. });
  647. },
  648. clear_cache_all_channels: function(){
  649. _.each(channels, function(channel){
  650. channel.cache = {
  651. '[]': {
  652. all_history_loaded: false,
  653. loaded: false,
  654. messages: []
  655. }
  656. }
  657. });
  658. },
  659. add_to_cache: function (message, domain) {
  660. _.each(message.channel_ids, function (channel_id) {
  661. var channel = chat_manager.get_channel(channel_id);
  662. if (channel) {
  663. var channel_cache = chat_manager.mail_tools.get_channel_cache(channel, domain);
  664. var index = _.sortedIndex(channel_cache.messages, message, 'id');
  665. if (channel_cache.messages[index] !== message) {
  666. channel_cache.messages.splice(index, 0, message);
  667. }
  668. }
  669. });
  670. },
  671. remove_from_cache: function(message, domain){
  672. var self = this;
  673. _.each(message.channel_ids, function (channel_id) {
  674. var channel = chat_manager.get_channel(channel_id);
  675. if (channel) {
  676. var channel_cache = self.get_channel_cache(channel, domain);
  677. var index = _.sortedIndex(channel_cache.messages, message, 'id');
  678. if (channel_cache.messages[index] === message) {
  679. channel_cache.messages.splice(index, 1);
  680. }
  681. }
  682. });
  683. },
  684. remove_message_from_channel: function (channel_id, message) {
  685. message.channel_ids = _.without(message.channel_ids, channel_id);
  686. var channel = _.findWhere(channels, { id: channel_id });
  687. _.each(channel.cache, function (cache) {
  688. cache.messages = _.without(cache.messages, message);
  689. });
  690. },
  691. get_domain: function(channel){
  692. return (channel.id === "channel_inbox") ? [['needaction', '=', true]] :
  693. (channel.id === "channel_starred") ? [['starred', '=', true]] : false;
  694. },
  695. // options: domain, load_more
  696. fetch_from_channel: function (channel, options) {
  697. options = options || {};
  698. var domain = chat_manager.mail_tools.get_domain(channel) || [['channel_ids', 'in', channel.id]];
  699. var cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain);
  700. if (options.domain) {
  701. domain = new data.CompoundDomain(domain, options.domain || []);
  702. }
  703. if (options.load_more) {
  704. var min_message_id = cache.messages[0].id;
  705. domain = new data.CompoundDomain([['id', '<', min_message_id]], domain);
  706. }
  707. return MessageModel.call('message_fetch', [domain], {limit: LIMIT}).then(function (msgs) {
  708. if (!cache.all_history_loaded) {
  709. cache.all_history_loaded = msgs.length < LIMIT;
  710. }
  711. cache.loaded = true;
  712. _.each(msgs, function (msg) {
  713. chat_manager.mail_tools.add_message(msg, {channel_id: channel.id, silent: true, domain: options.domain});
  714. });
  715. var channel_cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain || []);
  716. return channel_cache.messages;
  717. });
  718. },
  719. // options: force_fetch
  720. fetch_document_messages: function (ids, options) {
  721. var loaded_msgs = _.filter(messages, function (message) {
  722. return _.contains(ids, message.id);
  723. });
  724. var loaded_msg_ids = _.pluck(loaded_msgs, 'id');
  725. options = options || {};
  726. if (options.force_fetch || _.difference(ids.slice(0, LIMIT), loaded_msg_ids).length) {
  727. var ids_to_load = _.difference(ids, loaded_msg_ids).slice(0, LIMIT);
  728. return MessageModel.call('message_format', [ids_to_load]).then(function (msgs) {
  729. var processed_msgs = [];
  730. _.each(msgs, function (msg) {
  731. processed_msgs.push(chat_manager.mail_tools.add_message(msg, {silent: true}));
  732. });
  733. return _.sortBy(loaded_msgs.concat(processed_msgs), function (msg) {
  734. return msg.date;
  735. });
  736. });
  737. } else {
  738. return $.when(loaded_msgs);
  739. }
  740. },
  741. update_channel_unread_counter: function (channel, counter) {
  742. if (channel.unread_counter > 0 && counter === 0) {
  743. unread_conversation_counter = Math.max(0, unread_conversation_counter-1);
  744. } else if (channel.unread_counter === 0 && counter > 0) {
  745. unread_conversation_counter++;
  746. }
  747. if (channel.is_chat) {
  748. chat_unread_counter = Math.max(0, chat_unread_counter - channel.unread_counter + counter);
  749. }
  750. channel.unread_counter = counter;
  751. chat_manager.bus.trigger("update_channel_unread_counter", channel);
  752. },
  753. // Notification handlers
  754. // ---------------------------------------------------------------------------------
  755. on_notification: function (notifications) {
  756. // sometimes, the web client receives unsubscribe notification and an extra
  757. // notification on that channel. This is then followed by an attempt to
  758. // rejoin the channel that we just left. The next few lines remove the
  759. // extra notification to prevent that situation to occur.
  760. var unsubscribed_notif = _.find(notifications, function (notif) {
  761. return notif[1].info === "unsubscribe";
  762. });
  763. if (unsubscribed_notif) {
  764. notifications = _.reject(notifications, function (notif) {
  765. return notif[0][1] === "mail.channel" && notif[0][2] === unsubscribed_notif[1].id;
  766. });
  767. }
  768. _.each(notifications, function (notification) {
  769. var model = notification[0][1];
  770. if (model === 'ir.needaction') {
  771. // new message in the inbox
  772. chat_manager.mail_tools.on_needaction_notification(notification[1]);
  773. } else if (model === 'mail.channel') {
  774. // new message in a channel
  775. chat_manager.mail_tools.on_channel_notification(notification[1]);
  776. } else if (model === 'res.partner') {
  777. // channel joined/left, message marked as read/(un)starred, chat open/closed
  778. chat_manager.mail_tools.on_partner_notification(notification[1]);
  779. } else if (model === 'bus.presence') {
  780. // update presence of users
  781. chat_manager.mail_tools.on_presence_notification(notification[1]);
  782. } else if (model === 'mail_base.mail_sent') {
  783. // Delete cache in order to fetch new message
  784. // TODO find a solution without deleting cache. Currently
  785. // problem is that on inheriting send_mail in
  786. // mail.compose.message it's not possible to get id of new
  787. // message
  788. chat_manager.mail_tools.clear_cache_all_channels();
  789. }
  790. });
  791. },
  792. on_needaction_notification: function (message) {
  793. message = chat_manager.mail_tools.add_message(message, {
  794. channel_id: 'channel_inbox',
  795. show_notification: true,
  796. increment_unread: true
  797. });
  798. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  799. needaction_counter++;
  800. _.each(message.channel_ids, function (channel_id) {
  801. var channel = chat_manager.get_channel(channel_id);
  802. if (channel) {
  803. channel.needaction_counter++;
  804. }
  805. });
  806. chat_manager.bus.trigger('update_needaction', needaction_counter);
  807. },
  808. on_channel_notification: function (message) {
  809. var def;
  810. var channel_already_in_cache = true;
  811. if (message.channel_ids.length === 1) {
  812. channel_already_in_cache = !!chat_manager.get_channel(message.channel_ids[0]);
  813. def = chat_manager.join_channel(message.channel_ids[0], {autoswitch: false});
  814. } else {
  815. def = $.when();
  816. }
  817. def.then(function () {
  818. // don't increment unread if channel wasn't in cache yet as its unread counter has just been fetched
  819. chat_manager.mail_tools.add_message(message, { show_notification: true, increment_unread: channel_already_in_cache });
  820. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  821. });
  822. },
  823. on_partner_notification: function (data) {
  824. if (data.info === "unsubscribe") {
  825. var channel = chat_manager.get_channel(data.id);
  826. if (channel) {
  827. var msg;
  828. if (_.contains(['public', 'private'], channel.type)) {
  829. msg = _.str.sprintf(_t('You unsubscribed from <b>%s</b>.'), channel.name);
  830. } else {
  831. msg = _.str.sprintf(_t('You unpinned your conversation with <b>%s</b>.'), channel.name);
  832. }
  833. this.remove_channel(channel);
  834. chat_manager.bus.trigger("unsubscribe_from_channel", data.id);
  835. web_client.do_notify(_("Unsubscribed"), msg);
  836. }
  837. } else if (data.type === 'toggle_star') {
  838. chat_manager.mail_tools.on_toggle_star_notification(data);
  839. } else if (data.type === 'mark_as_read') {
  840. chat_manager.mail_tools.on_mark_as_read_notification(data);
  841. } else if (data.type === 'mark_as_unread') {
  842. chat_manager.mail_tools.on_mark_as_unread_notification(data);
  843. } else if (data.info === 'channel_seen') {
  844. chat_manager.mail_tools.on_channel_seen_notification(data);
  845. } else if (data.info === 'transient_message') {
  846. chat_manager.mail_tools.on_transient_message_notification(data);
  847. } else {
  848. chat_manager.mail_tools.on_chat_session_notification(data);
  849. }
  850. },
  851. on_toggle_star_notification: function (data) {
  852. _.each(data.message_ids, function (msg_id) {
  853. var message = _.findWhere(messages, { id: msg_id });
  854. if (message) {
  855. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  856. message.is_starred = data.starred;
  857. if (!message.is_starred) {
  858. chat_manager.mail_tools.remove_message_from_channel("channel_starred", message);
  859. starred_counter--;
  860. } else {
  861. chat_manager.mail_tools.add_to_cache(message, []);
  862. var channel_starred = chat_manager.get_channel('channel_starred');
  863. channel_starred.cache = _.pick(channel_starred.cache, "[]");
  864. starred_counter++;
  865. }
  866. chat_manager.bus.trigger('update_message', message);
  867. }
  868. });
  869. chat_manager.bus.trigger('update_starred', starred_counter);
  870. },
  871. on_mark_as_read_notification: function (data) {
  872. _.each(data.message_ids, function (msg_id) {
  873. var message = _.findWhere(messages, { id: msg_id });
  874. if (message) {
  875. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  876. chat_manager.mail_tools.remove_message_from_channel("channel_inbox", message);
  877. chat_manager.bus.trigger('update_message', message);
  878. }
  879. });
  880. if (data.channel_ids) {
  881. _.each(data.channel_ids, function (channel_id) {
  882. var channel = chat_manager.get_channel(channel_id);
  883. if (channel) {
  884. channel.needaction_counter = Math.max(channel.needaction_counter - data.message_ids.length, 0);
  885. }
  886. });
  887. } else { // if no channel_ids specified, this is a 'mark all read' in the inbox
  888. _.each(channels, function (channel) {
  889. channel.needaction_counter = 0;
  890. });
  891. }
  892. needaction_counter = Math.max(needaction_counter - data.message_ids.length, 0);
  893. chat_manager.bus.trigger('update_needaction', needaction_counter);
  894. },
  895. on_mark_as_unread_notification: function (data) {
  896. _.each(data.message_ids, function (message_id) {
  897. var message = _.findWhere(messages, { id: message_id });
  898. if (message) {
  899. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  900. chat_manager.mail_tools.add_channel_to_message(message, 'channel_inbox');
  901. chat_manager.mail_tools.add_to_cache(message, []);
  902. }
  903. });
  904. var channel_inbox = chat_manager.get_channel('channel_inbox');
  905. channel_inbox.cache = _.pick(channel_inbox.cache, "[]");
  906. _.each(data.channel_ids, function (channel_id) {
  907. var channel = chat_manager.get_channel(channel_id);
  908. if (channel) {
  909. channel.needaction_counter += data.message_ids.length;
  910. }
  911. });
  912. needaction_counter += data.message_ids.length;
  913. chat_manager.bus.trigger('update_needaction', needaction_counter);
  914. },
  915. on_channel_seen_notification: function (data) {
  916. var channel = chat_manager.get_channel(data.id);
  917. if (channel) {
  918. channel.last_seen_message_id = data.last_message_id;
  919. if (channel.unread_counter) {
  920. chat_manager.mail_tools.update_channel_unread_counter(channel, 0);
  921. }
  922. }
  923. },
  924. on_chat_session_notification: function (chat_session) {
  925. var channel;
  926. if ((chat_session.channel_type === "channel") && (chat_session.state === "open")) {
  927. chat_manager.mail_tools.add_channel(chat_session, {autoswitch: false});
  928. if (!chat_session.is_minimized && chat_session.info !== 'creation') {
  929. web_client.do_notify(_t("Invitation"), _t("You have been invited to: ") + chat_session.name);
  930. }
  931. }
  932. // partner specific change (open a detached window for example)
  933. if ((chat_session.state === "open") || (chat_session.state === "folded")) {
  934. channel = chat_session.is_minimized && chat_manager.get_channel(chat_session.id);
  935. if (channel) {
  936. channel.is_detached = true;
  937. channel.is_folded = (chat_session.state === "folded");
  938. chat_manager.bus.trigger("open_chat", channel);
  939. }
  940. } else if (chat_session.state === "closed") {
  941. channel = chat_manager.get_channel(chat_session.id);
  942. if (channel) {
  943. channel.is_detached = false;
  944. chat_manager.bus.trigger("close_chat", channel, {keep_open_if_unread: true});
  945. }
  946. }
  947. },
  948. on_presence_notification: function (data) {
  949. var dm = chat_manager.get_dm_from_partner_id(data.id);
  950. if (dm) {
  951. dm.status = data.im_status;
  952. chat_manager.bus.trigger('update_dm_presence', dm);
  953. }
  954. },
  955. on_transient_message_notification: function (data) {
  956. var last_message = _.last(messages);
  957. data.id = (last_message ? last_message.id : 0) + 0.01;
  958. data.author_id = data.author_id || ODOOBOT_ID;
  959. chat_manager.mail_tools.add_message(data);
  960. }
  961. });
  962. var cls = new MailTools();
  963. // Public interface
  964. //----------------------------------------------------------------------------------
  965. chat_manager.mail_tools = cls;
  966. // we add this function this way in order to make them extendable via MailTools.include({...})
  967. chat_manager.make_message = function(){
  968. return chat_manager.mail_tools.make_message.apply(chat_manager.mail_tools, arguments);
  969. };
  970. chat_manager.make_channel = function(){
  971. return chat_manager.mail_tools.make_channel.apply(chat_manager.mail_tools, arguments);
  972. };
  973. chat_manager.post_message = function (data, options) {
  974. options = options || {};
  975. var msg = {
  976. partner_ids: data.partner_ids,
  977. channel_ids: data.channel_ids,
  978. body: _.str.trim(data.content),
  979. attachment_ids: data.attachment_ids
  980. };
  981. if ('subject' in data) {
  982. msg.subject = data.subject;
  983. }
  984. if ('channel_id' in options) {
  985. // post a message in a channel or execute a command
  986. return ChannelModel.call(data.command ? 'execute_command' : 'message_post', [options.channel_id], _.extend(msg, {
  987. message_type: 'comment',
  988. content_subtype: 'html',
  989. subtype: 'mail.mt_comment',
  990. command: data.command,
  991. }));
  992. }
  993. if ('model' in options && 'res_id' in options) {
  994. // post a message in a chatter
  995. _.extend(msg, {
  996. content_subtype: data.content_subtype,
  997. context: data.context,
  998. message_type: data.message_type,
  999. subtype: data.subtype,
  1000. subtype_id: data.subtype_id
  1001. });
  1002. if (options.model && options.res_id){
  1003. var model = new Model(options.model);
  1004. return model.call('message_post', [options.res_id], msg).then(function (msg_id) {
  1005. return MessageModel.call('message_format', [msg_id]).then(function (msgs) {
  1006. msgs[0].model = options.model;
  1007. msgs[0].res_id = options.res_id;
  1008. chat_manager.mail_tools.add_message(msgs[0]);
  1009. });
  1010. });
  1011. } else {
  1012. options.model = 'mail.compose.message';
  1013. var compose_model = new Model(options.model);
  1014. return compose_model.call('create', [msg, {default_parent_id: options.parent_id}]).then(function(id){
  1015. return compose_model.call('send_mail_action', [id, {}]);
  1016. });
  1017. }
  1018. }
  1019. };
  1020. chat_manager.get_message = function (id) {
  1021. return _.findWhere(messages, {id: id});
  1022. };
  1023. chat_manager.get_messages = function (options) {
  1024. var channel;
  1025. if ('channel_id' in options && options.load_more) {
  1026. // get channel messages, force load_more
  1027. channel = this.get_channel(options.channel_id);
  1028. return chat_manager.mail_tools.fetch_from_channel(channel, {domain: options.domain || {}, load_more: true});
  1029. }
  1030. if ('channel_id' in options) {
  1031. // channel message, check in cache first
  1032. channel = this.get_channel(options.channel_id);
  1033. var channel_cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain);
  1034. if (channel_cache.loaded) {
  1035. return $.when(channel_cache.messages);
  1036. } else {
  1037. return chat_manager.mail_tools.fetch_from_channel(channel, {domain: options.domain});
  1038. }
  1039. }
  1040. if ('ids' in options) {
  1041. // get messages from their ids (chatter is the main use case)
  1042. return chat_manager.mail_tools.fetch_document_messages(options.ids, options).then(function(result) {
  1043. chat_manager.mark_as_read(options.ids);
  1044. return result;
  1045. });
  1046. }
  1047. if ('model' in options && 'res_id' in options) {
  1048. // get messages for a chatter, when it doesn't know the ids (use
  1049. // case is when using the full composer)
  1050. var domain = [['model', '=', options.model], ['res_id', '=', options.res_id]];
  1051. MessageModel.call('message_fetch', [domain], {limit: 30}).then(function (msgs) {
  1052. return _.map(msgs, chat_manager.mail_tools.add_message);
  1053. });
  1054. }
  1055. };
  1056. chat_manager.toggle_star_status = function (message_id) {
  1057. return MessageModel.call('toggle_message_starred', [[message_id]]);
  1058. };
  1059. chat_manager.unstar_all = function () {
  1060. return MessageModel.call('unstar_all', [[]], {});
  1061. };
  1062. chat_manager.mark_as_read = function (message_ids) {
  1063. var ids = _.filter(message_ids, function (id) {
  1064. var message = _.findWhere(messages, {id: id});
  1065. // If too many messages, not all are fetched, and some might not be found
  1066. return !message || message.is_needaction;
  1067. });
  1068. if (ids.length) {
  1069. return MessageModel.call('set_message_done', [ids]);
  1070. } else {
  1071. return $.when();
  1072. }
  1073. };
  1074. chat_manager.mark_all_as_read = function (channel, domain) {
  1075. if ((channel.id === "channel_inbox" && needaction_counter) || (channel && channel.needaction_counter)) {
  1076. return MessageModel.call('mark_all_as_read', [], {channel_ids: channel.id !== "channel_inbox" ? [channel.id] : [], domain: domain});
  1077. }
  1078. return $.when();
  1079. };
  1080. chat_manager.undo_mark_as_read = function (message_ids, channel) {
  1081. return MessageModel.call('mark_as_unread', [message_ids, [channel.id]]);
  1082. };
  1083. chat_manager.mark_channel_as_seen = function (channel) {
  1084. if (channel.unread_counter > 0 && channel.type !== 'static') {
  1085. chat_manager.mail_tools.update_channel_unread_counter(channel, 0);
  1086. channel_seen(channel);
  1087. }
  1088. };
  1089. chat_manager.get_channels = function () {
  1090. return _.clone(channels);
  1091. };
  1092. chat_manager.get_channel = function (id) {
  1093. return _.findWhere(channels, {id: id});
  1094. };
  1095. chat_manager.get_dm_from_partner_id = function (partner_id) {
  1096. return _.findWhere(channels, {direct_partner_id: partner_id});
  1097. };
  1098. chat_manager.all_history_loaded = function (channel, domain) {
  1099. return chat_manager.mail_tools.get_channel_cache(channel, domain).all_history_loaded;
  1100. };
  1101. chat_manager.get_mention_partner_suggestions = function (channel) {
  1102. if (!channel) {
  1103. return mention_partner_suggestions;
  1104. }
  1105. if (!channel.members_deferred) {
  1106. channel.members_deferred = ChannelModel
  1107. .call("channel_fetch_listeners", [channel.uuid], {}, {shadow: true})
  1108. .then(function (members) {
  1109. var suggestions = [];
  1110. _.each(mention_partner_suggestions, function (partners) {
  1111. suggestions.push(_.filter(partners, function (partner) {
  1112. return !_.findWhere(members, { id: partner.id });
  1113. }));
  1114. });
  1115. return [members];
  1116. });
  1117. }
  1118. return channel.members_deferred;
  1119. };
  1120. chat_manager.get_commands = function (channel) {
  1121. return _.filter(commands, function (command) {
  1122. return !command.channel_types || _.contains(command.channel_types, channel.server_type);
  1123. });
  1124. };
  1125. chat_manager.get_canned_responses = function () {
  1126. return canned_responses;
  1127. };
  1128. chat_manager.get_needaction_counter = function () {
  1129. return needaction_counter;
  1130. };
  1131. chat_manager.get_starred_counter = function () {
  1132. return starred_counter;
  1133. };
  1134. chat_manager.get_chat_unread_counter = function () {
  1135. return chat_unread_counter;
  1136. };
  1137. chat_manager.get_unread_conversation_counter = function () {
  1138. return unread_conversation_counter;
  1139. };
  1140. chat_manager.get_last_seen_message = function (channel) {
  1141. if (channel.last_seen_message_id) {
  1142. var messages = channel.cache['[]'].messages;
  1143. var msg = _.findWhere(messages, {id: channel.last_seen_message_id});
  1144. if (msg) {
  1145. var i = _.sortedIndex(messages, msg, 'id') + 1;
  1146. while (i < messages.length && (messages[i].is_author || messages[i].is_system_notification)) {
  1147. msg = messages[i];
  1148. i++;
  1149. }
  1150. return msg;
  1151. }
  1152. }
  1153. };
  1154. chat_manager.get_discuss_menu_id = function () {
  1155. return discuss_menu_id;
  1156. };
  1157. chat_manager.detach_channel = function (channel) {
  1158. return ChannelModel.call("channel_minimize", [channel.uuid, true], {}, {shadow: true});
  1159. };
  1160. chat_manager.remove_chatter_messages = function (model) {
  1161. messages = _.reject(messages, function (message) {
  1162. return message.channel_ids.length === 0 && message.model === model;
  1163. });
  1164. };
  1165. chat_manager.create_channel = function (name, type) {
  1166. var method = type === "dm" ? "channel_get" : "channel_create";
  1167. var args = type === "dm" ? [[name]] : [name, type];
  1168. return ChannelModel
  1169. .call(method, args)
  1170. .then(chat_manager.mail_tools.add_channel);
  1171. };
  1172. chat_manager.join_channel = function (channel_id, options) {
  1173. if (channel_id in channel_defs) {
  1174. // prevents concurrent calls to channel_join_and_get_info
  1175. return channel_defs[channel_id];
  1176. }
  1177. var channel = this.get_channel(channel_id);
  1178. if (channel) {
  1179. // channel already joined
  1180. channel_defs[channel_id] = $.when(channel);
  1181. } else {
  1182. channel_defs[channel_id] = ChannelModel
  1183. .call('channel_join_and_get_info', [[channel_id]])
  1184. .then(function (result) {
  1185. return chat_manager.mail_tools.add_channel(result, options);
  1186. });
  1187. }
  1188. return channel_defs[channel_id];
  1189. };
  1190. chat_manager.open_and_detach_dm = function (partner_id) {
  1191. return ChannelModel.call('channel_get_and_minimize', [[partner_id]]).then(chat_manager.mail_tools.add_channel);
  1192. };
  1193. chat_manager.open_channel = function (channel) {
  1194. chat_manager.bus.trigger(client_action_open ? 'open_channel' : 'detach_channel', channel);
  1195. };
  1196. chat_manager.unsubscribe = function (channel) {
  1197. var def;
  1198. if (_.contains(['public', 'private'], channel.type)) {
  1199. return ChannelModel.call('action_unfollow', [[channel.id]]);
  1200. } else {
  1201. return ChannelModel.call('channel_pin', [channel.uuid, false]);
  1202. }
  1203. };
  1204. chat_manager.close_chat_session = function (channel_id) {
  1205. var channel = this.get_channel(channel_id);
  1206. ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"}, {shadow: true});
  1207. };
  1208. chat_manager.fold_channel = function (channel_id, folded) {
  1209. var args = {
  1210. uuid: this.get_channel(channel_id).uuid
  1211. };
  1212. if (_.isBoolean(folded)) {
  1213. args.state = folded ? 'folded' : 'open';
  1214. }
  1215. return ChannelModel.call("channel_fold", [], args, {shadow: true});
  1216. };
  1217. /**
  1218. * Special redirection handling for given model and id
  1219. *
  1220. * If the model is res.partner, and there is a user associated with this
  1221. * partner which isn't the current user, open the DM with this user.
  1222. * Otherwhise, open the record's form view, if this is not the current user's.
  1223. */
  1224. chat_manager.redirect = function (res_model, res_id, dm_redirection_callback) {
  1225. var self = this;
  1226. var redirect_to_document = function (res_model, res_id, view_id) {
  1227. web_client.do_action({
  1228. type:'ir.actions.act_window',
  1229. view_type: 'form',
  1230. view_mode: 'form',
  1231. res_model: res_model,
  1232. views: [[view_id || false, 'form']],
  1233. res_id: res_id
  1234. });
  1235. };
  1236. if (res_model === "res.partner") {
  1237. var domain = [["partner_id", "=", res_id]];
  1238. UserModel.call("search", [domain]).then(function (user_ids) {
  1239. if (user_ids.length && user_ids[0] !== session.uid && dm_redirection_callback) {
  1240. self.create_channel(res_id, 'dm').then(dm_redirection_callback);
  1241. } else {
  1242. redirect_to_document(res_model, res_id);
  1243. }
  1244. });
  1245. } else {
  1246. new Model(res_model).call('get_formview_id', [[res_id], session.context]).then(function (view_id) {
  1247. redirect_to_document(res_model, res_id, view_id);
  1248. });
  1249. }
  1250. };
  1251. chat_manager.get_channels_preview = function (channels) {
  1252. var channels_preview = _.map(channels, function (channel) {
  1253. var info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
  1254. info.last_message = _.last(channel.cache['[]'].messages);
  1255. if (!info.is_chat) {
  1256. info.image_src = '/web/image/mail.channel/'+channel.id+'/image_small';
  1257. } else if (channel.direct_partner_id) {
  1258. info.image_src = '/web/image/res.partner/'+channel.direct_partner_id+'/image_small';
  1259. } else {
  1260. info.image_src = '/mail/static/src/img/smiley/avatar.jpg';
  1261. }
  1262. return info;
  1263. });
  1264. var missing_channels = _.where(channels_preview, {last_message: undefined});
  1265. if (!channels_preview_def) {
  1266. if (missing_channels.length) {
  1267. var missing_channel_ids = _.pluck(missing_channels, 'id');
  1268. channels_preview_def = ChannelModel.call('channel_fetch_preview', [missing_channel_ids], {}, {shadow: true});
  1269. } else {
  1270. channels_preview_def = $.when();
  1271. }
  1272. }
  1273. return channels_preview_def.then(function (channels) {
  1274. _.each(missing_channels, function (channel_preview) {
  1275. var channel = _.findWhere(channels, {id: channel_preview.id});
  1276. if (channel) {
  1277. channel_preview.last_message = chat_manager.mail_tools.add_message(channel.last_message);
  1278. }
  1279. });
  1280. return _.filter(channels_preview, function (channel) {
  1281. return channel.last_message; // remove empty channels
  1282. });
  1283. });
  1284. };
  1285. chat_manager.get_message_body_preview = function (message_body) {
  1286. return utils.parse_and_transform(message_body, utils.inline);
  1287. };
  1288. chat_manager.search_partner = function (search_val, limit) {
  1289. var def = $.Deferred();
  1290. var values = [];
  1291. // search among prefetched partners
  1292. var search_regexp = new RegExp(_.str.escapeRegExp(utils.unaccent(search_val)), 'i');
  1293. _.each(mention_partner_suggestions, function (partners) {
  1294. if (values.length < limit) {
  1295. values = values.concat(_.filter(partners, function (partner) {
  1296. return session.partner_id !== partner.id && search_regexp.test(partner.name);
  1297. })).splice(0, limit);
  1298. }
  1299. });
  1300. if (!values.length) {
  1301. // extend the research to all users
  1302. def = PartnerModel.call('im_search', [search_val, limit || 20], {}, {shadow: true});
  1303. } else {
  1304. def = $.when(values);
  1305. }
  1306. return def.then(function (values) {
  1307. var autocomplete_data = _.map(values, function (value) {
  1308. return { id: value.id, value: value.name, label: value.name };
  1309. });
  1310. return _.sortBy(autocomplete_data, 'label');
  1311. });
  1312. };
  1313. chat_manager.bus.on('client_action_open', null, function (open) {
  1314. client_action_open = open;
  1315. });
  1316. // In order to extend init use chat_manager.is_ready Derrered object. See example in mail_arhive module
  1317. function init () {
  1318. chat_manager.mail_tools.add_channel({
  1319. id: "channel_inbox",
  1320. name: _lt("Inbox"),
  1321. type: "static",
  1322. }, { display_needactions: true });
  1323. chat_manager.mail_tools.add_channel({
  1324. id: "channel_starred",
  1325. name: _lt("Starred"),
  1326. type: "static"
  1327. });
  1328. // unsubscribe and then subscribe to the event, to avoid duplication of new messages
  1329. bus.off('notification');
  1330. bus.on('notification', null, function(){
  1331. chat_manager.mail_tools.on_notification.apply(chat_manager.mail_tools, arguments);
  1332. });
  1333. return session.rpc('/mail/client_action').then(function (result) {
  1334. _.each(result.channel_slots, function (channels) {
  1335. _.each(channels, chat_manager.mail_tools.add_channel);
  1336. });
  1337. needaction_counter = result.needaction_inbox_counter;
  1338. starred_counter = result.starred_counter;
  1339. commands = _.map(result.commands, function (command) {
  1340. return _.extend({ id: command.name }, command);
  1341. });
  1342. mention_partner_suggestions = result.mention_partner_suggestions;
  1343. discuss_menu_id = result.menu_id;
  1344. // Shortcodes: canned responses and emojis
  1345. _.each(result.shortcodes, function (s) {
  1346. if (s.shortcode_type === 'text') {
  1347. canned_responses.push(_.pick(s, ['id', 'source', 'substitution']));
  1348. } else {
  1349. emojis.push(_.pick(s, ['id', 'source', 'substitution', 'description']));
  1350. emoji_substitutions[_.escape(s.source)] = s.substitution;
  1351. }
  1352. });
  1353. bus.start_polling();
  1354. });
  1355. }
  1356. chat_manager.is_ready = init();
  1357. return {
  1358. ODOOBOT_ID: ODOOBOT_ID,
  1359. chat_manager: chat_manager,
  1360. MailTools: MailTools,
  1361. MailComposer: MailComposer
  1362. };
  1363. });