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.

1431 lines
52 KiB

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