summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/models/messages/message.js
diff options
context:
space:
mode:
authorAlexandre Kühn <aku@odoo.com>2018-06-12 15:46:46 +0200
committerAlexandre Kühn <aku@odoo.com>2018-07-02 15:06:54 +0200
commitcd34f6de727d5b3858420cff3c9d3c5995c4e75c (patch)
tree8d25861f98a038b71728355375d634a5c178290e /addons/mail/static/src/js/models/messages/message.js
parentee9c1f52c316c83775969a3c9038e7e9f560f7e7 (diff)
[REF] mail: JS mail refactoring
---------------------- Summary ---------------------- The purpose of this commit is to improve the JS code of the `mail` module. It applies the new coding guidelines and makes some changes on the design of some modules, such as the old ChatManager module. Here is a short summary of the changes that have been made: 1. New coding guidelines - snake_case to camelCase - prefix private attributes and methods with '_' - jsdoc on most methods - one class per module 2. Rename/Merge some classes - 'chat manager' becomes 'mail manager' (internal) and 'mail service' (external) - 'chat window manager' is now included in mail manager - 'thread' widget now named 'thread widget' 3. New model abstraction for mail objects: - modules 'mail.model.*' - modeling: 0..1 0..1 * * ThreadWindow <------> Thread <-------> Message / \ / \ Thread With Cache Document Thread / | \ / | \ Mailbox Channel Support Channel | | DM - Thread: the superclass of threads. - ThreadWindow: the window component of a thread. - Message: mail objects representing messages. - Thread With Cache: threads that can be used with search view (Discuss app compatible). - Document Thread: represents the thread part of a chatter. - Mailbox: represents what was previously called 'static' channel, e.g. 'Inbox'. - Channel: mail objects representing channels, including livechat. - DM: special kind of Channel for 1:1 communication in the backend. - Support Channel: special channel for im_support module. This new modeling approach let us easily add features on all threads, such as the possibility to put any thread in a small window. ---------------------- Known issues ---------------------- [Already Present in Master] 1. When the Discuss app is in the background with 'Inbox' as the selected Thread, when clicking on a document thread preview in the messaging menu of the systray, the rainbow man appears. 2. When a document with the chatter is in the background, when receiving an inbox notification from this document thread, the document thread is automatically marked as read, which removes the notification right away. 3. Sometimes, opening a DM window from the "blank" thread window does not work. 4. Reply-to feature on Inbox is not working: no message is sent in the document thread. 5. On the first login of admin user with demo data, the inbox counter is wrong (it displays 6, instead of 3). Explanation after investigation: > On page load, it fetches the correct number of Inbox messages (3), but the server notifies of 3 needaction messages right away, so it wrongly assumes these are new needaction messages. > Not possible for web client to detect that these messages should not increment the Inbox counter while keeping same API. 6. Notifications for new document thread messages only work when the user sets 'handle with Odoo' for the Notification Management in the preferences. > due to notifications on the longpoll bus for document thread messages that come from needaction notifications. > requires server-side changes to send notification on the longpoll bus to mentionned user. [New] 7. When receiving a message on a unjoined channel, thread window flickers ('open' > 'close' > 'open') Explanation after investigation: > JS logic: a) On auto-join, ask server to join the channel and get channel infos. b) The info tells the channel is not detached, but JS code makes decision to detach it, and tells server the channel is now detached. c) From (a), server notifies on longpoll bus the state of channel, which is not detached. The web client thinks that the window state of the channel has been changed somewhere else, and the channel is now closed. d) From (b), server notifies on longpoll bus the state of channel, which is detached. The web client opens the thread window of this channel. > The flicker didn't occur before refactoring because the web client was only updating the model of channel when it receives the longpoll notification. > Server behaviour on 1st longpoll notification is necessary for cross-tab synchronization for channel window state. > New design implies that model and view should be synchronized, hence the issue now. > Solution: remove server-side thread window synchronization and replace with client-side synchronization. ---------------------- Hacks ---------------------- The module `im_livechat` now uses mail objects that are compatible with Message and Window objects: Modeling for messages: AbstractMessage / \ / \ LivechatMessage Message - AbstractMessage: message compatible with the thread widget. - LivechatMessage: message used by im_livechat. - Message: message used with the mail manager. Modeling for thread windows: AbstractThreadWindow / \ / \ LivechatWindow ThreadWindow - AbstractThreadWindow: behaviour share between all types of thread windows. - LivechatWindow: window used by im_livechat. - ThreadWindow: window used with mail_manager. The reason for these hacks are twofold: 1. Use the thread widget in the frontend and livechat external lib bundles. 2. Do not have a dependency with the mail manager in the frontend and external lib bundles.
Diffstat (limited to 'addons/mail/static/src/js/models/messages/message.js')
-rw-r--r--addons/mail/static/src/js/models/messages/message.js780
1 files changed, 780 insertions, 0 deletions
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
new file mode 100644
index 00000000000..06b776ea580
--- /dev/null
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -0,0 +1,780 @@
+odoo.define('mail.model.Message', function (require) {
+"use strict";
+
+var emojis = require('mail.emojis');
+var AbstractMessage = require('mail.model.AbstractMessage');
+var mailUtils = require('mail.utils');
+
+var core = require('web.core');
+var Mixins = require('web.mixins');
+var ServicesMixin = require('web.ServicesMixin');
+var session = require('web.session');
+
+var _t = core._t;
+
+/**
+ * This is the main class for the modeling messages in JS.
+ * Such messages are stored in the mail manager, and any piece of JS code whose
+ * logic relies on threads must ideally interact with such objects.
+ */
+var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
+ /**
+ * @override
+ * @param {web.Manager} parent
+ * @param {Object} data
+ * @param {string} [data.body = ""]
+ * @param {(string|integer)[]} [data.channel_ids]
+ * @param {Object[]} [data.customer_email_data]
+ * @param {string} [data.customer_email_status]
+ * @param {string} [data.email_from]
+ * @param {string} [data.info]
+ * @param {string} [data.model]
+ * @param {string} [data.moderation_status='accepted']
+ * @param {string} [data.module_icon]
+ * @param {Array} [data.needaction_partner_ids = []]
+ * @param {string} [data.record_name]
+ * @param {integer} [data.res_id]
+ * @param {Array} [data.starred_partner_ids = []]
+ * @param {string} [data.subject]
+ * @param {string} [data.subtype_description]
+ * @param {Object[]} [data.tracking_value_ids]
+ * @param {Object[]} emojis
+ */
+ init: function (parent, data, emojis) {
+ this._super.apply(this, arguments);
+
+ Mixins.EventDispatcherMixin.init.call(this, arguments);
+ this.setParent(parent);
+
+ this._customerEmailData = data.customer_email_data || [];
+ this._customerEmailStatus = data.customer_email_status;
+ this._documentModel = data.model;
+ this._documentName = data.record_name;
+ this._documentID = data.res_id;
+ this._emailFrom = data.email_from;
+ this._info = data.info;
+ this._moduleIcon = data.module_icon;
+ this._needactionPartnerIDs = data.needaction_partner_ids || [];
+ this._starredPartnerIDs = data.starred_partner_ids || [];
+ this._subject = data.subject;
+ this._subtypeDescription = data.subtype_description;
+ this._threadIDs = data.channel_ids || [];
+ this._trackingValueIDs = data.tracking_value_ids;
+
+ this._moderationStatus = data.moderation_status || 'accepted';
+
+ this._processBody(emojis);
+ this._processMailboxes();
+ this._processModeration();
+ this._processDocumentThread();
+ this._processTrackingValues();
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @param {Object} data
+ */
+ addCustomerEmailData: function (data) {
+ this._customerEmailData.push(data);
+ },
+ /**
+ * Get the name of the author of this message
+ * If there are no author, return "".
+ *
+ * @return {string}
+ */
+ getAuthorName: function () {
+ return this._getAuthorName();
+ },
+ /**
+ * Get the relative url of the avatar to display next to the message
+ *
+ * @override
+ * @return {string}
+ */
+ getAvatarSource: function () {
+ if (this._isOdoobotAuthor()) {
+ return '/mail/static/src/img/odoo_o.png';
+ } else if (this.hasAuthor()) {
+ return '/web/image/res.partner/' + this.getAuthorID() + '/image_small';
+ } else if (this.getType() === 'email') {
+ return '/mail/static/src/img/email_icon.png';
+ }
+ return '/mail/static/src/img/smiley/avatar.jpg';
+ },
+ /**
+ * Get the customer email data of this email, if any.
+ * If this message has no such data, returns 'undefined'
+ *
+ * @return {Object[]|undefined}
+ */
+ getCustomerEmailData: function () {
+ if (!this.hasCustomerEmailData()) {
+ return undefined;
+ }
+ return this._customerEmailData;
+ },
+ /**
+ * Get the customer email status of this email, if any.
+ * If this message has no such data, returns 'undefined'
+ *
+ * @return {string|undefined}
+ */
+ getCustomerEmailStatus: function () {
+ if (!this.hasCustomerEmailData()) {
+ return undefined;
+ }
+ return this._customerEmailStatus;
+ },
+ /**
+ * Get the text to display for the author of the message
+ *
+ * Rule of precedence for the displayed author:
+ *
+ * author name > sender email > "anonymous"
+ *
+ * @override
+ * @return {string}
+ */
+ getDisplayedAuthor: function () {
+ var res = this._super.apply(this, arguments);
+ if (res) {
+ return res;
+ } else {
+ return this.hasEmailFrom() ? this.getEmailFrom() :
+ _t("Anonymous");
+ }
+ },
+ /**
+ * Get the ID of the document that this message is linked.
+ *
+ * @override
+ * @return {integer}
+ */
+ getDocumentID: function () {
+ return this._documentID;
+ },
+ /**
+ * Get the model of the document that this message is linked.
+ * If this message is not linked to a document, returns "".
+ *
+ * @override
+ * @return {string}
+ */
+ getDocumentModel: function () {
+ return this._documentModel;
+ },
+ /**
+ * Get the name of the document that this message is linked.
+ * If this message is not linked to a document, returns "".
+ *
+ * It uses the name of an existing document thread, in order to have the
+ * updated document name. If there is no document thread yet, use the
+ * currently aware document name.
+ *
+ * As a result, this method should ideally be called internally, in order
+ * to avoid having different names for messages linked to a same modified
+ * document.
+ *
+ * @override
+ * @return {string}
+ */
+ getDocumentName: function () {
+ if (!this.isLinkedToDocumentThread()) {
+ return "";
+ }
+ var model = this.getDocumentModel();
+ var id = this.getDocumentID();
+ var documentThread = this.call('mail_service', 'getDocumentThread', model, id);
+ if (documentThread) {
+ this._documentName = documentThread.getName();
+ }
+ return this._documentName;
+ },
+ /**
+ * Get the email of the sender of this message.
+ * If this email has no sender email, returns "".
+ *
+ * @return {string}
+ */
+ getEmailFrom: function () {
+ if (!this.hasEmailFrom()) {
+ return "";
+ }
+ return this._emailFrom;
+ },
+ /**
+ * Get the ID of the channel that this message originates from.
+ * If this message does not originate from a channel, returns `-1`.
+ *
+ * @override
+ * @return {integer}
+ */
+ getOriginChannelID: function () {
+ if (!this.originatesFromChannel()) {
+ return -1;
+ }
+ return this._documentID;
+ },
+ /**
+ * Get the name of the channel that this message originates from.
+ * If this message does not originate from a channel, returns "".
+ *
+ * @override
+ * @returns {string}
+ */
+ getOriginChannelName: function () {
+ if (!this.originatesFromChannel()) {
+ return "";
+ }
+ var originChannelID = this.getOriginChannelID();
+ var channel = originChannelID && this.call(
+ 'mail_service',
+ 'getChannel',
+ originChannelID);
+ if (!channel) {
+ return "";
+ }
+ return channel.getName();
+ },
+ /**
+ * Returns the information required to render the preview of this channel.
+ *
+ * @return {Object}
+ */
+ getPreview: function () {
+ var id, title;
+ if (this.isLinkedToDocumentThread()) {
+ id = this.getDocumentModel() + '_' + this.getDocumentID();
+ title = this.getDocumentName();
+ } else {
+ id = 'mailbox_inbox';
+ title = this.hasSubject() ? this.getSubject() : this.getDisplayedAuthor();
+ }
+ return {
+ author: this.getDisplayedAuthor(),
+ body: mailUtils.parseAndTransform(this.getBody(), mailUtils.inline),
+ date: this.getDate(),
+ documentModel: this.getDocumentModel(),
+ documentID: this.getDocumentID(),
+ id: id,
+ imageSRC: this._getModuleIcon() || this.getAvatarSource(),
+ status: this.status,
+ title: title,
+ };
+ },
+ /**
+ * Get the subject of this message
+ * If this message has no subject, returns "".
+ *
+ * @override
+ * @return {string}
+ */
+ getSubject: function () {
+ if (!this.hasSubject()) {
+ return "";
+ }
+ return this._subject;
+ },
+ /**
+ * @override
+ * @return {string}
+ */
+ getSubtypeDescription: function () {
+ return this._subtypeDescription;
+ },
+ /**
+ * Get the list of thread IDs that this message is linked to
+ * If this message is not linked to a thread, returns 'undefined'
+ *
+ * @return {string[]|undefined} list of thread IDs, if any
+ */
+ getThreadIDs: function () {
+ return this._threadIDs;
+ },
+ /**
+ * Get the tracking values of this message
+ * If this message has no tracking values, returns 'undefined'
+ *
+ * @override
+ * @return {Object[]|undefined}
+ */
+ getTrackingValues: function () {
+ if (!this.hasTrackingValues()) {
+ return undefined;
+ }
+ return this._trackingValueIDs;
+ },
+ /**
+ * @override
+ * @return {string}
+ */
+ getURL: function () {
+ return session.url('/mail/view?message_id=' + this._id);
+ },
+ /**
+ * State whether this message contains some customer email data
+ *
+ * @override
+ * @return {boolean}
+ */
+ hasCustomerEmailData: function () {
+ return !!(this._customerEmailData && (this._customerEmailData.length > 0));
+ },
+ /**
+ * State whether this message has an email of its sender.
+ *
+ * @override
+ * @return {string}
+ */
+ hasEmailFrom: function () {
+ return !!(this._emailFrom);
+ },
+ /**
+ * State whether this message has a subject
+ *
+ * @return {boolean}
+ */
+ hasSubject: function () {
+ return !!(this._subject);
+ },
+ /**
+ * @override
+ * @return {boolean}
+ */
+ hasSubtypeDescription: function () {
+ return !!(this._subtypeDescription);
+ },
+ /**
+ * State whether this message contains some tracking values
+ *
+ * @override
+ * @return {boolean}
+ */
+ hasTrackingValues: function () {
+ return !!(this._trackingValueIDs && (this._trackingValueIDs.length > 0));
+ },
+ /**
+ * State whether the current user is the author of this message
+ *
+ * @return {boolean}
+ */
+ isAuthor: function () {
+ return this._isAuthor();
+ },
+ /**
+ * State whether this message is linked to a document thread (not channel)
+ *
+ * Usually, if this is true, then this message comes from a document thread,
+ * but the document model could be a channel. In that case, the document
+ * resID tells the channel that this message originally comes from.
+ *
+ * To detect whether the message comes from a channel, see the method
+ * 'originatesFromChannel'.
+ *
+ * @override
+ * @return {boolean}
+ */
+ isLinkedToDocumentThread: function () {
+ return !!(this._documentModel !== 'mail.channel' && this._documentID);
+ },
+ /**
+ * States whether the current message needs moderation in general.
+ *
+ * @override
+ * @returns {boolean}
+ */
+ needsModeration: function () {
+ return this._moderationStatus === 'pending_moderation';
+ },
+ /**
+ * States whether the current message needs moderation by the current user.
+ * Such a message should be in the moderation mailbox.
+ *
+ * @returns {boolean}
+ */
+ needsModerationByUser: function () {
+ return _.contains(this._threadIDs, 'mailbox_moderation');
+ },
+ /**
+ * State whether this message is needaction
+ *
+ * @override
+ * @returns {boolean}
+ */
+ isNeedaction: function () {
+ return _.contains(this._threadIDs, 'mailbox_inbox');
+ },
+ /**
+ * State whether this message is starred
+ *
+ * @override
+ * @returns {boolean}
+ */
+ isStarred: function () {
+ return _.contains(this._threadIDs, 'mailbox_starred');
+ },
+ /**
+ * State whether this message is a system notification
+ *
+ * @override
+ * @returns {boolean}
+ */
+ isSystemNotification: function () {
+ return (
+ this.getType() === 'notification' &&
+ this._documentModel === 'mail.channel'
+ ) || this._isTransient();
+ },
+ /**
+ * States whether the message originates from a channel or not
+ *
+ * @returns {boolean}
+ */
+ originatesFromChannel: function () {
+ return this._documentModel === 'mail.channel';
+ },
+ /**
+ * Unregister thread with ID `threadID` from this message
+ *
+ * @param {string|integer} threadID ID of thread
+ */
+ removeThread: function (threadID) {
+ this._threadIDs = _.without(this._threadIDs, threadID);
+ },
+ /**
+ * Update the moderation status of the message, so that it is now accepted
+ * or rejected. When the message is accepted, it may be linked to more
+ * threads, which is the case for relay channels on moderated channels.
+ *
+ * @param {string} newModerationStatus ['accepted', 'rejected']
+ * @param {Object} [options]
+ * @param {Object} [options.additionalThreadIDs] contains additional thread
+ * IDs to be registered on the message.
+ */
+ setModerationStatus: function (newModerationStatus, options) {
+ var self = this;
+ this._moderationStatus = newModerationStatus;
+ if (newModerationStatus === 'accepted' && options) {
+ _.each(options.additionalThreadIDs, function (threadID) {
+ self._addThread(threadID);
+ });
+ }
+ this._warnMessageModerated();
+ },
+ /**
+ * Set whether the message is starred or not.
+ * If it is starred, the message is moved to the "Starred" mailbox.
+ * Note that this function only applies it locally, the server is not aware
+ *
+ * @param {boolean} starred if set, the message is starred
+ */
+ setStarred: function (starred) {
+ if (starred) {
+ this._addThread('mailbox_starred');
+ } else {
+ this.removeThread('mailbox_starred');
+ }
+ },
+ /**
+ * State whether this message should display the subject
+ *
+ * @return {boolean}
+ */
+ shouldDisplaySubject: function () {
+ return this.hasSubject() &&
+ this.getType() !== 'notification' &&
+ !this.originatesFromChannel();
+ },
+ /**
+ * State whether this message should redirect to the author
+ * when clicking on the author of this message.
+ *
+ * Do not redirect on author clicked of self-posted or Odoobot messages
+ * (note: Odoobot is the default author of transient messages)
+ *
+ * @override
+ * @return {boolean}
+ */
+ shouldRedirectToAuthor: function () {
+ return this._super.apply(this, arguments) && !this._isOdoobotAuthor();
+ },
+ /**
+ * Toggle the star status of the message
+ *
+ * It relies on the star status of the message from the date of the server.
+ * The star status is updated from a 'toggle_star' notification on the
+ * longpoll bus
+ *
+ * @see {mail.Manager.Notification} for the receipt of 'toggle_star'
+ * notification after this rpc.
+ *
+ * @return {$.Promise}
+ */
+ toggleStarStatus: function () {
+ return this._rpc({
+ model: 'mail.message',
+ method: 'toggle_message_starred',
+ args: [[this._id]],
+ });
+ },
+ /**
+ * Update the customer email status
+ *
+ * @param {string} newCustomerEmailStatus
+ */
+ updateCustomerEmailStatus: function (newCustomerEmailStatus) {
+ this._customerEmailStatus = newCustomerEmailStatus;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Register a thread to the message
+ * Useful when you mark a message as 'to do'.
+ * This message will be available in 'Starred' mailbox.
+ *
+ * @private
+ * @param {string|integer} threadID
+ */
+ _addThread: function (threadID) {
+ if (!this._threadIDs) {
+ this._threadIDs = [];
+ }
+ this._threadIDs.push(threadID);
+ this._threadIDs = _.uniq(this._threadIDs);
+ },
+ /**
+ * Get the name of the author of this message.
+ * If Odoobot is the author (default author for transient messages),
+ * returns 'Odoobot'.
+ *
+ * @override
+ * @private
+ * @returns {string}
+ */
+ _getAuthorName: function () {
+ if (this._isOdoobotAuthor()) {
+ return "Odoobot";
+ }
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @private
+ * @return {string}
+ */
+ _getModuleIcon: function () {
+ return this._moduleIcon;
+ },
+ /**
+ * State if the author of this message is Odoobot
+ * This is the default author for transient messages.
+ *
+ * @private
+ * @return {boolean}
+ */
+ _isOdoobotAuthor: function () {
+ return this._serverAuthorID === this.call('mail_service', 'getOdoobotID');
+ },
+ /**
+ * State whether the message is transient or not
+ *
+ * @private
+ * @return {boolean}
+ */
+ _isTransient: function () {
+ return this._info === 'transient_message';
+ },
+ /**
+ * Convert the server-format body of the message to the client-format.
+ * Basically, it processes emojis and url.
+ *
+ * @private
+ */
+ _processBody: function () {
+ var self = this;
+
+ _.each(emojis, function (emoji) {
+ var unicode = emoji.unicode;
+ var regexp = new RegExp("(?:^|\\s|<[a-z]*>)(" + unicode + ")(?=\\s|$|</[a-z]*>)", 'g');
+ var originalBody = self.body;
+ self._body = self._body.replace(regexp,
+ ' <span class="o_mail_emoji">' + unicode + '</span> ');
+ // Idiot-proof limit. If the user had the amazing idea of
+ // copy-pasting thousands of emojis, the image rendering can lead
+ // to memory overflow errors on some browsers (e.g. Chrome). Set an
+ // arbitrary limit to 200 from which we simply don't replace them
+ // (anyway, they are already replaced by the unicode counterpart).
+ if (_.str.count(self._body, 'o_mail_emoji') > 200) {
+ self._body = originalBody;
+ }
+ });
+
+ // add anchor tags to urls
+ self._body = mailUtils.parseAndTransform(self._body, mailUtils.addLink);
+ },
+ /**
+ * If the message is linked to a document, link this message to the related
+ * document thread. If there is no document thread yet, create it
+ */
+ _processDocumentThread: function () {
+ if (!this.isLinkedToDocumentThread()) {
+ return;
+ }
+ var resModel = this.getDocumentModel();
+ var resID = this.getDocumentID();
+ var documentThread = this.call('mail_service', 'getOrAddDocumentThread', {
+ resModel: resModel,
+ resID: resID,
+ name: this.getDocumentName(),
+ });
+ this._threadIDs.push(documentThread.getID());
+ },
+ /**
+ * Set the appropriate mailboxes to this message based on server data
+ *
+ * @private
+ */
+ _processMailboxes: function () {
+ if (_.contains(this._needactionPartnerIDs, session.partner_id)) {
+ this._setNeedaction(true);
+ }
+ if (_.contains(this._starredPartnerIDs, session.partner_id)) {
+ this.setStarred(true);
+ }
+ if (
+ this.originatesFromChannel() &&
+ _.contains(
+ this.call('mail_service', 'getModeratedChannelIDs'),
+ this.getOriginChannelID()
+ ) &&
+ this.needsModeration()
+ ) {
+ this._setModeratedByUser(true);
+ }
+ },
+ /**
+ * Do some extra processing at message init, related to the
+ * moderated status of the message.
+ *
+ * If the message needs moderation, it is not yet linked to the
+ * moderated channel server-side. Therefore, it is not registered
+ * in the list of threadIDs, as it is built on server-side information
+ * at message initialisation (using data.channel_ids).
+ *
+ * Since the web client uses the list of thread IDs to show visually the
+ * message in a thread, we should hack the response of the server so that
+ * it assumes the message really belongs to this thread.
+ *
+ * @private
+ */
+ _processModeration: function () {
+ if (this.needsModeration()) {
+ // the message is not linked to the moderated channel on the
+ // server, therefore this message has not this channel in
+ // channel_ids. Here, just to show this message in the channel
+ // visually, it links this message to the channel
+ this._threadIDs.push(this.getOriginChannelID());
+ }
+ },
+ /**
+ * Process the tracking values on message creation, which
+ * basically format date to the local only once by message
+ *
+ * Cannot be done in preprocess, since it alter the original value
+ *
+ * @private
+ */
+ _processTrackingValues: function () {
+ if (this.hasTrackingValues()) {
+ _.each(this.getTrackingValues(), function (trackingValue) {
+ if (trackingValue.field_type === 'datetime') {
+ if (trackingValue.old_value) {
+ trackingValue.old_value =
+ moment
+ .utc(trackingValue.old_value)
+ .local()
+ .format('LLL');
+ }
+ if (trackingValue.new_value) {
+ trackingValue.new_value =
+ moment
+ .utc(trackingValue.new_value)
+ .local()
+ .format('LLL');
+ }
+ } else if (trackingValue.field_type === 'date') {
+ if (trackingValue.old_value) {
+ trackingValue.old_value =
+ moment(trackingValue.old_value)
+ .local()
+ .format('LL');
+ }
+ if (trackingValue.new_value) {
+ trackingValue.new_value =
+ moment(trackingValue.new_value)
+ .local()
+ .format('LL');
+ }
+ }
+ });
+ }
+ },
+ /**
+ * Set whether the message is moderated by current user or not.
+ * If it is moderated by the current user, the message is moved to the
+ * "Moderation" mailbox. Note that this function only applies it locally,
+ * the server is not aware
+ *
+ * @private
+ * @param {boolean} moderated if set, the message is moderated by user
+ */
+ _setModeratedByUser: function (moderated) {
+ if (moderated) {
+ this._addThread('mailbox_moderation');
+ } else {
+ this.removeThread('mailbox_moderation');
+ }
+ },
+ /**
+ * Set whether the message is needaction or not.
+ * If it is needaction, the message is moved to the "Inbox" mailbox.
+ * Note that this function only applies it locally, the server is not aware
+ *
+ * @private
+ * @param {boolean} needaction if set, the message is needaction
+ */
+ _setNeedaction: function (needaction) {
+ if (needaction) {
+ this._addThread('mailbox_inbox');
+ } else {
+ this.removeThread('mailbox_inbox');
+ }
+ },
+ /**
+ * @private
+ */
+ _warnMessageModerated: function () {
+ var mailBus = this.call('mail_service', 'getMailBus');
+ if (this.needsModerationByUser()) {
+ var moderationBox = this.call('mail_service', 'getMailbox', 'moderation');
+ moderationBox.decrementMailboxCounter();
+ moderationBox.removeMessage(this.getID());
+ mailBus.trigger('update_moderation_counter');
+ }
+ if (this._moderationStatus !== 'accepted') {
+ this.call('mail_service', 'removeMessageFromThreads', this);
+ }
+ mailBus.trigger('update_message', this);
+ },
+
+});
+
+return Message;
+
+});