diff options
| author | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
|---|---|---|
| committer | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
| commit | 3fea5b2136627c1d7e83b2a5e111952fe3eb10a1 (patch) | |
| tree | 5eefad07972a87ce4689b83260f981bc87aadad4 /addons/mail/static/src/js/models/threads | |
| parent | 0a82848606151ef60eb6523e9c4c161171f214e7 (diff) | |
[REF] mail, *: refactor messaging with OWL
This commit is a significant rewriting of client-side discuss, chatter,
chat window, and messaging menu using OWL. The behavior should be broadly
the same, with some slight functional changes here and there.
From a technical standpoint, the code of messaging is mainly organized in 2
main groups of modules:
- models, which are logical entities that depict the client-side state of
messaging as a whole.
- components, which are in charge of displaying information from models.
This refactoring also introduces new JS guidelines regarding folder structure
(/static) and naming rules for JS modules.
Community PR: https://github.com/odoo/odoo/pull/39023
Enterprise PR: https://github.com/odoo/enterprise/pull/6249
Task-1914207
This PR is a collaborative work by Alexandre, Julien, Sébastien and Xavier,
with the precious help of Lucas to speed it up towards the end.
closes odoo/odoo#39023
Related: odoo/enterprise#6249
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Co-authored-by: Alexandre Kühn <aku@odoo.com>
Co-authored-by: Julien Giannone <jgi@odoo.com>
Co-authored-by: Lucas Perais <lpe@odoo.com>
Co-authored-by: Sébastien Theys <seb@odoo.com>
Co-authored-by: Xavier Dubuc <xdu@odoo.com>
Diffstat (limited to 'addons/mail/static/src/js/models/threads')
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; - -}); |
