summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/models/threads
diff options
context:
space:
mode:
Diffstat (limited to 'addons/mail/static/src/js/models/threads')
-rw-r--r--addons/mail/static/src/js/models/threads/abstract_thread.js231
-rw-r--r--addons/mail/static/src/js/models/threads/channel.js474
-rw-r--r--addons/mail/static/src/js/models/threads/create_mode_document_thread.js75
-rw-r--r--addons/mail/static/src/js/models/threads/dm_chat.js109
-rw-r--r--addons/mail/static/src/js/models/threads/document_thread.js364
-rw-r--r--addons/mail/static/src/js/models/threads/livechat.js95
-rw-r--r--addons/mail/static/src/js/models/threads/mailbox.js224
-rw-r--r--addons/mail/static/src/js/models/threads/mixins/channel_seen_mixin.js229
-rw-r--r--addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js360
-rw-r--r--addons/mail/static/src/js/models/threads/multi_user_channel.js73
-rw-r--r--addons/mail/static/src/js/models/threads/searchable_thread.js244
-rw-r--r--addons/mail/static/src/js/models/threads/thread.js383
-rw-r--r--addons/mail/static/src/js/models/threads/two_user_channel.js32
13 files changed, 0 insertions, 2893 deletions
diff --git a/addons/mail/static/src/js/models/threads/abstract_thread.js b/addons/mail/static/src/js/models/threads/abstract_thread.js
deleted file mode 100644
index 619bf95b8e3..00000000000
--- a/addons/mail/static/src/js/models/threads/abstract_thread.js
+++ /dev/null
@@ -1,231 +0,0 @@
-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
- * (which are compatible with mail service) or website livechats.
- *
- * Abstract threads contain abstract messages
- */
-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;
- this._status = params.data.status || '';
- this._unreadCounter = 0; // amount of messages not yet been read
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * 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
- */
- fold: function (folded) {
- this._folded = folded;
- },
- /**
- * Get the ID of this thread
- *
- * @returns {integer|string}
- */
- getID: function () {
- return this._id;
- },
- /**
- * @abstract
- * @returns {mail.model.AbstractMessage[]}
- */
- getMessages: function () {},
- /**
- * Get the name of this thread. If the name of the thread has been created
- * by the user from an input, it may be escaped.
- *
- * @returns {string}
- */
- getName: function () {
- return this._name;
- },
- /**
- * By default, threads do not have out-of-office feature
- *
- * @return {string|undefined}
- */
- getOutOfOfficeInfo: function () {
- return undefined;
- },
- /**
- * Get the status of the thread (e.g. 'online', 'offline', etc.)
- *
- * @returns {string}
- */
- getStatus: function () {
- return this._status;
- },
- /**
- * Returns the title to display in thread window's headers.
- *
- * @returns {string} the name of the thread by default (see @getName)
- */
- getTitle: function () {
- return this.getName();
- },
- getType: function () {},
- /**
- * @returns {integer}
- */
- getUnreadCounter: function () {
- return this._unreadCounter;
- },
- /**
- * @returns {boolean}
- */
- hasMessages: function () {
- return !_.isEmpty(this.getMessages());
- },
- /**
- * @return {boolean}
- */
- hasOutOfOffice() {
- return !!this.getOutOfOfficeInfo();
- },
- /**
- * States whether this thread is compatible with the 'seen' feature.
- * By default, threads do not have thsi feature active.
- * @see {mail.model.ThreadSeenMixin} to enable this feature on a thread.
- *
- * @returns {boolean}
- */
- hasSeenFeature: function () {
- return false;
- },
- /**
- * States whether this thread is compatible with the 'is typing...' feature.
- * By default, threads do not have this feature active.
- * @see {mail.model.ThreadTypingMixin} to enable this feature on a thread.
- *
- * @returns {boolean}
- */
- hasTypingNotification: function () {
- return false;
- },
- /**
- * States whether this thread is folded or not.
- *
- * @return {boolean}
- */
- isFolded: function () {
- 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 Promise.resolve();
- },
- /**
- * 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;
- this._warnUpdatedUnreadCounter();
- },
-
- //--------------------------------------------------------------------------
- // 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();
- return Promise.resolve();
- },
- /**
- * Post a message on this thread
- *
- * @abstract
- * @private
- * @returns {Promise} resolved with the message object to be sent to the
- * server
- */
- _postMessage: function () {
- return Promise.resolve();
- },
- /**
- * Warn views (e.g. discuss app, thread window, etc.) to update visually
- * the unread counter of this thread.
- *
- * @abstract
- * @private
- */
- _warnUpdatedUnreadCounter: function () {},
-});
-
-return AbstractThread;
-
-});
diff --git a/addons/mail/static/src/js/models/threads/channel.js b/addons/mail/static/src/js/models/threads/channel.js
deleted file mode 100644
index dced861c00a..00000000000
--- a/addons/mail/static/src/js/models/threads/channel.js
+++ /dev/null
@@ -1,474 +0,0 @@
-odoo.define('mail.model.Channel', function (require) {
-"use strict";
-
-var SearchableThread = require('mail.model.SearchableThread');
-var ThreadTypingMixin = require('mail.model.ThreadTypingMixin');
-var mailUtils = require('mail.utils');
-
-const config = require('web.config');
-var session = require('web.session');
-var time = require('web.time');
-
-/**
- * This class represent channels in JS. In this context, the word channel
- * has the same meaning of channel on the server, meaning that direct messages
- * (DM) and livechats are also channels.
- *
- * 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 = SearchableThread.extend(ThreadTypingMixin, {
- /**
- * @override
- * @param {Object} params
- * @param {Object} params.data
- * @param {string} params.data.channel_type
- * @param {integer} [params.data.create_uid] the ID of the user that has
- * created the channel.
- * @param {boolean} params.data.group_based_subscription
- * @param {boolean} [params.data.is_minimized=false]
- * @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 {Object[]} [params.data.members=[]]
- * @param {integer} [params.data.members[i].id]
- * @param {string} [params.data.members[i].name]
- * @param {string} [params.data.members[i].email]
- * @param {integer} [params.data.message_unread_counter]
- * @param {boolean} [params.data.moderation=false] whether the channel is
- * moderated or not
- * @param {Object[]} [params.data.partners_info=[]]
- * @param {integer} [params.data.partners_info[i].partner_id]
- * @param {integer} [params.data.partners_info[i].fetched_message_id]
- * @param {integer} [params.data.partners_info[i].seen_message_id]
- * @param {string} params.data.state
- * @param {string} [params.data.uuid]
- * @param {Object} params.options
- * @param {boolean} [params.options.autoswitch=true]
- * @param {Object[]} params.commands
- */
- init: function (params) {
- var self = this;
- this._super.apply(this, arguments);
- ThreadTypingMixin.init.apply(this, arguments);
-
- var data = params.data;
- var options = params.options;
- var commands = params.commands;
-
- // If set, autoswitch channel on joining this channel in discuss
- // the default behaviour is to autoswitch on join.
- // exception: receiving channel session notifications
- this._autoswitch = 'autoswitch' in options ? options.autoswitch : true;
- this._commands = undefined;
- this._creatorUID = data.create_uid;
- this._detached = data.is_minimized || false;
- this._directPartnerID = undefined;
- this._folded = data.state === 'folded';
- // if set: hide 'Leave channel' button
- this._groupBasedSubscription = data.group_based_subscription;
- this._isModerated = data.moderation;
- this._isMyselfModerator = data.is_moderator;
- this._lastMessageDate = undefined;
- this._members = data.members || [];
- // Promise that is resolved on fetched members of this channel.
- this._membersDef = undefined;
- // 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;
- this._serverType = data.channel_type;
- // unique identifier for this channel, which is required for some rpc
- this._uuid = data.uuid;
-
- // * list of commands available for this channel
- this._commands = _.filter(commands, function (command) {
- return !command.channel_types ||
- _.contains(command.channel_types, self._serverType);
- });
- if (data.last_message_date) {
- this._lastMessageDate = moment(time.str_to_datetime(data.last_message_date));
- }
- if (data.message_unread_counter) {
- this._unreadCounter = data.message_unread_counter;
- }
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Close the channel
- *
- * This operation is executed server-side and the chat window will be
- * folded in all potential tabs from all browsers.
- *
- * @override
- */
- close: function () {
- this._super.apply(this, arguments);
- // Do not notify the server to avoid desktop chat window from closing
- // when a chat window is closed on mobile.
- if (!config.device.isMobile) {
- this._rpc({
- model: 'mail.channel',
- method: 'channel_fold',
- kwargs: { uuid: this.getUUID(), state: 'closed' },
- }, { shadow: true });
- }
- },
- /**
- * Decrement the needaction counter of the channel
- * Floor value at 0.
- *
- * @param {integer} [num=1] the amount to decrement at most
- */
- decrementNeedactionCounter: function (num) {
- num = _.isNumber(num) ? num : 1;
- this._needactionCounter = Math.max(this._needactionCounter - 1, 0);
- },
- /**
- * Open the chat window for this channel
- * (in all potential tabs from all browsers)
- *
- * @pverride
- */
- detach: function () {
- var self = this;
- return this._super.apply(this, arguments).then(function () {
- // Do not notify the server to avoid desktop chat window from opening
- // when a chat window is opened on mobile.
- if (!config.device.isMobile) {
- self._rpc({
- model: 'mail.channel',
- method: 'channel_minimize',
- args: [self.getUUID(), true],
- }, {
- shadow: true,
- });
- }
- });
- },
- /**
- * Force fetch members of the channel
- *
- * @returns {Promise<Object[]>} resolved with list of channel listeners
- */
- forceFetchMembers: function () {
- this._membersDef = undefined;
- return this.getMentionPartnerSuggestions();
- },
- /**
- * Folds/Minimize the channel
- * (in all potential tabs from all browsers)
- *
- * @override
- * @param {boolean} folded
- */
- fold: function (folded) {
- this._super.apply(this, arguments);
- var args = {
- uuid: this.getUUID(),
- };
- if (_.isBoolean(folded)) {
- args.state = folded ? 'folded' : 'open';
- }
- this._rpc({
- model: 'mail.channel',
- method: 'channel_fold',
- kwargs: args,
- }, {shadow: true});
- },
- /**
- * Show the list of available commands on this channel (e.g. '/help')
- *
- * @override
- * @returns {Array} list of commands
- */
- getCommands: function () {
- return this._commands;
- },
- /**
- * @returns {mail.model.Message|undefined} last message of channel, if any
- */
- getLastMessage: function () {
- return _.last(this._cache['[]'].messages);
- },
- /**
- * Get listeners of a channel
- *
- * @returns {Promise<Object[]>} resolved with list of list of
- * channel listeners.
- */
- getMentionPartnerSuggestions: function () {
- var self = this;
- if (!this._membersDef) {
- this._membersDef = this._rpc({
- model: 'mail.channel',
- method: 'channel_fetch_listeners',
- args: [this.getUUID()],
- }, {
- shadow: true
- })
- .then(function (members) {
- self._members = members;
- return [members];
- });
- }
- return this._membersDef;
- },
- /**
- * @returns {integer}
- */
- getNeedactionCounter: function () {
- return this._needactionCounter;
- },
- /**
- * @override
- */
- getPreview: function () {
- var result = this._super.apply(this, arguments);
- if (!this.isTwoUserThread()) {
- result.imageSRC = '/web/image/mail.channel/' + this.getID() + '/image_128';
- }
- var lastMessage = this.getLastMessage();
- return _.extend(result, {
- author: lastMessage ? lastMessage.getDisplayedAuthor() : '',
- body: lastMessage ? mailUtils.htmlToTextContentInline(lastMessage.getBody()) : '',
- date: lastMessage ? lastMessage.getDate() : undefined,
- isMyselfAuthor: this.hasMessages() && this.getLastMessage().isMyselfAuthor(),
- });
- },
- /**
- * Get the UUID of the channel.
- * This is a string that uniquely links this channel to a server channel.
- *
- * @returns {string} uuid of this channel
- */
- getUUID: function () {
- return this._uuid;
- },
- /**
- * Increment the needaction counter of this channel by 1 unit
- */
- incrementNeedactionCounter: function () {
- this._needactionCounter++;
- },
- /**
- * States whether the channel should be auto-selected on creation
- *
- * Note that this is not of the responsibility of the channel
- * (see mail.model.Thread for more information)
- *
- * @override
- * @returns {boolean}
- */
- isAutoswitch: function () {
- return this._autoswitch;
- },
- /**
- * All instances of this class are chanels
- *
- * @override
- * @returns {boolean}
- */
- isChannel: function () {
- return true;
- },
- /**
- * States whether the channel auto-subscribes some users in a group
- *
- * @returns {boolean}
- */
- isGroupBasedSubscription: function () {
- return this._groupBasedSubscription;
- },
- /**
- * States whether the channel is moderated or not.
- *
- * @override
- * @returns {boolean}
- */
- isModerated: function () {
- return this._isModerated;
- },
- /**
- * Tells whether the current user is administrator of the channel.
- * Note that there is no administrator for two-user channels
- *
- * @returns {boolean}
- */
- isMyselfAdministrator: function () {
- return session.uid === this._creatorUID && !this.isTwoUserThread();
- },
- /**
- * States whether the current user is moderator of this channel.
- *
- * @returns {boolean}
- */
- isMyselfModerator: function () {
- return this._isMyselfModerator;
- },
- /**
- * Unsubscribes from channel
- *
- * @abstract
- * @returns {Promise} resolve when unsubscribed
- */
- unsubscribe: function () {},
- /**
- * Updates the internal state of the channel, and reflects the changes in
- * the UI.
- *
- * Called by {mail.Manager.Notification} on receiving a channel window
- * notification. A notification is received after a client-side action
- * that changes the state of the channel window visually, so that cross tab
- * channels are updated similarly.
- *
- * TODO: This method should be dropped if the state of windows are
- * completely handled client-side, which should be the case in the near
- * future.
- *
- * @param {Object} params
- * @param {boolean} params.detached
- * @param {boolean} params.folded
- */
- updateWindowState: function (params) {
- this._folded = 'folded' in params ? params.folded : this._folded;
- this._detached = 'detached' in params ? params.detached : this._detached;
- this._warnUpdatedWindowState();
- },
- /**
- * Reset the needaction counter to 0.
- */
- resetNeedactionCounter: function () {
- this._needactionCounter = 0;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Override so that it tells whether the channel is moderated or not. This
- * is useful in order to display pending moderation messages when the
- * current user is either moderator of the channel or has posted some
- * messages that are pending moderation.
- *
- * @override
- * @private
- * @returns {Object}
- */
- _getFetchMessagesKwargs: function () {
- var kwargs = this._super.apply(this, arguments);
- if (this.isModerated()) {
- kwargs.moderated_channel_ids = [this.getID()];
- }
- return kwargs;
- },
- /**
- * Get the domain to fetch all the messages in the current channel
- *
- * @override
- * @private
- * @returns {Array}
- */
- _getThreadDomain: function () {
- return [['channel_ids', 'in', [this._id]]];
- },
- /**
- * @override {mail.model.ThreadTypingMixin}
- * @private
- * @param {Object} params
- * @param {integer} params.partnerID
- */
- _isTypingMyselfInfo: function (params) {
- return session.partner_id === params.partnerID;
- },
- /**
- * 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._notifySeen();
- return Promise.all([superDef, seenDef]);
- },
- /**
- * @override {mail.model.ThreadTypingMixin}
- * @private
- * @param {Object} params
- * @param {boolean} params.typing
- * @returns {Promise}
- */
- _notifyMyselfTyping: function (params) {
- return this._rpc({
- model: 'mail.channel',
- method: 'notify_typing',
- args: [this.getID()],
- kwargs: { is_typing: params.typing },
- }, { shadow: true });
- },
- /**
- * @private
- * @returns {$.Promise<integer>} resolved with ID of last seen message
- */
- _notifySeen: function () {
- var self = this;
- return this._rpc({
- model: 'mail.channel',
- method: 'channel_seen',
- args: [[this._id]],
- }, { shadow: true }).then(function (lastSeenMessageID) {
- self._lastSeenMessageID = lastSeenMessageID;
- return lastSeenMessageID;
- });
- },
- /**
- * 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_xmlid: '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;
- });
- });
- },
- /**
- * Warn views that the list of users that are currently typing on this
- * thread has been updated.
- *
- * @override {mail.model.ThreadTypingMixin}
- * @private
- */
- _warnUpdatedTypingPartners: function () {
- this.call('mail_service', 'getMailBus')
- .trigger('update_typing_partners', this.getID());
- },
-});
-
-return Channel;
-
-});
diff --git a/addons/mail/static/src/js/models/threads/create_mode_document_thread.js b/addons/mail/static/src/js/models/threads/create_mode_document_thread.js
deleted file mode 100644
index da632de0470..00000000000
--- a/addons/mail/static/src/js/models/threads/create_mode_document_thread.js
+++ /dev/null
@@ -1,75 +0,0 @@
-odoo.define('mail.model.CreateModeDocumentThread', function (require) {
-"use strict";
-
-var AbstractMessage = require('mail.model.AbstractMessage');
-var AbstractThread = require('mail.model.AbstractThread');
-
-var core = require('web.core');
-var session = require('web.session');
-
-var _t = core._t;
-
-/**
- * Special document thread that is linked to a new document that is in create
- * mode. This thread only display a single (fake) message which displays the
- * user avatar and the message 'Creating a new record...'.
- *
- * Note: it uses AbstractThread and AbstractMessage, because this is a fake
- * thread which contains a fake message. We do not want to register both of them
- * in the mail service.
- */
-var CreateModeDocumentThread = AbstractThread.extend({
-
- /**
- * @override
- */
- init: function () {
- var params = {
- data: {
- id: '_createModeDocumentThread'
- }
- };
-
- this._super(params);
-
- this._messages = this._forgeCreateMessages();
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * @returns {mail.model.AbstractMessage[]}
- */
- getMessages: function () {
- return this._messages;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Make a fake list of messages to render in create mode.
- * Instead of rendering no messages at all, it displays a single message
- * with body content "Creating a new record".
- *
- * @private
- * @returns {mail.model.AbstractMessage[]} an array containing a single
- * message 'Creating a new record...'
- */
- _forgeCreateMessages: function () {
- var createMessage = new AbstractMessage(this, {
- id: 0,
- body: _t("<p>Creating a new record...</p>"),
- author_id: [session.partner_id, session.partner_display_name],
- });
- return [createMessage];
- },
-
-});
-
-return CreateModeDocumentThread;
-
-});
diff --git a/addons/mail/static/src/js/models/threads/dm_chat.js b/addons/mail/static/src/js/models/threads/dm_chat.js
deleted file mode 100644
index 177c5224ea4..00000000000
--- a/addons/mail/static/src/js/models/threads/dm_chat.js
+++ /dev/null
@@ -1,109 +0,0 @@
-odoo.define('mail.model.DMChat', function (require) {
-"use strict";
-
-var ChannelSeenMixin = require('mail.model.ChannelSeenMixin');
-var TwoUserChannel = require('mail.model.TwoUserChannel');
-
-var core = require('web.core');
-const { str_to_datetime } = require('web.time');
-
-var _t = core._t;
-
-/**
- * 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 DMChat = TwoUserChannel.extend(ChannelSeenMixin, {
- /**
- * @override
- * @param {Object} params
- * @param {Object} params.data
- * @param {string|undefined} [params.custom_channel_name] if set, use this
- * custom name for this DM
- * @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
- * @param {string} [params.data.direct_partner[0].out_of_office_date_end]
- */
- init: function (params) {
- this._super.apply(this, arguments);
- ChannelSeenMixin.init.apply(this, arguments);
-
- var data = params.data;
-
- this._directPartnerID = data.direct_partner[0].id;
- this._name = data.custom_channel_name || data.direct_partner[0].name;
- this._outOfOfficeDateEnd = data.direct_partner[0].out_of_office_date_end;
- this._type = 'dm_chat';
-
- this.call('mail_service', 'updateImStatus', [{
- id: this._directPartnerID,
- im_status: data.direct_partner[0].im_status
- }]);
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * 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 chats do have a
- * direct partner iD.
- *
- * @returns {integer}
- */
- getDirectPartnerID: function () {
- return this._directPartnerID;
- },
- /**
- * Get the out of office info
- *
- * @override {mail.model.AbstractThread}
- * @returns {string|undefined}
- */
- getOutOfOfficeInfo: function () {
- if (!this._outOfOfficeDateEnd) {
- return undefined;
- }
- var currentDate = new Date();
- var date = str_to_datetime(this._outOfOfficeDateEnd);
- var options = { day: 'numeric', month: 'short' };
- if (currentDate.getFullYear() !== date.getFullYear()) {
- options.year = 'numeric';
- }
- var formattedDate = date.toLocaleDateString(window.navigator.language, options);
- return _.str.sprintf(_t("Out of office until %s"), formattedDate);
- },
- /**
- * @override
- */
- getPreview: function () {
- var result = this._super.apply(this, arguments);
- result.imageSRC = '/web/image/res.partner/' + this.getDirectPartnerID() + '/image_128';
- return result;
- },
- /**
- * @override
- * @return {string}
- */
- getStatus: function () {
- return this.call('mail_service', 'getImStatus', { partnerID: this._directPartnerID });
- },
-
- /**
- * @param {Object} data
- * @param {string} data.outOfOfficeDateEnd
- */
- updateOutOfOfficeInfo: function (data) {
- this._outOfOfficeDateEnd = data.outOfOfficeDateEnd;
- this.call('mail_service', 'getMailBus').trigger('updated_out_of_office', {
- threadID: this.getID(),
- });
- }
-});
-
-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
deleted file mode 100644
index eefc8f9bd79..00000000000
--- a/addons/mail/static/src/js/models/threads/document_thread.js
+++ /dev/null
@@ -1,364 +0,0 @@
-odoo.define('mail.model.DocumentThread', function (require) {
-"use strict";
-
-var Thread = require('mail.model.Thread');
-
-var session = require('web.session');
-
-/**
- * These threads represent threads that are directly attached to documents.
- * It is sometimes called the "chatter", but technically this is just the
- * part of the chatter containing the thread, as the chatter also contains
- * the activity and listener fields.
- *
- * Note that there are still some hacks in the handling of document threads,
- * due to document threads are instantiated from fetched messages, either from
- * mail.ThreadField or by fetching messages in Inbox.
- *
- * Also, because messages are not sent on the longpoll bus for this kind of
- * threads, it stores the list of message IDs that it contains, which should
- * be up-to-date whenever someone wants to get the messages in the document
- * thread.
- */
-var DocumentThread = Thread.extend({
-
- /**
- * @override
- * @param {Object} params
- * @param {Object} params.data
- * @param {string} [params.data.messagesIDs] the list of message ids linked
- * to the document (if not given, they will be fetched before fetching the
- * messages)
- * @param {string} params.data.model
- * @param {integer} params.data.resID
- */
- init: function (params) {
- var data = params.data;
- data.id = data.resModel + '_' + data.resID;
- data.type = 'document_thread';
-
- this._super.apply(this, arguments);
-
- // ID of the related document
- this._documentID = data.resID;
- // model of the related document
- this._documentModel = data.resModel;
- // used to handle history of messages
- this._messageIDs = data.messageIDs || [];
- // list of loaded messages
- this._messages = [];
-
- // if the messageIDs haven't been given, fetch them before fetching the
- // messages
- this._mustFetchMessageIDs = !_.isArray(data.messageIDs);
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Overrides to store the thread's state in the LocalStorage, so that it is
- * shared between tabs, and restored on F5.
- *
- * @override
- * @param {Object} [options]
- * @param {boolean} [options.skipCrossTabSync=false] if set, it should
- * not notify other tabs from updating document thread state. This happens
- * in case the `close` operation comes from local storage event.
- */
- close: function (options) {
- this._super.apply(this, arguments);
- if (options && options.skipCrossTabSync) {
- return;
- }
- this.call('mail_service', 'updateDocumentThreadState', this._id, {
- name: this.getName(),
- windowState: 'closed',
- });
- },
- /**
- * Overrides to store the thread's state in the LocalStorage, so that it is
- * shared between tabs, and restored on F5.
- *
- * @override
- * @param {Object} [options]
- * @param {boolean} [options.skipCrossTabSync=false] if set, it should
- * not notify other tabs from updating document thread state. This happens
- * in case the `detach` operation comes from local storage event.
- */
- detach: function (options) {
- this._super.apply(this, arguments);
- if (options && options.skipCrossTabSync) {
- return;
- }
- var windowState = this._folded ? 'folded' : 'open';
- this.call('mail_service', 'updateDocumentThreadState', this._id, {
- name: this.getName(),
- windowState: windowState,
- });
- },
- /**
- *
- * Fetch messages of the document thread
- *
- * @override
- * @param {Object} options
- * @param {boolean} [options.forceFetch] if true, fetch anyway, as user
- * clicked on 'load more'.
- * @returns {Promise<mail.model.Message[]>}
- */
- fetchMessages: function (options) {
- var self = this;
- return this._fetchMessages(options).then(function () {
- return self._messages;
- });
- },
- /**
- * Overrides to store the thread's state in the LocalStorage, so that it is
- * shared between tabs, and restored on F5.
- *
- * @override
- * @param {Object} [options]
- * @param {boolean} [options.skipCrossTabSync=false] if set, it should
- * not notify other tabs from updating document thread state. This happens
- * in case the `detach` operation comes from local storage event.
- */
- fold: function (folded, options) {
- this._super.apply(this, arguments);
- if (options && options.skipCrossTabSync) {
- return;
- }
- var windowState = this._folded ? 'folded' : 'open';
- this.call('mail_service', 'updateDocumentThreadState', this._id, {
- name: this.getName(),
- windowState: windowState,
- });
- },
- /**
- * Get the model name of the document that is linked to this document thread
- *
- * @returns {string}
- */
- getDocumentModel: function () {
- return this._documentModel;
- },
- /**
- * Get the ID of the document that is linked to this document thread
- *
- * @returns {integer}
- */
- getDocumentID: function () {
- return this._documentID;
- },
- /**
- * Get he list of message IDs that this document threads contain.
- *
- * @returns {integer[]}
- */
- getMessageIDs: function () {
- return this._messageIDs;
- },
- /**
- * @override
- * @returns {mail.model.Message[]}
- */
- getMessages: function () {
- return this._messages;
- },
- /**
- * States whether the thread is linked to a document
- * Document thread's are always linked to a document.
- *
- * @override
- * @returns {boolean}
- */
- isLinkedToDocument: function () {
- return true;
- },
- /**
- * @param {integer[]} attachmentIDs
- */
- removeAttachmentsFromMessages: function (attachmentIDs) {
- _.each(this.getMessages(), function (message) {
- message.removeAttachments(attachmentIDs);
- });
- },
- /**
- * Set list of message IDs of this document thread
- *
- * Useful in order to handle message history of the document thread,
- * in particular to fetch messages when necessary and/or display 'load more'.
- *
- * @param {integer[]} messageIDs
- */
- setMessageIDs: function (messageIDs) {
- this._mustFetchMessageIDs = false;
- this._messageIDs = messageIDs;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Add this message to this document thread.
- *
- * This is ignored if the message is already linked to this document thread.
- * Also, there is no fetch of unread message counter in a document thread,
- * as this object is built from messages and/or thread field widget. The
- * unread counter of a document thread is the sum of needaction messages
- * 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());
- }
- // update unread counter
- if (message.isNeedaction()) {
- this._unreadCounter++;
- }
- },
- /**
- * Get most up to date messageIDs
- *
- * @private
- * @returns {Promise} resolved when message IDs have been fetched and set
- * in the model
- */
- _fetchMessageIDs: function () {
- var self = this;
- var resID = this.getDocumentID();
- if (!resID || !this._mustFetchMessageIDs) {
- return Promise.resolve();
- }
- return this._rpc({
- model: this.getDocumentModel(),
- method: 'read',
- args: [[resID], ['message_ids']],
- }).then(function (result) {
- self.setMessageIDs(result[0].message_ids);
- });
- },
- /**
- * @override
- * @private
- * @param {Object} options
- * @param {boolean} [options.forceFetch]
- * @returns {Promise} resolved when messages have been fetched + document
- * thread has updated messages
- */
- _fetchMessages: function (options) {
- var self = this;
- return this._fetchMessageIDs().then(function () {
- var messageIDs = self._messageIDs;
- var loadedMessages = _.filter(self._messages, function (message) {
- return _.contains(messageIDs, message.getID());
- });
- var loadedMessageIDs = _.map(loadedMessages, function (message) {
- return message.getID();
- });
-
- options = options || {};
- var shouldFetch = _.difference(
- messageIDs.slice(0, self._FETCH_LIMIT),
- loadedMessageIDs
- ).length > 0;
- if (options.forceFetch || shouldFetch) {
- var idsToLoad = _.difference(messageIDs, loadedMessageIDs)
- .slice(0, self._FETCH_LIMIT);
- return self._rpc({
- model: 'mail.message',
- method: 'message_format',
- args: [idsToLoad],
- context: session.user_context,
- })
- .then(function (messagesData) {
- _.each(messagesData, function (messageData) {
- self.call('mail_service', 'addMessage', messageData, { silent: true });
- });
- });
- } else {
- return Promise.resolve();
- }
-
- });
- },
- /**
- * 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_xmlid: data.subtype_xmlid || "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], {
- postedFromDocumentThread: true,
- });
- 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
deleted file mode 100644
index c3b4a5e2b4d..00000000000
--- a/addons/mail/static/src/js/models/threads/livechat.js
+++ /dev/null
@@ -1,95 +0,0 @@
-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
- */
- init: function (params) {
- this._super.apply(this, arguments);
- this._name = params.data.correspondent_name;
- this._WEBSITE_USER_NAME = this._name;
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Override so that the list of members has the website use. This is
- * necessary in order for the 'is_typing' feature to compute the name to
- * display of a user that is typing.
- *
- * @override
- * @returns {Promise<Object[]>} resolved with list of 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];
- });
- },
- /**
- * Called when someone starts typing something on the livechat.
- *
- * Overrides it so that it determine the partner based on the received
- * userID. The reason is that anonymous users have the partner ID of the
- * admin, which is likely also an operator, so userID must be used in order
- * to distinct them.
- *
- * @override {mail.model.ThreadTypingMixin}
- * @param {Object} params
- * @param {integer} params.partnerID ID of the partner that is currently
- * typing something on the thread.
- * @param {boolean} [params.isWebsiteUser=false] whether the typing partner
- * is an anonymous user (reminder: they share partnerID with admin).
- */
- registerTyping: function (params) {
- params.partnerID = this._WEBSITE_USER_ID;
- this._super.call(this, params);
- },
- /**
- * Called when someone stops typing something on the livechat.
- *
- * Overrides it so that it determine the partner based on the received
- * userID. The reason is that anonymous users have the partner ID of the
- * admin, which is likely also an operator, so userID must be used in order
- * to distinct them.
- *
- * @override {mail.model.ThreadTypingMixin}
- * @param {Object} params
- * @param {integer} params.partnerID ID of the partner that stops typing
- * something on the thread.
- * @param {boolean} [params.isWebsiteUser=false] whether the typing partner
- * is an anonymous user (reminder: they share partnerID with admin).
- */
- unregisterTyping: function (params) {
- params.partnerID = this._WEBSITE_USER_ID;
- this._super.call(this, params);
- },
-});
-
-return Livechat;
-
-});
diff --git a/addons/mail/static/src/js/models/threads/mailbox.js b/addons/mail/static/src/js/models/threads/mailbox.js
deleted file mode 100644
index a08f92f0ab8..00000000000
--- a/addons/mail/static/src/js/models/threads/mailbox.js
+++ /dev/null
@@ -1,224 +0,0 @@
-odoo.define('mail.model.Mailbox', function (require) {
-"use strict";
-
-var SearchableThread = require('mail.model.SearchableThread');
-
-var core = require('web.core');
-
-var _t = core._t;
-
-/**
- * Mailboxes, called "static channels" in earlier version of Odoo, are threads
- * that are not represented on the server. We can see them as "threads that are
- * not conversations". For instance, the well-known "Inbox" folder contains a
- * list of messages, but the inbox does not represent a conversation: Inbox is
- * modeled as a mailbox.
- */
-var Mailbox = SearchableThread.extend({
-
- /**
- * @override
- * @param {Object} params
- * @param {Object} params.data
- * @param {string} params.data.id the ID of the mailbox, without the ``mailbox_`` prefix.
- * @param {integer} [params.data.mailboxCounter=0] the initial mailbox counter of this mailbox.
- */
- init: function (params) {
- var data = params.data;
- // ID or mailboxes are always prefixed with 'mailbox_'
- data.id = 'mailbox_' + data.id;
- data.type = 'mailbox';
- this._super.apply(this, arguments);
- this._mailboxCounter = data.mailboxCounter || 0;
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Decrement the counter of this mailbox. It floors to 0.
- *
- * @param {integer} [num=1] how many, at most, the counter is decremented
- */
- decrementMailboxCounter: function (num) {
- num = _.isNumber(num) ? num : 1;
- this._mailboxCounter = Math.max(this._mailboxCounter - num, 0);
- },
- /**
- * Get the mailbox counter of this mailbox.
- *
- * @returns {integer}
- */
- getMailboxCounter: function () {
- return this._mailboxCounter;
- },
- /**
- * Get the preview of the messages from the inbox mailbox.
- * If there are multiple messages associated to a similar document or
- * channel, only keep the lastest message of this document/channel in the
- * previews.
- *
- * @returns {Promise<Object[]>}
- */
- getMessagePreviews: function () {
- var self = this;
-
- return this.fetchMessages().then(function (messages) {
- // pick only last message of chatter
- // items = list of objects
- // {
- // unreadCounter: {integer},
- // message: {mail.model.Message},
- // messageIDs: {integer[]},
- // }
- var items = [];
- _.each(messages, function (message) {
- var unreadCounter = 1;
- var messageIDs = [message.getID()];
- var similarItem = _.find(items, function (item) {
- return self._areMessagesFromSameDocumentThread(item.message, message) ||
- self._areMessagesFromSameChannel(item.message, message);
- });
- if (similarItem) {
- unreadCounter = similarItem.unreadCounter + 1;
- messageIDs = similarItem.messageIDs.concat(messageIDs);
- var index = _.findIndex(items, similarItem);
- items[index] = {
- unreadCounter: unreadCounter,
- message: message,
- messageIDs: messageIDs
- };
- } else {
- items.push({
- unreadCounter: unreadCounter,
- message: message,
- messageIDs: messageIDs,
- });
- }
- });
- return _.map(items, function (item) {
- return _.extend(item.message.getPreview(), {
- unreadCounter: item.unreadCounter,
- messageIDs: item.messageIDs,
- });
- });
- });
- },
- /**
- * Increment the counter of this mailbox
- *
- * @param {integer} [num=1] how many, the counter is incremented
- */
- incrementMailboxCounter: function (num) {
- num = _.isNumber(num) ? num : 1;
- this._mailboxCounter = Math.max(this._mailboxCounter + num, 0);
- },
- /**
- * Marks all messages from the mailbox as read. At the moment, this method
- * makes only sense for 'Inbox'.
- *
- * @param {Array} domain
- * @return {Promise} resolved when all messages have been marked as read
- * on the server
- */
- markAllMessagesAsRead: function (domain) {
- if (this._id === 'mailbox_inbox' && this.getMailboxCounter() > 0) {
- return this._rpc({
- model: 'mail.message',
- method: 'mark_all_as_read',
- kwargs: {
- domain: domain,
- },
- });
- }
- return Promise.resolve();
- },
- /**
- * @param {integer} newCounter
- */
- setMailboxCounter(newCounter) {
- this._mailboxCounter = newCounter;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Tell whether the two messages originate from the same channel.
- * If any one of them does not originate from a channel, returns false.
- *
- * @private
- * @param {mail.model.Message} message1
- * @param {mail.model.Message} message2
- * @returns {boolean}
- */
- _areMessagesFromSameChannel: function (message1, message2) {
- return message1.originatesFromChannel() &&
- message2.originatesFromChannel() &&
- message1.getOriginChannelID() === message2.getOriginChannelID();
- },
- /**
- * Tell whether the two messages are linked to the same document thread.
- * If any one of them is not linked to a document, returns false.
- *
- * @private
- * @param {mail.model.Message} message1
- * @param {mail.model.Message} message2
- * @returns {boolean}
- */
- _areMessagesFromSameDocumentThread: function (message1, message2) {
- return message1.isLinkedToDocumentThread() &&
- message2.isLinkedToDocumentThread() &&
- message1.getDocumentModel() === message2.getDocumentModel() &&
- message1.getDocumentID() === message2.getDocumentID();
- },
- /**
- * Get the domain to fetch all the messages in the current mailbox
- *
- * @override
- * @private
- * @returns {Array}
- * @throws on missing domain for the provided mailbox (mailboxes should
- * always have a domain on messages to fetch)
- */
- _getThreadDomain: function () {
- if (this._id === 'mailbox_inbox') {
- return [['needaction', '=', true]];
- } else if (this._id === 'mailbox_starred') {
- return [['starred', '=', true]];
- } else if (this._id === 'mailbox_history') {
- return [['needaction', '=', false]];
- } else if (this._id === 'mailbox_moderation') {
- return [['need_moderation', '=', true]];
- } else {
- throw (_.str(_t("Missing domain for mailbox with ID '%s'"), this._id));
- }
- },
- /**
- * Post a message from inbox. This is used when using the 'reply' feature
- * on a message that is linked to a document thread.
- *
- * @override
- * @private
- * @param {Object} messageData
- * @param {Object} options
- * @param {integer} options.documentID
- * @param {string} options.documentModel
- * @returns {$.Promise}
- */
- _postMessage: function (messageData, options) {
- var documentThread = this.call(
- 'mail_service',
- 'getDocumentThread',
- options.documentModel,
- options.documentID
- );
- return documentThread.postMessage(messageData);
- },
-});
-
-return Mailbox;
-
-});
diff --git a/addons/mail/static/src/js/models/threads/mixins/channel_seen_mixin.js b/addons/mail/static/src/js/models/threads/mixins/channel_seen_mixin.js
deleted file mode 100644
index 20a5bba5407..00000000000
--- a/addons/mail/static/src/js/models/threads/mixins/channel_seen_mixin.js
+++ /dev/null
@@ -1,229 +0,0 @@
-odoo.define('mail.model.ChannelSeenMixin', function (require) {
-"use strict";
-
-var session = require('web.session');
-
-/**
- * Mixin for enabling the "seen" feature on channels.
- */
-var ChannelSeenMixin = {
- /**
- * Initialize the internal data for seen feature on channels.
- *
- * Also listens on some internal events of the channel:
- *
- * - 'message_added': used to notify channel has been fetched
- *
- * @param {Object} params
- * @param {Object[]} [params.data.partners_info=[]]
- * @param {integer} [params.data.seen_partners_info[i].partner_id]
- * @param {integer|boolean} [params.data.seen_partners_info[i].fetched_message_id=false]
- * @param {integer|boolean} [params.data.seen_partners_info[i].seen_message_id=false]
- */
- init: function (params) {
- this._seenPartnersInfo = params.data.seen_partners_info || [];
-
- this.on('message_added', this, this._onSeenMessageAdded);
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Get list of members that have fetched the provided message on this
- * channel, but not yet seen it. Ignore current user in this list.
- *
- * @param {mail.model.Message} message
- * @returns {Object[]} list of members
- */
- getFetchedNotSeenMembers: function (message) {
- var otherPartnersInfo = _.filter(this._seenPartnersInfo, function (info) {
- return info.partner_id !== session.partner_id;
- });
-
- // seen partner IDs
- var seenPartnersInfo = _.filter(otherPartnersInfo, function (info) {
- return info.seen_message_id && info.seen_message_id >= message.getID();
- });
- var seenPartnerIDs = _.map(seenPartnersInfo, function (info) {
- return info.partner_id;
- });
-
- // fetched partner IDs (ignore seen)
- var fetchedPartnersInfo = _.filter(otherPartnersInfo, function (info) {
- var lastFetchedID = info.fetched_message_id || info.seen_message_id || 0;
- return lastFetchedID >= message.getID();
- });
- var fetchedPartnerIDs = _.map(fetchedPartnersInfo, function (info) {
- return info.partner_id;
- });
- fetchedPartnerIDs = _.reject(fetchedPartnerIDs, function (fetchedPartnerID) {
- return _.contains(seenPartnerIDs, fetchedPartnerID);
- });
-
- // fetched members
- var fetchedMembers = _.filter(this._members, function (member) {
- return _.contains(fetchedPartnerIDs, member.id);
- });
-
- return fetchedMembers;
- },
- /**
- * @returns {integer}
- */
- getLastMessageIDSeenByEveryone: function () {
- var otherPartnersInfo = _.filter(this._seenPartnersInfo, function (info) {
- return info.partner_id !== session.partner_id;
- });
- var seenMessageIDs = _.map(otherPartnersInfo, function (info) {
- return info.seen_message_id || 0;
- });
- if (_.isEmpty(seenMessageIDs)) {
- return 0;
- }
- return _.min(seenMessageIDs);
- },
- /**
- * Get list of members that have seen the provided message on this channel.
- * Ignore current user in this list.
- *
- * @param {mail.model.Message} message
- * @returns {Object[]} list of members
- */
- getSeenMembers: function (message) {
- var otherPartnersInfo = _.filter(this._seenPartnersInfo, function (info) {
- return info.partner_id !== session.partner_id;
- });
-
- // seen partner IDs
- var seenPartnersInfo = _.filter(otherPartnersInfo, function (info) {
- return info.seen_message_id >= message.getID();
- });
- var seenPartnerIDs = _.map(seenPartnersInfo, function (info) {
- return info.partner_id;
- });
-
- // seen members
- var seenMembers = _.filter(this._members, function (member) {
- return _.contains(seenPartnerIDs, member.id);
- });
-
- return seenMembers;
- },
- /**
- * @param {mail.model.Message} message
- * @returns {boolean}
- */
- hasEveryoneFetched: function (message) {
- var otherPartnersInfo = _.filter(this._seenPartnersInfo, function (info) {
- return info.partner_id !== session.partner_id;
- });
- var lastFetchedMessageIDs = _.map(otherPartnersInfo, function (info) {
- return info.fetched_message_id || info.seen_message_id || 0;
- });
- if (_.isEmpty(lastFetchedMessageIDs)) {
- return 0;
- }
- return message.getID() <= _.min(lastFetchedMessageIDs);
- },
- /**
- * @param {mail.model.Message} message
- * @returns {boolean}
- */
- hasEveryoneSeen: function (message) {
- var otherPartnersInfo = _.filter(this._seenPartnersInfo, function (info) {
- return info.partner_id !== session.partner_id;
- });
- var lastSeenMessageIDs = _.map(otherPartnersInfo, function (info) {
- return info.seen_message_id || 0;
- });
- if (_.isEmpty(lastSeenMessageIDs)) {
- return 0;
- }
- return message.getID() <= _.min(lastSeenMessageIDs);
- },
- /**
- * @param {mail.model.Message} message
- * @returns {boolean}
- */
- hasSomeoneFetched: function (message) {
- var otherPartnersInfo = _.filter(this._seenPartnersInfo, function (info) {
- return info.partner_id !== session.partner_id;
- });
- var lastFetchedMessageIDs = _.map(otherPartnersInfo, function (info) {
- return info.fetched_message_id || info.seen_message_id || 0;
- });
- if (_.isEmpty(lastFetchedMessageIDs)) {
- return false;
- }
- var fetched = _.find(lastFetchedMessageIDs, function (msgID) {
- return msgID >= message.getID();
- });
- return fetched;
- },
- /**
- * @param {mail.model.Message} message
- * @returns {boolean}
- */
- hasSomeoneSeen: function (message) {
- var otherPartnersInfo = _.filter(this._seenPartnersInfo, function (info) {
- return info.partner_id !== session.partner_id;
- });
- var lastSeenMessageIDs = _.map(otherPartnersInfo, function (info) {
- return info.seen_message_id || 0;
- });
- if (_.isEmpty(lastSeenMessageIDs)) {
- return false;
- }
- var seen = _.find(lastSeenMessageIDs, function (msgID) {
- return msgID >= message.getID();
- });
- return seen;
- },
- /**
- * @returns {boolean}
- */
- hasSeenFeature: function () {
- return true;
- },
- /**
- * @param {Object} data
- * @param {string} data.info either 'channel_seen' or 'channel_fetched'
- * @param {integer} data.partner_id
- * @param {integer} data.last_message_id
- */
- updateSeenPartnersInfo: function (data) {
- var seenInfo = _.findWhere(this._seenPartnersInfo, { partner_id: data.partner_id });
- if (!seenInfo) {
- seenInfo = {
- partner_id: data.partner_id,
- seen_message_id: 0,
- fetched_message_id: 0,
- };
- this._seenPartnersInfo.push(seenInfo);
- }
- if (data.info === 'channel_fetched') {
- seenInfo.fetched_message_id = data.last_message_id;
- } else if (data.info === 'channel_seen') {
- seenInfo.fetched_message_id = data.last_message_id;
- seenInfo.seen_message_id = data.last_message_id;
- }
- this.call('mail_service', 'getMailBus').trigger('update_channel', this.getID());
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * @private
- */
- _onSeenMessageAdded: function () {
- this.call('mail_service', 'notifyChannelFetched', { channelID: this._id });
- },
-};
-
-return ChannelSeenMixin;
-
-});
diff --git a/addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js b/addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js
deleted file mode 100644
index 3c98836dc70..00000000000
--- a/addons/mail/static/src/js/models/threads/mixins/thread_typing_mixin.js
+++ /dev/null
@@ -1,360 +0,0 @@
-odoo.define('mail.model.ThreadTypingMixin', function (require) {
-"use strict";
-
-var CCThrottleFunction = require('mail.model.CCThrottleFunction');
-var Timer = require('mail.model.Timer');
-var Timers = require('mail.model.Timers');
-
-var core = require('web.core');
-
-var _t = core._t;
-
-/**
- * Mixin for enabling the "is typing..." notification on a type of thread.
- */
-var ThreadTypingMixin = {
- // Default partner infos
- _DEFAULT_TYPING_PARTNER_ID: '_default',
- _DEFAULT_TYPING_PARTNER_NAME: 'Someone',
-
- /**
- * Initialize the internal data for typing feature on threads.
- *
- * Also listens on some internal events of the thread:
- *
- * - 'message_added': when a message is added, remove the author in the
- * typing partners.
- * - 'message_posted': when a message is posted, let the user have the
- * possibility to immediately notify if he types something right away,
- * instead of waiting for a throttle behaviour.
- */
- init: function () {
- // Store the last "myself typing" status that has been sent to the
- // server. This is useful in order to not notify the same typing
- // status multiple times.
- this._lastNotifiedMyselfTyping = false;
-
- // Timer of current user that is typing a very long text. When the
- // receivers do not receive any typing notification for a long time,
- // they assume that the related partner is no longer typing
- // something (e.g. they have closed the browser tab).
- // This is a timer to let others know that we are still typing
- // something, so that they do not assume we stopped typing
- // something.
- this._myselfLongTypingTimer = new Timer({
- duration: 50*1000,
- onTimeout: this._onMyselfLongTypingTimeout.bind(this),
- });
-
- // Timer of current user that was currently typing something, but
- // there is no change on the input for several time. This is used
- // in order to automatically notify other users that we have stopped
- // typing something, due to making no changes on the composer for
- // some time.
- this._myselfTypingInactivityTimer = new Timer({
- duration: 5*1000,
- onTimeout: this._onMyselfTypingInactivityTimeout.bind(this),
- });
-
- // Timers of users currently typing in the thread. This is useful
- // in order to automatically unregister typing users when we do not
- // receive any typing notification after a long time. Timers are
- // internally indexed by partnerID. The current user is ignored in
- // this list of timers.
- this._othersTypingTimers = new Timers({
- duration: 60*1000,
- onTimeout: this._onOthersTypingTimeout.bind(this),
- });
-
- // Clearable and cancellable throttled version of the
- // `doNotifyMyselfTyping` method. (basically `notifyMyselfTyping`
- // with slight pre- and post-processing)
- // @see {mail.model.ResetableThrottleFunction}
- // This is useful when the user posts a message and types something
- // else: he must notify immediately that he is typing something,
- // instead of waiting for the throttle internal timer.
- this._throttleNotifyMyselfTyping = CCThrottleFunction({
- duration: 2.5*1000,
- func: this._onNotifyMyselfTyping.bind(this),
- });
-
- // This is used to track the order of registered partners typing
- // something, in order to display the oldest typing partners.
- this._typingPartnerIDs = [];
-
- this.on('message_added', this, this._onTypingMessageAdded);
- this.on('message_posted', this, this._onTypingMessagePosted);
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Get the text to display when some partners are typing something on the
- * thread:
- *
- * - single typing partner:
- *
- * A is typing...
- *
- * - two typing partners:
- *
- * A and B are typing...
- *
- * - three or more typing partners:
- *
- * A, B and more are typing...
- *
- * The choice of the members name for display is not random: it displays
- * the user that have been typing for the longest time. Also, this function
- * is hard-coded to display at most 2 partners. This limitation comes from
- * how translation works in Odoo, for which unevaluated string cannot be
- * translated.
- *
- * @returns {string} list of members that are typing something on the thread
- * (excluding the current user).
- */
- getTypingMembersToText: function () {
- var typingPartnerIDs = this._typingPartnerIDs;
- var typingMembers = _.filter(this._members, function (member) {
- return _.contains(typingPartnerIDs, member.id);
- });
- var sortedTypingMembers = _.sortBy(typingMembers, function (member) {
- return _.indexOf(typingPartnerIDs, member.id);
- });
- var displayableTypingMembers = sortedTypingMembers.slice(0, 3);
-
- if (displayableTypingMembers.length === 0) {
- return '';
- } else if (displayableTypingMembers.length === 1) {
- return _.str.sprintf(_t("%s is typing..."), displayableTypingMembers[0].name);
- } else if (displayableTypingMembers.length === 2) {
- return _.str.sprintf(_t("%s and %s are typing..."),
- displayableTypingMembers[0].name,
- displayableTypingMembers[1].name);
- } else {
- return _.str.sprintf(_t("%s, %s and more are typing..."),
- displayableTypingMembers[0].name,
- displayableTypingMembers[1].name);
- }
- },
- /**
- * Threads with this mixin have the typing notification feature
- *
- * @returns {boolean}
- */
- hasTypingNotification: function () {
- return true;
- },
- /**
- * Tells if someone other than current user is typing something on this
- * thread.
- *
- * @returns {boolean}
- */
- isSomeoneTyping: function () {
- return !(_.isEmpty(this._typingPartnerIDs));
- },
- /**
- * Register someone that is currently typing something in this thread.
- * If this is the current user that is typing something, don't do anything
- * (we do not have to display anything)
- *
- * This method is ignored if we try to register the current user.
- *
- * @param {Object} params
- * @param {integer} params.partnerID ID of the partner linked to the user
- * currently typing something on the thread.
- */
- registerTyping: function (params) {
- if (this._isTypingMyselfInfo(params)) {
- return;
- }
- var partnerID = params.partnerID;
- this._othersTypingTimers.registerTimer({
- timeoutCallbackArguments: [partnerID],
- timerID: partnerID,
- });
- if (_.contains(this._typingPartnerIDs, partnerID)) {
- return;
- }
- this._typingPartnerIDs.push(partnerID);
- this._warnUpdatedTypingPartners();
- },
- /**
- * This method must be called when the user starts or stops typing something
- * in the composer of the thread.
- *
- * @param {Object} params
- * @param {boolean} params.typing tell whether the current is typing or not.
- */
- setMyselfTyping: function (params) {
- var typing = params.typing;
- if (this._lastNotifiedMyselfTyping === typing) {
- this._throttleNotifyMyselfTyping.cancel();
- } else {
- this._throttleNotifyMyselfTyping(params);
- }
-
- if (typing) {
- this._myselfTypingInactivityTimer.reset();
- } else {
- this._myselfTypingInactivityTimer.clear();
- }
- },
- /**
- * Unregister someone from currently typing something in this thread.
- *
- * @param {Object} params
- * @param {integer} params.partnerID ID of the partner related to the user
- * that is currently typing something
- */
- unregisterTyping: function (params) {
- var partnerID = params.partnerID;
- this._othersTypingTimers.unregisterTimer({ timerID: partnerID });
- if (!_.contains(this._typingPartnerIDs, partnerID)) {
- return;
- }
- this._typingPartnerIDs = _.reject(this._typingPartnerIDs, function (id) {
- return id === partnerID;
- });
- this._warnUpdatedTypingPartners();
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Tells whether the provided information on a partner is related to the
- * current user or not.
- *
- * @abstract
- * @private
- * @param {Object} params
- * @param {integer} params.partner ID of partner to check
- */
- _isTypingMyselfInfo: function (params) {
- return false;
- },
- /**
- * Notify to the server that the current user either starts or stops typing
- * something.
- *
- * @abstract
- * @private
- * @param {Object} params
- * @param {boolean} params.typing whether we are typing something or not
- * @returns {Promise} resolved if the server is notified, rejected
- * otherwise
- */
- _notifyMyselfTyping: function (params) {
- return Promise.resolve();
- },
- /**
- * Warn views that the list of users that are currently typing on this
- * thread has been updated.
- *
- * @abstract
- * @private
- */
- _warnUpdatedTypingPartners: function () {},
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Called when current user is typing something for a long time. In order
- * to not let other users assume that we are no longer typing something, we
- * must notify again that we are typing something.
- *
- * @private
- */
- _onMyselfLongTypingTimeout: function () {
- this._throttleNotifyMyselfTyping.clear();
- this._throttleNotifyMyselfTyping({ typing: true });
- },
- /**
- * Called when current user has something typed in the composer, but is
- * inactive for some time. In this case, he automatically notifies that he
- * is no longer typing something
- *
- * @private
- */
- _onMyselfTypingInactivityTimeout: function () {
- this._throttleNotifyMyselfTyping.clear();
- this._throttleNotifyMyselfTyping({ typing: false });
- },
- /**
- * Called by throttled version of notify myself typing
- *
- * Notify to the server that the current user either starts or stops typing
- * something. Remember last notified stuff from the server, and update
- * related typing timers.
- *
- * @private
- * @param {Object} params
- * @param {boolean} params.typing whether we are typing something or not.
- */
- _onNotifyMyselfTyping: function (params) {
- var typing = params.typing;
- this._lastNotifiedMyselfTyping = typing;
- this._notifyMyselfTyping(params);
- if (typing) {
- this._myselfLongTypingTimer.reset();
- } else {
- this._myselfLongTypingTimer.clear();
- }
- },
- /**
- * Called when current user do not receive a typing notification of someone
- * else typing for a long time. In this case, we assume that this person is
- * no longer typing something.
- *
- * @private
- * @param {integer} partnerID partnerID of the person we assume he is no
- * longer typing something.
- */
- _onOthersTypingTimeout: function (partnerID) {
- this.unregisterTyping({ partnerID: partnerID });
- },
- /**
- * Called when a new message is added to the thread
- * On receiving a message from a typing partner, unregister this partner
- * from typing partners (otherwise, it will still display it until timeout).
- *
- * @private
- * @param {mail.model.AbstractMessage} message
- */
- _onTypingMessageAdded: function (message) {
- var partnerID = message.hasAuthor() ?
- message.getAuthorID() :
- this._DEFAULT_TYPING_PARTNER_ID;
- this.unregisterTyping({ partnerID: partnerID });
- },
- /**
- * Called when current user has posted a message on this thread.
- *
- * The current user receives the possibility to immediately notify the
- * other users if he is typing something else.
- *
- * Refresh the context for the current user to notify that he starts or
- * stops typing something. In other words, when this function is called and
- * then the current user types something, it immediately notifies the
- * server as if it is the first time he is typing something.
- *
- * @private
- */
- _onTypingMessagePosted: function () {
- this._lastNotifiedMyselfTyping = false;
- this._throttleNotifyMyselfTyping.clear();
- this._myselfLongTypingTimer.clear();
- this._myselfTypingInactivityTimer.clear();
- },
-};
-
-return ThreadTypingMixin;
-
-});
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
deleted file mode 100644
index 3dab9337502..00000000000
--- a/addons/mail/static/src/js/models/threads/multi_user_channel.js
+++ /dev/null
@@ -1,73 +0,0 @@
-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 = 'multi_user_channel';
- this._public = data.public !== 'private';
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Returns the title to display in thread window's headers.
- * For channels, the title is prefixed with "#".
- *
- * @override
- * @returns {string|Object} the name of the thread by default (see getName)
- */
- getTitle: function () {
- return "#" + this._super.apply(this, arguments);
- },
- /**
- * 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;
- },
- /**
- * @returns {boolean}
- */
- isPublic: function () {
- return this._public;
- },
- /**
- * 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/searchable_thread.js b/addons/mail/static/src/js/models/threads/searchable_thread.js
deleted file mode 100644
index c2a48a2d410..00000000000
--- a/addons/mail/static/src/js/models/threads/searchable_thread.js
+++ /dev/null
@@ -1,244 +0,0 @@
-odoo.define('mail.model.SearchableThread', function (require) {
-"use strict";
-
-var Thread = require('mail.model.Thread');
-
-var session = require('web.session');
-
-/**
- * This is a version of a thread that is handling a cache.
- * Basically, this cache is an object indexed by stringified indexes.
- * These indexes represent the domain of messages that match this domain.
- *
- * 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 SearchableThread = Thread.extend({
-
- /**
- * @override
- * @param {Object} params
- * @param {Object} params.data
- * @param {integer} [params.data.seen_message_id=undefined]
- */
- init: function (params) {
- this._super.apply(this, arguments);
-
- this._cache = {
- '[]': {
- allHistoryLoaded: false,
- loaded: false,
- messages: [],
- }
- };
- this._lastSeenMessageID = params.data.seen_message_id;
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * @override
- * @param {Array} [domain]
- * @param {boolean} [loadMore=false]
- */
- fetchMessages: function (domain, loadMore) {
- if (loadMore) {
- return this._fetchMessages(domain, true);
- }
- var cache = this._getCache(domain);
- if (cache.loaded) {
- return Promise.all(cache.messages);
- } else {
- return this._fetchMessages(domain);
- }
- },
- /**
- * Get the last seen message for a given thread
- *
- * @return {integer|null} last seen message ID (if any)
- */
- getLastSeenMessageID: function () {
- return this._lastSeenMessageID || null;
- },
- /**
- * @override
- * @param {Object} [options={}]
- * @param {Array} [options.domain=[]]
- * @return {mail.model.Message[]}
- */
- getMessages: function (options) {
- options = options || {};
- var domain = options.domain || [];
- return this._getCache(domain).messages;
- },
- /**
- * Override so that a thread can tell whether there are message based on
- * the domain.
- *
- * @override
- * @param {Object} [options={}]
- * @param {Array} [options.domain=[]]
- * @returns {boolean}
- */
- hasMessages: function (options) {
- options = options || {};
- var domain = options.domain || [];
- return !_.isEmpty(this.getMessages({ domain: domain }));
- },
- /**
- * Invalidate the caches of the thread
- */
- invalidateCaches: function () {
- this._cache = _.pick(this._cache, '[]');
- },
- /**
- * State whether all messages have been loaded or not
- *
- * @param {Array} domain
- * @return {boolean}
- */
- isAllHistoryLoaded: function (domain) {
- return this._getCache(domain).allHistoryLoaded;
- },
- /**
- * Remove message with ID `messageID` from this thread
- *
- * This is equivalent to removing this message from all the caches
- *
- * @param {integer} messageID
- */
- removeMessage: function (messageID) {
- _.each(this._cache, function (cache) {
- cache.messages = _.reject(cache.messages, function (message) {
- return message.getID() === messageID;
- });
- });
- },
- /**
- * Set 'messageID' as the ID of the last message seen in this thread
- *
- * @param {integer} messageID
- */
- setLastSeenMessageID: function (messageID) {
- this._lastSeenMessageID = messageID;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Add the message to this thread
- *
- * @override
- * @private
- * @param {mail.model.Message} message
- * @param {Object} [options={}]
- * @param {Array} [options.domain=[]]
- * @param {boolean} [options.incrementUnread=false]
- */
- _addMessage: function (message, options) {
- options = options || {};
- this._super.apply(this, arguments);
- var cache = this._getCache(options.domain || []);
- var index = _.sortedIndex(cache.messages, message, function (msg) {
- return msg.getID();
- });
- if (cache.messages[index] !== message) {
- cache.messages.splice(index, 0, message);
- }
- if (
- !message.isMyselfAuthor() &&
- options.incrementUnread &&
- message.getType() !== 'notification'
- ) {
- this._incrementUnreadCounter();
- }
- },
- /**
- * Gets messages from thread
- *
- * @override
- * @private
- * @param {Array} [pDomain] filter on the messages of the channel
- * @param {boolean} [loadMore] Whether it should load more message
- * @return {Promise<mail.model.Message[]>} resolved with list of messages
- */
- _fetchMessages: function (pDomain, loadMore) {
- var self = this;
- var domain = this._getThreadDomain();
- var cache = this._getCache(pDomain);
- if (pDomain) {
- domain = domain.concat(pDomain || []);
- }
- if (loadMore) {
- var minMessageID = cache.messages[0].getID();
- domain = [['id', '<', minMessageID]].concat(domain);
- }
- return this._rpc({
- model: 'mail.message',
- method: 'message_fetch',
- args: [domain],
- kwargs: this._getFetchMessagesKwargs(),
- }).then(function (messages) {
- if (!cache.allHistoryLoaded) {
- cache.allHistoryLoaded = messages.length < self._FETCH_LIMIT;
- }
- cache.loaded = true;
- _.each(messages, function (message) {
- self.call('mail_service', 'addMessage', message, {
- silent: true,
- domain: pDomain,
- });
- });
- cache = self._getCache(pDomain || []);
- return cache.messages;
- });
- },
- /**
- * Get thread content from the cache
- * Useful to get cached messages.
- *
- * @private
- * @param {Array} domain
- * @return {Object|undefined}
- */
- _getCache: function (domain) {
- var stringifiedDomain = JSON.stringify(domain || []);
- if (!this._cache[stringifiedDomain]) {
- this._cache[stringifiedDomain] = {
- allHistoryLoaded: false,
- loaded: false,
- messages: [],
- };
- }
- return this._cache[stringifiedDomain];
- },
- /**
- * Get the kwargs that are passed to the server with the 'message_fetch'
- * RPC.
- *
- * @private
- * @returns {Object}
- */
- _getFetchMessagesKwargs: function () {
- return {
- limit: this._FETCH_LIMIT,
- context: session.user_context
- };
- },
- /**
- * Get the domain to fetch all the messages in the current thread
- *
- * @abstract
- * @private
- * @returns {Array}
- */
- _getThreadDomain: function () {},
-});
-
-return SearchableThread;
-
-});
diff --git a/addons/mail/static/src/js/models/threads/thread.js b/addons/mail/static/src/js/models/threads/thread.js
deleted file mode 100644
index 1e99ee84c11..00000000000
--- a/addons/mail/static/src/js/models/threads/thread.js
+++ /dev/null
@@ -1,383 +0,0 @@
-odoo.define('mail.model.Thread', function (require) {
-"use strict";
-
-var emojis = require('mail.emojis');
-var AbstractThread = require('mail.model.AbstractThread');
-var mailUtils = require('mail.utils');
-
-var ServicesMixin = require('web.ServicesMixin');
-
-/**
- * This is the super class modeling threads in JS.
- * Such threads are stored in the mail manager, and any piece of JS code whose
- * logic relies on threads must ideally interact with such objects.
- *
- * In particular, channels and mailboxes are two different kinds of threads.
- */
-var Thread = AbstractThread.extend(ServicesMixin, {
- // max number of fetched messages from the server
- _FETCH_LIMIT: 30,
- /**
- * @override
- * @param {Object} params
- * @param {mail.Manager} params.parent
- * @param {Object} params.data
- * @param {string} [params.data.channel_type]
- * @param {string} params.data.name
- * @param {string} [params.data.type]
- */
- init: function (params) {
- this._super.apply(this, arguments);
- // threads are not detached by default
- this._detached = false;
- // on 1st request to getPreview, fetch data if incomplete. Otherwise it
- // means that there is no message in this channel.
- this._previewed = false;
- this._type = params.data.type || params.data.channel_type;
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Updates the _detached state of the thread. Must be overridden to reflect
- * the new state in the interface.
- */
- close: function () {
- this._detached = false;
- this._folded = false;
- this._warnUpdatedWindowState();
- },
- /**
- * Updates the _detached state of the thread. Must be overridden to reflect
- * the new state in the interface.
- *
- * @param {Object} [options={}]
- * @param {boolean} [options.keepFoldState=false] if set, keep the fold state
- * of this thread. Otherwise unfold it while detaching it.
- * @param {boolean} [options.passively=false] if set, if the thread window
- * will be created passively.
- * @returns {Promise}
- */
- detach: function (options) {
- options = options || {};
- this._detached = true;
- this._folded = options.keepFoldState ? this._folded : false;
- return this._warnUpdatedWindowState({
- passively: options.passively,
- });
- },
- /**
- * Fetch the list of messages in this thread.
- * By default, a thread has no messages.
- *
- * Note that this method only returns some messages, as we do not want to
- * fetch all messages of a thread at once, just to read the last message.
- *
- * As a result, this method fetches only some messages of the thread,
- * starting from the last message in the thread. At most, it fetches
- * `this.LIMIT` number of messages at a time.
- *
- * @abstract
- * @returns {Promise<mail.model.Message[]>}
- */
- fetchMessages: function () {
- return Promise.resolve([]);
- },
- /**
- * Updates the folded state of the thread. Must be overridden to reflect
- * the new state in the interface.
- *
- * @override
- * @param {boolean} folded
- */
- fold: function (folded) {
- this._super.apply(this, arguments);
- this._detached = true; // auto-detach the thread
- this._warnUpdatedWindowState();
- },
- /**
- * Get the list of available commands for the thread.
- * By default, threads do not have any available command.
- *
- * @returns {Array}
- */
- getCommands: function () {
- return [];
- },
- /**
- * Get the listeners of the thread.
- * By default, a thread has not listener.
- *
- * @abstract
- * @returns {Promise<Array<Object[]>>}
- */
- getMentionPartnerSuggestions: function () {
- return Promise.resolve([]);
- },
- /**
- * Returns the information required to render the preview of this channel.
- *
- * @returns {Object} a valid object for the rendering of previews
- * (@see mail.Preview template)
- */
- getPreview: function () {
- return {
- id: this.getID(),
- imageSRC: '/mail/static/src/img/smiley/avatar.jpg',
- isTwoUserThread: this.isTwoUserThread(),
- status: this.getStatus(),
- title: this.getName(),
- unreadCounter: this.getUnreadCounter(),
- };
- },
- /**
- * @returns {string}
- */
- getType: function () {
- return this._type;
- },
- /**
- * State whether this channel has been previewed
- *
- * A channel that has been previewed means that it had the necessary data
- * to display its preview format. A channel needs its meta data and the
- * last message in order to build its preview format.
- *
- * This is useful in order to not fetch preview info on this channel more
- * than once on channels that have no message at all.
- *
- * Any received message updates the last_message, so a channel should
- * always have all the necessary information to display its preview after
- * the 1st time.
- *
- * @returns {boolean}
- */
- hasBeenPreviewed: function () {
- return this._previewed;
- },
- /**
- * State whether there are unread messages in this thread
- *
- * @returns {boolean}
- */
- hasUnreadMessages: function () {
- return this._unreadCounter !== 0;
- },
- /**
- * Increments the needaction counter of this thread.
- *
- * @abstract
- */
- incrementNeedactionCounter: function () {},
- /**
- * States whether the thread should be auto-selected on creation
- *
- * Note that this is not of the responsibility of the thread: it only
- * provides guidance for the object that uses threads (e.g. mail.Discuss
- * must listen on threads and auto-select the thread if autoswitch is set).
- *
- * By default, threads are not in autoswitch mode.
- *
- * @returns {boolean}
- */
- isAutoswitch: function () {
- return false;
- },
- /**
- * States whether this thread is a channel or not. A thread is a channel if
- * it is an instance of mail.model.Channel (direct or indirect).
- * By default, any thread is not a channel
- *
- * @returns {boolean}
- */
- isChannel: function () {
- return false;
- },
- /**
- * States whether this thread is detached or not.
- * A thread that is detached must have a thread window linked to itself.
- *
- * @return {boolean}
- */
- isDetached: function () {
- return this._detached;
- },
- /**
- * States whether the thread is linked to a document
- * By default, threads are not linked to a document.
- *
- * @returns {boolean}
- */
- isLinkedToDocument: function () {
- return false;
- },
- /**
- * 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).
- *
- * By default, thread have not mass-mailings feature set.
- *
- * @return {boolean}
- */
- isMassMailing: function () {
- return false;
- },
- /**
-
- * States whether the thread is moderated or not.
- * By default, threads are not moderated.
- *
- * @returns {boolean}
- */
- isModerated: function () {
- return false;
- },
- /**
- * States whether the current user is moderator of this thread.
- * By default, the current user is not moderator of this thread.
- *
- * @returns {boolean}
- */
- 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;
- },
- /**
- * Mark this channel as previewed
- *
- * This is useful in order to not fetch preview info on this channel
- * is the server has no preview in the first place.
- *
- * Note: preview fetch is useful only when the channel contains messages
- * that have not been fetched at all. After that, this channel instance
- * is updated regularly so that the most up-to-date info are available
- * to make the preview of this channel.
- */
- markAsPreviewed: function () {
- this._previewed = true;
- },
- /**
- * Set the name of this thread
- *
- * @param {string} newName
- */
- setName: function (newName) {
- this._name = newName;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @abstract
- * @private
- * @return {Promise}
- */
- _fetchMessages: function () {
- return Promise.resolve();
- },
- /**
- * 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) {
- // 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
- // linkification a bit everywhere. Ideally we want to keep the content
- // as text internally and only make html enrichment at display time but
- // the current design makes this quite hard to do.
- var body = mailUtils.parseAndTransform(_.str.trim(data.content), mailUtils.addLink);
- body = this._generateEmojis(body);
- var messageData = {
- partner_ids: data.partner_ids,
- channel_ids: data.channel_ids,
- body: body,
- attachment_ids: data.attachment_ids,
- canned_response_ids: data.canned_response_ids,
- };
- if ('subject' in data) {
- messageData.subject = data.subject;
- }
- return this._super.apply(this, arguments).then(function () {
- return messageData;
- });
- },
- /**
- * 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);
- },
- /**
- * Warn other mail components that the window state of this thread has
- * changed (it has become closed, folded, unfolded, detached, etc.)
- *
- * @private
- * @param {Object} [options={}]
- * @param {boolean} [options.passively=false]
- * @returns {Promise}
- */
- _warnUpdatedWindowState: function (options) {
- options = options || {};
- return this.call('mail_service', 'updateThreadWindow', this.getID(), options);
- },
-});
-
-return Thread;
-
-});
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
deleted file mode 100644
index 54a4145bd13..00000000000
--- a/addons/mail/static/src/js/models/threads/two_user_channel.js
+++ /dev/null
@@ -1,32 +0,0 @@
-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;
-
-});