From 915da49d173e9ab8d0c6d94cbeec124c83d23fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Wed, 4 Jul 2018 15:57:03 +0200 Subject: [PATCH] [IMP] mail, *: template rendering with abstract thread, adapt thread instantiation, thread `getMessages`/`fetchMessages`, and create mode document thread. * = im_livechat, im_support - thread widget now uses abstract thread in `render` method - thread models have their `init` parameters changed, so that the parent may be optional - reworked 'creating a new record...' in chatter in create mode: > new model CreateModeDocumentThread, which basically is a fake document thread with a fake message for its rendering in create mode of the chatter > special rendering method of thread_widget for such case: `renderChatterCreateMode` - renamed thread methods previously named `getMessages` to `fetchMessages`: > in order to not conflict with `getMessages`, used for rendering > now, `getMessages` always get messages that are locally, while `fetchMessages` may fetch messages. TODO: - special thread model for im_livechat (rendering is broken ATM) --- .../im_livechat/static/src/js/im_livechat.js | 2 +- .../static/src/js/website_livechat_window.js | 8 +- .../im_support/static/src/js/mail_manager.js | 7 +- .../static/src/js/support_channel.js | 15 +++- addons/mail/static/src/js/discuss.js | 35 +++++---- .../js/models/messages/abstract_message.js | 16 +--- .../src/js/models/threads/abstract_thread.js | 29 ++++++- .../static/src/js/models/threads/channel.js | 37 +++++---- .../threads/create_mode_document_thread.js | 75 +++++++++++++++++++ .../mail/static/src/js/models/threads/dm.js | 15 ++-- .../src/js/models/threads/document_thread.js | 44 ++++++----- .../static/src/js/models/threads/mailbox.js | 16 ++-- .../static/src/js/models/threads/thread.js | 70 ++++++++--------- .../js/models/threads/thread_with_cache.js | 34 +++++---- .../services/mail_document_thread_manager.js | 13 ++-- .../static/src/js/services/mail_manager.js | 21 +++++- .../src/js/services/mail_window_manager.js | 12 +-- addons/mail/static/src/js/thread_field.js | 54 +++++-------- addons/mail/static/src/js/thread_widget.js | 18 ++--- .../thread_windows/abstract_thread_window.js | 27 ++++--- .../src/js/thread_windows/thread_window.js | 18 ----- addons/mail/static/src/xml/chatter.xml | 1 + addons/mail/static/src/xml/thread.xml | 22 +++--- addons/mail/views/mail_templates.xml | 2 + 24 files changed, 353 insertions(+), 238 deletions(-) create mode 100644 addons/mail/static/src/js/models/threads/create_mode_document_thread.js diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js index acc1ed8e559..964edddbadc 100644 --- a/addons/im_livechat/static/src/js/im_livechat.js +++ b/addons/im_livechat/static/src/js/im_livechat.js @@ -260,7 +260,7 @@ var LivechatButton = Widget.extend({ */ _renderMessages: function () { var shouldScroll = !this._chatWindow.isFolded() && this._chatWindow.threadWidget.isAtBottom(); - this._chatWindow.render(this._messages); + this._chatWindow.render(this._messages); // AKU: TODO store messages in website livechat thread if (shouldScroll) { this._chatWindow.threadWidget.scrollToBottom(); } diff --git a/addons/im_livechat/static/src/js/website_livechat_window.js b/addons/im_livechat/static/src/js/website_livechat_window.js index 77eaf40b99b..660db0ad71e 100644 --- a/addons/im_livechat/static/src/js/website_livechat_window.js +++ b/addons/im_livechat/static/src/js/website_livechat_window.js @@ -95,15 +95,13 @@ var LivechatWindow = AbstractThreadWindow.extend({ //-------------------------------------------------------------------------- /** - * If there are no chat linked to this livechat window, returns null. + * AKU: TODO drop this method when there's the website livechat thread + * model linked to this website livechat window. * * @override - * @returns {integer|null} + * @returns {integer} */ _getThreadID: function () { - if (!this.hasThread()) { - return null; - } return this._data.id; }, /** diff --git a/addons/im_support/static/src/js/mail_manager.js b/addons/im_support/static/src/js/mail_manager.js index b25b47cc1ba..56ea4b736b6 100644 --- a/addons/im_support/static/src/js/mail_manager.js +++ b/addons/im_support/static/src/js/mail_manager.js @@ -169,7 +169,12 @@ MailManager.include({ */ _makeChannel: function (data, options) { if (data.id === this.supportChannelUUID) { - return new SupportChannel(this, data, options, this._commands); + return new SupportChannel({ + parent: this, + data: data, + options: options, + commands: this._commands + }); } return this._super.apply(this, arguments); }, diff --git a/addons/im_support/static/src/js/support_channel.js b/addons/im_support/static/src/js/support_channel.js index adcf2a0c51e..dee74a6650d 100644 --- a/addons/im_support/static/src/js/support_channel.js +++ b/addons/im_support/static/src/js/support_channel.js @@ -20,7 +20,20 @@ var _t = core._t; * mail.model.ThreadWithCache */ var SupportChannel = ThreadWithCache.extend({ - init: function (parent, data, options) { + /** + * @param {Object} params + * @param {Object} params.data + * @param {boolean} params.data.available + * @param {string|integer} params.data.id + * @param {boolean} params.data.is_minimized + * @param {Object} params.data.operator + * @param {string} params.data.state ['open', 'closed', 'folded'] + * @param {string} params.data.uuid + * @param {string} params.data.welcome_message + * @param {Object} params.options + */ + init: function (params) { + var data = params.data; data.type = 'support_channel'; data.name = _t("Support"); diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 4b6bd2efacc..531f5f6559a 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -383,22 +383,21 @@ var Discuss = AbstractAction.extend(ControlPanelMixin, { */ _fetchAndRenderThread: function () { var self = this; - return this._thread.getMessages(this.domain) - .then(function (messages) { + return this._thread.fetchMessages(this.domain) + .then(function () { self._threadWidget.render( - messages, - self._getThreadRenderingOptions(messages) + self._thread, + self._getThreadRenderingOptions() ); - self._updateButtonStatus(messages.length === 0); + self._updateButtonStatus(!self._thread.hasMessages()); return self._loadEnoughMessages(); }); }, /** * @private - * @param {mail.model.Message} messages * @returns {Object} */ - _getThreadRenderingOptions: function (messages) { + _getThreadRenderingOptions: function () { // Compute position of the 'New messages' separator, only once when // joining a channel to keep it in the thread when new messages arrive if (_.isUndefined(this.messagesSeparatorPosition)) { @@ -416,8 +415,8 @@ var Discuss = AbstractAction.extend(ControlPanelMixin, { messagesSeparatorPosition: this.messagesSeparatorPosition, squashCloseMessages: this._thread.getType() !== 'mailbox' && !this._thread.isMassMailing(), - displayEmptyThread: !messages.length && !this.domain.length, - displayNoMatchFound: !messages.length && this.domain.length, + displayEmptyThread: !this._thread.hasMessages() && !this.domain.length, + displayNoMatchFound: !this._thread.hasMessages() && this.domain.length, displaySubjectOnMessages: ( this._thread.getType() !== 'mailbox' && @@ -483,15 +482,15 @@ var Discuss = AbstractAction.extend(ControlPanelMixin, { var oldestMessageID = this.$('.o_thread_message').first().data('messageId'); var oldestMessageSelector = '.o_thread_message[data-message-id="' + oldestMessageID + '"]'; var offset = -dom.getPosition(document.querySelector(oldestMessageSelector)).top; - return this._thread.getMessages(this.domain, true) - .then(function (messages) { + return this._thread.fetchMessages(this.domain, true) + .then(function () { if (self.messagesSeparatorPosition === 'top') { // reset value to re-compute separator position self.messagesSeparatorPosition = undefined; } self._threadWidget.render( - messages, - self._getThreadRenderingOptions(messages) + self._thread, + self._getThreadRenderingOptions() ); offset += dom.getPosition(document.querySelector(oldestMessageSelector) ).top; @@ -1169,12 +1168,12 @@ var Discuss = AbstractAction.extend(ControlPanelMixin, { (currentThreadID === 'mailbox_inbox' && !message.isNeedaction()) || (currentThreadID === 'mailbox_moderation' && !message.needsModeration()) ) { - this._thread.getMessages(this.domain) - .then(function (messages) { - var options = self._getThreadRenderingOptions(messages); - self._threadWidget.removeMessageAndRender(message.getID(), messages, options) + this._thread.fetchMessages(this.domain) + .then(function () { + var options = self._getThreadRenderingOptions(); + self._threadWidget.removeMessageAndRender(message.getID(), self._thread, options) .then(function () { - self._updateButtonStatus(messages.length === 0, type); + self._updateButtonStatus(!self._thread.hasMessages(), type); }); }); } else if (_.contains(message.getThreadIDs(), currentThreadID)) { diff --git a/addons/mail/static/src/js/models/messages/abstract_message.js b/addons/mail/static/src/js/models/messages/abstract_message.js index 6c5f247386f..420ac2da41b 100644 --- a/addons/mail/static/src/js/models/messages/abstract_message.js +++ b/addons/mail/static/src/js/models/messages/abstract_message.js @@ -44,8 +44,6 @@ var AbstractMessage = Class.extend({ this._id = data.id; this._isDiscussion = data.is_discussion; this._isNotification = data.is_notification; - // if set, the message should display the author of the message - this._displayAuthor = false; this._serverAuthorID = data.author_id; this._type = data.message_type || undefined; @@ -268,12 +266,12 @@ var AbstractMessage = Class.extend({ return this._isDiscussion; }, /** - * State whether this message is linked to a document - * By default, messages are not linked to a document. + * State whether this message is linked to a document thread + * By default, messages are not linked to a document thread. * * @return {boolean} */ - isLinkedToDocument: function () { + isLinkedToDocumentThread: function () { return false; }, /** @@ -337,14 +335,6 @@ var AbstractMessage = Class.extend({ needsModeration: function () { return false; }, - /** - * State whether this message should display the author - * - * @return {boolean} - */ - shouldDisplayAuthor: function () { - return this._displayAuthor; - }, /** * State whether this message should redirect to the author * when clicking on the author of this 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 index 3ea263fa996..788a04307a7 100644 --- a/addons/mail/static/src/js/models/threads/abstract_thread.js +++ b/addons/mail/static/src/js/models/threads/abstract_thread.js @@ -6,15 +6,42 @@ var Class = require('web.Class'); /** * 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({ - init: function (parent, data) {}, + /** + * @param {Object} params + * @param {Object} params.data + * @param {Object} params.data.id the ID of this thread + */ + init: function (params) { + this._id = params.data.id; + }, //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- + /** + * Get the ID of this thread + * + * @returns {integer|string} + */ + getID: function () { + return this._id; + }, + /** + * @abstract + * @returns {mail.model.AbstractMessage[]} + */ getMessages: function () {}, + /** + * @returns {boolean} + */ + hasMessages: function () { + return !_.isEmpty(this.getMessages()); + }, }); return AbstractThread; diff --git a/addons/mail/static/src/js/models/threads/channel.js b/addons/mail/static/src/js/models/threads/channel.js index e63cab2f907..86482a71a6d 100644 --- a/addons/mail/static/src/js/models/threads/channel.js +++ b/addons/mail/static/src/js/models/threads/channel.js @@ -17,30 +17,35 @@ var time = require('web.time'); var Channel = ThreadWithCache.extend({ /** * @override - * @param {string} [data.anonymous_name] - * @param {string} data.channel_type - * @param {boolean} data.group_based_subscription - * @param {boolean} [data.is_minimized=false] - * @param {boolean} data.is_moderator whether the current user is + * @param {Object} params + * @param {Object} params.data + * @param {string} [params.data.anonymous_name] + * @param {string} params.data.channel_type + * @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} [data.last_message_date] date in server-format - * @param {boolean} [data.mass_mailing] - * @param {integer} [data.message_unread_counter] - * @param {string} [data.public] either 'public' or 'private' - * @param {string} data.state - * @param {string} [data.uuid] - * @param {Object} options - * @param {boolean} [options.autoswitch=true] - * @param {Object[]} commands + * @param {string} [params.data.last_message_date] date in server-format + * @param {boolean} [params.data.mass_mailing] + * @param {integer} [params.data.message_unread_counter] + * @param {string} [params.data.public] either 'public' or 'private' + * @param {string} params.data.state + * @param {string} [params.data.uuid] + * @param {Object} params.options + * @param {boolean} [params.options.autoswitch=true] + * @param {Object[]} params.commands */ - init: function (parent, data, options, commands) { + init: function (params) { var self = this; this._super.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 or chat session notifications - // this._autoswitch = 'autoswitch' in options ? options.autoswitch : true; this._chat = undefined; // FIXME: could be dropped when livechat and DM are moved out of this class this._commands = undefined; 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 new file mode 100644 index 00000000000..ab396984b54 --- /dev/null +++ b/addons/mail/static/src/js/models/threads/create_mode_document_thread.js @@ -0,0 +1,75 @@ +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("

Creating a new record...

"), + author_id: [session.partner_id, session.partner_display_name], + }); + return [createMessage]; + }, + +}); + +return CreateModeDocumentThread; + +}); diff --git a/addons/mail/static/src/js/models/threads/dm.js b/addons/mail/static/src/js/models/threads/dm.js index cd08dd7526d..dc24681990b 100644 --- a/addons/mail/static/src/js/models/threads/dm.js +++ b/addons/mail/static/src/js/models/threads/dm.js @@ -11,15 +11,18 @@ var Channel = require('mail.model.Channel'); var DM = Channel.extend({ /** * @override - * @param {Object} data - * @param {Object} data.direct_partner - * @param {integer} data.direct_partner[0].id - * @param {string} data.direct_partner[0].im_status - * @param {string} data.direct_partner[0].name + * @param {Object} params + * @param {Object} params.data + * @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 */ - init: function (parent, data) { + init: function (params) { this._super.apply(this, arguments); + var data = params.data; + this._type = 'dm'; this._name = data.direct_partner[0].name; this._directPartnerID = data.direct_partner[0].id; diff --git a/addons/mail/static/src/js/models/threads/document_thread.js b/addons/mail/static/src/js/models/threads/document_thread.js index 92cc26e845b..cf3e27c8c81 100644 --- a/addons/mail/static/src/js/models/threads/document_thread.js +++ b/addons/mail/static/src/js/models/threads/document_thread.js @@ -24,13 +24,16 @@ var DocumentThread = Thread.extend({ /** * @override - * @param {string} [data.messagesIDs] the list of message ids linked + * @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} data.model - * @param {integer} data.resID + * @param {string} params.data.model + * @param {integer} params.data.resID */ - init: function (parent, data) { + init: function (params) { + var data = params.data; data.id = data.resModel + '_' + data.resID; data.type = 'document_thread'; @@ -98,6 +101,23 @@ var DocumentThread = Thread.extend({ 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. @@ -137,21 +157,11 @@ var DocumentThread = Thread.extend({ return this._messageIDs; }, /** - * - * Get 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} + * @returns {mail.model.Message[]} */ - getMessages: function (options) { - var self = this; - return this._fetchMessages(options).then(function () { - self.call('mail_service', 'markMessagesAsRead', self._messageIDs); - return self._messages; - }); + getMessages: function () { + return this._messages; }, /** * States whether the thread is linked to a document diff --git a/addons/mail/static/src/js/models/threads/mailbox.js b/addons/mail/static/src/js/models/threads/mailbox.js index bcb5ae609b7..6d2e75c59b1 100644 --- a/addons/mail/static/src/js/models/threads/mailbox.js +++ b/addons/mail/static/src/js/models/threads/mailbox.js @@ -18,13 +18,15 @@ var Mailbox = ThreadWithCache.extend({ /** * @override - * @param {Object} data - * @param {string} data.id the ID of the mailbox, without the 'mailbox_' - * prefix. - * @param {integer} [data.mailboxCounter=0] the initial mailbox counter of - * this mailbox. + * @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 (parent, data) { + init: function (params) { + var data = params.data; // ID or mailboxes are always prefixed with 'mailbox_' data.id = 'mailbox_' + data.id; data.type = 'mailbox'; @@ -89,7 +91,7 @@ var Mailbox = ThreadWithCache.extend({ getMessagePreviews: function () { var self = this; - return this.getMessages().then(function (messages) { + return this.fetchMessages().then(function (messages) { // pick only last message of chatter // items = list of objects // { unreadCounter: integer, message: mail.model.Message } diff --git a/addons/mail/static/src/js/models/threads/thread.js b/addons/mail/static/src/js/models/threads/thread.js index de2c6628d67..5a376f8f7e2 100644 --- a/addons/mail/static/src/js/models/threads/thread.js +++ b/addons/mail/static/src/js/models/threads/thread.js @@ -2,9 +2,9 @@ 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 Class = require('web.Class'); var Mixins = require('web.mixins'); var ServicesMixin = require('web.ServicesMixin'); @@ -15,20 +15,20 @@ var ServicesMixin = require('web.ServicesMixin'); * * In particular, channels and mailboxes are two different kinds of threads. */ -var Thread = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { +var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, { /** - * @param {mail.Manager} parent - * @param {Object} data - * @param {string} [data.channel_type] - * @param {integer|string} data.id - * @param {string} data.name - * @param {string} [data.type] + * @override + * @param {Object} params + * @param {mail.Manager} param.parent + * @param {Object} params.data + * @param {string} [params.data.channel_type] + * @param {string} params.data.name + * @param {string} [params.data.type] */ - init: function (parent, data) { + init: function (params) { + this._super.apply(this, arguments); Mixins.EventDispatcherMixin.init.call(this, arguments); - this.setParent(parent); - - this._id = data.id; + this.setParent(params.parent); // threads are not detached by default this._detached = false; @@ -38,11 +38,11 @@ var Thread = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { // extended composer and show "Send by messages by email" on discuss // sidebar this._massMailing = false; - this._name = data.name; + this._name = params.data.name; // 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 = data.type || data.channel_type; + this._type = params.data.type || params.data.channel_type; // any new message that has not been read yet this._unreadCounter = 0; // max number of fetched messages from the server @@ -87,6 +87,23 @@ var Thread = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { 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} + */ + fetchMessages: function () { + return $.when([]); + }, /** * Updates the _folded state of the thread. Must be overriden to reflect * the new state in the interface. @@ -105,14 +122,6 @@ var Thread = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { getCommands: function () { return []; }, - /** - * Get the ID of the thread - * - * @returns {integer|string} string for mailboxes (e.g. 'mailbox_inbox') - */ - getID: function () { - return this._id; - }, /** * Get the listeners of the thread. * By default, a thread has not listener. @@ -123,23 +132,6 @@ var Thread = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { getMentionPartnerSuggestions: function () { return $.when([]); }, - /** - * Get 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} - */ - getMessages: function () { - return $.when([]); - }, /** * Get the name of this thread * diff --git a/addons/mail/static/src/js/models/threads/thread_with_cache.js b/addons/mail/static/src/js/models/threads/thread_with_cache.js index f25a19005f2..ef064072bbf 100644 --- a/addons/mail/static/src/js/models/threads/thread_with_cache.js +++ b/addons/mail/static/src/js/models/threads/thread_with_cache.js @@ -16,9 +16,11 @@ var session = require('web.session'); var ThreadWithCache = Thread.extend({ /** * @override - * @param {integer} [data.seen_message_id=undefined] + * @param {Object} params + * @param {Object} params.data + * @param {integer} [params.data.seen_message_id=undefined] */ - init: function (parent, data) { + init: function (params) { this._super.apply(this, arguments); this._cache = { @@ -28,7 +30,7 @@ var ThreadWithCache = Thread.extend({ messages: [], } }; - this._lastSeenMessageID = data.seen_message_id; + this._lastSeenMessageID = params.data.seen_message_id; }, //-------------------------------------------------------------------------- @@ -52,20 +54,12 @@ var ThreadWithCache = Thread.extend({ cache.messages.splice(index, 0, message); } }, - /** - * 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 {Array} [domain] * @param {boolean} [loadMore=false] */ - getMessages: function (domain, loadMore) { + fetchMessages: function (domain, loadMore) { if (loadMore) { return this._fetchMessages(domain, true); } @@ -77,11 +71,19 @@ var ThreadWithCache = Thread.extend({ } }, /** - * @private - * @return {boolean} true if there is at least a message, false otherwise + * Get the last seen message for a given thread + * + * @return {integer|null} last seen message ID (if any) */ - hasMessages: function () { - return !!(_.last(this._cache['[]'].messages)); + getLastSeenMessageID: function () { + return this._lastSeenMessageID || null; + }, + /** + * @override + * @return {mail.model.Message[]} + */ + getMessages: function () { + return this._cache['[]'].messages; }, /** * Invalidate the caches of the thread diff --git a/addons/mail/static/src/js/services/mail_document_thread_manager.js b/addons/mail/static/src/js/services/mail_document_thread_manager.js index 40301ad724c..b1417d62fc6 100644 --- a/addons/mail/static/src/js/services/mail_document_thread_manager.js +++ b/addons/mail/static/src/js/services/mail_document_thread_manager.js @@ -99,11 +99,14 @@ MailManager.include({ getOrAddDocumentThread: function (params) { var thread = this.getDocumentThread(params.resModel, params.resID); if (!thread) { - thread = new DocumentThread(this, { - messageIDs: params.messageIDs, - name: params.name, - resID: params.resID, - resModel: params.resModel, + thread = new DocumentThread({ + parent: this, + data: { + messageIDs: params.messageIDs, + name: params.name, + resID: params.resID, + resModel: params.resModel, + }, }); this._threads.push(thread); } else if (params.name) { diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js index 841c2c5cbfd..8b34399b331 100644 --- a/addons/mail/static/src/js/services/mail_manager.js +++ b/addons/mail/static/src/js/services/mail_manager.js @@ -498,7 +498,12 @@ var MailManager = AbstractService.extend({ */ _addMailbox: function (data, options) { options = typeof options === 'object' ? options : {}; - var mailbox = new Mailbox(this, data, options, this._commands); + var mailbox = new Mailbox({ + parent: this, + data: data, + options: options, + commands: this._commands + }); this._threads.push(mailbox); this._sortThreads(); return mailbox; @@ -957,9 +962,19 @@ var MailManager = AbstractService.extend({ */ _makeChannel: function (data, options) { if (_.size(data.direct_partner) > 0) { - return new DM(this, data, options, this._commands); + return new DM({ + parent: this, + data: data, + options: options, + commands: this._commands + }); } - return new Channel(this, data, options, this._commands); + return new Channel({ + parent: this, + data: data, + options: options, + commands: this._commands, + }); }, /** * Creates a new instance of Message with the given data. diff --git a/addons/mail/static/src/js/services/mail_window_manager.js b/addons/mail/static/src/js/services/mail_window_manager.js index c72d6418f08..ba8068a8c87 100644 --- a/addons/mail/static/src/js/services/mail_window_manager.js +++ b/addons/mail/static/src/js/services/mail_window_manager.js @@ -125,9 +125,9 @@ MailManager.include({ threadWindow.appendTo($(this.THREAD_WINDOW_APPENDTO)) .then(function () { self._repositionThreadWindows(); - return thread.getMessages(); - }).then(function (messages) { - threadWindow.render(messages); + return thread.fetchMessages(); + }).then(function () { + threadWindow.render(); threadWindow.threadWidget.scrollToBottom(); // setTimeout to prevent to execute handler on first // scrollTo, which is asynchronous @@ -544,9 +544,9 @@ MailManager.include({ if (messageVisible && !threadWindow.isPassive()) { thread.markAsRead(); } - thread.getMessages() - .then(function (messages) { - threadWindow.render(messages); + thread.fetchMessages() + .then(function () { + threadWindow.render(); if (scrollBottom && messageVisible) { threadWindow.threadWidget.scrollToBottom(); } diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js index d3021ca1ce5..a239481931b 100644 --- a/addons/mail/static/src/js/thread_field.js +++ b/addons/mail/static/src/js/thread_field.js @@ -1,14 +1,13 @@ odoo.define('mail.ThreadField', function (require) { "use strict"; -var Message = require('mail.model.Message'); +var CreateModeDocumentThread = require('mail.model.CreateModeDocumentThread'); var ThreadWidget = require('mail.widget.Thread'); var AbstractField = require('web.AbstractField'); var core = require('web.core'); var field_registry = require('web.field_registry'); var concurrency = require('web.concurrency'); -var session = require('web.session'); var _t = core._t; @@ -105,37 +104,20 @@ var ThreadField = AbstractField.extend({ */ _fetchAndRenderThread: function (options) { var self = this; - options = options || {}; - options.ids = this._messageIDs; - var fetchDef = this.dp.add(this._documentThread.getMessages(options)); - return fetchDef.then(function (rawMessages) { - var isCreateMode = false; - if (!self.res_id) { - rawMessages = self._forgeCreateMessages(); - isCreateMode = true; - } - self._threadWidget.render(rawMessages, { - displayLoadMore: rawMessages.length < self._documentThread.getMessageIDs().length, - isCreateMode: isCreateMode, + if (!this._documentThread) { + var thread = new CreateModeDocumentThread(); + options = { isCreateMode: true }; + self._threadWidget.render(thread, options); + return $.when(); + } else { + var fetchDef = this.dp.add(this._documentThread.fetchMessages(options)); + return fetchDef.then(function () { + self._threadWidget.render(self._documentThread, { + displayLoadMore: self._documentThread.getMessages().length < self._documentThread.getMessageIDs().length, + }); + return self._documentThread.markAsRead(); }); - return self._documentThread.markAsRead(); - }); - }, - /** - * 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.Message[]} an array containing a single message 'Creating a new record...' - */ - _forgeCreateMessages: function () { - var createMessage = new Message(this, { - id: 0, - body: _t("

Creating a new record...

"), - author_id: [session.partner_id, session.partner_display_name], - }); - return [createMessage]; + } }, /** * @override @@ -162,7 +144,7 @@ var ThreadField = AbstractField.extend({ * Sets this._documentThread, the DocumentThread associated with the current * model and resID. * - * @returns {mail.model.DocumentThread} + * If it is a new document in create mode, unset the document thread. */ _setDocumentThread: function () { var params = { @@ -171,7 +153,11 @@ var ThreadField = AbstractField.extend({ resID: this.res_id, resModel: this.model, }; - this._documentThread = this.call('mail_service', 'getOrAddDocumentThread', params); + if (!params.resID) { + this._documentThread = null; + } else { + this._documentThread = this.call('mail_service', 'getOrAddDocumentThread', params); + } }, //-------------------------------------------------------------------------- diff --git a/addons/mail/static/src/js/thread_widget.js b/addons/mail/static/src/js/thread_widget.js index 78efdcde707..65fb90fb35a 100644 --- a/addons/mail/static/src/js/thread_widget.js +++ b/addons/mail/static/src/js/thread_widget.js @@ -124,7 +124,7 @@ var ThreadWidget = Widget.extend({ // dict where key is message ID, and value is whether it should hide // the author of message or not visually - var hideAuthorMessages = {}; + var displayAuthorMessages = {}; // Hide avatar and info of a message if that message and the previous // one are both comments wrote by the same author at the same minute @@ -154,16 +154,15 @@ var ThreadWidget = Widget.extend({ ) ) ) { - hideAuthorMessages[message.getID()] = false; + displayAuthorMessages[message.getID()] = true; } else { - hideAuthorMessages[message.getID()] = options.squashCloseMessages; + displayAuthorMessages[message.getID()] = !options.squashCloseMessages; } prevMessage = message; }); this.$el.html(QWeb.render('mail.widget.Thread', { thread: thread, - hideAuthorMessages: hideAuthorMessages, - // messages: messages, + displayAuthorMessages: displayAuthorMessages, options: options, ORDER: ORDER, dateFormat: time.getLangDatetimeFormat(), @@ -207,17 +206,18 @@ var ThreadWidget = Widget.extend({ * Removes a message and re-renders the thread * * @param {integer} [messageID] the id of the removed message - * @param {mail.model.AbstractMessage[]} [messages] the list of messages to - * display, without the removed one + * @param {mail.model.AbstractThread} thread the thread which contains + * updated list of messages (so it does not contain any message with ID + * `messageID`). * @param {Object} [options] options for the thread rendering */ - removeMessageAndRender: function (messageID, messages, options) { + removeMessageAndRender: function (messageID, thread, options) { var self = this; var done = $.Deferred(); this.$('.o_thread_message[data-message-id="' + messageID + '"]') .fadeOut({ done: function () { - self.render(messages, options); + self.render(thread, options); done.resolve(); }, duration: 200, diff --git a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js index ae756b6f624..3325cd8be43 100644 --- a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js +++ b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js @@ -46,12 +46,14 @@ var AbstractThreadWindow = Widget.extend({ * * @abstract * @param {Widget} parent - * @param {Object} thread + * @param {Object} thread the thread that this thread window is linked to. * @param {Object} [options={}] */ init: function (parent, thread, options) { this._super(parent); + this._thread = thread; + this.options = _.defaults(options || {}, { autofocus: true, displayStars: true, @@ -174,15 +176,11 @@ var AbstractThreadWindow = Widget.extend({ return this._hidden; }, /** - * Render the thread window, using the provided list of messages - * - * TODO: use messages in thread, instead of providing list of messages - * - * @param {mail.model.AbstractMessage[]} messages + * Render the thread window */ - render: function (messages) { + render: function () { this._renderHeader(); - this.threadWidget.render(messages, { displayLoadMore: false }); + this.threadWidget.render(this._thread, { displayLoadMore: false }); }, /** * Toggle the fold state of this thread window. Also update the fold state @@ -256,12 +254,19 @@ var AbstractThreadWindow = Widget.extend({ }, /** * Get the ID of the related thread. + * If this window is not related to a thread, it means this is the "blank" + * thread window, therefore it returns "_blank" as its ID. * - * @abstract * @private - * @return {integer} + * @returns {integer|string} the threadID, or '_blank' for the window that + * is not related to any thread. */ - _getThreadID: function () {}, + _getThreadID: function () { + if (!this.hasThread()) { + return '_blank'; + } + return this._thread.getID(); + }, /** * Post a message on this thread window * diff --git a/addons/mail/static/src/js/thread_windows/thread_window.js b/addons/mail/static/src/js/thread_windows/thread_window.js index c0d7a22ea33..42cbfcec2e3 100644 --- a/addons/mail/static/src/js/thread_windows/thread_window.js +++ b/addons/mail/static/src/js/thread_windows/thread_window.js @@ -198,24 +198,6 @@ var ThreadWindow = AbstractThreadWindow.extend({ // Private //-------------------------------------------------------------------------- - /** - * Get the ID of the thread window, which is equivalent to the ID of the - * related thread. - * - * If this window is not related to a thread, it means this is the "blank" - * thread window, therefore it returns "_blank" as its ID. - * - * @override - * @private - * @returns {integer|string} the threadID, or '_blank' for the window that - * is not related to any thread. - */ - _getThreadID: function () { - if (!this.hasThread()) { - return '_blank'; - } - return this._thread.getID(); - }, /** * @override * @private diff --git a/addons/mail/static/src/xml/chatter.xml b/addons/mail/static/src/xml/chatter.xml index 7a5959789cf..9b6d0586bdc 100644 --- a/addons/mail/static/src/xml/chatter.xml +++ b/addons/mail/static/src/xml/chatter.xml @@ -51,6 +51,7 @@
- - - + -
-

+

Note by @@ -223,7 +223,7 @@ on - + (from #) diff --git a/addons/mail/views/mail_templates.xml b/addons/mail/views/mail_templates.xml index f5a3a840b55..be08c5e1df8 100644 --- a/addons/mail/views/mail_templates.xml +++ b/addons/mail/views/mail_templates.xml @@ -23,7 +23,9 @@ + +