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.

1197 lines
49 KiB

  1. odoo.define('mail_base.base', function (require) {
  2. "use strict";
  3. var bus = require('bus.bus').bus;
  4. var config = require('web.config');
  5. var core = require('web.core');
  6. var data = require('web.data');
  7. var Model = require('web.Model');
  8. var session = require('web.session');
  9. var time = require('web.time');
  10. var web_client = require('web.web_client');
  11. var _t = core._t;
  12. var _lt = core._lt;
  13. var LIMIT = 100;
  14. var preview_msg_max_size = 350; // optimal for native english speakers
  15. var MessageModel = new Model('mail.message', session.context);
  16. var ChannelModel = new Model('mail.channel', session.context);
  17. var UserModel = new Model('res.users', session.context);
  18. var PartnerModel = new Model('res.partner', session.context);
  19. var chat_manager = require('mail.chat_manager');
  20. // Private model
  21. //----------------------------------------------------------------------------------
  22. var messages = [];
  23. var channels = [];
  24. var channels_preview_def;
  25. var channel_defs = {};
  26. var chat_unread_counter = 0;
  27. var unread_conversation_counter = 0;
  28. var emojis = [];
  29. var emoji_substitutions = {};
  30. var needaction_counter = 0;
  31. var mention_partner_suggestions = [];
  32. var discuss_ids = {};
  33. var global_unread_counter = 0;
  34. var pinned_dm_partners = []; // partner_ids we have a pinned DM with
  35. var client_action_open = false;
  36. // Utils: Window focus/unfocus, beep, tab title, parsing html strings
  37. //----------------------------------------------------------------------------------
  38. var beep = (function () {
  39. if (typeof(Audio) === "undefined") {
  40. return function () {};
  41. }
  42. var audio = new Audio();
  43. var ext = audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3";
  44. audio.src = session.url("/mail/static/src/audio/ting" + ext);
  45. return function () { audio.play(); };
  46. })();
  47. bus.on("window_focus", null, function() {
  48. global_unread_counter = 0;
  49. web_client.set_title_part("_chat");
  50. });
  51. var url_regexp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;
  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 MailTools = core.Class.extend({
  126. send_native_notification: function (title, content) {
  127. var notification = new Notification(title, {body: content, icon: "/mail/static/src/img/odoo_o.png"});
  128. notification.onclick = function (e) {
  129. window.focus();
  130. if (this.cancel) {
  131. this.cancel();
  132. } else if (this.close) {
  133. this.close();
  134. }
  135. };
  136. },
  137. notify_incoming_message: function (msg, options) {
  138. if (bus.is_odoo_focused() && options.is_displayed) {
  139. // no need to notify
  140. return;
  141. }
  142. var title = _t('New message');
  143. if (msg.author_id[1]) {
  144. title = _.escape(msg.author_id[1]);
  145. }
  146. var content = chat_manager.mail_tools.parse_and_transform(msg.body, chat_manager.mail_tools.strip_html).substr(0, preview_msg_max_size);
  147. if (!bus.is_odoo_focused()) {
  148. global_unread_counter++;
  149. var tab_title = _.str.sprintf(_t("%d Messages"), global_unread_counter);
  150. web_client.set_title_part("_chat", tab_title);
  151. }
  152. if (Notification && Notification.permission === "granted") {
  153. if (bus.is_master) {
  154. chat_manager.mail_tools.send_native_notification(title, content);
  155. }
  156. } else {
  157. web_client.do_notify(title, content);
  158. if (bus.is_master) {
  159. beep();
  160. }
  161. }
  162. },
  163. parse_and_transform: function (html_string, transform_function) {
  164. var open_token = "OPEN" + Date.now();
  165. var string = html_string.replace(/&lt;/g, open_token);
  166. var children = $('<div>').html(string).contents();
  167. return chat_manager.mail_tools._parse_and_transform(children, transform_function)
  168. .replace(new RegExp(open_token, "g"), "&lt;");
  169. },
  170. _parse_and_transform: function (nodes, transform_function) {
  171. return _.map(nodes, function (node) {
  172. return transform_function(node, function () {
  173. return chat_manager.mail_tools._parse_and_transform(node.childNodes, transform_function);
  174. });
  175. }).join("");
  176. },
  177. add_link: function (node, transform_children) {
  178. if (node.nodeType === 3) { // text node
  179. return node.data.replace(url_regexp, function (url) {
  180. var href = (!/^(f|ht)tps?:\/\//i.test(url)) ? "http://" + url : url;
  181. return '<a target="_blank" href="' + href + '">' + url + '</a>';
  182. });
  183. }
  184. if (node.tagName === "A") return node.outerHTML;
  185. node.innerHTML = transform_children();
  186. return node.outerHTML;
  187. },
  188. strip_html: function (node, transform_children) {
  189. if (node.nodeType === 3) return node.data; // text node
  190. if (node.tagName === "BR") return "\n";
  191. return transform_children();
  192. },
  193. inline: function (node, transform_children) {
  194. if (node.nodeType === 3) return node.data;
  195. if (node.tagName === "BR") return " ";
  196. if (node.tagName.match(/^(A|P|DIV|PRE|BLOCKQUOTE)$/)) return transform_children();
  197. node.innerHTML = transform_children();
  198. return node.outerHTML;
  199. },
  200. // Message and channel manipulation helpers
  201. //----------------------------------------------------------------------------------
  202. // options: channel_id, silent
  203. add_message: function (data, options) {
  204. options = options || {};
  205. var msg = _.findWhere(messages, { id: data.id });
  206. if (!msg) {
  207. msg = chat_manager.mail_tools.make_message(data);
  208. // Keep the array ordered by id when inserting the new message
  209. messages.splice(_.sortedIndex(messages, msg, 'id'), 0, msg);
  210. _.each(msg.channel_ids, function (channel_id) {
  211. var channel = chat_manager.get_channel(channel_id);
  212. if (channel) {
  213. chat_manager.mail_tools.add_to_cache(msg, []);
  214. if (options.domain && options.domain !== []) {
  215. chat_manager.mail_tools.add_to_cache(msg, options.domain);
  216. }
  217. if (channel.hidden) {
  218. channel.hidden = false;
  219. chat_manager.bus.trigger('new_channel', channel);
  220. }
  221. if (channel.type !== 'static' && !msg.is_author && !msg.is_system_notification) {
  222. if (options.increment_unread) {
  223. chat_manager.mail_tools.update_channel_unread_counter(channel, channel.unread_counter+1);
  224. }
  225. if (channel.is_chat && options.show_notification) {
  226. if (!client_action_open && config.device.size_class !== config.device.SIZES.XS) {
  227. // automatically open chat window
  228. chat_manager.bus.trigger('open_chat', channel, { passively: true });
  229. }
  230. var query = {is_displayed: false};
  231. chat_manager.bus.trigger('anyone_listening', channel, query);
  232. chat_manager.mail_tools.notify_incoming_message(msg, query);
  233. }
  234. }
  235. }
  236. });
  237. if (!options.silent) {
  238. chat_manager.bus.trigger('new_message', msg);
  239. }
  240. } else if (options.domain && options.domain !== []) {
  241. chat_manager.mail_tools.add_to_cache(msg, options.domain);
  242. }
  243. return msg;
  244. },
  245. property_descr: function (channel, msg, self) {
  246. return {
  247. enumerable: true,
  248. get: function () {
  249. return _.contains(msg.channel_ids, channel);
  250. },
  251. set: function (bool) {
  252. if (bool) {
  253. self.add_channel_to_message(msg, channel);
  254. } else {
  255. msg.channel_ids = _.without(msg.channel_ids, channel);
  256. }
  257. }
  258. };
  259. },
  260. get_properties: function(msg){
  261. return {
  262. is_starred: chat_manager.mail_tools.property_descr("channel_starred", msg, chat_manager.mail_tools),
  263. is_needaction: chat_manager.mail_tools.property_descr("channel_inbox", msg, chat_manager.mail_tools)
  264. };
  265. },
  266. set_channel_flags: function(data, msg){
  267. if (_.contains(data.needaction_partner_ids, session.partner_id)) {
  268. msg.is_needaction = true;
  269. }
  270. if (_.contains(data.starred_partner_ids, session.partner_id)) {
  271. msg.is_starred = true;
  272. }
  273. return msg;
  274. },
  275. get_channel_array: function(msg){
  276. return [ msg.channel_ids, 'channel_inbox', 'channel_starred' ];
  277. },
  278. make_message: function (data) {
  279. var msg = {
  280. id: data.id,
  281. author_id: data.author_id,
  282. body_short: data.body_short || "",
  283. body: data.body || "",
  284. date: moment(time.str_to_datetime(data.date)),
  285. message_type: data.message_type,
  286. subtype_description: data.subtype_description,
  287. is_author: data.author_id && data.author_id[0] === session.partner_id,
  288. is_note: data.is_note,
  289. is_system_notification: data.message_type === 'notification' && data.model === 'mail.channel',
  290. attachment_ids: data.attachment_ids,
  291. subject: data.subject,
  292. email_from: data.email_from,
  293. record_name: data.record_name,
  294. tracking_value_ids: data.tracking_value_ids,
  295. channel_ids: data.channel_ids,
  296. model: data.model,
  297. res_id: data.res_id,
  298. url: session.url("/mail/view?message_id=" + data.id)
  299. };
  300. _.each(_.keys(emoji_substitutions), function (key) {
  301. var escaped_key = String(key).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1');
  302. var regexp = new RegExp("(?:^|\\s|<[a-z]*>)(" + escaped_key + ")(?=\\s|$|</[a-z]*>)", "g");
  303. msg.body = msg.body.replace(regexp, ' <span class="o_mail_emoji">'+emoji_substitutions[key]+'</span> ');
  304. });
  305. Object.defineProperties(msg, chat_manager.mail_tools.get_properties(msg));
  306. msg = chat_manager.mail_tools.set_channel_flags(data, msg);
  307. if (msg.model === 'mail.channel') {
  308. var real_channels = _.without(chat_manager.mail_tools.get_channel_array(msg));
  309. var origin = real_channels.length === 1 ? real_channels[0] : undefined;
  310. var channel = origin && chat_manager.get_channel(origin);
  311. if (channel) {
  312. msg.origin_id = origin;
  313. msg.origin_name = channel.name;
  314. }
  315. }
  316. // Compute displayed author name or email
  317. if ((!msg.author_id || !msg.author_id[0]) && msg.email_from) {
  318. msg.mailto = msg.email_from;
  319. } else {
  320. msg.displayed_author = msg.author_id && msg.author_id[1] ||
  321. msg.email_from || _t('Anonymous');
  322. }
  323. // Don't redirect on author clicked of self-posted messages
  324. msg.author_redirect = !msg.is_author;
  325. // Compute the avatar_url
  326. if (msg.author_id && msg.author_id[0]) {
  327. msg.avatar_src = "/web/image/res.partner/" + msg.author_id[0] + "/image_small";
  328. } else if (msg.message_type === 'email') {
  329. msg.avatar_src = "/mail/static/src/img/email_icon.png";
  330. } else {
  331. msg.avatar_src = "/mail/static/src/img/smiley/avatar.jpg";
  332. }
  333. // add anchor tags to urls
  334. msg.body = chat_manager.mail_tools.parse_and_transform(msg.body, chat_manager.mail_tools.add_link);
  335. // Compute url of attachments
  336. _.each(msg.attachment_ids, function(a) {
  337. a.url = '/web/content/' + a.id + '?download=true';
  338. });
  339. return msg;
  340. },
  341. add_channel_to_message: function (message, channel_id) {
  342. message.channel_ids.push(channel_id);
  343. message.channel_ids = _.uniq(message.channel_ids);
  344. },
  345. add_channel: function (data, options) {
  346. options = typeof options === "object" ? options : {};
  347. var channel = chat_manager.get_channel(data.id);
  348. if (channel) {
  349. if (channel.is_folded !== (data.state === "folded")) {
  350. channel.is_folded = (data.state === "folded");
  351. chat_manager.bus.trigger("channel_toggle_fold", channel);
  352. }
  353. } else {
  354. channel = chat_manager.mail_tools.make_channel(data, options);
  355. channels.push(channel);
  356. channels = _.sortBy(channels, function (channel) { return _.isString(channel.name) ? channel.name.toLowerCase() : ''; });
  357. if (!options.silent) {
  358. chat_manager.bus.trigger("new_channel", channel);
  359. }
  360. if (channel.is_detached) {
  361. chat_manager.bus.trigger("open_chat", channel);
  362. }
  363. }
  364. return channel;
  365. },
  366. make_channel: function (data, options) {
  367. var channel = {
  368. id: data.id,
  369. name: data.name,
  370. type: data.type || data.channel_type,
  371. all_history_loaded: false,
  372. uuid: data.uuid,
  373. is_detached: data.is_minimized,
  374. is_folded: data.state === "folded",
  375. autoswitch: 'autoswitch' in options ? options.autoswitch : true,
  376. hidden: options.hidden,
  377. display_needactions: options.display_needactions,
  378. mass_mailing: data.mass_mailing,
  379. needaction_counter: data.message_needaction_counter || 0,
  380. unread_counter: 0,
  381. last_seen_message_id: data.seen_message_id,
  382. cache: {'[]': {
  383. all_history_loaded: false,
  384. loaded: false,
  385. messages: []
  386. }}
  387. };
  388. if (channel.type === "channel" && data.public !== "private") {
  389. channel.type = "public";
  390. } else if (data.public === "private") {
  391. channel.type = "private";
  392. }
  393. if (_.size(data.direct_partner) > 0) {
  394. channel.type = "dm";
  395. channel.name = data.direct_partner[0].name;
  396. channel.direct_partner_id = data.direct_partner[0].id;
  397. channel.status = data.direct_partner[0].im_status;
  398. pinned_dm_partners.push(channel.direct_partner_id);
  399. bus.update_option('bus_presence_partner_ids', pinned_dm_partners);
  400. } else if ('anonymous_name' in data) {
  401. channel.name = data.anonymous_name;
  402. }
  403. channel.is_chat = !channel.type.match(/^(public|private|static)$/);
  404. if (data.message_unread_counter) {
  405. chat_manager.mail_tools.update_channel_unread_counter(channel, data.message_unread_counter);
  406. }
  407. return channel;
  408. },
  409. remove_channel: function (channel) {
  410. if (!channel) { return; }
  411. if (channel.type === 'dm') {
  412. var index = pinned_dm_partners.indexOf(channel.direct_partner_id);
  413. if (index > -1) {
  414. pinned_dm_partners.splice(index, 1);
  415. bus.update_option('bus_presence_partner_ids', pinned_dm_partners);
  416. }
  417. }
  418. channels = _.without(channels, channel);
  419. delete channel_defs[channel.id];
  420. },
  421. get_channel_cache: function (channel, domain) {
  422. var stringified_domain = JSON.stringify(domain || []);
  423. if (!channel.cache[stringified_domain]) {
  424. channel.cache[stringified_domain] = {
  425. all_history_loaded: false,
  426. loaded: false,
  427. messages: []
  428. };
  429. }
  430. return channel.cache[stringified_domain];
  431. },
  432. invalidate_caches: function (channel_ids) {
  433. _.each(channel_ids, function (channel_id) {
  434. var channel = chat_manager.get_channel(channel_id);
  435. if (channel) {
  436. channel.cache = { '[]': channel.cache['[]']};
  437. }
  438. });
  439. },
  440. clear_cache_all_channels: function(){
  441. _.each(channels, function(channel){
  442. channel.cache = {};
  443. });
  444. },
  445. add_to_cache: function (message, domain) {
  446. _.each(message.channel_ids, function (channel_id) {
  447. var channel = chat_manager.get_channel(channel_id);
  448. if (channel) {
  449. var channel_cache = chat_manager.mail_tools.get_channel_cache(channel, domain);
  450. var index = _.sortedIndex(channel_cache.messages, message, 'id');
  451. if (channel_cache.messages[index] !== message) {
  452. channel_cache.messages.splice(index, 0, message);
  453. }
  454. }
  455. });
  456. },
  457. remove_from_cache: function(message, domain){
  458. var self = this;
  459. _.each(message.channel_ids, function (channel_id) {
  460. var channel = chat_manager.get_channel(channel_id);
  461. if (channel) {
  462. var channel_cache = self.get_channel_cache(channel, domain);
  463. var index = _.sortedIndex(channel_cache.messages, message, 'id');
  464. if (channel_cache.messages[index] === message) {
  465. channel_cache.messages.splice(index, 1);
  466. }
  467. }
  468. });
  469. },
  470. remove_message_from_channel: function (channel_id, message) {
  471. message.channel_ids = _.without(message.channel_ids, channel_id);
  472. var channel = _.findWhere(channels, { id: channel_id });
  473. _.each(channel.cache, function (cache) {
  474. cache.messages = _.without(cache.messages, message);
  475. });
  476. },
  477. get_domain: function(channel){
  478. return (channel.id === "channel_inbox") ? [['needaction', '=', true]] :
  479. (channel.id === "channel_starred") ? [['starred', '=', true]] : false;
  480. },
  481. // options: domain, load_more
  482. fetch_from_channel: function (channel, options) {
  483. options = options || {};
  484. var domain = chat_manager.mail_tools.get_domain(channel) || [['channel_ids', 'in', channel.id]];
  485. var cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain);
  486. if (options.domain) {
  487. domain = new data.CompoundDomain(domain, options.domain || []);
  488. }
  489. if (options.load_more) {
  490. var min_message_id = cache.messages[0].id;
  491. domain = new data.CompoundDomain([['id', '<', min_message_id]], domain);
  492. }
  493. return MessageModel.call('message_fetch', [domain], {limit: LIMIT}).then(function (msgs) {
  494. if (!cache.all_history_loaded) {
  495. cache.all_history_loaded = msgs.length < LIMIT;
  496. }
  497. cache.loaded = true;
  498. _.each(msgs, function (msg) {
  499. chat_manager.mail_tools.add_message(msg, {channel_id: channel.id, silent: true, domain: options.domain});
  500. });
  501. var channel_cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain || []);
  502. return channel_cache.messages;
  503. });
  504. },
  505. // options: force_fetch
  506. fetch_document_messages: function (ids, options) {
  507. var loaded_msgs = _.filter(messages, function (message) {
  508. return _.contains(ids, message.id);
  509. });
  510. var loaded_msg_ids = _.pluck(loaded_msgs, 'id');
  511. options = options || {};
  512. if (options.force_fetch || _.difference(ids.slice(0, LIMIT), loaded_msg_ids).length) {
  513. var ids_to_load = _.difference(ids, loaded_msg_ids).slice(0, LIMIT);
  514. return MessageModel.call('message_format', [ids_to_load]).then(function (msgs) {
  515. var processed_msgs = [];
  516. _.each(msgs, function (msg) {
  517. processed_msgs.push(chat_manager.mail_tools.add_message(msg, {silent: true}));
  518. });
  519. return _.sortBy(loaded_msgs.concat(processed_msgs), function (msg) {
  520. return msg.date;
  521. });
  522. });
  523. } else {
  524. return $.when(loaded_msgs);
  525. }
  526. },
  527. update_channel_unread_counter: function (channel, counter) {
  528. if (channel.unread_counter > 0 && counter === 0) {
  529. unread_conversation_counter = Math.max(0, unread_conversation_counter-1);
  530. } else if (channel.unread_counter === 0 && counter > 0) {
  531. unread_conversation_counter++;
  532. }
  533. if (channel.is_chat) {
  534. chat_unread_counter = Math.max(0, chat_unread_counter - channel.unread_counter + counter);
  535. }
  536. channel.unread_counter = counter;
  537. chat_manager.bus.trigger("update_channel_unread_counter", channel);
  538. },
  539. // Notification handlers
  540. // ---------------------------------------------------------------------------------
  541. on_notification: function (notifications) {
  542. // sometimes, the web client receives unsubscribe notification and an extra
  543. // notification on that channel. This is then followed by an attempt to
  544. // rejoin the channel that we just left. The next few lines remove the
  545. // extra notification to prevent that situation to occur.
  546. var unsubscribed_notif = _.find(notifications, function (notif) {
  547. return notif[1].info === "unsubscribe";
  548. });
  549. if (unsubscribed_notif) {
  550. notifications = _.reject(notifications, function (notif) {
  551. return notif[0][1] === "mail.channel" && notif[0][2] === unsubscribed_notif[1].id;
  552. });
  553. }
  554. _.each(notifications, function (notification) {
  555. var model = notification[0][1];
  556. if (model === 'ir.needaction') {
  557. // new message in the inbox
  558. chat_manager.mail_tools.on_needaction_notification(notification[1]);
  559. } else if (model === 'mail.channel') {
  560. // new message in a channel
  561. chat_manager.mail_tools.on_channel_notification(notification[1]);
  562. } else if (model === 'res.partner') {
  563. // channel joined/left, message marked as read/(un)starred, chat open/closed
  564. chat_manager.mail_tools.on_partner_notification(notification[1]);
  565. } else if (model === 'bus.presence') {
  566. // update presence of users
  567. chat_manager.mail_tools.on_presence_notification(notification[1]);
  568. } else if (model === 'mail_base.mail_sent') {
  569. // Delete cache in order to fetch new message
  570. // TODO find a solution without deleting cache. Currently
  571. // problem is that on inheriting send_mail in
  572. // mail.compose.message it's not possible to get id of new
  573. // message
  574. chat_manager.mail_tools.clear_cache_all_channels();
  575. }
  576. });
  577. },
  578. on_needaction_notification: function (message) {
  579. message = chat_manager.mail_tools.add_message(message, {
  580. channel_id: 'channel_inbox',
  581. show_notification: true,
  582. increment_unread: true
  583. });
  584. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  585. needaction_counter++;
  586. _.each(message.channel_ids, function (channel_id) {
  587. var channel = chat_manager.get_channel(channel_id);
  588. if (channel) {
  589. channel.needaction_counter++;
  590. }
  591. });
  592. chat_manager.bus.trigger('update_needaction', needaction_counter);
  593. },
  594. on_channel_notification: function (message) {
  595. var def;
  596. var channel_already_in_cache = true;
  597. if (message.channel_ids.length === 1) {
  598. channel_already_in_cache = !!chat_manager.get_channel(message.channel_ids[0]);
  599. def = chat_manager.join_channel(message.channel_ids[0], {autoswitch: false});
  600. } else {
  601. def = $.when();
  602. }
  603. def.then(function () {
  604. // don't increment unread if channel wasn't in cache yet as its unread counter has just been fetched
  605. chat_manager.mail_tools.add_message(message, { show_notification: true, increment_unread: channel_already_in_cache });
  606. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  607. });
  608. },
  609. on_partner_notification: function (data) {
  610. if (data.info === "unsubscribe") {
  611. chat_manager.mail_tools.remove_channel(chat_manager.get_channel(data.id));
  612. chat_manager.bus.trigger("unsubscribe_from_channel", data.id);
  613. } else if (data.type === 'toggle_star') {
  614. chat_manager.mail_tools.on_toggle_star_notification(data);
  615. } else if (data.type === 'mark_as_read') {
  616. chat_manager.mail_tools.on_mark_as_read_notification(data);
  617. } else if (data.type === 'mark_as_unread') {
  618. chat_manager.mail_tools.on_mark_as_unread_notification(data);
  619. } else if (data.info === 'channel_seen') {
  620. chat_manager.mail_tools.on_channel_seen_notification(data);
  621. } else {
  622. chat_manager.mail_tools.on_chat_session_notification(data);
  623. }
  624. },
  625. on_toggle_star_notification: function (data) {
  626. _.each(data.message_ids, function (msg_id) {
  627. var message = _.findWhere(messages, { id: msg_id });
  628. if (message) {
  629. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  630. message.is_starred = data.starred;
  631. if (!message.is_starred) {
  632. chat_manager.mail_tools.remove_message_from_channel("channel_starred", message);
  633. } else {
  634. chat_manager.mail_tools.add_to_cache(message, []);
  635. var channel_starred = chat_manager.get_channel('channel_starred');
  636. channel_starred.cache = _.pick(channel_starred.cache, "[]");
  637. }
  638. chat_manager.bus.trigger('update_message', message);
  639. }
  640. });
  641. },
  642. on_mark_as_read_notification: function (data) {
  643. _.each(data.message_ids, function (msg_id) {
  644. var message = _.findWhere(messages, { id: msg_id });
  645. if (message) {
  646. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  647. chat_manager.mail_tools.remove_message_from_channel("channel_inbox", message);
  648. chat_manager.bus.trigger('update_message', message);
  649. }
  650. });
  651. if (data.channel_ids) {
  652. _.each(data.channel_ids, function (channel_id) {
  653. var channel = chat_manager.get_channel(channel_id);
  654. if (channel) {
  655. channel.needaction_counter = Math.max(channel.needaction_counter - data.message_ids.length, 0);
  656. }
  657. });
  658. } else { // if no channel_ids specified, this is a 'mark all read' in the inbox
  659. _.each(channels, function (channel) {
  660. channel.needaction_counter = 0;
  661. });
  662. }
  663. needaction_counter = Math.max(needaction_counter - data.message_ids.length, 0);
  664. chat_manager.bus.trigger('update_needaction', needaction_counter);
  665. },
  666. on_mark_as_unread_notification: function (data) {
  667. _.each(data.message_ids, function (message_id) {
  668. var message = _.findWhere(messages, { id: message_id });
  669. if (message) {
  670. chat_manager.mail_tools.invalidate_caches(message.channel_ids);
  671. chat_manager.mail_tools.add_channel_to_message(message, 'channel_inbox');
  672. chat_manager.mail_tools.add_to_cache(message, []);
  673. }
  674. });
  675. var channel_inbox = chat_manager.get_channel('channel_inbox');
  676. channel_inbox.cache = _.pick(channel_inbox.cache, "[]");
  677. _.each(data.channel_ids, function (channel_id) {
  678. var channel = chat_manager.get_channel(channel_id);
  679. if (channel) {
  680. channel.needaction_counter += data.message_ids.length;
  681. }
  682. });
  683. needaction_counter += data.message_ids.length;
  684. chat_manager.bus.trigger('update_needaction', needaction_counter);
  685. },
  686. on_channel_seen_notification: function (data) {
  687. var channel = chat_manager.get_channel(data.id);
  688. if (channel) {
  689. channel.last_seen_message_id = data.last_message_id;
  690. if (channel.unread_counter) {
  691. chat_manager.mail_tools.update_channel_unread_counter(channel, 0);
  692. }
  693. }
  694. },
  695. on_chat_session_notification: function (chat_session) {
  696. var channel;
  697. if ((chat_session.channel_type === "channel") && (chat_session.state === "open")) {
  698. chat_manager.mail_tools.add_channel(chat_session, {autoswitch: false});
  699. if (!chat_session.is_minimized && chat_session.info !== 'creation') {
  700. web_client.do_notify(_t("Invitation"), _t("You have been invited to: ") + chat_session.name);
  701. }
  702. }
  703. // partner specific change (open a detached window for example)
  704. if ((chat_session.state === "open") || (chat_session.state === "folded")) {
  705. channel = chat_session.is_minimized && chat_manager.get_channel(chat_session.id);
  706. if (channel) {
  707. channel.is_detached = true;
  708. channel.is_folded = (chat_session.state === "folded");
  709. chat_manager.bus.trigger("open_chat", channel);
  710. }
  711. } else if (chat_session.state === "closed") {
  712. channel = chat_manager.get_channel(chat_session.id);
  713. if (channel) {
  714. channel.is_detached = false;
  715. chat_manager.bus.trigger("close_chat", channel, {keep_open_if_unread: true});
  716. }
  717. }
  718. },
  719. on_presence_notification: function (data) {
  720. var dm = chat_manager.get_dm_from_partner_id(data.id);
  721. if (dm) {
  722. dm.status = data.im_status;
  723. chat_manager.bus.trigger('update_dm_presence', dm);
  724. }
  725. }
  726. });
  727. var cls = new MailTools();
  728. // Public interface
  729. //----------------------------------------------------------------------------------
  730. chat_manager.mail_tools = cls;
  731. // we add this function this way in order to make them extendable via MailTools.include({...})
  732. chat_manager.make_message = function(){
  733. return chat_manager.mail_tools.make_message.apply(chat_manager.mail_tools, arguments);
  734. };
  735. chat_manager.make_channel = function(){
  736. return chat_manager.mail_tools.make_channel.apply(chat_manager.mail_tools, arguments);
  737. };
  738. chat_manager.post_message = function (data, options) {
  739. options = options || {};
  740. var msg = {
  741. partner_ids: data.partner_ids,
  742. body: _.str.trim(data.content),
  743. attachment_ids: data.attachment_ids
  744. };
  745. if ('subject' in data) {
  746. msg.subject = data.subject;
  747. }
  748. if ('channel_id' in options) {
  749. // post a message in a channel
  750. return ChannelModel.call('message_post', [options.channel_id], _.extend(msg, {
  751. message_type: 'comment',
  752. content_subtype: 'html',
  753. subtype: 'mail.mt_comment'
  754. }));
  755. }
  756. if ('model' in options && 'res_id' in options) {
  757. // post a message in a chatter
  758. _.extend(msg, {
  759. content_subtype: data.content_subtype,
  760. context: data.context,
  761. message_type: data.message_type,
  762. subtype: data.subtype,
  763. subtype_id: data.subtype_id
  764. });
  765. if (options.model && options.res_id){
  766. var model = new Model(options.model);
  767. return model.call('message_post', [options.res_id], msg).then(function (msg_id) {
  768. return MessageModel.call('message_format', [msg_id]).then(function (msgs) {
  769. msgs[0].model = options.model;
  770. msgs[0].res_id = options.res_id;
  771. chat_manager.mail_tools.add_message(msgs[0]);
  772. });
  773. });
  774. } else {
  775. options.model = 'mail.compose.message';
  776. var compose_model = new Model(options.model);
  777. return compose_model.call('create', [msg, {default_parent_id: options.parent_id}]).then(function(id){
  778. return compose_model.call('send_mail_action', [id, {}]);
  779. });
  780. }
  781. }
  782. };
  783. chat_manager.get_message = function (id) {
  784. return _.findWhere(messages, {id: id});
  785. };
  786. chat_manager.get_messages = function (options) {
  787. var channel;
  788. if ('channel_id' in options && options.load_more) {
  789. // get channel messages, force load_more
  790. channel = this.get_channel(options.channel_id);
  791. return chat_manager.mail_tools.fetch_from_channel(channel, {domain: options.domain || {}, load_more: true});
  792. }
  793. if ('channel_id' in options) {
  794. // channel message, check in cache first
  795. channel = this.get_channel(options.channel_id);
  796. var channel_cache = chat_manager.mail_tools.get_channel_cache(channel, options.domain);
  797. if (channel_cache.loaded) {
  798. return $.when(channel_cache.messages);
  799. } else {
  800. return chat_manager.mail_tools.fetch_from_channel(channel, {domain: options.domain});
  801. }
  802. }
  803. if ('ids' in options) {
  804. // get messages from their ids (chatter is the main use case)
  805. return chat_manager.mail_tools.fetch_document_messages(options.ids, options).then(function(result) {
  806. chat_manager.mark_as_read(options.ids);
  807. return result;
  808. });
  809. }
  810. if ('model' in options && 'res_id' in options) {
  811. // get messages for a chatter, when it doesn't know the ids (use
  812. // case is when using the full composer)
  813. var domain = [['model', '=', options.model], ['res_id', '=', options.res_id]];
  814. MessageModel.call('message_fetch', [domain], {limit: 30}).then(function (msgs) {
  815. return _.map(msgs, chat_manager.mail_tools.add_message);
  816. });
  817. }
  818. };
  819. chat_manager.toggle_star_status = function (message_id) {
  820. var msg = _.findWhere(messages, { id: message_id });
  821. return MessageModel.call('set_message_starred', [[message_id], !msg.is_starred]);
  822. };
  823. chat_manager.unstar_all = function () {
  824. return MessageModel.call('unstar_all', [[]], {});
  825. };
  826. chat_manager.mark_as_read = function (message_ids) {
  827. var ids = _.filter(message_ids, function (id) {
  828. var message = _.findWhere(messages, {id: id});
  829. // If too many messages, not all are fetched, and some might not be found
  830. return !message || message.is_needaction;
  831. });
  832. if (ids.length) {
  833. return MessageModel.call('set_message_done', [ids]);
  834. } else {
  835. return $.when();
  836. }
  837. };
  838. chat_manager.mark_all_as_read = function (channel, domain) {
  839. if ((channel.id === "channel_inbox" && needaction_counter) || (channel && channel.needaction_counter)) {
  840. return MessageModel.call('mark_all_as_read', [], {channel_ids: channel.id !== "channel_inbox" ? [channel.id] : [], domain: domain});
  841. }
  842. return $.when();
  843. };
  844. chat_manager.undo_mark_as_read = function (message_ids, channel) {
  845. return MessageModel.call('mark_as_unread', [message_ids, [channel.id]]);
  846. };
  847. chat_manager.mark_channel_as_seen = function (channel) {
  848. if (channel.unread_counter > 0 && channel.type !== 'static') {
  849. chat_manager.mail_tools.update_channel_unread_counter(channel, 0);
  850. channel_seen(channel);
  851. }
  852. };
  853. chat_manager.get_channels = function () {
  854. return _.clone(channels);
  855. };
  856. chat_manager.get_channel = function (id) {
  857. return _.findWhere(channels, {id: id});
  858. };
  859. chat_manager.get_dm_from_partner_id = function (partner_id) {
  860. return _.findWhere(channels, {direct_partner_id: partner_id});
  861. };
  862. chat_manager.all_history_loaded = function (channel, domain) {
  863. return chat_manager.mail_tools.get_channel_cache(channel, domain).all_history_loaded;
  864. };
  865. chat_manager.get_mention_partner_suggestions = function (channel) {
  866. if (!channel) {
  867. return mention_partner_suggestions;
  868. }
  869. if (!channel.members_deferred) {
  870. channel.members_deferred = ChannelModel
  871. .call("channel_fetch_listeners", [channel.uuid], {}, {shadow: true})
  872. .then(function (members) {
  873. var suggestions = [];
  874. _.each(mention_partner_suggestions, function (partners) {
  875. suggestions.push(_.filter(partners, function (partner) {
  876. return !_.findWhere(members, { id: partner.id });
  877. }));
  878. });
  879. return [members];
  880. });
  881. }
  882. return channel.members_deferred;
  883. };
  884. chat_manager.get_emojis = function() {
  885. return emojis;
  886. };
  887. chat_manager.get_needaction_counter = function () {
  888. return needaction_counter;
  889. };
  890. chat_manager.get_chat_unread_counter = function () {
  891. return chat_unread_counter;
  892. };
  893. chat_manager.get_unread_conversation_counter = function () {
  894. return unread_conversation_counter;
  895. };
  896. chat_manager.get_last_seen_message = function (channel) {
  897. if (channel.last_seen_message_id) {
  898. var messages = channel.cache['[]'].messages;
  899. var msg = _.findWhere(messages, {id: channel.last_seen_message_id});
  900. if (msg) {
  901. var i = _.sortedIndex(messages, msg, 'id') + 1;
  902. while (i < messages.length && (messages[i].is_author || messages[i].is_system_notification)) {
  903. msg = messages[i];
  904. i++;
  905. }
  906. return msg;
  907. }
  908. }
  909. };
  910. chat_manager.get_discuss_ids = function () {
  911. return discuss_ids;
  912. };
  913. chat_manager.detach_channel = function (channel) {
  914. return ChannelModel.call("channel_minimize", [channel.uuid, true], {}, {shadow: true});
  915. };
  916. chat_manager.remove_chatter_messages = function (model) {
  917. messages = _.reject(messages, function (message) {
  918. return message.channel_ids.length === 0 && message.model === model;
  919. });
  920. };
  921. chat_manager.bus = new core.Bus();
  922. chat_manager.create_channel = function (name, type) {
  923. var method = type === "dm" ? "channel_get" : "channel_create";
  924. var args = type === "dm" ? [[name]] : [name, type];
  925. return ChannelModel
  926. .call(method, args)
  927. .then(chat_manager.mail_tools.add_channel);
  928. };
  929. chat_manager.join_channel = function (channel_id, options) {
  930. if (channel_id in channel_defs) {
  931. // prevents concurrent calls to channel_join_and_get_info
  932. return channel_defs[channel_id];
  933. }
  934. var channel = this.get_channel(channel_id);
  935. if (channel) {
  936. // channel already joined
  937. channel_defs[channel_id] = $.when(channel);
  938. } else {
  939. channel_defs[channel_id] = ChannelModel
  940. .call('channel_join_and_get_info', [[channel_id]])
  941. .then(function (result) {
  942. return chat_manager.mail_tools.add_channel(result, options);
  943. });
  944. }
  945. return channel_defs[channel_id];
  946. };
  947. chat_manager.open_and_detach_dm = function (partner_id) {
  948. return ChannelModel.call('channel_get_and_minimize', [[partner_id]]).then(chat_manager.mail_tools.add_channel);
  949. };
  950. chat_manager.open_channel = function (channel) {
  951. chat_manager.bus.trigger(client_action_open ? 'open_channel' : 'detach_channel', channel);
  952. };
  953. chat_manager.unsubscribe = function (channel) {
  954. var def;
  955. if (_.contains(['public', 'private'], channel.type)) {
  956. def = ChannelModel.call('action_unfollow', [[channel.id]]);
  957. } else {
  958. def = ChannelModel.call('channel_pin', [channel.uuid, false]);
  959. }
  960. return def.then(function () {
  961. chat_manager.mail_tools.remove_channel(channel);
  962. });
  963. };
  964. chat_manager.close_chat_session = function (channel_id) {
  965. var channel = this.get_channel(channel_id);
  966. ChannelModel.call("channel_fold", [], {uuid : channel.uuid, state : "closed"}, {shadow: true});
  967. };
  968. chat_manager.fold_channel = function (channel_id, folded) {
  969. var args = {
  970. uuid: this.get_channel(channel_id).uuid
  971. };
  972. if (_.isBoolean(folded)) {
  973. args.state = folded ? 'folded' : 'open';
  974. }
  975. return ChannelModel.call("channel_fold", [], args, {shadow: true});
  976. };
  977. /**
  978. * Special redirection handling for given model and id
  979. *
  980. * If the model is res.partner, and there is a user associated with this
  981. * partner which isn't the current user, open the DM with this user.
  982. * Otherwhise, open the record's form view, if this is not the current user's.
  983. */
  984. chat_manager.redirect = function (res_model, res_id, dm_redirection_callback) {
  985. var self = this;
  986. var redirect_to_document = function (res_model, res_id, view_id) {
  987. web_client.do_action({
  988. type:'ir.actions.act_window',
  989. view_type: 'form',
  990. view_mode: 'form',
  991. res_model: res_model,
  992. views: [[view_id || false, 'form']],
  993. res_id: res_id
  994. });
  995. };
  996. if (res_model === "res.partner") {
  997. var domain = [["partner_id", "=", res_id]];
  998. UserModel.call("search", [domain]).then(function (user_ids) {
  999. if (user_ids.length && user_ids[0] !== session.uid) {
  1000. self.create_channel(res_id, 'dm').then(dm_redirection_callback || function () {});
  1001. } else if (!user_ids.length) {
  1002. redirect_to_document(res_model, res_id);
  1003. }
  1004. });
  1005. } else {
  1006. new Model(res_model).call('get_formview_id', [res_id, session.context]).then(function (view_id) {
  1007. redirect_to_document(res_model, res_id, view_id);
  1008. });
  1009. }
  1010. };
  1011. chat_manager.get_channels_preview = function (channels) {
  1012. var channels_preview = _.map(channels, function (channel) {
  1013. var info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
  1014. info.last_message = _.last(channel.cache['[]'].messages);
  1015. if (!info.is_chat) {
  1016. info.image_src = '/web/image/mail.channel/'+channel.id+'/image_small';
  1017. } else if (channel.direct_partner_id) {
  1018. info.image_src = '/web/image/res.partner/'+channel.direct_partner_id+'/image_small';
  1019. } else {
  1020. info.image_src = '/mail/static/src/img/smiley/avatar.jpg';
  1021. }
  1022. return info;
  1023. });
  1024. var missing_channels = _.where(channels_preview, {last_message: undefined});
  1025. if (!channels_preview_def) {
  1026. if (missing_channels.length) {
  1027. var missing_channel_ids = _.pluck(missing_channels, 'id');
  1028. channels_preview_def = ChannelModel.call('channel_fetch_preview', [missing_channel_ids], {}, {shadow: true});
  1029. } else {
  1030. channels_preview_def = $.when();
  1031. }
  1032. }
  1033. return channels_preview_def.then(function (channels) {
  1034. _.each(missing_channels, function (channel_preview) {
  1035. var channel = _.findWhere(channels, {id: channel_preview.id});
  1036. if (channel) {
  1037. channel_preview.last_message = chat_manager.mail_tools.add_message(channel.last_message);
  1038. }
  1039. });
  1040. return _.filter(channels_preview, function (channel) {
  1041. return channel.last_message; // remove empty channels
  1042. });
  1043. });
  1044. };
  1045. chat_manager.get_message_body_preview = function (message_body) {
  1046. return chat_manager.mail_tools.parse_and_transform(message_body, chat_manager.mail_tools.inline);
  1047. };
  1048. chat_manager.search_partner = function (search_val, limit) {
  1049. return PartnerModel.call('im_search', [search_val, limit || 20], {}, {shadow: true}).then(function(result) {
  1050. var values = [];
  1051. _.each(result, function(user) {
  1052. var escaped_name = _.escape(user.name);
  1053. values.push(_.extend(user, {
  1054. 'value': escaped_name,
  1055. 'label': escaped_name
  1056. }));
  1057. });
  1058. return values;
  1059. });
  1060. };
  1061. chat_manager.send_native_notification = function(){
  1062. return chat_manager.mail_tools.send_native_notification.apply(chat_manager.mail_tools, arguments);
  1063. };
  1064. chat_manager.bus.on('client_action_open', null, function (open) {
  1065. client_action_open = open;
  1066. });
  1067. // In order to extend init use chat_manager.is_ready Derrered object. See example in mail_arhive module
  1068. function init(){
  1069. chat_manager.mail_tools.add_channel({
  1070. id: "channel_inbox",
  1071. name: _lt("Inbox"),
  1072. type: "static"
  1073. }, { display_needactions: true });
  1074. chat_manager.mail_tools.add_channel({
  1075. id: "channel_starred",
  1076. name: _lt("Starred"),
  1077. type: "static"
  1078. });
  1079. var load_channels = session.rpc('/mail/client_action').then(function (result) {
  1080. _.each(result.channel_slots, function (channels) {
  1081. _.each(channels, chat_manager.mail_tools.add_channel);
  1082. });
  1083. needaction_counter = result.needaction_inbox_counter;
  1084. mention_partner_suggestions = result.mention_partner_suggestions;
  1085. });
  1086. var load_emojis = session.rpc("/mail/chat_init").then(function (result) {
  1087. emojis = result.emoji;
  1088. _.each(emojis, function(emoji) {
  1089. emoji_substitutions[_.escape(emoji.source)] = emoji.substitution;
  1090. });
  1091. });
  1092. var ir_model = new Model("ir.model.data");
  1093. var load_menu_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_menu_root_chat"], {}, {shadow: true});
  1094. var load_action_id = ir_model.call("xmlid_to_res_id", ["mail.mail_channel_action_client_chat"], {}, {shadow: true});
  1095. // unsubscribe and then subscribe to the event, to avoid duplication of new messages
  1096. bus.off('notification');
  1097. bus.on('notification', null, function(){
  1098. chat_manager.mail_tools.on_notification.apply(chat_manager.mail_tools, arguments);
  1099. });
  1100. return $.when(load_menu_id, load_action_id, load_channels, load_emojis).then(function (menu_id, action_id) {
  1101. discuss_ids = {
  1102. menu_id: menu_id,
  1103. action_id: action_id
  1104. };
  1105. bus.start_polling();
  1106. });
  1107. }
  1108. chat_manager.is_ready = init();
  1109. return {
  1110. chat_manager: chat_manager,
  1111. MailTools: MailTools
  1112. };
  1113. });