[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:
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<!--
|
||||
Chatter (mail_thread widget) buttons
|
||||
|
||||
@param {boolean} [isMobile]
|
||||
@param {boolean} [logNoteButton]
|
||||
@param {boolean} [newMessageButton]
|
||||
@param {boolean} [scheduleActivityButton]
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user