[MOV] im_livechat: move Thread outside of public_livechat.js
Task-2212347 closes odoo/odoo#87597 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -634,689 +634,3 @@ return {
|
||||
|
||||
});
|
||||
|
||||
odoo.define('im_livechat.legacy.mail.widget.Thread', function (require) {
|
||||
"use strict";
|
||||
|
||||
var DocumentViewer = require('im_livechat.legacy.mail.DocumentViewer');
|
||||
var mailUtils = require('@mail/js/utils');
|
||||
|
||||
var core = require('web.core');
|
||||
var time = require('web.time');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _lt = core._lt;
|
||||
|
||||
var ORDER = {
|
||||
ASC: 1, // visually, ascending order of message IDs (from top to bottom)
|
||||
DESC: -1, // visually, descending order of message IDs (from top to bottom)
|
||||
};
|
||||
|
||||
var READ_MORE = _lt("read more");
|
||||
var READ_LESS = _lt("read less");
|
||||
|
||||
/**
|
||||
* This is a generic widget to render a thread.
|
||||
* Any thread that extends mail.model.AbstractThread can be used with this
|
||||
* widget.
|
||||
*/
|
||||
var ThreadWidget = Widget.extend({
|
||||
className: 'o_mail_thread',
|
||||
|
||||
events: {
|
||||
'click a': '_onClickRedirect',
|
||||
'click img': '_onClickRedirect',
|
||||
'click strong': '_onClickRedirect',
|
||||
'click .o_thread_show_more': '_onClickShowMore',
|
||||
'click .o_attachment_download': '_onAttachmentDownload',
|
||||
'click .o_attachment_view': '_onAttachmentView',
|
||||
'click .o_attachment_delete_cross': '_onDeleteAttachment',
|
||||
'click .o_thread_message_needaction': '_onClickMessageNeedaction',
|
||||
'click .o_thread_message_star': '_onClickMessageStar',
|
||||
'click .o_thread_message_reply': '_onClickMessageReply',
|
||||
'click .oe_mail_expand': '_onClickMailExpand',
|
||||
'click .o_thread_message': '_onClickMessage',
|
||||
'click': '_onClick',
|
||||
'click .o_thread_message_notification_error': '_onClickMessageNotificationError',
|
||||
'click .o_thread_message_moderation': '_onClickMessageModeration',
|
||||
'change .moderation_checkbox': '_onChangeModerationCheckbox',
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {widget} parent
|
||||
* @param {Object} options
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.attachments = [];
|
||||
// options when the thread is enabled (e.g. can send message,
|
||||
// interact on messages, etc.)
|
||||
this._enabledOptions = _.defaults(options || {}, {
|
||||
displayOrder: ORDER.ASC,
|
||||
displayMarkAsRead: true,
|
||||
displayModerationCommands: false,
|
||||
displayStars: true,
|
||||
displayDocumentLinks: true,
|
||||
displayAvatars: true,
|
||||
squashCloseMessages: true,
|
||||
displayNotificationIcons: true,
|
||||
displayReplyIcons: false,
|
||||
loadMoreOnScroll: false,
|
||||
hasMessageAttachmentDeletable: false,
|
||||
});
|
||||
// options when the thread is disabled
|
||||
this._disabledOptions = {
|
||||
displayOrder: this._enabledOptions.displayOrder,
|
||||
displayMarkAsRead: false,
|
||||
displayModerationCommands: false,
|
||||
displayStars: false,
|
||||
displayDocumentLinks: false,
|
||||
displayAvatars: this._enabledOptions.displayAvatars,
|
||||
squashCloseMessages: false,
|
||||
displayNotificationIcons: false,
|
||||
displayReplyIcons: false,
|
||||
loadMoreOnScroll: this._enabledOptions.loadMoreOnScroll,
|
||||
hasMessageAttachmentDeletable: false,
|
||||
};
|
||||
this._selectedMessageID = null;
|
||||
this._currentThreadID = null;
|
||||
this._messageMailPopover = null;
|
||||
this._messageSeenPopover = null;
|
||||
// used to track popover IDs to destroy on re-rendering of popovers
|
||||
this._openedSeenPopoverIDs = [];
|
||||
},
|
||||
/**
|
||||
* The message mail popover may still be shown at this moment. If we do not
|
||||
* remove it, it stays visible on the page until a page reload.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
destroy: function () {
|
||||
clearInterval(this._updateTimestampsInterval);
|
||||
if (this._messageMailPopover) {
|
||||
this._messageMailPopover.popover('hide');
|
||||
}
|
||||
if (this._messageSeenPopover) {
|
||||
this._messageSeenPopover.popover('hide');
|
||||
}
|
||||
this._destroyOpenSeenPopoverIDs();
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
* @param {im_livechat.legacy.mail.model.AbstractThread} thread the thread to render.
|
||||
* @param {Object} [options]
|
||||
* @param {integer} [options.displayOrder=ORDER.ASC] order of displaying
|
||||
* messages in the thread:
|
||||
* - ORDER.ASC: last message is at the bottom of the thread
|
||||
* - ORDER.DESC: last message is at the top of the thread
|
||||
* @param {boolean} [options.displayLoadMore]
|
||||
* @param {Array} [options.domain=[]] the domain for the messages in the
|
||||
* thread.
|
||||
* @param {boolean} [options.isCreateMode]
|
||||
* @param {boolean} [options.scrollToBottom=false]
|
||||
* @param {boolean} [options.squashCloseMessages]
|
||||
*/
|
||||
render: function (thread, options) {
|
||||
var self = this;
|
||||
|
||||
var shouldScrollToBottomAfterRendering = false;
|
||||
if (this._currentThreadID === thread.getID() && this.isAtBottom()) {
|
||||
shouldScrollToBottomAfterRendering = true;
|
||||
}
|
||||
this._currentThreadID = thread.getID();
|
||||
|
||||
// copy so that reverse do not alter order in the thread object
|
||||
var messages = _.clone(thread.getMessages({ domain: options.domain || [] }));
|
||||
|
||||
var modeOptions = options.isCreateMode ? this._disabledOptions :
|
||||
this._enabledOptions;
|
||||
|
||||
// attachments ordered by messages order (increasing ID)
|
||||
this.attachments = _.uniq(_.flatten(_.map(messages, function (message) {
|
||||
return message.getAttachments();
|
||||
})));
|
||||
|
||||
options = _.extend({}, modeOptions, options, {
|
||||
selectedMessageID: this._selectedMessageID,
|
||||
});
|
||||
|
||||
// dict where key is message ID, and value is whether it should display
|
||||
// the author of message or not visually
|
||||
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
|
||||
// and in the same document (users can now post message in documents
|
||||
// directly from a channel that follows it)
|
||||
var prevMessage;
|
||||
_.each(messages, function (message) {
|
||||
if (
|
||||
// is first message of thread
|
||||
!prevMessage ||
|
||||
// more than 1 min. elasped
|
||||
(Math.abs(message.getDate().diff(prevMessage.getDate())) > 60000) ||
|
||||
prevMessage.getType() !== 'comment' ||
|
||||
message.getType() !== 'comment' ||
|
||||
// from a different author
|
||||
(prevMessage.getAuthorID() !== message.getAuthorID()) ||
|
||||
(
|
||||
// messages are linked to a document thread
|
||||
(
|
||||
prevMessage.isLinkedToDocumentThread() &&
|
||||
message.isLinkedToDocumentThread()
|
||||
) &&
|
||||
(
|
||||
// are from different documents
|
||||
prevMessage.getDocumentModel() !== message.getDocumentModel() ||
|
||||
prevMessage.getDocumentID() !== message.getDocumentID()
|
||||
)
|
||||
)
|
||||
) {
|
||||
displayAuthorMessages[message.getID()] = true;
|
||||
} else {
|
||||
displayAuthorMessages[message.getID()] = !options.squashCloseMessages;
|
||||
}
|
||||
prevMessage = message;
|
||||
});
|
||||
|
||||
if (modeOptions.displayOrder === ORDER.DESC) {
|
||||
messages.reverse();
|
||||
}
|
||||
|
||||
this.$el.html(QWeb.render('im_livechat.legacy.mail.widget.Thread', {
|
||||
thread: thread,
|
||||
displayAuthorMessages: displayAuthorMessages,
|
||||
options: options,
|
||||
ORDER: ORDER,
|
||||
dateFormat: time.getLangDatetimeFormat(),
|
||||
}));
|
||||
|
||||
_.each(messages, function (message) {
|
||||
var $message = self.$('.o_thread_message[data-message-id="' + message.getID() + '"]');
|
||||
$message.find('.o_mail_timestamp').data('date', message.getDate());
|
||||
|
||||
self._insertReadMore($message);
|
||||
});
|
||||
|
||||
if (shouldScrollToBottomAfterRendering) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
if (!this._updateTimestampsInterval) {
|
||||
this.updateTimestampsInterval = setInterval(function () {
|
||||
self._updateTimestamps();
|
||||
}, 1000 * 60);
|
||||
}
|
||||
|
||||
this._renderMessageNotificationPopover(messages);
|
||||
if (thread.hasSeenFeature()) {
|
||||
this._renderMessageSeenPopover(thread, messages);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render thread widget when loading, i.e. when messaging is not yet ready.
|
||||
* @see /mail/init_messaging
|
||||
*/
|
||||
renderLoading: function () {
|
||||
this.$el.html(QWeb.render('im_livechat.legacy.mail.widget.ThreadLoading'));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
getScrolltop: function () {
|
||||
return this.$el.scrollTop();
|
||||
},
|
||||
/**
|
||||
* State whether the bottom of the thread is visible or not,
|
||||
* with a tolerance of 5 pixels
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAtBottom: function () {
|
||||
var fullHeight = this.el.scrollHeight;
|
||||
var topHiddenHeight = this.$el.scrollTop();
|
||||
var visibleHeight = this.$el.outerHeight();
|
||||
var bottomHiddenHeight = fullHeight - topHiddenHeight - visibleHeight;
|
||||
return bottomHiddenHeight < 5;
|
||||
},
|
||||
/**
|
||||
* Removes a message and re-renders the thread
|
||||
*
|
||||
* @param {integer} [messageID] the id of the removed message
|
||||
* @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, thread, options) {
|
||||
var self = this;
|
||||
this._currentThreadID = thread.getID();
|
||||
return new Promise(function (resolve, reject) {
|
||||
self.$('.o_thread_message[data-message-id="' + messageID + '"]')
|
||||
.fadeOut({
|
||||
done: function () {
|
||||
if (self._currentThreadID === thread.getID()) {
|
||||
self.render(thread, options);
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
duration: 200,
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Scroll to the bottom of the thread
|
||||
*/
|
||||
scrollToBottom: function () {
|
||||
this.$el.scrollTop(this.el.scrollHeight);
|
||||
},
|
||||
/**
|
||||
* Scrolls the thread to a given message
|
||||
*
|
||||
* @param {integer} options.msgID the ID of the message to scroll to
|
||||
* @param {integer} [options.duration]
|
||||
* @param {boolean} [options.onlyIfNecessary]
|
||||
*/
|
||||
scrollToMessage: function (options) {
|
||||
var $target = this.$('.o_thread_message[data-message-id="' + options.messageID + '"]');
|
||||
if (options.onlyIfNecessary) {
|
||||
var delta = $target.parent().height() - $target.height();
|
||||
var offset = delta < 0 ?
|
||||
0 :
|
||||
delta - ($target.offset().top - $target.offsetParent().offset().top);
|
||||
offset = - Math.min(offset, 0);
|
||||
this.$el.scrollTo("+=" + offset + "px", options.duration);
|
||||
} else if ($target.length) {
|
||||
this.$el.scrollTo($target);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Scroll to the specific position in pixel
|
||||
*
|
||||
* If no position is provided, scroll to the bottom of the thread
|
||||
*
|
||||
* @param {integer} [position] distance from top to position in pixels.
|
||||
* If not provided, scroll to the bottom.
|
||||
*/
|
||||
scrollToPosition: function (position) {
|
||||
if (position) {
|
||||
this.$el.scrollTop(position);
|
||||
} else {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Toggle all the moderation checkboxes in the thread
|
||||
*
|
||||
* @param {boolean} checked if true, check the boxes,
|
||||
* otherwise uncheck them.
|
||||
*/
|
||||
toggleModerationCheckboxes: function (checked) {
|
||||
this.$('.moderation_checkbox').prop('checked', checked);
|
||||
},
|
||||
/**
|
||||
* Unselect the selected message
|
||||
*/
|
||||
unselectMessage: function () {
|
||||
this.$('.o_thread_message').removeClass('o_thread_selected_message');
|
||||
this._selectedMessageID = null;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_destroyOpenSeenPopoverIDs: function () {
|
||||
_.each(this._openedSeenPopoverIDs, function (popoverID) {
|
||||
$('#' + popoverID).remove();
|
||||
});
|
||||
this._openedSeenPopoverIDs = [];
|
||||
},
|
||||
/**
|
||||
* Modifies $element to add the 'read more/read less' functionality
|
||||
* All element nodes with 'data-o-mail-quote' attribute are concerned.
|
||||
* All text nodes after a ``#stopSpelling`` element are concerned.
|
||||
* Those text nodes need to be wrapped in a span (toggle functionality).
|
||||
* All consecutive elements are joined in one 'read more/read less'.
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $element
|
||||
*/
|
||||
_insertReadMore: function ($element) {
|
||||
var self = this;
|
||||
|
||||
var groups = [];
|
||||
var readMoreNodes;
|
||||
|
||||
// nodeType 1: element_node
|
||||
// nodeType 3: text_node
|
||||
var $children = $element.contents()
|
||||
.filter(function () {
|
||||
return this.nodeType === 1 ||
|
||||
this.nodeType === 3 &&
|
||||
this.nodeValue.trim();
|
||||
});
|
||||
|
||||
_.each($children, function (child) {
|
||||
var $child = $(child);
|
||||
|
||||
// Hide Text nodes if "stopSpelling"
|
||||
if (
|
||||
child.nodeType === 3 &&
|
||||
$child.prevAll('[id*="stopSpelling"]').length > 0
|
||||
) {
|
||||
// Convert Text nodes to Element nodes
|
||||
$child = $('<span>', {
|
||||
text: child.textContent,
|
||||
'data-o-mail-quote': '1',
|
||||
});
|
||||
child.parentNode.replaceChild($child[0], child);
|
||||
}
|
||||
|
||||
// Create array for each 'read more' with nodes to toggle
|
||||
if (
|
||||
$child.attr('data-o-mail-quote') ||
|
||||
(
|
||||
$child.get(0).nodeName === 'BR' &&
|
||||
$child.prev('[data-o-mail-quote="1"]').length > 0
|
||||
)
|
||||
) {
|
||||
if (!readMoreNodes) {
|
||||
readMoreNodes = [];
|
||||
groups.push(readMoreNodes);
|
||||
}
|
||||
$child.hide();
|
||||
readMoreNodes.push($child);
|
||||
} else {
|
||||
readMoreNodes = undefined;
|
||||
self._insertReadMore($child);
|
||||
}
|
||||
});
|
||||
|
||||
_.each(groups, function (group) {
|
||||
// Insert link just before the first node
|
||||
var $readMore = $('<a>', {
|
||||
class: 'o_mail_read_more',
|
||||
href: '#',
|
||||
text: READ_MORE,
|
||||
}).insertBefore(group[0]);
|
||||
|
||||
// Toggle All next nodes
|
||||
var isReadMore = true;
|
||||
$readMore.click(function (e) {
|
||||
e.preventDefault();
|
||||
isReadMore = !isReadMore;
|
||||
_.each(group, function ($child) {
|
||||
$child.hide();
|
||||
$child.toggle(!isReadMore);
|
||||
});
|
||||
$readMore.text(isReadMore ? READ_MORE : READ_LESS);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onDeleteAttachment: function (ev) {
|
||||
ev.stopPropagation();
|
||||
var $target = $(ev.currentTarget);
|
||||
this.trigger_up('delete_attachment', {
|
||||
attachmentId: $target.data('id'),
|
||||
attachmentName: $target.data('name')
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @param {integer} [options.channelID]
|
||||
* @param {string} options.model
|
||||
* @param {integer} options.id
|
||||
*/
|
||||
_redirect: _.debounce(function (options) {
|
||||
if ('channelID' in options) {
|
||||
this.trigger('redirect_to_channel', options.channelID);
|
||||
} else {
|
||||
this.trigger('redirect', options.model, options.id);
|
||||
}
|
||||
}, 500, true),
|
||||
/**
|
||||
* Render the popover when mouse-hovering on the notification icon of a
|
||||
* message in the thread.
|
||||
* There is at most one such popover at any given time.
|
||||
*
|
||||
* @private
|
||||
* @param {im_livechat.legacy.mail.model.AbstractMessage[]} messages list of messages in the
|
||||
* rendered thread, for which popover on mouseover interaction is
|
||||
* permitted.
|
||||
*/
|
||||
_renderMessageNotificationPopover(messages) {
|
||||
if (this._messageMailPopover) {
|
||||
this._messageMailPopover.popover('hide');
|
||||
}
|
||||
if (!this.$('.o_thread_tooltip').length) {
|
||||
return;
|
||||
}
|
||||
this._messageMailPopover = this.$('.o_thread_tooltip').popover({
|
||||
html: true,
|
||||
boundary: 'viewport',
|
||||
placement: 'auto',
|
||||
trigger: 'hover',
|
||||
offset: '0, 1',
|
||||
content: function () {
|
||||
var messageID = $(this).data('message-id');
|
||||
var message = _.find(messages, function (message) {
|
||||
return message.getID() === messageID;
|
||||
});
|
||||
return QWeb.render('im_livechat.legacy.mail.widget.Thread.Message.MailTooltip', {
|
||||
notifications: message.getNotifications(),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Render the popover when mouse hovering on the seen icon of a message
|
||||
* in the thread. Only seen icons in non-squashed message have popover,
|
||||
* because squashed messages hides this icon on message mouseover.
|
||||
*
|
||||
* @private
|
||||
* @param {im_livechat.legacy.mail.model.AbstractThread} thread with thread seen mixin,
|
||||
* @see {im_livechat.legacy.mail.model.ThreadSeenMixin}
|
||||
* @param {im_livechat.legacy.mail.model.Message[]} messages list of messages in the
|
||||
* rendered thread.
|
||||
*/
|
||||
_renderMessageSeenPopover: function (thread, messages) {
|
||||
var self = this;
|
||||
this._destroyOpenSeenPopoverIDs();
|
||||
if (this._messageSeenPopover) {
|
||||
this._messageSeenPopover.popover('hide');
|
||||
}
|
||||
if (!this.$('.o_thread_message_core .o_mail_thread_message_seen_icon').length) {
|
||||
return;
|
||||
}
|
||||
this._messageSeenPopover = this.$('.o_thread_message_core .o_mail_thread_message_seen_icon').popover({
|
||||
html: true,
|
||||
boundary: 'viewport',
|
||||
placement: 'auto',
|
||||
trigger: 'hover',
|
||||
offset: '0, 1',
|
||||
content: function () {
|
||||
var $this = $(this);
|
||||
self._openedSeenPopoverIDs.push($this.attr('aria-describedby'));
|
||||
var messageID = $this.data('message-id');
|
||||
var message = _.find(messages, function (message) {
|
||||
return message.getID() === messageID;
|
||||
});
|
||||
return QWeb.render('im_livechat.legacy.mail.widget.Thread.Message.SeenIconPopoverContent', {
|
||||
thread: thread,
|
||||
message: message,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateTimestamps: function () {
|
||||
var isAtBottom = this.isAtBottom();
|
||||
this.$('.o_mail_timestamp').each(function () {
|
||||
var date = $(this).data('date');
|
||||
$(this).text(mailUtils.timeFromNow(date));
|
||||
});
|
||||
if (isAtBottom && !this.isAtBottom()) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onAttachmentDownload: function (event) {
|
||||
event.stopPropagation();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onAttachmentView: function (event) {
|
||||
event.stopPropagation();
|
||||
var activeAttachmentID = $(event.currentTarget).data('id');
|
||||
if (activeAttachmentID) {
|
||||
var attachmentViewer = new DocumentViewer(this, this.attachments, activeAttachmentID);
|
||||
attachmentViewer.appendTo($('body'));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onChangeModerationCheckbox: function (ev) {
|
||||
this.trigger_up('update_moderation_buttons');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClick: function () {
|
||||
if (this._selectedMessageID) {
|
||||
this.unselectMessage();
|
||||
this.trigger('unselect_message');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMailExpand: function (ev) {
|
||||
ev.preventDefault();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessage: function (ev) {
|
||||
$(ev.currentTarget).toggleClass('o_thread_selected_message');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageNeedaction: function (ev) {
|
||||
var messageID = $(ev.currentTarget).data('message-id');
|
||||
this.trigger('mark_as_read', messageID);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageNotificationError(ev) {
|
||||
const messageID = $(ev.currentTarget).data('message-id');
|
||||
this.do_action('mail.mail_resend_message_action', {
|
||||
additional_context: {
|
||||
mail_message_to_resend: messageID,
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageReply: function (ev) {
|
||||
this._selectedMessageID = $(ev.currentTarget).data('message-id');
|
||||
this.$('.o_thread_message').removeClass('o_thread_selected_message');
|
||||
this.$('.o_thread_message[data-message-id="' + this._selectedMessageID + '"]')
|
||||
.addClass('o_thread_selected_message');
|
||||
this.trigger('select_message', this._selectedMessageID);
|
||||
ev.stopPropagation();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageStar: function (ev) {
|
||||
var messageID = $(ev.currentTarget).data('message-id');
|
||||
this.trigger('toggle_star_status', messageID);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageModeration: function (ev) {
|
||||
var $button = $(ev.currentTarget);
|
||||
var messageID = $button.data('message-id');
|
||||
var decision = $button.data('decision');
|
||||
this.trigger_up('message_moderation', {
|
||||
messageID: messageID,
|
||||
decision: decision,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickRedirect: function (ev) {
|
||||
// ignore inherited branding
|
||||
if ($(ev.target).data('oe-field') !== undefined) {
|
||||
return;
|
||||
}
|
||||
var id = $(ev.target).data('oe-id');
|
||||
if (id) {
|
||||
ev.preventDefault();
|
||||
var model = $(ev.target).data('oe-model');
|
||||
var options;
|
||||
if (model && (model !== 'mail.channel')) {
|
||||
options = {
|
||||
model: model,
|
||||
id: id
|
||||
};
|
||||
} else {
|
||||
options = { channelID: id };
|
||||
}
|
||||
this._redirect(options);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickShowMore: function () {
|
||||
this.trigger('load_more_messages');
|
||||
},
|
||||
});
|
||||
|
||||
ThreadWidget.ORDER = ORDER;
|
||||
|
||||
return ThreadWidget;
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,685 @@
|
||||
odoo.define('im_livechat.legacy.mail.widget.Thread', function (require) {
|
||||
"use strict";
|
||||
|
||||
var DocumentViewer = require('im_livechat.legacy.mail.DocumentViewer');
|
||||
var mailUtils = require('@mail/js/utils');
|
||||
|
||||
var core = require('web.core');
|
||||
var time = require('web.time');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _lt = core._lt;
|
||||
|
||||
var ORDER = {
|
||||
ASC: 1, // visually, ascending order of message IDs (from top to bottom)
|
||||
DESC: -1, // visually, descending order of message IDs (from top to bottom)
|
||||
};
|
||||
|
||||
var READ_MORE = _lt("read more");
|
||||
var READ_LESS = _lt("read less");
|
||||
|
||||
/**
|
||||
* This is a generic widget to render a thread.
|
||||
* Any thread that extends mail.model.AbstractThread can be used with this
|
||||
* widget.
|
||||
*/
|
||||
var ThreadWidget = Widget.extend({
|
||||
className: 'o_mail_thread',
|
||||
|
||||
events: {
|
||||
'click a': '_onClickRedirect',
|
||||
'click img': '_onClickRedirect',
|
||||
'click strong': '_onClickRedirect',
|
||||
'click .o_thread_show_more': '_onClickShowMore',
|
||||
'click .o_attachment_download': '_onAttachmentDownload',
|
||||
'click .o_attachment_view': '_onAttachmentView',
|
||||
'click .o_attachment_delete_cross': '_onDeleteAttachment',
|
||||
'click .o_thread_message_needaction': '_onClickMessageNeedaction',
|
||||
'click .o_thread_message_star': '_onClickMessageStar',
|
||||
'click .o_thread_message_reply': '_onClickMessageReply',
|
||||
'click .oe_mail_expand': '_onClickMailExpand',
|
||||
'click .o_thread_message': '_onClickMessage',
|
||||
'click': '_onClick',
|
||||
'click .o_thread_message_notification_error': '_onClickMessageNotificationError',
|
||||
'click .o_thread_message_moderation': '_onClickMessageModeration',
|
||||
'change .moderation_checkbox': '_onChangeModerationCheckbox',
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {widget} parent
|
||||
* @param {Object} options
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.attachments = [];
|
||||
// options when the thread is enabled (e.g. can send message,
|
||||
// interact on messages, etc.)
|
||||
this._enabledOptions = _.defaults(options || {}, {
|
||||
displayOrder: ORDER.ASC,
|
||||
displayMarkAsRead: true,
|
||||
displayModerationCommands: false,
|
||||
displayStars: true,
|
||||
displayDocumentLinks: true,
|
||||
displayAvatars: true,
|
||||
squashCloseMessages: true,
|
||||
displayNotificationIcons: true,
|
||||
displayReplyIcons: false,
|
||||
loadMoreOnScroll: false,
|
||||
hasMessageAttachmentDeletable: false,
|
||||
});
|
||||
// options when the thread is disabled
|
||||
this._disabledOptions = {
|
||||
displayOrder: this._enabledOptions.displayOrder,
|
||||
displayMarkAsRead: false,
|
||||
displayModerationCommands: false,
|
||||
displayStars: false,
|
||||
displayDocumentLinks: false,
|
||||
displayAvatars: this._enabledOptions.displayAvatars,
|
||||
squashCloseMessages: false,
|
||||
displayNotificationIcons: false,
|
||||
displayReplyIcons: false,
|
||||
loadMoreOnScroll: this._enabledOptions.loadMoreOnScroll,
|
||||
hasMessageAttachmentDeletable: false,
|
||||
};
|
||||
this._selectedMessageID = null;
|
||||
this._currentThreadID = null;
|
||||
this._messageMailPopover = null;
|
||||
this._messageSeenPopover = null;
|
||||
// used to track popover IDs to destroy on re-rendering of popovers
|
||||
this._openedSeenPopoverIDs = [];
|
||||
},
|
||||
/**
|
||||
* The message mail popover may still be shown at this moment. If we do not
|
||||
* remove it, it stays visible on the page until a page reload.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
destroy: function () {
|
||||
clearInterval(this._updateTimestampsInterval);
|
||||
if (this._messageMailPopover) {
|
||||
this._messageMailPopover.popover('hide');
|
||||
}
|
||||
if (this._messageSeenPopover) {
|
||||
this._messageSeenPopover.popover('hide');
|
||||
}
|
||||
this._destroyOpenSeenPopoverIDs();
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
* @param {im_livechat.legacy.mail.model.AbstractThread} thread the thread to render.
|
||||
* @param {Object} [options]
|
||||
* @param {integer} [options.displayOrder=ORDER.ASC] order of displaying
|
||||
* messages in the thread:
|
||||
* - ORDER.ASC: last message is at the bottom of the thread
|
||||
* - ORDER.DESC: last message is at the top of the thread
|
||||
* @param {boolean} [options.displayLoadMore]
|
||||
* @param {Array} [options.domain=[]] the domain for the messages in the
|
||||
* thread.
|
||||
* @param {boolean} [options.isCreateMode]
|
||||
* @param {boolean} [options.scrollToBottom=false]
|
||||
* @param {boolean} [options.squashCloseMessages]
|
||||
*/
|
||||
render: function (thread, options) {
|
||||
var self = this;
|
||||
|
||||
var shouldScrollToBottomAfterRendering = false;
|
||||
if (this._currentThreadID === thread.getID() && this.isAtBottom()) {
|
||||
shouldScrollToBottomAfterRendering = true;
|
||||
}
|
||||
this._currentThreadID = thread.getID();
|
||||
|
||||
// copy so that reverse do not alter order in the thread object
|
||||
var messages = _.clone(thread.getMessages({ domain: options.domain || [] }));
|
||||
|
||||
var modeOptions = options.isCreateMode ? this._disabledOptions :
|
||||
this._enabledOptions;
|
||||
|
||||
// attachments ordered by messages order (increasing ID)
|
||||
this.attachments = _.uniq(_.flatten(_.map(messages, function (message) {
|
||||
return message.getAttachments();
|
||||
})));
|
||||
|
||||
options = _.extend({}, modeOptions, options, {
|
||||
selectedMessageID: this._selectedMessageID,
|
||||
});
|
||||
|
||||
// dict where key is message ID, and value is whether it should display
|
||||
// the author of message or not visually
|
||||
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
|
||||
// and in the same document (users can now post message in documents
|
||||
// directly from a channel that follows it)
|
||||
var prevMessage;
|
||||
_.each(messages, function (message) {
|
||||
if (
|
||||
// is first message of thread
|
||||
!prevMessage ||
|
||||
// more than 1 min. elasped
|
||||
(Math.abs(message.getDate().diff(prevMessage.getDate())) > 60000) ||
|
||||
prevMessage.getType() !== 'comment' ||
|
||||
message.getType() !== 'comment' ||
|
||||
// from a different author
|
||||
(prevMessage.getAuthorID() !== message.getAuthorID()) ||
|
||||
(
|
||||
// messages are linked to a document thread
|
||||
(
|
||||
prevMessage.isLinkedToDocumentThread() &&
|
||||
message.isLinkedToDocumentThread()
|
||||
) &&
|
||||
(
|
||||
// are from different documents
|
||||
prevMessage.getDocumentModel() !== message.getDocumentModel() ||
|
||||
prevMessage.getDocumentID() !== message.getDocumentID()
|
||||
)
|
||||
)
|
||||
) {
|
||||
displayAuthorMessages[message.getID()] = true;
|
||||
} else {
|
||||
displayAuthorMessages[message.getID()] = !options.squashCloseMessages;
|
||||
}
|
||||
prevMessage = message;
|
||||
});
|
||||
|
||||
if (modeOptions.displayOrder === ORDER.DESC) {
|
||||
messages.reverse();
|
||||
}
|
||||
|
||||
this.$el.html(QWeb.render('im_livechat.legacy.mail.widget.Thread', {
|
||||
thread: thread,
|
||||
displayAuthorMessages: displayAuthorMessages,
|
||||
options: options,
|
||||
ORDER: ORDER,
|
||||
dateFormat: time.getLangDatetimeFormat(),
|
||||
}));
|
||||
|
||||
_.each(messages, function (message) {
|
||||
var $message = self.$('.o_thread_message[data-message-id="' + message.getID() + '"]');
|
||||
$message.find('.o_mail_timestamp').data('date', message.getDate());
|
||||
|
||||
self._insertReadMore($message);
|
||||
});
|
||||
|
||||
if (shouldScrollToBottomAfterRendering) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
if (!this._updateTimestampsInterval) {
|
||||
this.updateTimestampsInterval = setInterval(function () {
|
||||
self._updateTimestamps();
|
||||
}, 1000 * 60);
|
||||
}
|
||||
|
||||
this._renderMessageNotificationPopover(messages);
|
||||
if (thread.hasSeenFeature()) {
|
||||
this._renderMessageSeenPopover(thread, messages);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render thread widget when loading, i.e. when messaging is not yet ready.
|
||||
* @see /mail/init_messaging
|
||||
*/
|
||||
renderLoading: function () {
|
||||
this.$el.html(QWeb.render('im_livechat.legacy.mail.widget.ThreadLoading'));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
getScrolltop: function () {
|
||||
return this.$el.scrollTop();
|
||||
},
|
||||
/**
|
||||
* State whether the bottom of the thread is visible or not,
|
||||
* with a tolerance of 5 pixels
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAtBottom: function () {
|
||||
var fullHeight = this.el.scrollHeight;
|
||||
var topHiddenHeight = this.$el.scrollTop();
|
||||
var visibleHeight = this.$el.outerHeight();
|
||||
var bottomHiddenHeight = fullHeight - topHiddenHeight - visibleHeight;
|
||||
return bottomHiddenHeight < 5;
|
||||
},
|
||||
/**
|
||||
* Removes a message and re-renders the thread
|
||||
*
|
||||
* @param {integer} [messageID] the id of the removed message
|
||||
* @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, thread, options) {
|
||||
var self = this;
|
||||
this._currentThreadID = thread.getID();
|
||||
return new Promise(function (resolve, reject) {
|
||||
self.$('.o_thread_message[data-message-id="' + messageID + '"]')
|
||||
.fadeOut({
|
||||
done: function () {
|
||||
if (self._currentThreadID === thread.getID()) {
|
||||
self.render(thread, options);
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
duration: 200,
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Scroll to the bottom of the thread
|
||||
*/
|
||||
scrollToBottom: function () {
|
||||
this.$el.scrollTop(this.el.scrollHeight);
|
||||
},
|
||||
/**
|
||||
* Scrolls the thread to a given message
|
||||
*
|
||||
* @param {integer} options.msgID the ID of the message to scroll to
|
||||
* @param {integer} [options.duration]
|
||||
* @param {boolean} [options.onlyIfNecessary]
|
||||
*/
|
||||
scrollToMessage: function (options) {
|
||||
var $target = this.$('.o_thread_message[data-message-id="' + options.messageID + '"]');
|
||||
if (options.onlyIfNecessary) {
|
||||
var delta = $target.parent().height() - $target.height();
|
||||
var offset = delta < 0 ?
|
||||
0 :
|
||||
delta - ($target.offset().top - $target.offsetParent().offset().top);
|
||||
offset = - Math.min(offset, 0);
|
||||
this.$el.scrollTo("+=" + offset + "px", options.duration);
|
||||
} else if ($target.length) {
|
||||
this.$el.scrollTo($target);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Scroll to the specific position in pixel
|
||||
*
|
||||
* If no position is provided, scroll to the bottom of the thread
|
||||
*
|
||||
* @param {integer} [position] distance from top to position in pixels.
|
||||
* If not provided, scroll to the bottom.
|
||||
*/
|
||||
scrollToPosition: function (position) {
|
||||
if (position) {
|
||||
this.$el.scrollTop(position);
|
||||
} else {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Toggle all the moderation checkboxes in the thread
|
||||
*
|
||||
* @param {boolean} checked if true, check the boxes,
|
||||
* otherwise uncheck them.
|
||||
*/
|
||||
toggleModerationCheckboxes: function (checked) {
|
||||
this.$('.moderation_checkbox').prop('checked', checked);
|
||||
},
|
||||
/**
|
||||
* Unselect the selected message
|
||||
*/
|
||||
unselectMessage: function () {
|
||||
this.$('.o_thread_message').removeClass('o_thread_selected_message');
|
||||
this._selectedMessageID = null;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_destroyOpenSeenPopoverIDs: function () {
|
||||
_.each(this._openedSeenPopoverIDs, function (popoverID) {
|
||||
$('#' + popoverID).remove();
|
||||
});
|
||||
this._openedSeenPopoverIDs = [];
|
||||
},
|
||||
/**
|
||||
* Modifies $element to add the 'read more/read less' functionality
|
||||
* All element nodes with 'data-o-mail-quote' attribute are concerned.
|
||||
* All text nodes after a ``#stopSpelling`` element are concerned.
|
||||
* Those text nodes need to be wrapped in a span (toggle functionality).
|
||||
* All consecutive elements are joined in one 'read more/read less'.
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $element
|
||||
*/
|
||||
_insertReadMore: function ($element) {
|
||||
var self = this;
|
||||
|
||||
var groups = [];
|
||||
var readMoreNodes;
|
||||
|
||||
// nodeType 1: element_node
|
||||
// nodeType 3: text_node
|
||||
var $children = $element.contents()
|
||||
.filter(function () {
|
||||
return this.nodeType === 1 ||
|
||||
this.nodeType === 3 &&
|
||||
this.nodeValue.trim();
|
||||
});
|
||||
|
||||
_.each($children, function (child) {
|
||||
var $child = $(child);
|
||||
|
||||
// Hide Text nodes if "stopSpelling"
|
||||
if (
|
||||
child.nodeType === 3 &&
|
||||
$child.prevAll('[id*="stopSpelling"]').length > 0
|
||||
) {
|
||||
// Convert Text nodes to Element nodes
|
||||
$child = $('<span>', {
|
||||
text: child.textContent,
|
||||
'data-o-mail-quote': '1',
|
||||
});
|
||||
child.parentNode.replaceChild($child[0], child);
|
||||
}
|
||||
|
||||
// Create array for each 'read more' with nodes to toggle
|
||||
if (
|
||||
$child.attr('data-o-mail-quote') ||
|
||||
(
|
||||
$child.get(0).nodeName === 'BR' &&
|
||||
$child.prev('[data-o-mail-quote="1"]').length > 0
|
||||
)
|
||||
) {
|
||||
if (!readMoreNodes) {
|
||||
readMoreNodes = [];
|
||||
groups.push(readMoreNodes);
|
||||
}
|
||||
$child.hide();
|
||||
readMoreNodes.push($child);
|
||||
} else {
|
||||
readMoreNodes = undefined;
|
||||
self._insertReadMore($child);
|
||||
}
|
||||
});
|
||||
|
||||
_.each(groups, function (group) {
|
||||
// Insert link just before the first node
|
||||
var $readMore = $('<a>', {
|
||||
class: 'o_mail_read_more',
|
||||
href: '#',
|
||||
text: READ_MORE,
|
||||
}).insertBefore(group[0]);
|
||||
|
||||
// Toggle All next nodes
|
||||
var isReadMore = true;
|
||||
$readMore.click(function (e) {
|
||||
e.preventDefault();
|
||||
isReadMore = !isReadMore;
|
||||
_.each(group, function ($child) {
|
||||
$child.hide();
|
||||
$child.toggle(!isReadMore);
|
||||
});
|
||||
$readMore.text(isReadMore ? READ_MORE : READ_LESS);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onDeleteAttachment: function (ev) {
|
||||
ev.stopPropagation();
|
||||
var $target = $(ev.currentTarget);
|
||||
this.trigger_up('delete_attachment', {
|
||||
attachmentId: $target.data('id'),
|
||||
attachmentName: $target.data('name')
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @param {integer} [options.channelID]
|
||||
* @param {string} options.model
|
||||
* @param {integer} options.id
|
||||
*/
|
||||
_redirect: _.debounce(function (options) {
|
||||
if ('channelID' in options) {
|
||||
this.trigger('redirect_to_channel', options.channelID);
|
||||
} else {
|
||||
this.trigger('redirect', options.model, options.id);
|
||||
}
|
||||
}, 500, true),
|
||||
/**
|
||||
* Render the popover when mouse-hovering on the notification icon of a
|
||||
* message in the thread.
|
||||
* There is at most one such popover at any given time.
|
||||
*
|
||||
* @private
|
||||
* @param {im_livechat.legacy.mail.model.AbstractMessage[]} messages list of messages in the
|
||||
* rendered thread, for which popover on mouseover interaction is
|
||||
* permitted.
|
||||
*/
|
||||
_renderMessageNotificationPopover(messages) {
|
||||
if (this._messageMailPopover) {
|
||||
this._messageMailPopover.popover('hide');
|
||||
}
|
||||
if (!this.$('.o_thread_tooltip').length) {
|
||||
return;
|
||||
}
|
||||
this._messageMailPopover = this.$('.o_thread_tooltip').popover({
|
||||
html: true,
|
||||
boundary: 'viewport',
|
||||
placement: 'auto',
|
||||
trigger: 'hover',
|
||||
offset: '0, 1',
|
||||
content: function () {
|
||||
var messageID = $(this).data('message-id');
|
||||
var message = _.find(messages, function (message) {
|
||||
return message.getID() === messageID;
|
||||
});
|
||||
return QWeb.render('im_livechat.legacy.mail.widget.Thread.Message.MailTooltip', {
|
||||
notifications: message.getNotifications(),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Render the popover when mouse hovering on the seen icon of a message
|
||||
* in the thread. Only seen icons in non-squashed message have popover,
|
||||
* because squashed messages hides this icon on message mouseover.
|
||||
*
|
||||
* @private
|
||||
* @param {im_livechat.legacy.mail.model.AbstractThread} thread with thread seen mixin,
|
||||
* @see {im_livechat.legacy.mail.model.ThreadSeenMixin}
|
||||
* @param {im_livechat.legacy.mail.model.Message[]} messages list of messages in the
|
||||
* rendered thread.
|
||||
*/
|
||||
_renderMessageSeenPopover: function (thread, messages) {
|
||||
var self = this;
|
||||
this._destroyOpenSeenPopoverIDs();
|
||||
if (this._messageSeenPopover) {
|
||||
this._messageSeenPopover.popover('hide');
|
||||
}
|
||||
if (!this.$('.o_thread_message_core .o_mail_thread_message_seen_icon').length) {
|
||||
return;
|
||||
}
|
||||
this._messageSeenPopover = this.$('.o_thread_message_core .o_mail_thread_message_seen_icon').popover({
|
||||
html: true,
|
||||
boundary: 'viewport',
|
||||
placement: 'auto',
|
||||
trigger: 'hover',
|
||||
offset: '0, 1',
|
||||
content: function () {
|
||||
var $this = $(this);
|
||||
self._openedSeenPopoverIDs.push($this.attr('aria-describedby'));
|
||||
var messageID = $this.data('message-id');
|
||||
var message = _.find(messages, function (message) {
|
||||
return message.getID() === messageID;
|
||||
});
|
||||
return QWeb.render('im_livechat.legacy.mail.widget.Thread.Message.SeenIconPopoverContent', {
|
||||
thread: thread,
|
||||
message: message,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateTimestamps: function () {
|
||||
var isAtBottom = this.isAtBottom();
|
||||
this.$('.o_mail_timestamp').each(function () {
|
||||
var date = $(this).data('date');
|
||||
$(this).text(mailUtils.timeFromNow(date));
|
||||
});
|
||||
if (isAtBottom && !this.isAtBottom()) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onAttachmentDownload: function (event) {
|
||||
event.stopPropagation();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onAttachmentView: function (event) {
|
||||
event.stopPropagation();
|
||||
var activeAttachmentID = $(event.currentTarget).data('id');
|
||||
if (activeAttachmentID) {
|
||||
var attachmentViewer = new DocumentViewer(this, this.attachments, activeAttachmentID);
|
||||
attachmentViewer.appendTo($('body'));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onChangeModerationCheckbox: function (ev) {
|
||||
this.trigger_up('update_moderation_buttons');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClick: function () {
|
||||
if (this._selectedMessageID) {
|
||||
this.unselectMessage();
|
||||
this.trigger('unselect_message');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMailExpand: function (ev) {
|
||||
ev.preventDefault();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessage: function (ev) {
|
||||
$(ev.currentTarget).toggleClass('o_thread_selected_message');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageNeedaction: function (ev) {
|
||||
var messageID = $(ev.currentTarget).data('message-id');
|
||||
this.trigger('mark_as_read', messageID);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageNotificationError(ev) {
|
||||
const messageID = $(ev.currentTarget).data('message-id');
|
||||
this.do_action('mail.mail_resend_message_action', {
|
||||
additional_context: {
|
||||
mail_message_to_resend: messageID,
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageReply: function (ev) {
|
||||
this._selectedMessageID = $(ev.currentTarget).data('message-id');
|
||||
this.$('.o_thread_message').removeClass('o_thread_selected_message');
|
||||
this.$('.o_thread_message[data-message-id="' + this._selectedMessageID + '"]')
|
||||
.addClass('o_thread_selected_message');
|
||||
this.trigger('select_message', this._selectedMessageID);
|
||||
ev.stopPropagation();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageStar: function (ev) {
|
||||
var messageID = $(ev.currentTarget).data('message-id');
|
||||
this.trigger('toggle_star_status', messageID);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickMessageModeration: function (ev) {
|
||||
var $button = $(ev.currentTarget);
|
||||
var messageID = $button.data('message-id');
|
||||
var decision = $button.data('decision');
|
||||
this.trigger_up('message_moderation', {
|
||||
messageID: messageID,
|
||||
decision: decision,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickRedirect: function (ev) {
|
||||
// ignore inherited branding
|
||||
if ($(ev.target).data('oe-field') !== undefined) {
|
||||
return;
|
||||
}
|
||||
var id = $(ev.target).data('oe-id');
|
||||
if (id) {
|
||||
ev.preventDefault();
|
||||
var model = $(ev.target).data('oe-model');
|
||||
var options;
|
||||
if (model && (model !== 'mail.channel')) {
|
||||
options = {
|
||||
model: model,
|
||||
id: id
|
||||
};
|
||||
} else {
|
||||
options = { channelID: id };
|
||||
}
|
||||
this._redirect(options);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickShowMore: function () {
|
||||
this.trigger('load_more_messages');
|
||||
},
|
||||
});
|
||||
|
||||
ThreadWidget.ORDER = ORDER;
|
||||
|
||||
return ThreadWidget;
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user