[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)
This commit is contained in:
Alexandre Kühn
2018-07-19 13:58:34 +02:00
parent ec8c505bc1
commit 915da49d17
24 changed files with 353 additions and 238 deletions
@@ -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();
}
@@ -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;
},
/**
@@ -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);
},
@@ -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");
+17 -18
View File
@@ -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)) {
@@ -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.
@@ -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;
@@ -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;
@@ -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("<p>Creating a new record...</p>"),
author_id: [session.partner_id, session.partner_display_name],
});
return [createMessage];
},
});
return CreateModeDocumentThread;
});
@@ -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;
@@ -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<mail.model.Message[]>}
*/
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<mail.model.Message[]>}
* @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
@@ -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 }
@@ -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<mail.model.Message[]>}
*/
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<mail.model.Message[]>}
*/
getMessages: function () {
return $.when([]);
},
/**
* Get the name of this thread
*
@@ -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
@@ -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) {
@@ -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.
@@ -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();
}
+20 -34
View File
@@ -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("<p>Creating a new record...</p>"),
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);
}
},
//--------------------------------------------------------------------------
+9 -9
View File
@@ -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,
@@ -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
*
@@ -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
+1
View File
@@ -51,6 +51,7 @@
<!--
Chatter (mail_thread widget) buttons
@param {boolean} [isMobile]
@param {boolean} [logNoteButton]
@param {boolean} [newMessageButton]
@param {boolean} [scheduleActivityButton]
+11 -11
View File
@@ -3,10 +3,6 @@
<!--
@param {mail.model.AbstractThread} thread
@param {Object[]} hideAuthorMessages dict where key is message ID and
value is boolean. It tells whether the message with this ID should
hide the author visually or not.
@param {Object} options
@param {boolean} [options.displayEmptyThread]
@param {boolean} [options.displayLoadMore]
@param {boolean} [options.displayNoMatchFound]
@@ -159,7 +155,12 @@
</t>
<!--
@param {mail.model.AbstractThread} thread
@param {string} dateFormat
@param {Object[]} hideAuthorMessages dict where key is message ID and
value is boolean. It tells whether the message with this ID should
hide the author visually or not.
@param {Object} options
@param {mail.model.AbstractMessage} message
@param {Object} options
@param {boolean} [options.displayAvatars]
@@ -172,13 +173,12 @@
@param {string|integer} [options.messagesSeparatorPosition] 'top' or
message ID, the separator is placed just after this message.
@param {integer} [options.selectedMessageID]
@param {string|integer} [options.threadID]
-->
<t t-name="mail.widget.Thread.Message">
<div t-att-class="'o_thread_message' + (message.getID() === options.selectedMessageID ? 'o_thread_selected_message ' : ' ') + (message.isDiscussion() or message.isNotification() ? ' o_mail_discussion' : ' o_mail_not_discussion')" t-att-data-message-id="message.getID()">
<div t-if="options.displayAvatars" class="o_thread_message_sidebar">
<t t-if="message.hasAuthor()">
<img t-if="message.shouldDisplayAuthor()"
<img t-if="displayAuthorMessages[message.getID()]"
alt=""
t-att-src="message.getAvatarSource()"
data-oe-model="res.partner"
@@ -186,20 +186,20 @@
t-attf-class="o_thread_message_avatar img-circle #{message.shouldRedirectToAuthor() ? 'o_mail_redirect' : ''}"/>
</t>
<t t-else="">
<img t-if="message.shouldDisplayAuthor()"
<img t-if="displayAuthorMessages[message.getID()]"
alt=""
t-att-src="message.getAvatarSource()"
class="o_thread_message_avatar img-circle"/>
</t>
<span t-if="!message.shouldDisplayAuthor()" t-att-title="message.getDate().format(dateFormat)" class="o_thread_message_side_date">
<span t-if="!displayAuthorMessages[message.getID()]" t-att-title="message.getDate().format(dateFormat)" class="o_thread_message_side_date">
<t t-esc="message.getDate().format('hh:mm')"/>
</span>
<i t-if="!message.shouldDisplayAuthor() and options.displayStars and message.getType() !== 'notification'"
<i t-if="!displayAuthorMessages[message.getID()] and options.displayStars and message.getType() !== 'notification'"
t-att-class="'fa o_thread_message_star o_thread_icon ' + (message.isStarred() ? 'fa-star' : 'fa-star-o')"
t-att-data-message-id="message.getID()" title="Mark as Todo" role="img" aria-label="Mark as todo"/>
</div>
<div class="o_thread_message_core">
<p t-if="message.shouldDisplayAuthor()" class="o_mail_info text-muted">
<p t-if="displayAuthorMessages[message.getID()]" class="o_mail_info text-muted">
<input t-if="message.needsModeration() and message.needsModerationByUser()" type="checkbox" class="moderation_checkbox" t-att-data-message-id="message.getID()"/>
<t t-if="message.isNote()">
Note by
@@ -223,7 +223,7 @@
<t t-if="message.isLinkedToDocumentThread() and options.displayDocumentLinks">
on <a t-att-href="message.getURL()" t-att-data-oe-model="message.getDocumentModel()" t-att-data-oe-id="message.getDocumentID()" class="o_document_link"><t t-esc="message.getDocumentName()"/></a>
</t>
<t t-if="message.originatesFromChannel() and (message.getOriginChannelID() !== options.threadID)">
<t t-if="message.originatesFromChannel() and (message.getOriginChannelID() !== thread.getID())">
(from <a t-att-data-oe-id="message.getOriginChannelID()" href="#">#<t t-esc="message.getOriginChannelName()"/></a>)
</t>
<span t-if="options.displayEmailIcons and message.hasCustomerEmailData()" class="o_thread_tooltip_container">
+2
View File
@@ -23,7 +23,9 @@
<script type="text/javascript" src="/mail/static/src/js/models/messages/abstract_message.js"></script>
<script type="text/javascript" src="/mail/static/src/js/models/messages/message.js"></script>
<!-- threads -->
<script type="text/javascript" src="/mail/static/src/js/models/threads/abstract_thread.js"></script>
<script type="text/javascript" src="/mail/static/src/js/models/threads/channel.js"></script>
<script type="text/javascript" src="/mail/static/src/js/models/threads/create_mode_document_thread.js"></script>
<script type="text/javascript" src="/mail/static/src/js/models/threads/dm.js"></script>
<script type="text/javascript" src="/mail/static/src/js/models/threads/document_thread.js"></script>
<script type="text/javascript" src="/mail/static/src/js/models/threads/mailbox.js"></script>