summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/thread_widget.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/mail/static/src/js/thread_widget.js')
-rw-r--r--addons/mail/static/src/js/thread_widget.js686
1 files changed, 0 insertions, 686 deletions
diff --git a/addons/mail/static/src/js/thread_widget.js b/addons/mail/static/src/js/thread_widget.js
deleted file mode 100644
index 583d14e949a..00000000000
--- a/addons/mail/static/src/js/thread_widget.js
+++ /dev/null
@@ -1,686 +0,0 @@
-odoo.define('mail.widget.Thread', function (require) {
-"use strict";
-
-var DocumentViewer = require('mail.DocumentViewer');
-var mailUtils = require('mail.utils');
-
-var core = require('web.core');
-var time = require('web.time');
-var Widget = require('web.Widget');
-
-var QWeb = core.qweb;
-var _t = core._t;
-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 {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('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('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 {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('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 {mail.model.AbstractThread} thread with thread seen mixin,
- * @see {mail.model.ThreadSeenMixin}
- * @param {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('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).html(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;
-
-});