summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/models
diff options
context:
space:
mode:
authorAlexandre Kühn <aku@odoo.com>2018-08-11 20:30:51 +0200
committerAlexandre Kühn <aku@odoo.com>2018-08-13 14:17:27 +0200
commitf2302dfb27dd7f6a0dee7c65ab75b62a01706264 (patch)
treed58fef9e2e8a5b98c0680f28fc92e24992236029 /addons/mail/static/src/js/models
parent3de97f168956a7c22c3af6b72950cf28554f4139 (diff)
[REF][FIX] mail, im_livechat: various improvements (JS)
This commit makes a lot of small improvements of the JS of mail and related modules. A. Mail Manager --------------- im_support: - [REF] Slightly more robust way to determine a support message, so that it more reliably falls back on default kind of message (i.e. support channel UUID should be clearly set). mail: - [REF] Improved method names for readability purposes. Some examples: - `fetchFromServer` becomes `fetchMailStateFromServer` - `_initialize{*}` becomes `_update{*}FromServer` - `_isModerator` becomes `_isMyselfModerator` - [REF] Use 'template' pattern design to make Include on Mail Manager much more robust. In particular, the flow is always as follow: 1. Initialize internal state. 2. Listen on buses. 3. Fetch mail data from server. - [REF] Improved workflow for thread window workflow (Shorter & more readable). B. Mail Tests ------------- - [REF] Thread Window Tests crashes with clean message when there are some thread windows open before running the tests. C. (Model) Message ------------------ - [REF] Improved method names for readability purposes (e.g. `isAuthor` becomes `isMyselfAuthor`). D. (Model) Thread ----------------- - [REF] Improved `init` parameters on each kind of thread. - [REF] 'Thread With Cache' has been renamed to 'Searchable Thread': it embraces all kinds of threads that can be used with the search view. - [REF] 'DM' has been renamed to 'DM Chat' (for clarity). - [IMP] New Livechat model in the backend. - [REF] No more direct instances of the Channel class: public and private channels are now 'Multi-User' channels, while DM Chat and Livechat are 'Two-User' channels: This change has been made so that instances of classes always have direct classes as "leaf" in the Class modeling. It eases making changes to a certain model without being too constrained by another model. - [REF] As a consequence of above changed, the method `isChat` has been renamed to `isTwoUserThread`. This is to avoid confusion with the terminology 'chat', which is more like a shortcut for 'quick real- time conversation'. In other words, the property of 'chat' depends of the way to interact on a thread, not of the intrinsic type of the thread. Still, some parts have kept the terminology 'chat', such as in the systray messagin menu. - [IMP] Added `message_added` and `message_posted` hooks, which may be useful to easily hook on those events. E. Thread Widget ---------------- - [REF] Now uses a `mail.model.AbstractThread` for the rendering of a thread, instead of a list of `{mail.model.AbstractMessage}`. - [FIX] Stick thread scroll height to bottom on new rendering, if it was previously at the bottom of the thread. - [REF] Option ORDER has its meaning changed: it now refers to the chronological order of messages in the thread, in which DESC means 'from top to bottom', while ASC means 'from bottom to top'. - [REF] Improved doc on templates, in particular with the option `ORDER`. F. Thread Window ---------------- - [REF] Moved more logic to thread windows, which were previously in Mail Window Manager. - [REF] Now hides the thread widget attribute. So instead of `threadWindow.threadWidget.isAtBottom()`, it uses `threadWindow.isAtBottom()`. - [FIX] New received messages when not having focus on Odoo tab should always make the corresponding thread windows "passive" (i.e. windows that keep threads unread until click on them). - [FIX] Blank thread window (i.e. 'New message' TW) correctly opens Discuss with 'Inbox' when clicking on 'Expand' Button in the title - [FIX] Do not display the composer for channels with property 'mass-mailing' set in thread windows. G. (Website) Livechat --------------------- - [REF] Some `trigger` have been turned into `trigger_up`. - [REF] Some logic has been moved to models (e.g. `{im_livechat.model.WebsiteLivechat}`), from `{im_livechat.im_livechat.LivechatButton}`. - [FIX] New messages should be received in real-time. The longpolling was broken with a recent refactoring on bus service (6448420c5dd160470e465dee7729d19d8d5e7bab).
Diffstat (limited to 'addons/mail/static/src/js/models')
-rw-r--r--addons/mail/static/src/js/models/messages/abstract_message.js4
-rw-r--r--addons/mail/static/src/js/models/messages/message.js16
-rw-r--r--addons/mail/static/src/js/models/threads/abstract_thread.js88
-rw-r--r--addons/mail/static/src/js/models/threads/channel.js162
-rw-r--r--addons/mail/static/src/js/models/threads/dm_chat.js (renamed from addons/mail/static/src/js/models/threads/dm.js)41
-rw-r--r--addons/mail/static/src/js/models/threads/document_thread.js156
-rw-r--r--addons/mail/static/src/js/models/threads/livechat.js57
-rw-r--r--addons/mail/static/src/js/models/threads/mailbox.js4
-rw-r--r--addons/mail/static/src/js/models/threads/multi_user_channel.js56
-rw-r--r--addons/mail/static/src/js/models/threads/searchable_thread.js (renamed from addons/mail/static/src/js/models/threads/thread_with_cache.js)41
-rw-r--r--addons/mail/static/src/js/models/threads/thread.js155
-rw-r--r--addons/mail/static/src/js/models/threads/two_user_channel.js32
12 files changed, 474 insertions, 338 deletions
diff --git a/addons/mail/static/src/js/models/messages/abstract_message.js b/addons/mail/static/src/js/models/messages/abstract_message.js
index 420ac2da41b..f5cf55f0ef1 100644
--- a/addons/mail/static/src/js/models/messages/abstract_message.js
+++ b/addons/mail/static/src/js/models/messages/abstract_message.js
@@ -344,7 +344,7 @@ var AbstractMessage = Class.extend({
* @return {boolean}
*/
shouldRedirectToAuthor: function () {
- return !this._isAuthor();
+ return !this._isMyselfAuthor();
},
//--------------------------------------------------------------------------
@@ -370,7 +370,7 @@ var AbstractMessage = Class.extend({
* @private
* @return {boolean}
*/
- _isAuthor: function () {
+ _isMyselfAuthor: function () {
return this.hasAuthor() && (this.getAuthorID() === session.partner_id);
},
/**
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
index 0c6480f3264..881a5ae2c97 100644
--- a/addons/mail/static/src/js/models/messages/message.js
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -358,14 +358,6 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
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,
@@ -382,6 +374,14 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
return !!(this._documentModel !== 'mail.channel' && this._documentID);
},
/**
+ * State whether the current user is the author of this message
+ *
+ * @return {boolean}
+ */
+ isMyselfAuthor: function () {
+ return this._isMyselfAuthor();
+ },
+ /**
* States whether the current message needs moderation in general.
*
* @override
diff --git a/addons/mail/static/src/js/models/threads/abstract_thread.js b/addons/mail/static/src/js/models/threads/abstract_thread.js
index 67929517ed4..fb6695c6a6c 100644
--- a/addons/mail/static/src/js/models/threads/abstract_thread.js
+++ b/addons/mail/static/src/js/models/threads/abstract_thread.js
@@ -2,6 +2,7 @@ odoo.define('mail.model.AbstractThread', function (require) {
"use strict";
var Class = require('web.Class');
+var Mixins = require('web.mixins');
/**
* Abstract thread is the super class of all threads, either backend threads
@@ -9,15 +10,20 @@ var Class = require('web.Class');
*
* Abstract threads contain abstract messages
*/
-var AbstractThread = Class.extend({
+var AbstractThread = Class.extend(Mixins.EventDispatcherMixin, {
/**
* @param {Object} params
* @param {Object} params.data
* @param {integer|string} params.data.id the ID of this thread
* @param {string} params.data.name the name of this thread
* @param {string} params.data.status the status of this thread
+ * @param {Object} params.parent Object with the event-dispatcher mixin
+ * (@see {web.mixins.EventDispatcherMixin})
*/
init: function (params) {
+ Mixins.EventDispatcherMixin.init.call(this, arguments);
+ this.setParent(params.parent);
+
this._folded = false; // threads are unfolded by default
this._id = params.data.id;
this._name = params.data.name;
@@ -30,6 +36,15 @@ var AbstractThread = Class.extend({
//--------------------------------------------------------------------------
/**
+ * Add a message to this thread.
+ *
+ * @param {mail.model.AbstractMessage} message
+ */
+ addMessage: function (message) {
+ this._addMessage.apply(this, arguments);
+ this.trigger('message_added', message);
+ },
+ /**
* Updates the folded state of the thread
*
* @param {boolean} folded
@@ -88,12 +103,6 @@ var AbstractThread = Class.extend({
return !_.isEmpty(this.getMessages());
},
/**
- * Increments the unread counter of this thread by 1 unit.
- */
- incrementUnreadCounter: function () {
- this._unreadCounter++;
- },
- /**
* States whether this thread is folded or not.
*
* @return {boolean}
@@ -102,11 +111,76 @@ var AbstractThread = Class.extend({
return this._folded;
},
/**
+ * Mark the thread as read, which resets the unread counter to 0. This is
+ * only performed if the unread counter is not 0.
+ *
+ * @returns {$.Promise}
+ */
+ markAsRead: function () {
+ if (this._unreadCounter > 0) {
+ return this._markAsRead();
+ }
+ return $.when();
+ },
+ /**
+ * Post a message on this thread
+ *
+ * @returns {$.Promise} resolved with the message object to be sent to the
+ * server
+ */
+ postMessage: function () {
+ return this._postMessage.apply(this, arguments)
+ .then(this.trigger.bind(this, 'message_posted'));
+ },
+ /**
* Resets the unread counter of this thread to 0.
*/
resetUnreadCounter: function () {
this._unreadCounter = 0;
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Add a message to this thread.
+ *
+ * @abstract
+ * @private
+ * @param {mail.model.AbstractMessage} message
+ */
+ _addMessage: function (message) {},
+ /**
+ * Increments the unread counter of this thread by 1 unit.
+ *
+ * @private
+ */
+ _incrementUnreadCounter: function () {
+ this._unreadCounter++;
+ },
+ /**
+ * Mark the thread as read
+ *
+ * @private
+ * @returns {$.Promise}
+ */
+ _markAsRead: function () {
+ this.resetUnreadCounter();
+ this._warnUpdatedUnreadCounter();
+ return $.when();
+ },
+ /**
+ * Post a message on this thread
+ *
+ * @abstract
+ * @private
+ * @returns {$.Promise} resolved with the message object to be sent to the
+ * server
+ */
+ _postMessage: function () {
+ return $.when();
+ },
});
return AbstractThread;
diff --git a/addons/mail/static/src/js/models/threads/channel.js b/addons/mail/static/src/js/models/threads/channel.js
index a33319c7f1f..3f932ccb8ec 100644
--- a/addons/mail/static/src/js/models/threads/channel.js
+++ b/addons/mail/static/src/js/models/threads/channel.js
@@ -1,7 +1,7 @@
odoo.define('mail.model.Channel', function (require) {
"use strict";
-var ThreadWithCache = require('mail.model.ThreadWithCache');
+var SearchableThread = require('mail.model.SearchableThread');
var mailUtils = require('mail.utils');
var session = require('web.session');
@@ -15,12 +15,11 @@ var time = require('web.time');
* Any piece of code in JS that make use of channels must ideally interact with
* such objects, instead of direct data from the server.
*/
-var Channel = ThreadWithCache.extend({
+var Channel = SearchableThread.extend({
/**
* @override
* @param {Object} params
* @param {Object} params.data
- * @param {string} [params.data.anonymous_name]
* @param {string} params.data.channel_type
* @param {integer} [params.data.create_uid] the ID of the user that has
* created the channel.
@@ -29,9 +28,7 @@ var Channel = ThreadWithCache.extend({
* @param {boolean} params.data.is_moderator whether the current user is
* moderator of this channel.
* @param {string} [params.data.last_message_date] date in server-format
- * @param {boolean} [params.data.mass_mailing]
* @param {integer} [params.data.message_unread_counter]
- * @param {string} [params.data.public] either 'public' or 'private'
* @param {string} params.data.state
* @param {string} [params.data.uuid]
* @param {Object} params.options
@@ -48,9 +45,8 @@ var Channel = ThreadWithCache.extend({
// If set, autoswitch channel on joining this channel in discuss
// the default behaviour is to autoswitch on join.
- // exception: receiving channel or chat session notifications
+ // exception: receiving channel session notifications
this._autoswitch = 'autoswitch' in options ? options.autoswitch : true;
- this._chat = undefined; // FIXME: could be dropped when livechat and DM are moved out of this class
this._commands = undefined;
this._creatorUID = data.create_uid;
this._detached = data.is_minimized;
@@ -58,12 +54,12 @@ var Channel = ThreadWithCache.extend({
this._folded = data.state === 'folded';
// if set: hide 'Leave channel' button
this._groupBasedSubscription = data.group_based_subscription;
- this._isModerator = data.is_moderator;
+ this._isModerated = data.is_moderation;
+ this._isMyselfModerator = data.is_moderator;
this._lastMessageDate = undefined;
- this._massMailing = data.mass_mailing;
+ this._members = [];
// Deferred that is resolved on fetched members of this channel.
this._membersDef = undefined;
- this._moderation = data.is_moderation;
// number of messages that are 'needaction', which is equivalent to the
// number of messages in this channel that are in inbox.
this._needactionCounter = data.message_needaction_counter || 0;
@@ -78,16 +74,9 @@ var Channel = ThreadWithCache.extend({
return !command.channel_types ||
_.contains(command.channel_types, self._serverType);
});
- if (this._type === 'channel') {
- this._type = data.public !== 'private' ? 'public' : 'private';
- }
- if ('anonymous_name' in data) {
- this._name = data.anonymous_name;
- }
if (data.last_message_date) {
this._lastMessageDate = moment(time.str_to_datetime(data.last_message_date));
}
- this._chat = !this.getType().match(/^(public|private)$/);
if (data.message_unread_counter) {
this._unreadCounter = data.message_unread_counter;
}
@@ -190,6 +179,7 @@ var Channel = ThreadWithCache.extend({
* @returns {$.Promise<Object[]>} resolved with list of channel listeners
*/
getMentionPartnerSuggestions: function () {
+ var self = this;
if (!this._membersDef) {
this._membersDef = this._rpc({
model: 'mail.channel',
@@ -199,6 +189,7 @@ var Channel = ThreadWithCache.extend({
shadow: true
})
.then(function (members) {
+ self._members = members;
return members;
});
}
@@ -215,7 +206,7 @@ var Channel = ThreadWithCache.extend({
*/
getPreview: function () {
var result = this._super.apply(this, arguments);
- if (!this.isChat()) {
+ if (!this.isTwoUserThread()) {
result.imageSRC = '/web/image/mail.channel/' + this.getID() + '/image_small';
}
var lastMessage = this.getLastMessage();
@@ -223,7 +214,7 @@ var Channel = ThreadWithCache.extend({
author: lastMessage ? lastMessage.getDisplayedAuthor() : '',
body: lastMessage ? mailUtils.parseAndTransform(lastMessage.getBody(), mailUtils.inline) : '',
date: lastMessage ? lastMessage.getDate() : moment(),
- isAuthor: this.hasMessages() && this.getLastMessage().isAuthor(),
+ isMyselfAuthor: this.hasMessages() && this.getLastMessage().isMyselfAuthor(),
});
},
/**
@@ -252,15 +243,6 @@ var Channel = ThreadWithCache.extend({
this._needactionCounter++;
},
/**
- * Tells whether the current user is administrator of the channel.
- * Note that there is no administrator for chat channels
- *
- * @returns {boolean}
- */
- isAdministrator: function () {
- return session.uid === this._creatorUID && !this.isChat();
- },
- /**
* States whether the channel should be auto-selected on creation
*
* Note that this is not of the responsibility of the channel
@@ -282,18 +264,6 @@ var Channel = ThreadWithCache.extend({
return true;
},
/**
- * States whether this channel is a chat or not.
- * These types of channels are chat:
- * - direct messages (DM)
- * - livechat
- *
- * @override
- * @returns {boolean}
- */
- isChat: function () {
- return this._chat;
- },
- /**
* States whether the channel auto-subscribes some users in a group
*
* @returns {boolean}
@@ -308,81 +278,32 @@ var Channel = ThreadWithCache.extend({
* @returns {boolean}
*/
isModerated: function () {
- return this._moderation;
+ return this._isModerated;
},
/**
- * States whether the current user is moderator of this channel.
+ * Tells whether the current user is administrator of the channel.
+ * Note that there is no administrator for two-user channels
*
* @returns {boolean}
*/
- isModerator: function () {
- return this._isModerator;
- },
- /**
- * Marks this channel as read.
- * The last seen message will be the last message.
- * Resolved with the last seen message, only for non-mailbox channels
- *
- * @override
- * @returns {$.Promise<integer|undefined>} resolved with last message ID
- * seen in the channel, and when the channel has been marked as seen on
- * the server.
- */
- markAsRead: function () {
- if (this._unreadCounter > 0) {
- this.resetUnreadCounter();
- return this._throttleFetchSeen();
- }
- return this._super.apply(this, arguments);
+ isMyselfAdministrator: function () {
+ return session.uid === this._creatorUID && !this.isTwoUserThread();
},
/**
- * Prepare and send a message to the server on this channel
+ * States whether the current user is moderator of this channel.
*
- * @override
- * @param {Object} data data related to the new message
- * @returns {$.Promise<Object>} resolved when the message has been sent to
- * the server, with the object message that has been sent to the server.
+ * @returns {boolean}
*/
- postMessage: function (data) {
- var self = this;
- return this._super.apply(this, arguments).then(function (messageData) {
- _.extend(messageData, {
- message_type: 'comment',
- subtype: 'mail.mt_comment',
- command: data.command,
- });
- return self._rpc({
- model: 'mail.channel',
- method: data.command ? 'execute_command' : 'message_post',
- args: [self._id],
- kwargs: messageData,
- }).then(function () {
- return messageData;
- });
- });
+ isMyselfModerator: function () {
+ return this._isMyselfModerator;
},
/**
* Unsubscribes from channel
*
+ * @abstract
* @returns {$.Promise} resolve when unsubscribed
*/
- unsubscribe: function () {
- if (_.contains(['public', 'private'], this.getType())) {
- // unfollow channel
- return this._rpc({
- model: 'mail.channel',
- method: 'action_unfollow',
- args: [[this._id]],
- });
- } else {
- // unpin livechat
- return this._rpc({
- model: 'mail.channel',
- method: 'channel_pin',
- args: [this.getUUID(), false],
- });
- }
- },
+ unsubscribe: function () {},
/**
* Updates the internal state of the channel, and reflects the changes in
* the UI.
@@ -453,6 +374,47 @@ var Channel = ThreadWithCache.extend({
['need_moderation', '=', true],
['channel_ids', 'in', [this._id]]];
},
+ /**
+ * Marks this channel as read.
+ * The last seen message will be the last message.
+ * Resolved with the last seen message, only for non-mailbox channels
+ *
+ * @override
+ * @private
+ * @returns {$.Promise} resolved when message has been marked as read
+ */
+ _markAsRead: function () {
+ var superDef = this._super.apply(this, arguments);
+ var seenDef = this._throttleFetchSeen();
+ return $.when(superDef, seenDef);
+ },
+ /**
+ * Prepare and send a message to the server on this channel.
+ *
+ * @override
+ * @private
+ * @param {Object} data data related to the new message
+ * @returns {$.Promise<Object>} resolved when the message has been sent to
+ * the server, with the object message that has been sent to the server.
+ */
+ _postMessage: function (data) {
+ var self = this;
+ return this._super.apply(this, arguments).then(function (messageData) {
+ _.extend(messageData, {
+ message_type: 'comment',
+ subtype: 'mail.mt_comment',
+ command: data.command,
+ });
+ return self._rpc({
+ model: 'mail.channel',
+ method: data.command ? 'execute_command' : 'message_post',
+ args: [self._id],
+ kwargs: messageData,
+ }).then(function () {
+ return messageData;
+ });
+ });
+ },
});
return Channel;
diff --git a/addons/mail/static/src/js/models/threads/dm.js b/addons/mail/static/src/js/models/threads/dm_chat.js
index fba63830a55..767a6e32edd 100644
--- a/addons/mail/static/src/js/models/threads/dm.js
+++ b/addons/mail/static/src/js/models/threads/dm_chat.js
@@ -1,19 +1,18 @@
-odoo.define('mail.model.DM', function (require) {
+odoo.define('mail.model.DMChat', function (require) {
"use strict";
-var Channel = require('mail.model.Channel');
+var TwoUserChannel = require('mail.model.TwoUserChannel');
/**
- *
* Any piece of code in JS that make use of DMs must ideally interact with
* such objects, instead of direct data from the server.
*/
-var DM = Channel.extend({
+var DMChat = TwoUserChannel.extend({
/**
* @override
* @param {Object} params
* @param {Object} params.data
- * @param {Object} params.data.direct_partner
+ * @param {Object[]} params.data.direct_partner
* @param {integer} params.data.direct_partner[0].id
* @param {string} params.data.direct_partner[0].im_status
* @param {string} params.data.direct_partner[0].name
@@ -23,10 +22,10 @@ var DM = Channel.extend({
var data = params.data;
- this._type = 'dm';
- this._name = data.direct_partner[0].name;
this._directPartnerID = data.direct_partner[0].id;
+ this._name = data.direct_partner[0].name;
this._status = data.direct_partner[0].im_status;
+ this._type = 'dm_chat';
},
//--------------------------------------------------------------------------
@@ -35,8 +34,8 @@ var DM = Channel.extend({
/**
* Get the direct partner ID linked to the DM, i.e. the partner ID of the
- * user at the other end of the DM conversation. All DM do have a direct
- * partner iD.
+ * user at the other end of the DM conversation. All DM chats do have a
+ * direct partner iD.
*
* @returns {integer}
*/
@@ -52,35 +51,13 @@ var DM = Channel.extend({
return result;
},
/**
- * DM are chat
- *
- * @override
- * @returns {boolean}
- */
- isChat: function () {
- return true;
- },
- /**
* @param {string} newStatus
*/
setStatus: function (newStatus) {
this._status = newStatus;
},
- /**
- * Unpin this DM
- *
- * @override
- * @return {$.Promise} resolve when unpinned
- */
- unsubscribe: function () {
- return this._rpc({
- model: 'mail.channel',
- method: 'channel_pin',
- args: [this.getUUID(), false],
- });
- },
});
-return DM;
+return DMChat;
});
diff --git a/addons/mail/static/src/js/models/threads/document_thread.js b/addons/mail/static/src/js/models/threads/document_thread.js
index 831c46939a8..283bcf3ca66 100644
--- a/addons/mail/static/src/js/models/threads/document_thread.js
+++ b/addons/mail/static/src/js/models/threads/document_thread.js
@@ -58,28 +58,6 @@ var DocumentThread = Thread.extend({
//--------------------------------------------------------------------------
/**
- * Add this message to this document thread.
- *
- * This is ignored if the message is already linked to this document thread.
- *
- * @override
- * @param {mail.model.Message} message
- */
- addMessage: function (message) {
- if (_.contains(this._messages, message)) {
- return;
- }
- // update internal list of messages
- this._messages.push(message);
- this._messages = _.sortBy(this._messages, function (msg) {
- return msg.getID();
- });
- // update message ids associated to this document thread
- if (!_.contains(this._messageIDs, message.getID())) {
- this._messageIDs.push(message.getID());
- }
- },
- /**
* Overrides to store the thread's state in the LocalStorage, so that it is
* shared between tabs, and restored on F5.
*
@@ -179,59 +157,6 @@ var DocumentThread = Thread.extend({
return true;
},
/**
- * Overrides this method so that all the messages of this document thread
- * are marked as read on the server.
- *
- * @override
- * @returns {$.Promise} resolved when messages have been marked as read on
- * the server.
- */
- markAsRead: function () {
- this._super.apply(this, arguments);
- return this.call('mail_service', 'markMessagesAsRead', this._messageIDs);
- },
- /**
- * Post message for document thread
- *
- * @override
- * @param {Object} data data related to the new message
- * @return {$.Promise<Object>} resolved when the message has been sent to
- * the server, with the object message that has been sent to the server.
- */
- postMessage: function (data) {
- var self = this;
- var resModel = this.getDocumentModel();
- var resID = this.getDocumentID();
- return this._super.apply(this, arguments)
- .then(function (messageData) {
- _.extend(messageData, {
- context: data.context,
- message_type: data.message_type,
- subtype: data.subtype || "mail.mt_comment",
- subtype_id: data.subtype_id,
- });
- return self._rpc({
- model: resModel,
- method: 'message_post',
- args: [resID],
- kwargs: messageData,
- })
- .then(function (messageID) {
- return self._rpc({
- model: 'mail.message',
- method: 'message_format',
- args: [[messageID]],
- })
- .then(function (messages) {
- messages[0].model = resModel;
- messages[0].res_id = resID;
- self.call('mail_service', 'addMessage', messages[0]);
- return messages[0];
- });
- });
- });
- },
- /**
* Set list of message IDs of this document thread
*
* Useful in order to handle message history of the document thread,
@@ -260,6 +185,30 @@ var DocumentThread = Thread.extend({
//--------------------------------------------------------------------------
/**
+ * Add this message to this document thread.
+ *
+ * This is ignored if the message is already linked to this document thread.
+ *
+ * @override
+ * @private
+ * @param {mail.model.Message} message
+ */
+ _addMessage: function (message) {
+ this._super.apply(this, arguments);
+ if (_.contains(this._messages, message)) {
+ return;
+ }
+ // update internal list of messages
+ this._messages.push(message);
+ this._messages = _.sortBy(this._messages, function (msg) {
+ return msg.getID();
+ });
+ // update message ids associated to this document thread
+ if (!_.contains(this._messageIDs, message.getID())) {
+ this._messageIDs.push(message.getID());
+ }
+ },
+ /**
* Get most up to date messageIDs
*
* @private
@@ -324,6 +273,63 @@ var DocumentThread = Thread.extend({
});
},
+ /**
+ * Overrides this method so that all the messages of this document thread
+ * are marked as read on the server.
+ *
+ * @override
+ * @private
+ * @returns {$.Promise} resolved when messages have been marked as read on
+ * the server.
+ */
+ _markAsRead: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ self.call('mail_service', 'markMessagesAsRead', self._messageIDs);
+ });
+ },
+ /**
+ * Post message for document thread
+ *
+ * @override
+ * @private
+ * @param {Object} data data related to the new message
+ * @return {$.Promise<Object>} resolved when the message has been sent to
+ * the server, with the object message that has been sent to the server.
+ */
+ _postMessage: function (data) {
+ var self = this;
+ var resModel = this.getDocumentModel();
+ var resID = this.getDocumentID();
+ return this._super.apply(this, arguments)
+ .then(function (messageData) {
+ _.extend(messageData, {
+ context: data.context,
+ message_type: data.message_type,
+ subtype: data.subtype || "mail.mt_comment",
+ subtype_id: data.subtype_id,
+ });
+ return self._rpc({
+ model: resModel,
+ method: 'message_post',
+ args: [resID],
+ kwargs: messageData,
+ })
+ .then(function (messageID) {
+ return self._rpc({
+ model: 'mail.message',
+ method: 'message_format',
+ args: [[messageID]],
+ })
+ .then(function (messages) {
+ messages[0].model = resModel;
+ messages[0].res_id = resID;
+ self.call('mail_service', 'addMessage', messages[0]);
+ return messages[0];
+ });
+ });
+ });
+ },
});
return DocumentThread;
diff --git a/addons/mail/static/src/js/models/threads/livechat.js b/addons/mail/static/src/js/models/threads/livechat.js
new file mode 100644
index 00000000000..d2aa04f926a
--- /dev/null
+++ b/addons/mail/static/src/js/models/threads/livechat.js
@@ -0,0 +1,57 @@
+odoo.define('mail.model.Livechat', function (require) {
+"use strict";
+
+var TwoUserChannel = require('mail.model.TwoUserChannel');
+
+/**
+ * backend-side of the livechat.
+ *
+ * Any piece of code in JS that make use of Livechats must ideally interact with
+ * such objects, instead of direct data from the server.
+ */
+var Livechat = TwoUserChannel.extend({
+ _WEBSITE_USER_ID: '_websiteUser',
+ _WEBSITE_USER_NAME: 'Website user',
+
+ /**
+ * @override
+ * @param {Object} params
+ * @param {Object} params.data
+ * @param {string} params.data.anonymous_name name of the website user
+ */
+ init: function (params) {
+ this._super.apply(this, arguments);
+
+ this._name = params.data.anonymous_name;
+
+ this._WEBSITE_USER_NAME = this._name;
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * For the livechat,
+ *
+ * @override
+ * @returns {$.Promise<Object[]>} resolved with list of livechat members
+ */
+ getMentionPartnerSuggestions: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ var websiteUser = _.findWhere(self._members, { id: self._WEBSITE_USER_ID });
+ if (!websiteUser) {
+ self._members.push({
+ id: self._WEBSITE_USER_ID,
+ name: self._WEBSITE_USER_NAME,
+ });
+ }
+ return self._members;
+ });
+ },
+});
+
+return Livechat;
+
+});
diff --git a/addons/mail/static/src/js/models/threads/mailbox.js b/addons/mail/static/src/js/models/threads/mailbox.js
index 4ba7c4e8d1f..172b5af9e3b 100644
--- a/addons/mail/static/src/js/models/threads/mailbox.js
+++ b/addons/mail/static/src/js/models/threads/mailbox.js
@@ -1,7 +1,7 @@
odoo.define('mail.model.Mailbox', function (require) {
"use strict";
-var ThreadWithCache = require('mail.model.ThreadWithCache');
+var SearchableThread = require('mail.model.SearchableThread');
var core = require('web.core');
@@ -14,7 +14,7 @@ var _t = core._t;
* list of messages, but the inbox does not represent a conversation: Inbox is
* modeled as a mailbox.
*/
-var Mailbox = ThreadWithCache.extend({
+var Mailbox = SearchableThread.extend({
/**
* @override
diff --git a/addons/mail/static/src/js/models/threads/multi_user_channel.js b/addons/mail/static/src/js/models/threads/multi_user_channel.js
new file mode 100644
index 00000000000..c1681744127
--- /dev/null
+++ b/addons/mail/static/src/js/models/threads/multi_user_channel.js
@@ -0,0 +1,56 @@
+odoo.define('mail.model.MultiUserChannel', function (require) {
+"use strict";
+
+var Channel = require('mail.model.Channel');
+
+var MultiUserChannel = Channel.extend({
+ /**
+ * @override
+ * @param {Object} params
+ * @param {Object} params.data
+ * @param {boolean} [params.data.mass_mailing=false]
+ * @param {string} [params.data.public='public'] either 'public' or
+ * 'private'
+ */
+ init: function (params) {
+ this._super.apply(this, arguments);
+
+ var data = params.data;
+
+ this._isMassMailing = data.mass_mailing || false;
+ this._type = data.public !== 'private' ? 'public' : 'private';
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * States whether this thread has the mass mailing setting active or not.
+ * This is a server-side setting, that determine the type of composer that
+ * is used (basic or extended composer).
+ *
+ * @return {boolean}
+ */
+ isMassMailing: function () {
+ return this._isMassMailing;
+ },
+ /**
+ * Unsubscribes from channel
+ *
+ * @override
+ * @returns {$.Promise} resolve when unsubscribed
+ */
+ unsubscribe: function () {
+ return this._rpc({
+ model: 'mail.channel',
+ method: 'action_unfollow',
+ args: [[this.getID()]],
+ });
+ },
+
+});
+
+return MultiUserChannel;
+
+});
diff --git a/addons/mail/static/src/js/models/threads/thread_with_cache.js b/addons/mail/static/src/js/models/threads/searchable_thread.js
index c82432c64d7..fef42c3ffc4 100644
--- a/addons/mail/static/src/js/models/threads/thread_with_cache.js
+++ b/addons/mail/static/src/js/models/threads/searchable_thread.js
@@ -1,4 +1,4 @@
-odoo.define('mail.model.ThreadWithCache', function (require) {
+odoo.define('mail.model.SearchableThread', function (require) {
"use strict";
var Thread = require('mail.model.Thread');
@@ -13,7 +13,7 @@ var session = require('web.session');
* Any threads that are instances of this Class can be used with a search view,
* in order to make searches on messages in a thread.
*/
-var ThreadWithCache = Thread.extend({
+var SearchableThread = Thread.extend({
/**
* @override
@@ -39,23 +39,6 @@ var ThreadWithCache = Thread.extend({
//--------------------------------------------------------------------------
/**
- * Add the message to this thread
- *
- * @override
- * @private
- * @param {mail.model.Message} message
- * @param {Array} domain
- */
- addMessage: function (message, domain) {
- var cache = this._getCache(domain);
- var index = _.sortedIndex(cache.messages, message, function (msg) {
- return msg.getID();
- });
- if (cache.messages[index] !== message) {
- cache.messages.splice(index, 0, message);
- }
- },
- /**
* @override
* @param {Array} [domain]
* @param {boolean} [loadMore=false]
@@ -129,6 +112,24 @@ var ThreadWithCache = Thread.extend({
//--------------------------------------------------------------------------
/**
+ * Add the message to this thread
+ *
+ * @override
+ * @private
+ * @param {mail.model.Message} message
+ * @param {Array} domain
+ */
+ _addMessage: function (message, domain) {
+ this._super.apply(this, arguments);
+ var cache = this._getCache(domain);
+ var index = _.sortedIndex(cache.messages, message, function (msg) {
+ return msg.getID();
+ });
+ if (cache.messages[index] !== message) {
+ cache.messages.splice(index, 0, message);
+ }
+ },
+ /**
* Gets messages from thread
*
* @override
@@ -202,6 +203,6 @@ var ThreadWithCache = Thread.extend({
_getThreadDomain: function () {},
});
-return ThreadWithCache;
+return SearchableThread;
});
diff --git a/addons/mail/static/src/js/models/threads/thread.js b/addons/mail/static/src/js/models/threads/thread.js
index 5fcd0285a82..c9dda28dd93 100644
--- a/addons/mail/static/src/js/models/threads/thread.js
+++ b/addons/mail/static/src/js/models/threads/thread.js
@@ -5,7 +5,6 @@ var emojis = require('mail.emojis');
var AbstractThread = require('mail.model.AbstractThread');
var mailUtils = require('mail.utils');
-var Mixins = require('web.mixins');
var ServicesMixin = require('web.ServicesMixin');
/**
@@ -15,7 +14,7 @@ var ServicesMixin = require('web.ServicesMixin');
*
* In particular, channels and mailboxes are two different kinds of threads.
*/
-var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
+var Thread = AbstractThread.extend(ServicesMixin, {
/**
* @override
@@ -27,15 +26,9 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
* @param {string} [params.data.type]
*/
init: function (params) {
- Mixins.EventDispatcherMixin.init.call(this, arguments);
- this.setParent(params.parent);
this._super.apply(this, arguments);
// threads are not detached by default
this._detached = false;
- // if this._massMailing is set, display subject on messages, use
- // extended composer and show "Send by messages by email" on discuss
- // sidebar
- this._massMailing = false;
// on 1st request to getPreview, fetch data if incomplete. Otherwise it
// means that there is no message in this channel.
this._previewed = false;
@@ -49,13 +42,6 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
//--------------------------------------------------------------------------
/**
- * Add a message to this thread.
- *
- * @abstract
- * @param {mail.model.Message} message
- */
- addMessage: function (message) {},
- /**
* Updates the _detached state of the thread. Must be overriden to reflect
* the new state in the interface.
*/
@@ -140,7 +126,7 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
return {
id: this.getID(),
imageSRC: '/mail/static/src/img/smiley/avatar.jpg',
- isChat: this.isChat(),
+ isTwoUserThread: this.isTwoUserThread(),
status: this.getStatus(),
title: this.getName(),
unreadCounter: this.getUnreadCounter(),
@@ -189,16 +175,6 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
*/
incrementNeedactionCounter: function () {},
/**
- * Increment the unread counter of this thread by 1 unit, and warn that the
- * counter has been changed.
- *
- * @override
- */
- incrementUnreadCounter: function () {
- this._super.apply(this, arguments);
- this._warnUpdatedUnreadCounter();
- },
- /**
* States whether the thread should be auto-selected on creation
*
* Note that this is not of the responsibility of the thread: it only
@@ -223,17 +199,6 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
return false;
},
/**
- * States whether this thread is a chat or not. In particular, public and
- * private channels are not chat, but DMs and Livechats are chats. Chats
- * are threads used for communication between two users.
- * By default, any thread is not a chat
- *
- * @returns {boolean}
- */
- isChat: function () {
- return false;
- },
- /**
* States whether this thread is detached or not.
* A thread that is detached must have a thread window linked to itself.
*
@@ -256,10 +221,12 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
* This is a server-side setting, that determine the type of composer that
* is used (basic or extended composer).
*
+ * By default, thread have not mass-mailings feature set.
+ *
* @return {boolean}
*/
isMassMailing: function () {
- return this._massMailing;
+ return false;
},
/**
@@ -277,7 +244,18 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
*
* @returns {boolean}
*/
- isModerator: function () {
+ isMyselfModerator: function () {
+ return false;
+ },
+ /**
+ * States whether this is a two-user thread or not. In particular, public
+ * and private channels are not two-user threads, but DMs and Livechats are
+ * two-user threads. Two-user threads are used for communication between
+ * two users. By default, any thread is not two-user thread.
+ *
+ * @returns {boolean}
+ */
+ isTwoUserThread: function () {
return false;
},
/**
@@ -294,29 +272,61 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
markAsPreviewed: function () {
this._previewed = true;
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
/**
- * Mark the thread as read, which resets the unread counter to 0.
- *
- * @returns {$.Promise} resolved
+ * @abstract
+ * @private
+ * @return {$.Promise}
*/
- markAsRead: function () {
- if (this._unreadCounter > 0) {
- this.resetUnreadCounter();
- }
+ _fetchMessages: function () {
return $.when();
},
/**
+ * Replace character representations of emojis by their unicode
+ * representation in the provided HTML string. Note that the provided html
+ * string is altered by this function.
+ *
+ * @param {string} htmlString
+ * @returns {string}
+ */
+ _generateEmojis: function (htmlString) {
+ _.each(emojis, function (emoji) {
+ _.each(emoji.sources, function (source) {
+ var escapedSource = String(source).replace(/([.*+?=^!:${}()|[\]/\\])/g, '\\$1');
+ var regexp = new RegExp("(\\s|^)(" + escapedSource + ")(?=\\s|$)", 'g');
+ htmlString = htmlString.replace(regexp, '$1' + emoji.unicode);
+ });
+ });
+ return htmlString;
+ },
+ /**
+ * Increment the unread counter of this thread by 1 unit, and warn that the
+ * counter has been changed.
+ *
+ * @override
+ * @private
+ */
+ _incrementUnreadCounter: function () {
+ this._super.apply(this, arguments);
+ this._warnUpdatedUnreadCounter();
+ },
+ /**
* Post a message on the thread.
*
* This method must be completed by concrete threads,
* As it currently only pre-process the messages at the moment.
*
* @abstract
+ * @private
* @param {Object} data
* @returns {$.Promise<Object>} resolved with the message object to be sent
* to the server
*/
- postMessage: function (data) {
+ _postMessage: function (data) {
// This message will be received from the mail composer as html content
// subtype but the urls will not be linkified. If the mail composer
// takes the responsibility to linkify the urls we end up with double
@@ -334,58 +344,18 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
if ('subject' in data) {
messageData.subject = data.subject;
}
- return $.when(messageData);
- },
- /**
- * Overrides the method so that it also warns that the counter has been
- * changed on this thread.
- *
- * @override
- * @private
- */
- resetUnreadCounter: function () {
- this._super.apply(this, arguments);
- this._warnUpdatedUnreadCounter();
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @abstract
- * @private
- * @return {$.Promise}
- */
- _fetchMessages: function () {
- return $.when();
- },
- /**
- * Replace character representations of emojis by their unicode
- * representation in the provided HTML string. Note that the provided html
- * string is altered by this function.
- *
- * @param {string} htmlString
- * @returns {string}
- */
- _generateEmojis: function (htmlString) {
- _.each(emojis, function (emoji) {
- _.each(emoji.sources, function (source) {
- var escapedSource = String(source).replace(/([.*+?=^!:${}()|[\]/\\])/g, '\\$1');
- var regexp = new RegExp("(\\s|^)(" + escapedSource + ")(?=\\s|$)", 'g');
- htmlString = htmlString.replace(regexp, '$1' + emoji.unicode);
- });
+ return this._super.apply(this, arguments).then(function () {
+ return $.when(messageData);
});
- return htmlString;
},
/**
- * Warn on the chat bus that the unread counter has been updated
+ * Warn on the mail bus that the unread counter has been updated
*
+ * @override
* @private
*/
_warnUpdatedUnreadCounter: function () {
- this.call('mail_service', 'getMailBus')
- .trigger('update_thread_unread_counter', this);
+ this.call('mail_service', 'getMailBus').trigger('update_thread_unread_counter', this);
},
/**
* Warn other mail components that the window state of this thread has
@@ -393,6 +363,7 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
*
* @private
* @param {Object} [options={}]
+ * @param {boolean} [options.passively=false]
*/
_warnUpdatedWindowState: function (options) {
options = options || {};
diff --git a/addons/mail/static/src/js/models/threads/two_user_channel.js b/addons/mail/static/src/js/models/threads/two_user_channel.js
new file mode 100644
index 00000000000..204fd95eb82
--- /dev/null
+++ b/addons/mail/static/src/js/models/threads/two_user_channel.js
@@ -0,0 +1,32 @@
+odoo.define('mail.model.TwoUserChannel', function (require) {
+"use strict";
+
+var Channel = require('mail.model.Channel');
+
+var TwoUserChannel = Channel.extend({
+ /**
+ * @override
+ * @returns {boolean}
+ */
+ isTwoUserThread: function () {
+ return true;
+ },
+ /**
+ * Unpin from two-user thread
+ *
+ * @override
+ * @returns {$.Promise} resolve when unsubscribed
+ */
+ unsubscribe: function () {
+ return this._rpc({
+ model: 'mail.channel',
+ method: 'channel_pin',
+ args: [this.getUUID(), false],
+ });
+ },
+
+});
+
+return TwoUserChannel;
+
+});