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 */ close: function () { this._super.apply(this, arguments); 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 */ detach: function () { this._super.apply(this, arguments); 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} */ fetchMessages: function (options) { var self = this; return this._fetchMessages(options).then(function () { self.call('mail_service', 'markMessagesAsRead', self._messageIDs); 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 */ fold: function () { this._super.apply(this, arguments); 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} resolved when the message has been sent to * the server, with the object message that has been sent to the server. */ _postMessage: function (data) { var self = this; var resModel = this.getDocumentModel(); var resID = this.getDocumentID(); return this._super.apply(this, arguments) .then(function (messageData) { _.extend(messageData, { context: data.context, message_type: data.message_type, subtype: data.subtype || "mail.mt_comment", subtype_id: data.subtype_id, }); return self._rpc({ model: resModel, method: 'message_post', args: [resID], kwargs: messageData, }) .then(function (messageID) { return self._rpc({ model: 'mail.message', method: 'message_format', args: [[messageID]], }) .then(function (messages) { messages[0].model = resModel; messages[0].res_id = resID; self.call('mail_service', 'addMessage', messages[0]); return messages[0]; }); }); }); }, }); return DocumentThread; });