summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/models
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-06-15 18:12:21 +0000
committerSébastien Theys <seb@odoo.com>2020-06-15 18:12:21 +0000
commit3fea5b2136627c1d7e83b2a5e111952fe3eb10a1 (patch)
tree5eefad07972a87ce4689b83260f981bc87aadad4 /addons/mail/static/src/js/models
parent0a82848606151ef60eb6523e9c4c161171f214e7 (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')
-rw-r--r--addons/mail/static/src/js/models/mail_failure.js127
-rw-r--r--addons/mail/static/src/js/models/messages/abstract_message.js462
-rw-r--r--addons/mail/static/src/js/models/messages/message.js806
-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
-rw-r--r--addons/mail/static/src/js/models/utils/cc_throttle_function.js181
-rw-r--r--addons/mail/static/src/js/models/utils/timer.js69
-rw-r--r--addons/mail/static/src/js/models/utils/timers.js82
19 files changed, 0 insertions, 4620 deletions
diff --git a/addons/mail/static/src/js/models/mail_failure.js b/addons/mail/static/src/js/models/mail_failure.js
deleted file mode 100644
index 815c95a034f..00000000000
--- a/addons/mail/static/src/js/models/mail_failure.js
+++ /dev/null
@@ -1,127 +0,0 @@
-odoo.define('mail.model.MailFailure', function (require) {
-"use strict";
-
-var Class = require('web.Class');
-var core = require('web.core');
-var Mixins = require('web.mixins');
-var ServicesMixin = require('web.ServicesMixin');
-var time = require('web.time');
-
-var _t = core._t;
-
-var MailFailure = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
- /**
- * @param {mail.Manager} parent
- * @param {Object} data
- * @param {string} data.failure_type
- * @param {string} data.last_message_date
- * @param {integer} data.message_id
- * @param {string} data.model
- * @param {string} data.model_name
- * @param {string} data.module_icon
- * @param {Array} data.notifications
- * @param {string} data.record_name
- * @param {integer} data.res_id
- * @param {string} data.uuid
- */
- init: function (parent, data) {
- Mixins.EventDispatcherMixin.init.call(this, arguments);
- this.setParent(parent);
-
- this._documentID = data.res_id;
- this._documentModel = data.model;
- this._lastMessageDate = moment(); // by default: current datetime
- this._messageID = data.message_id;
- this._messageType = data.message_type;
- this._modelName = data.model_name;
- this._moduleIcon = data.module_icon;
- this._notifications = data.notifications;
-
- if (data.last_message_date) {
- this._lastMessageDate = moment(time.str_to_datetime(data.last_message_date));
- }
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Get the document model that this mail failure is linked to.
- *
- * @returns {string}
- */
- getDocumentModel: function () {
- return this._documentModel;
- },
- /**
- * Get the document ID that this mail failure is linked to.
- *
- * @returns {integer}
- */
- getDocumentID: function () {
- return this._documentID;
- },
- /**
- * Get the ID of the message that this mail failure is related to.
- *
- * @returns {integer}
- */
- getMessageID: function () {
- return this._messageID;
- },
- /**
- * Get the type of the message that this mail failure is related to.
- *
- * @returns {string}
- */
- getMessageType() {
- return this._messageType;
- },
- /**
- * Get a valid object for the 'mail.preview' template
- *
- * @returns {Object}
- */
- getPreview: function () {
- var preview = {
- body: this._getPreviewBody(),
- date: this._lastMessageDate,
- documentID: this._documentID,
- documentModel: this._documentModel,
- id: 'mail_failure',
- imageSRC: this._getPreviewImage(),
- title: this._modelName,
- };
- return preview;
- },
- /**
- * Tell whether the mail failure comes from a message in a document.
- *
- * @returns {boolean}
- */
- isLinkedToDocument: function () {
- return !!(this._documentModel && this._documentID);
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @returns {string}
- */
- _getPreviewBody() {
- return _t("An error occurred when sending an email.");
- },
- /**
- * @returns {string}
- */
- _getPreviewImage() {
- return '/mail/static/src/img/smiley/mailfailure.jpg';
- },
-});
-
-return MailFailure;
-
-});
diff --git a/addons/mail/static/src/js/models/messages/abstract_message.js b/addons/mail/static/src/js/models/messages/abstract_message.js
deleted file mode 100644
index b293f6f1db0..00000000000
--- a/addons/mail/static/src/js/models/messages/abstract_message.js
+++ /dev/null
@@ -1,462 +0,0 @@
-odoo.define('mail.model.AbstractMessage', function (require) {
-"use strict";
-
-var mailUtils = require('mail.utils');
-
-var Class = require('web.Class');
-var core = require('web.core');
-var session = require('web.session');
-var time = require('web.time');
-
-var _t = core._t;
-
-/**
- * This is an abstract class for modeling messages in JS.
- * The purpose of this interface is to make im_livechat compatible with
- * mail.widget.Thread, as this widget was designed to work with messages that
- * are instances of mail.model.Messages.
- *
- * Ideally, im_livechat should also handle mail.model.Message, but this is not
- * feasible for the moment, as mail.model.Message requires mail.Manager to work,
- * and this module should not leak outside of the backend, hence the use of
- * mail.model.AbstractMessage as a work-around.
- */
-var AbstractMessage = Class.extend({
-
- /**
- * @param {Widget} parent
- * @param {Object} data
- * @param {Array} [data.attachment_ids=[]]
- * @param {Array} [data.author_id]
- * @param {string} [data.body = ""]
- * @param {string} [data.date] the server-format date time of the message.
- * If not provided, use current date time for this message.
- * @param {integer} data.id
- * @param {boolean} [data.is_discussion = false]
- * @param {boolean} [data.is_notification = false]
- * @param {string} [data.message_type = undefined]
- */
- init: function (parent, data) {
- this._attachmentIDs = data.attachment_ids || [];
- this._body = data.body || "";
- // by default: current datetime
- this._date = data.date ? moment(time.str_to_datetime(data.date)) : moment();
- this._id = data.id;
- this._isDiscussion = data.is_discussion;
- this._isNotification = data.is_notification;
- this._serverAuthorID = data.author_id;
- this._type = data.message_type || undefined;
-
- this._processAttachmentURL();
- this._attachmentIDs.forEach(function (attachment) {
- attachment.filename = attachment.filename || attachment.name || _t("unnamed");
- });
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Get the list of files attached to this message.
- * Note that attachments are stored with server-format
- *
- * @return {Object[]}
- */
- getAttachments: function () {
- return this._attachmentIDs;
- },
- /**
- * Get the server ID (number) of the author of this message
- * If there are no author, return -1;
- *
- * @return {integer}
- */
- getAuthorID: function () {
- if (!this.hasAuthor()) {
- return -1;
- }
- return this._serverAuthorID[0];
- },
- /**
- * Threads do not have an im status by default
- *
- * @return {undefined}
- */
- getAuthorImStatus: function () {
- return undefined;
- },
- /**
- * Get the relative url of the avatar to display next to the message
- *
- * @abstract
- * @return {string}
- */
- getAvatarSource: function () {
- if (this.hasAuthor()) {
- return '/web/image/res.partner/' + this.getAuthorID() + '/image_128';
- }
- },
- /**
- * Get the body content of this message
- *
- * @return {string}
- */
- getBody: function () {
- return this._body;
- },
- /**
- * @return {moment}
- */
- getDate: function () {
- return this._date;
- },
- /**
- * Get the date day of this message
- *
- * @return {string}
- */
- getDateDay: function () {
- var date = this.getDate().format('YYYY-MM-DD');
- if (date === moment().format('YYYY-MM-DD')) {
- return _t("Today");
- } else if (date === moment().subtract(1, 'days').format('YYYY-MM-DD')) {
- return _t("Yesterday");
- }
- return this.getDate().format('LL');
- },
- /**
- * Get the name of the author, if there is an author of this message
- * If there are no author of this message, returns 'null'
- *
- * @return {string}
- */
- getDisplayedAuthor: function () {
- return this.hasAuthor() ? this._getAuthorName() : null;
- },
- /**
- * Get the server ID (number) of this message
- *
- * @override
- * @return {integer}
- */
- getID: function () {
- return this._id;
- },
- /**
- * Get the list of images attached to this message.
- * Note that attachments are stored with server-format
- *
- * @return {Object[]}
- */
- getImageAttachments: function () {
- return _.filter(this.getAttachments(), function (attachment) {
- return attachment.mimetype && attachment.mimetype.split('/')[0] === 'image';
- });
- },
- /**
- * Get the list of non-images attached to this message.
- * Note that attachments are stored with server-format
- *
- * @return {Object[]}
- */
- getNonImageAttachments: function () {
- return _.difference(this.getAttachments(), this.getImageAttachments());
- },
- /**
- * Gets the class to use as the notification icon.
- *
- * @returns {string}
- */
- getNotificationIcon() {
- if (!this.hasNotificationsError()) {
- return 'fa fa-envelope-o';
- }
- return 'fa fa-envelope';
- },
- /**
- * Gets the list of notifications of this message, in no specific order.
- * By default messages do not have notifications.
- *
- * @returns {Object[]}
- */
- getNotifications() {
- return [];
- },
- /**
- * Gets the text to display next to the notification icon.
- *
- * @returns {string}
- */
- getNotificationText() {
- return '';
- },
- /**
- * Get the time elapsed between sent message and now
- *
- * @return {string}
- */
- getTimeElapsed: function () {
- return mailUtils.timeFromNow(this.getDate());
- },
- /**
- * Get the type of message (e.g. 'comment', 'email', 'notification', ...)
- * By default, messages are of type 'undefined'
- *
- * @override
- * @return {string|undefined}
- */
- getType: function () {
- return this._type;
- },
- /**
- * State whether this message contains some attachments.
- *
- * @override
- * @return {boolean}
- */
- hasAttachments: function () {
- return this.getAttachments().length > 0;
- },
- /**
- * State whether this message has an author
- *
- * @return {boolean}
- */
- hasAuthor: function () {
- return !!(this._serverAuthorID && this._serverAuthorID[0]);
- },
- /**
- * State whether this message has an email of its sender.
- * By default, messages do not have any email of its sender.
- *
- * @return {string}
- */
- hasEmailFrom: function () {
- return false;
- },
- /**
- * State whether this image contains images attachments
- *
- * @return {boolean}
- */
- hasImageAttachments: function () {
- return _.some(this.getAttachments(), function (attachment) {
- return attachment.mimetype && attachment.mimetype.split('/')[0] === 'image';
- });
- },
- /**
- * State whether this image contains non-images attachments
- *
- * @return {boolean}
- */
- hasNonImageAttachments: function () {
- return _.some(this.getAttachments(), function (attachment) {
- return !(attachment.mimetype && attachment.mimetype.split('/')[0] === 'image');
- });
- },
- /**
- * States whether this message has some notifications.
- *
- * @returns {boolean}
- */
- hasNotifications() {
- return this.getNotifications().length > 0;
- },
- /**
- * States whether this message has notifications that are in error.
- *
- * @returns {boolean}
- */
- hasNotificationsError() {
- return this.getNotifications().some(notif =>
- notif.notification_status === 'exception' ||
- notif.notification_status === 'bounce'
- );
- },
- /**
- * State whether this message originates from a channel.
- * By default, messages do not originate from a channel.
- *
- * @override
- * @return {boolean}
- */
- originatesFromChannel: function () {
- return false;
- },
- /**
- * State whether this message has a subject
- * By default, messages do not have any subject.
- *
- * @return {boolean}
- */
- hasSubject: function () {
- return false;
- },
- /**
- * State whether this message is empty
- *
- * @return {boolean}
- */
- isEmpty: function () {
- return !this.hasTrackingValues() &&
- !this.hasAttachments() &&
- !this.getBody();
- },
- /**
- * By default, messages do not have any subtype description
- *
- * @return {boolean}
- */
- hasSubtypeDescription: function () {
- return false;
- },
- /**
- * State whether this message contains some tracking values
- * By default, messages do not have any tracking values.
- *
- * @return {boolean}
- */
- hasTrackingValues: function () {
- return false;
- },
- /**
- * State whether this message is a discussion
- *
- * @return {boolean}
- */
- isDiscussion: function () {
- return this._isDiscussion;
- },
- /**
- * State whether this message is linked to a document thread
- * By default, messages are not linked to a document thread.
- *
- * @return {boolean}
- */
- isLinkedToDocumentThread: function () {
- return false;
- },
- /**
- * State whether this message is needaction
- * By default, messages are not needaction.
- *
- * @return {boolean}
- */
- isNeedaction: function () {
- return false;
- },
- /**
- * State whether this message is a note (i.e. a message from "Log note")
- *
- * @return {boolean}
- */
- isNote: function () {
- return this._isNote;
- },
- /**
- * State whether this message is a notification
- *
- * User notifications are defined as either
- * - notes
- * - pushed to user Inbox or email through classic notification process
- * - not linked to any document, meaning model and res_id are void
- *
- * This is useful in order to display white background for user
- * notifications in chatter
- *
- * @returns {boolean}
- */
- isNotification: function () {
- return this._isNotification;
- },
- /**
- * State whether this message is starred
- * By default, messages are not starred.
- *
- * @return {boolean}
- */
- isStarred: function () {
- return false;
- },
- /**
- * State whether this message is a system notification
- * By default, messages are not system notifications
- *
- * @override
- * @return {boolean}
- */
- isSystemNotification: function () {
- return false;
- },
- /**
- * States whether the current message needs moderation in general.
- * By default, messages do not require any moderation.
- *
- * @returns {boolean}
- */
- needsModeration: function () {
- return false;
- },
- /**
- * @params {integer[]} attachmentIDs
- */
- removeAttachments: function (attachmentIDs) {
- this._attachmentIDs = _.reject(this._attachmentIDs, function (attachment) {
- return _.contains(attachmentIDs, attachment.id);
- });
- },
- /**
- * State whether this message should redirect to the author
- * when clicking on the author of this message.
- *
- * Do not redirect on author clicked of self-posted messages.
- *
- * @return {boolean}
- */
- shouldRedirectToAuthor: function () {
- return !this._isMyselfAuthor();
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Get the name of the author of this message.
- * If there are no author of this messages, returns '' (empty string).
- *
- * @private
- * @returns {string}
- */
- _getAuthorName: function () {
- if (!this.hasAuthor()) {
- return "";
- }
- return this._serverAuthorID[1];
- },
- /**
- * State whether the current user is the author of this message
- *
- * @private
- * @return {boolean}
- */
- _isMyselfAuthor: function () {
- return this.hasAuthor() && (this.getAuthorID() === session.partner_id);
- },
- /**
- * Compute url of attachments of this message
- *
- * @private
- */
- _processAttachmentURL: function () {
- _.each(this.getAttachments(), function (attachment) {
- attachment.url = '/web/content/' + attachment.id + '?download=true';
- });
- },
-
-});
-
-return AbstractMessage;
-
-});
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
deleted file mode 100644
index 1e3ae8c5289..00000000000
--- a/addons/mail/static/src/js/models/messages/message.js
+++ /dev/null
@@ -1,806 +0,0 @@
-odoo.define('mail.model.Message', function (require) {
-"use strict";
-
-var emojis = require('mail.emojis');
-var AbstractMessage = require('mail.model.AbstractMessage');
-var mailUtils = require('mail.utils');
-
-var core = require('web.core');
-var Mixins = require('web.mixins');
-var ServicesMixin = require('web.ServicesMixin');
-var session = require('web.session');
-
-var _t = core._t;
-
-/**
- * This is the main class for the modeling messages in JS.
- * Such messages are stored in the mail manager, and any piece of JS code whose
- * logic relies on threads must ideally interact with such objects.
- */
-var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
- /**
- * @override
- * @param {web.Manager} parent
- * @param {Object} data
- * @param {string} [data.body = ""]
- * @param {(string|integer)[]} [data.channel_ids]
- * @param {string} [data.email_from]
- * @param {Array} [data.history_partner_ids = []]
- * @param {string} [data.info]
- * @param {string} [data.model]
- * @param {string} [data.moderation_status='accepted']
- * @param {string} [data.module_icon]
- * @param {Array} [data.needaction_partner_ids = []]
- * @param {Object[]} [data.notifications]
- * @param {string} [data.record_name]
- * @param {integer} [data.res_id]
- * @param {Array} [data.starred_partner_ids = []]
- * @param {string} [data.subject]
- * @param {string} [data.subtype_description]
- * @param {Object[]} [data.tracking_value_ids]
- * @param {Object[]} emojis
- */
- init: function (parent, data, emojis) {
- this._super.apply(this, arguments);
-
- Mixins.EventDispatcherMixin.init.call(this, arguments);
- this.setParent(parent);
-
- this._setInitialData(data);
-
- this._processBody(emojis);
- this._processMailboxes();
- this._processModeration();
- this._processDocumentThread();
- this._processTrackingValues();
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * @override
- * @return {string|undefined}
- */
- getAuthorImStatus: function () {
- if (!this.hasAuthor()) {
- return undefined;
- }
- return this.call('mail_service', 'getImStatus', { partnerID: this.getAuthorID() });
- },
- /**
- * Get the name of the author of this message
- * If there are no author, return "".
- *
- * @return {string}
- */
- getAuthorName: function () {
- return this._getAuthorName();
- },
- /**
- * Get the relative url of the avatar to display next to the message
- *
- * @override
- * @return {string}
- */
- getAvatarSource: function () {
- if (this._isOdoobotAuthor()) {
- return '/mail/static/src/img/odoobot.png';
- } else if (this.hasAuthor()) {
- return '/web/image/res.partner/' + this.getAuthorID() + '/image_128';
- } else if (this.getType() === 'email') {
- return '/mail/static/src/img/email_icon.png';
- }
- return '/mail/static/src/img/smiley/avatar.jpg';
- },
- /**
- * Get the text to display for the author of the message
- *
- * Rule of precedence for the displayed author::
- *
- * author name > sender email > "anonymous"
- *
- * @override
- * @return {string}
- */
- getDisplayedAuthor: function () {
- var res = this._super.apply(this, arguments);
- if (res) {
- return res;
- } else {
- return this.hasEmailFrom() ? this.getEmailFrom() :
- _t("Anonymous");
- }
- },
- /**
- * Get the ID of the document that this message is linked.
- *
- * @override
- * @return {integer}
- */
- getDocumentID: function () {
- return this._documentID;
- },
- /**
- * Get the model of the document that this message is linked.
- * If this message is not linked to a document, returns "".
- *
- * @override
- * @return {string}
- */
- getDocumentModel: function () {
- return this._documentModel;
- },
- /**
- * Get the name of the document that this message is linked.
- * If this message is not linked to a document, returns "".
- *
- * It uses the name of an existing document thread, in order to have the
- * updated document name. If there is no document thread yet, use the
- * currently aware document name.
- *
- * As a result, this method should ideally be called internally, in order
- * to avoid having different names for messages linked to a same modified
- * document.
- *
- * @override
- * @return {string}
- */
- getDocumentName: function () {
- if (!this.isLinkedToDocumentThread()) {
- return "";
- }
- var model = this.getDocumentModel();
- var id = this.getDocumentID();
- var documentThread = this.call('mail_service', 'getDocumentThread', model, id);
- if (documentThread) {
- this._documentName = documentThread.getName();
- }
- return this._documentName;
- },
- /**
- * Get the email of the sender of this message.
- * If this email has no sender email, returns "".
- *
- * @return {string}
- */
- getEmailFrom: function () {
- if (!this.hasEmailFrom()) {
- return "";
- }
- return this._emailFrom;
- },
- /**
- * @override
- */
- getNotifications() {
- return Object.values(this._notifications);
- },
- /**
- * Get the ID of the channel that this message originates from.
- * If this message does not originate from a channel, returns `-1`.
- *
- * @override
- * @return {integer}
- */
- getOriginChannelID: function () {
- if (!this.originatesFromChannel()) {
- return -1;
- }
- return this._documentID;
- },
- /**
- * Get the name of the channel that this message originates from.
- * If this message does not originate from a channel, returns "".
- *
- * @override
- * @returns {string}
- */
- getOriginChannelName: function () {
- if (!this.originatesFromChannel()) {
- return "";
- }
- var originChannelID = this.getOriginChannelID();
- var channel = originChannelID && this.call(
- 'mail_service',
- 'getChannel',
- originChannelID);
- if (!channel) {
- return "";
- }
- return channel.getName();
- },
- /**
- * Returns the information required to render the preview of this channel.
- *
- * @return {Object}
- */
- getPreview: function () {
- var id, title;
- if (this.isLinkedToDocumentThread()) {
- id = this.getDocumentModel() + '_' + this.getDocumentID();
- title = this.getDocumentName();
- } else {
- id = 'mailbox_inbox';
- title = this.hasSubject() ? this.getSubject() : this.getDisplayedAuthor();
- }
- return {
- author: this.getDisplayedAuthor(),
- body: mailUtils.htmlToTextContentInline(this.getBody()),
- date: this.getDate(),
- documentModel: this.getDocumentModel(),
- documentID: this.getDocumentID(),
- id: id,
- imageSRC: this._getModuleIcon() || this.getAvatarSource(),
- messageID: this.getID(),
- status: this.status,
- title: title,
- isLinkedToDocumentThread: this.isLinkedToDocumentThread(),
- };
- },
- /**
- * Get the subject of this message
- * If this message has no subject, returns "".
- *
- * @override
- * @return {string}
- */
- getSubject: function () {
- if (!this.hasSubject()) {
- return "";
- }
- return this._subject;
- },
- /**
- * @override
- * @return {string}
- */
- getSubtypeDescription: function () {
- return this._subtypeDescription;
- },
- /**
- * Get the list of thread IDs that this message is linked to
- * If this message is not linked to a thread, returns 'undefined'
- *
- * @return {string[]|undefined} list of thread IDs, if any
- */
- getThreadIDs: function () {
- return this._threadIDs;
- },
- /**
- * Get the tracking values of this message
- * If this message has no tracking values, returns 'undefined'
- *
- * @override
- * @return {Object[]|undefined}
- */
- getTrackingValues: function () {
- if (!this.hasTrackingValues()) {
- return undefined;
- }
- return this._trackingValueIDs;
- },
- /**
- * @override
- * @return {string}
- */
- getURL: function () {
- return session.url('/mail/view?message_id=' + this._id);
- },
- /**
- * State whether this message has an email of its sender.
- *
- * @override
- * @return {string}
- */
- hasEmailFrom: function () {
- return !!(this._emailFrom);
- },
- /**
- * State whether this message has a subject
- *
- * @return {boolean}
- */
- hasSubject: function () {
- return !!(this._subject);
- },
- /**
- * @override
- * @return {boolean}
- */
- hasSubtypeDescription: function () {
- return !!(this._subtypeDescription);
- },
- /**
- * State whether this message contains some tracking values
- *
- * @override
- * @return {boolean}
- */
- hasTrackingValues: function () {
- return !!(this._trackingValueIDs && (this._trackingValueIDs.length > 0));
- },
- /**
- * State whether this message is linked to a document thread (not channel)
- *
- * Usually, if this is true, then this message comes from a document thread,
- * but the document model could be a channel. In that case, the document
- * resID tells the channel that this message originally comes from.
- *
- * To detect whether the message comes from a channel, see the method
- * 'originatesFromChannel'.
- *
- * @override
- * @return {boolean}
- */
- isLinkedToDocumentThread: function () {
- return !!(this._documentModel !== 'mail.channel' && this._documentID && this._type !== 'user_notification');
- },
- /**
- * State whether the current user is the author of this message
- *
- * @return {boolean}
- */
- isMyselfAuthor: function () {
- return this._isMyselfAuthor();
- },
- /**
- * States whether the current message needs moderation in general.
- *
- * @override
- * @returns {boolean}
- */
- needsModeration: function () {
- return this._moderationStatus === 'pending_moderation';
- },
- /**
- * States whether the current message needs moderation by the current user.
- * Such a message should be in the moderation mailbox.
- *
- * @returns {boolean}
- */
- needsModerationByUser: function () {
- return _.contains(this._threadIDs, 'mailbox_moderation');
- },
- /**
- * State whether this message is needaction
- *
- * @override
- * @returns {boolean}
- */
- isNeedaction: function () {
- return _.contains(this._threadIDs, 'mailbox_inbox');
- },
- /**
- * State whether this message is starred
- *
- * @override
- * @returns {boolean}
- */
- isStarred: function () {
- return _.contains(this._threadIDs, 'mailbox_starred');
- },
- /**
- * State whether this message is a system notification
- *
- * @override
- * @returns {boolean}
- */
- isSystemNotification: function () {
- return (
- this.getType() === 'notification' &&
- this._documentModel === 'mail.channel'
- ) || this._isTransient();
- },
- /**
- * States whether the message originates from a channel or not
- *
- * @returns {boolean}
- */
- originatesFromChannel: function () {
- return this._documentModel === 'mail.channel';
- },
- /**
- * Unregister thread with ID `threadID` from this message
- *
- * @param {string|integer} threadID ID of thread
- */
- removeThread: function (threadID) {
- this._threadIDs = _.without(this._threadIDs, threadID);
- },
- /**
- * Update the moderation status of the message, so that it is now accepted
- * or rejected. When the message is accepted, it may be linked to more
- * threads, which is the case for relay channels on moderated channels.
- *
- * @param {string} newModerationStatus ['accepted', 'rejected']
- * @param {Object} [options]
- * @param {Object} [options.additionalThreadIDs] contains additional thread
- * IDs to be registered on the message.
- */
- setModerationStatus: function (newModerationStatus, options) {
- var self = this;
- if (newModerationStatus === this._moderationStatus) {
- return;
- }
- this._moderationStatus = newModerationStatus;
- if (newModerationStatus === 'accepted' && options) {
- _.each(options.additionalThreadIDs, function (threadID) {
- self._addThread(threadID);
- });
- }
- this._warnMessageModerated();
- },
- /**
- * Sets notifications based on data from the server.
- *
- * @param {Object} notifications
- */
- setNotifications(notifications) {
- this._notifications = notifications;
- },
- /**
- * Set whether the message is starred or not.
- * If it is starred, the message is moved to the "Starred" mailbox.
- * Note that this function only applies it locally, the server is not aware
- *
- * @param {boolean} starred if set, the message is starred
- */
- setStarred: function (starred) {
- if (starred) {
- this._addThread('mailbox_starred');
- } else {
- this.removeThread('mailbox_starred');
- }
- },
- /**
- * State whether this message should display the subject
- *
- * @return {boolean}
- */
- shouldDisplaySubject: function () {
- return this.hasSubject() &&
- this.getType() !== 'notification' &&
- !this.originatesFromChannel();
- },
- /**
- * State whether this message should redirect to the author
- * when clicking on the author of this message.
- *
- * Do not redirect on author clicked of self-posted or Odoobot messages
- * (note: Odoobot is the default author of transient messages)
- *
- * @override
- * @return {boolean}
- */
- shouldRedirectToAuthor: function () {
- return this._super.apply(this, arguments) && !this._isOdoobotAuthor();
- },
- /**
- * Toggle the star status of the message
- *
- * It relies on the star status of the message from the date of the server.
- * The star status is updated from a 'toggle_star' notification on the
- * longpoll bus
- *
- * @see {mail.Manager.Notification} for the receipt of 'toggle_star'
- * notification after this rpc.
- *
- * @return {Promise}
- */
- toggleStarStatus: function () {
- return this._rpc({
- model: 'mail.message',
- method: 'toggle_message_starred',
- args: [[this._id]],
- });
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Register a thread to the message
- * Useful when you mark a message as 'to do'.
- * This message will be available in 'Starred' mailbox.
- *
- * @private
- * @param {string|integer} threadID
- */
- _addThread: function (threadID) {
- if (!this._threadIDs) {
- this._threadIDs = [];
- }
- this._threadIDs.push(threadID);
- this._threadIDs = _.uniq(this._threadIDs);
- },
- /**
- * Get the name of the author of this message.
- * If Odoobot is the author (default author for transient messages),
- * returns 'Odoobot'.
- *
- * @override
- * @private
- * @returns {string}
- */
- _getAuthorName: function () {
- if (this._isOdoobotAuthor()) {
- return "OdooBot";
- }
- return this._super.apply(this, arguments);
- },
- /**
- * @private
- * @return {string}
- */
- _getModuleIcon: function () {
- return this._moduleIcon;
- },
- /**
- * State if the author of this message is OdooBot
- * This is the default author for transient messages.
- *
- * @private
- * @return {boolean}
- */
- _isOdoobotAuthor: function () {
- return this._serverAuthorID &&
- this._serverAuthorID[0] === this.call('mail_service', 'getOdoobotID')[0];
- },
- /**
- * State whether the message is transient or not
- *
- * @private
- * @return {boolean}
- */
- _isTransient: function () {
- return this._info === 'transient_message';
- },
- /**
- * Convert the server-format body of the message to the client-format.
- * Basically, it processes emojis and url.
- *
- * @private
- */
- _processBody: function () {
- var self = this;
-
- _.each(emojis, function (emoji) {
- var unicode = emoji.unicode;
- var regexp = new RegExp("(?:^|\\s|<[a-z]*>)(" + unicode + ")(?=\\s|$|</[a-z]*>)", 'g');
- var originalBody = self._body;
- self._body = self._body.replace(regexp,
- ' <span class="o_mail_emoji">' + unicode + '</span> ');
- // Idiot-proof limit. If the user had the amazing idea of
- // copy-pasting thousands of emojis, the image rendering can lead
- // to memory overflow errors on some browsers (e.g. Chrome). Set an
- // arbitrary limit to 200 from which we simply don't replace them
- // (anyway, they are already replaced by the unicode counterpart).
- if (_.str.count(self._body, 'o_mail_emoji') > 200) {
- self._body = originalBody;
- }
- });
-
- // add anchor tags to urls
- self._body = mailUtils.parseAndTransform(self._body, mailUtils.addLink);
- },
- /**
- * If the message is linked to a document, link this message to the related
- * document thread. If there is no document thread yet, create it
- */
- _processDocumentThread: function () {
- if (!this.isLinkedToDocumentThread()) {
- return;
- }
- var resModel = this.getDocumentModel();
- var resID = this.getDocumentID();
- var documentThread = this.call('mail_service', 'getOrAddDocumentThread', {
- resModel: resModel,
- resID: resID,
- name: this.getDocumentName(),
- });
- this._threadIDs.push(documentThread.getID());
- },
- /**
- * Set the appropriate mailboxes to this message based on server data
- *
- * @private
- */
- _processMailboxes: function () {
- if (_.contains(this._needactionPartnerIDs, session.partner_id)) {
- this._setNeedaction(true);
- }
- if (_.contains(this._starredPartnerIDs, session.partner_id)) {
- this.setStarred(true);
- }
- if (_.contains(this._historyPartnerIDs, session.partner_id)) {
- this._setHistory(true);
- }
- if (
- this.originatesFromChannel() &&
- _.contains(
- this.call('mail_service', 'getModeratedChannelIDs'),
- this.getOriginChannelID()
- ) &&
- this.needsModeration()
- ) {
- this._setModeratedByUser(true);
- }
- },
- /**
- * Do some extra processing at message init, related to the
- * moderated status of the message.
- *
- * If the message needs moderation, it is not yet linked to the
- * moderated channel server-side. Therefore, it is not registered
- * in the list of threadIDs, as it is built on server-side information
- * at message initialisation (using data.channel_ids).
- *
- * Since the web client uses the list of thread IDs to show visually the
- * message in a thread, we should hack the response of the server so that
- * it assumes the message really belongs to this thread.
- *
- * @private
- */
- _processModeration: function () {
- if (this.needsModeration()) {
- // the message is not linked to the moderated channel on the
- // server, therefore this message has not this channel in
- // channel_ids. Here, just to show this message in the channel
- // visually, it links this message to the channel
- this._threadIDs.push(this.getOriginChannelID());
- }
- },
- /**
- * Process the tracking values on message creation, which
- * basically format date to the local only once by message
- *
- * Cannot be done in preprocess, since it alter the original value
- *
- * @private
- */
- _processTrackingValues: function () {
- if (this.hasTrackingValues()) {
- _.each(this.getTrackingValues(), function (trackingValue) {
- if (trackingValue.field_type === 'datetime') {
- if (trackingValue.old_value) {
- trackingValue.old_value =
- moment
- .utc(trackingValue.old_value)
- .local()
- .format('LLL');
- }
- if (trackingValue.new_value) {
- trackingValue.new_value =
- moment
- .utc(trackingValue.new_value)
- .local()
- .format('LLL');
- }
- } else if (trackingValue.field_type === 'date') {
- if (trackingValue.old_value) {
- trackingValue.old_value =
- moment(trackingValue.old_value)
- .local()
- .format('LL');
- }
- if (trackingValue.new_value) {
- trackingValue.new_value =
- moment(trackingValue.new_value)
- .local()
- .format('LL');
- }
- }
- });
- }
- },
- /**
- * @private
- * @param {Object} data
- * @param {string} [data.body = ""]
- * @param {(string|integer)[]} [data.channel_ids]
- * @param {Object[]} [data.notifications]
- * @param {string} [data.email_from]
- * @param {string} [data.info]
- * @param {string} [data.model]
- * @param {string} [data.moderation_status='accepted']
- * @param {string} [data.module_icon]
- * @param {Array} [data.needaction_partner_ids = []]
- * @param {Array} [data.history_partner_ids = []]
- * @param {string} [data.record_name]
- * @param {integer} [data.res_id]
- * @param {Array} [data.starred_partner_ids = []]
- * @param {string} [data.subject]
- * @param {string} [data.subtype_description]
- * @param {Object[]} [data.tracking_value_ids]
- */
- _setInitialData(data) {
- this._notifications = data.notifications || {};
- this._documentModel = data.model;
- this._documentName = data.record_name;
- this._documentID = data.res_id;
- this._emailFrom = data.email_from;
- this._info = data.info;
- this._isNote = data.is_note;
- this._moduleIcon = data.module_icon;
- this._needactionPartnerIDs = data.needaction_partner_ids || [];
- this._starredPartnerIDs = data.starred_partner_ids || [];
- this._historyPartnerIDs = data.history_partner_ids || [];
- this._subject = data.subject;
- this._subtypeDescription = data.subtype_description;
- this._threadIDs = data.channel_ids || [];
- this._trackingValueIDs = data.tracking_value_ids;
-
- this._moderationStatus = data.moderation_status || 'accepted';
- },
- /*
- * Set whether the message is history or not.
- * If it is history, the message is moved to the "History" mailbox.
- * Note that this function only applies it locally, the server is not aware
- *
- * @private
- * @param {boolean} history if set, the message is history
- */
- _setHistory: function (history) {
- if (history) {
- this._addThread('mailbox_history');
- } else {
- this.removeThread('mailbox_history');
- }
- },
- /**
- * Set whether the message is moderated by current user or not.
- * If it is moderated by the current user, the message is moved to the
- * "Moderation" mailbox. Note that this function only applies it locally,
- * the server is not aware
- *
- * @private
- * @param {boolean} moderated if set, the message is moderated by user
- */
- _setModeratedByUser: function (moderated) {
- if (moderated) {
- this._addThread('mailbox_moderation');
- } else {
- this.removeThread('mailbox_moderation');
- }
- },
- /**
- * Set whether the message is needaction or not.
- * If it is needaction, the message is moved to the "Inbox" mailbox.
- * Note that this function only applies it locally, the server is not aware
- *
- * @private
- * @param {boolean} needaction if set, the message is needaction
- */
- _setNeedaction: function (needaction) {
- if (needaction) {
- this._addThread('mailbox_inbox');
- } else {
- this.removeThread('mailbox_inbox');
- }
- },
- /**
- * @private
- */
- _warnMessageModerated: function () {
- var mailBus = this.call('mail_service', 'getMailBus');
- if (this.needsModerationByUser()) {
- this._setModeratedByUser(false);
- var moderationBox = this.call('mail_service', 'getMailbox', 'moderation');
- moderationBox.decrementMailboxCounter();
- moderationBox.removeMessage(this.getID());
- mailBus.trigger('update_moderation_counter');
- }
- if (this._moderationStatus !== 'accepted') {
- this.call('mail_service', 'removeMessageFromThreads', this);
- }
- mailBus.trigger('update_message', this);
- },
-
-});
-
-return Message;
-
-});
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;
-
-});
diff --git a/addons/mail/static/src/js/models/utils/cc_throttle_function.js b/addons/mail/static/src/js/models/utils/cc_throttle_function.js
deleted file mode 100644
index b4b87087e6d..00000000000
--- a/addons/mail/static/src/js/models/utils/cc_throttle_function.js
+++ /dev/null
@@ -1,181 +0,0 @@
-odoo.define('mail.model.CCThrottleFunctionObject', function (require) {
-"use strict";
-
-var Class = require('web.Class');
-
-/**
- * This object models the behaviour of the clearable and cancellable (CC)
- * throttle version of a provided function.
- */
-var CCThrottleFunctionObject = Class.extend({
-
- /**
- * @param {Object} params
- * @param {integer} params.duration duration of the 'cooldown' phase, i.e.
- * the minimum duration between the most recent function call that has
- * been made and the following function call.
- * @param {function} params.func provided function for making the CC
- * throttled version.
- */
- init: function (params) {
- this._arguments = undefined;
- this._cooldownTimeout = undefined;
- this._duration = params.duration;
- this._func = params.func;
- this._shouldCallFunctionAfterCD = false;
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Cancel any buffered function call, but keep the cooldown phase running.
- */
- cancel: function () {
- this._arguments = undefined;
- this._shouldCallFunctionAfterCD = false;
- },
- /**
- * Clear the internal throttle timer, so that the following function call
- * is immediate. For instance, if there is a cooldown stage, it is aborted.
- */
- clear: function () {
- if (this._cooldownTimeout) {
- clearTimeout(this._cooldownTimeout);
- this._onCooldownTimeout();
- }
- },
- /**
- * Called when there is a call to the function. This function is throttled,
- * so the time it is called depends on whether the "cooldown stage" occurs
- * or not:
- *
- * - no cooldown stage: function is called immediately, and it starts
- * the cooldown stage when successful.
- * - in cooldown stage: function is called when the cooldown stage has
- * ended from timeout.
- *
- * Note that after the cooldown stage, only the last attempted function
- * call will be considered.
- */
- do: function () {
- this._arguments = Array.prototype.slice.call(arguments);
- if (this._cooldownTimeout === undefined) {
- this._callFunction();
- } else {
- this._shouldCallFunctionAfterCD = true;
- }
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Immediately calls the function with arguments of last buffered function
- * call. It initiates the cooldown stage after this function call.
- *
- * @private
- */
- _callFunction: function () {
- this._func.apply(null, this._arguments);
- this._cooldown();
- },
- /**
- * Called when the function has been successfully called. The following
- * calls to the function with this object should suffer a "cooldown stage",
- * which prevents the function from being called until this stage has ended.
- *
- * @private
- */
- _cooldown: function () {
- this.cancel();
- this._cooldownTimeout = setTimeout(
- this._onCooldownTimeout.bind(this),
- this._duration
- );
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Called when the cooldown stage ended from timeout. Calls the function if
- * a function call was buffered.
- *
- * @private
- */
- _onCooldownTimeout: function () {
- if (this._shouldCallFunctionAfterCD) {
- this._callFunction();
- } else {
- this._cooldownTimeout = undefined;
- }
- },
-});
-
-return CCThrottleFunctionObject;
-
-});
-
-
-odoo.define('mail.model.CCThrottleFunction', function (require) {
-"use strict";
-
-var CCThrottleFunctionObject = require('mail.model.CCThrottleFunctionObject');
-
-/**
- * A function that creates a cancellable and clearable (CC) throttle version
- * of a provided function.
- *
- * This throttle mechanism allows calling a function at most once during a
- * certain period:
- *
- * - When a function call is made, it enters a 'cooldown' phase, in which any
- * attempt to call the function is buffered until the cooldown phase ends.
- * - At most 1 function call can be buffered during the cooldown phase, and the
- * latest one in this phase will be considered at its end.
- * - When a cooldown phase ends, any buffered function call will be performed
- * and another cooldown phase will follow up.
- *
- * This throttle version has the following interesting properties:
- *
- * - cancellable: it allows removing a buffered function call during the
- * cooldown phase, but it keeps the cooldown phase running.
- * - clearable: it allows to clear the internal clock of the throttled function,
- * so that any cooldown phase is immediately ending.
- *
- * @param {Object} params
- * @param {integer} params.duration a duration for the throttled behaviour,
- * in milli-seconds.
- * @param {function} params.func the function to throttle
- * @returns {function} the cancellable and clearable throttle version of the
- * provided function in argument.
- */
-var CCThrottleFunction = function (params) {
- var duration = params.duration;
- var func = params.func;
-
- var throttleFunctionObject = new CCThrottleFunctionObject({
- duration: duration,
- func: func,
- });
-
- var callable = function () {
- return throttleFunctionObject.do.apply(throttleFunctionObject, arguments);
- };
- callable.cancel = function () {
- throttleFunctionObject.cancel();
- };
- callable.clear = function () {
- throttleFunctionObject.clear();
- };
-
- return callable;
-};
-
-return CCThrottleFunction;
-
-});
diff --git a/addons/mail/static/src/js/models/utils/timer.js b/addons/mail/static/src/js/models/utils/timer.js
deleted file mode 100644
index 9aaae471d2d..00000000000
--- a/addons/mail/static/src/js/models/utils/timer.js
+++ /dev/null
@@ -1,69 +0,0 @@
-odoo.define('mail.model.Timer', function (require) {
-"use strict";
-
-var Class = require('web.Class');
-
-/**
- * This class creates a timer which, when times out, calls a function.
- */
-var Timer = Class.extend({
-
- /**
- * Instantiate a new timer. Note that the timer is not started on
- * initialization (@see start method).
- *
- * @param {Object} params
- * @param {number} params.duration duration of timer before timeout in
- * milli-seconds.
- * @param {function} params.onTimeout function that is called when the
- * timer times out.
- */
- init: function (params) {
- this._duration = params.duration;
- this._timeout = undefined;
- this._timeoutCallback = params.onTimeout;
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Clears the countdown of the timer.
- */
- clear: function () {
- clearTimeout(this._timeout);
- },
- /**
- * Resets the timer, i.e. resets its duration.
- */
- reset: function () {
- this.clear();
- this.start();
- },
- /**
- * Starts the timer, i.e. after a certain duration, it times out and calls
- * a function back.
- */
- start: function () {
- this._timeout = setTimeout(this._onTimeout.bind(this), this._duration);
- },
-
- //--------------------------------------------------------------------------
- // Handler
- //--------------------------------------------------------------------------
-
- /**
- * Called when the timer times out, calls back a function on timeout.
- *
- * @private
- */
- _onTimeout: function () {
- this._timeoutCallback();
- },
-
-});
-
-return Timer;
-
-});
diff --git a/addons/mail/static/src/js/models/utils/timers.js b/addons/mail/static/src/js/models/utils/timers.js
deleted file mode 100644
index 757945223f7..00000000000
--- a/addons/mail/static/src/js/models/utils/timers.js
+++ /dev/null
@@ -1,82 +0,0 @@
-odoo.define('mail.model.Timers', function (require) {
-"use strict";
-
-var Timer = require('mail.model.Timer');
-
-var Class = require('web.Class');
-
-/**
- * This class lists several timers that use a same callback and duration.
- */
-var Timers = Class.extend({
-
- /**
- * Instantiate a new list of timers
- *
- * @param {Object} params
- * @param {integer} params.duration duration of the underlying timers from
- * start to timeout, in milli-seconds.
- * @param {function} params.onTimeout a function to call back for underlying
- * timers on timeout.
- */
- init: function (params) {
- this._duration = params.duration;
- this._timeoutCallback = params.onTimeout;
- this._timers = {};
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Register a timer with ID `timerID` to start.
- *
- * - an already registered timer with this ID is reset.
- * - (optional) can provide a list of arguments that is passed to the
- * function callback when timer times out.
- *
- * @param {Object} params
- * @param {Array} [params.timeoutCallbackArguments]
- * @param {integer} params.timerID
- */
- registerTimer: function (params) {
- var timerID = params.timerID;
- if (this._timers[timerID]) {
- this._timers[timerID].clear();
- }
- var timerParams = {
- duration: this._duration,
- onTimeout: this._timeoutCallback,
- };
- if ('timeoutCallbackArguments' in params) {
- timerParams.onTimeout = this._timeoutCallback.bind.apply(
- this._timeoutCallback,
- [null].concat(params.timeoutCallbackArguments)
- );
- } else {
- timerParams.onTimeout = this._timeoutCallback;
- }
- this._timers[timerID] = new Timer(timerParams);
- this._timers[timerID].start();
- },
- /**
- * Unregister a timer with ID `timerID`. The unregistered timer is aborted
- * and will not time out.
- *
- * @param {Object} params
- * @param {integer} params.timerID
- */
- unregisterTimer: function (params) {
- var timerID = params.timerID;
- if (this._timers[timerID]) {
- this._timers[timerID].clear();
- delete this._timers[timerID];
- }
- },
-
-});
-
-return Timers;
-
-});