summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/mail/static/src/js/thread_windows/abstract_thread_window.js')
-rw-r--r--addons/mail/static/src/js/thread_windows/abstract_thread_window.js390
1 files changed, 390 insertions, 0 deletions
diff --git a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
new file mode 100644
index 00000000000..1d49d89eeb7
--- /dev/null
+++ b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
@@ -0,0 +1,390 @@
+odoo.define('mail.AbstractThreadWindow', function (require) {
+"use strict";
+
+var ThreadWidget = require('mail.widget.Thread');
+
+var config = require('web.config');
+var core = require('web.core');
+var Widget = require('web.Widget');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+var HEIGHT_OPEN = '400px';
+var HEIGHT_FOLDED = '34px';
+
+/**
+ * This is an abstract widget for rendering thread windows.
+ *
+ * It contains logic that are shared between mail.ThreadWindow and
+ * mail.LivechatWindow.
+ *
+ * The reason for having two different implementation of thread windows is
+ * that mail.ThreadWindow makes use of mail.Manager, which is used in the
+ * backend, while mail.LivechatWindow must work without this mail service.
+ */
+var AbstractThreadWindow = Widget.extend({
+ template: 'mail.AbstractThreadWindow',
+ custom_events: {
+ escape_pressed: '_onEscapePressed',
+ document_viewer_closed: '_onDocumentViewerClose',
+ },
+ events: {
+ 'click .o_thread_window_close': '_onClickClose',
+ 'click .o_thread_window_title': '_onClickFold',
+ 'click .o_thread_composer': '_onComposerClick',
+ 'click .o_mail_thread': '_onThreadWindowClicked',
+ 'keydown .o_thread_composer': '_onKeydown',
+ 'keypress .o_thread_composer': '_onKeypress',
+ },
+ /**
+ * Children of this class must make use of `thread`, which is an object that
+ * represent the thread that is linked to this thread window.
+ *
+ * If no thread is provided, this will represent the "blank" thread window.
+ *
+ * @abstract
+ * @param {Widget} parent
+ * @param {Object} thread
+ * @param {Object} [options={}]
+ */
+ init: function (parent, thread, options) {
+ this._super(parent);
+
+ this.options = _.defaults(options || {}, {
+ autofocus: true,
+ displayStars: true,
+ displayReplyIcons: false,
+ displayEmailIcons: false,
+ placeholder: _t("Say something"),
+ });
+
+ this._hidden = false;
+ },
+ start: function () {
+ this.$input = this.$('.o_composer_text_field');
+ this.$header = this.$('.o_thread_window_header');
+
+ this.threadWidget = new ThreadWidget(this, {
+ threadID: this._getThreadID(),
+ displayDocumentLinks: false,
+ displayMarkAsRead: false,
+ displayStars: this.options.displayStars,
+ });
+
+ if (this.isFolded()) {
+ this.$el.css('height', HEIGHT_FOLDED);
+ } else if (this.options.autofocus) {
+ this._focusInput();
+ }
+ if (!config.device.isMobile) {
+ this.$el.css('margin-right', $.position.scrollbarWidth());
+ }
+ var def = this.threadWidget.replace(this.$('.o_chat_content'));
+ return $.when(this._super(), def);
+ },
+ /**
+ * @override
+ */
+ do_hide: function () {
+ this._hidden = true;
+ this._super.apply(this, arguments);
+ },
+ /**
+ * @override
+ */
+ do_show: function () {
+ this._hidden = false;
+ this._super.apply(this, arguments);
+ },
+ /**
+ * @override
+ */
+ do_toggle: function (display) {
+ this._hidden = _.isBoolean(display) ? !display : !this._hidden;
+ this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Close this window
+ *
+ * @abstract
+ */
+ close: function () {},
+ /**
+ * Get the status of the thread, such as the im status of a DM.
+ *
+ * @abstract
+ * @returns {string}
+ */
+ getStatus: function () {},
+ /**
+ * Get the title of the thread window, which usually contains the name of
+ * the thread.
+ *
+ * @abstract
+ * @returns {string}
+ */
+ getTitle: function () {},
+ /**
+ * Get the unread counter of the thread linked to this thread window.
+ *
+ * @abstract
+ * @returns {integer}
+ */
+ getUnreadCounter: function () {},
+ /**
+ * States whether this thread window is related to a thread or not.
+ *
+ * This is useful in order to provide specific behaviour for thread windows
+ * without any thread, e.g. let them open a thread from this "blank" thread
+ * window.
+ *
+ * @abstract
+ * @returns {boolean}
+ */
+ hasThread: function () {},
+ /**
+ * States whether this thread is folded or not. This information is stored
+ * on the thread model.
+ *
+ * @abstract
+ * @returns {boolean}
+ */
+ isFolded: function () {},
+ /**
+ * States whether the current environment is in mobile or not. This is
+ * useful in order to customize the template rendering for mobile view.
+ *
+ * @returns {boolean}
+ */
+ isMobile: function () {
+ return config.device.isMobile;
+ },
+ /**
+ * States whether the thread window is hidden or not.
+ *
+ * @returns {boolean}
+ */
+ isHidden: function () {
+ 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: function (messages) {
+ this._renderHeader();
+ this.threadWidget.render(messages, { displayLoadMore: false });
+ },
+ /**
+ * Toggle the fold state of this thread window. Also update the fold state
+ * of the thread model.
+ *
+ * @param {boolean} folded
+ */
+ toggleFold: function (folded) {
+ if (!_.isBoolean(folded)) {
+ folded = !this.isFolded();
+ }
+ this._updateThreadFoldState(folded);
+ },
+ /**
+ * Update the visual state of the window so that it matched the internal
+ * fold state. This is useful in case the related thread has its fold state
+ * that has been changed.
+ */
+ updateVisualFoldState: function () {
+ if (!this.isFolded()) {
+ this.threadWidget.scrollToBottom();
+ this._focusInput();
+ }
+ this._animateFold();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when there is a change of the fold state of the thread window.
+ * This method animates the change of fold state of this thread window.
+ *
+ * @private
+ */
+ _animateFold: function () {
+ this.$el.animate({
+ height: this.isFolded() ? HEIGHT_FOLDED : HEIGHT_OPEN
+ }, 200);
+ },
+ /**
+ * Set the focus on the composer of the thread window. This operation is
+ * ignored in mobile context.
+ *
+ * @private
+ * Set the focus on the input of the window
+ */
+ _focusInput: function () {
+ if (
+ config.device.touch &&
+ config.device.size_class <= config.device.SIZES.SM
+ ) {
+ return;
+ }
+ this.$input.focus();
+ },
+ /**
+ * Returns the options used by the rendering of the window's header
+ *
+ * @private
+ * @returns {Object}
+ */
+ _getHeaderRenderingOptions: function () {
+ return {
+ status: this.getStatus(),
+ title: this.getTitle(),
+ unreadCounter: this.getUnreadCounter(),
+ widget: this,
+ };
+ },
+ /**
+ * Get the ID of the related thread.
+ *
+ * @abstract
+ * @private
+ * @return {integer}
+ */
+ _getThreadID: function () {},
+ /**
+ * Post a message on this thread window
+ *
+ * @abstract
+ * @private
+ * @param {Object} messageData
+ */
+ _postMessage: function (messageData) {},
+ /**
+ * Update the fold state of the related thread.
+ * This function is called when toggling the fold state of this window
+ *
+ * @abstract
+ * @private
+ * @param {boolean} folded
+ */
+ _updateThreadFoldState: function (folded) {},
+ /**
+ * Render the header of this thread window
+ *
+ * @private
+ */
+ _renderHeader: function () {
+ var options = this._getHeaderRenderingOptions();
+ this.$header.html(
+ QWeb.render('mail.AbstractThreadWindowHeaderContent', options));
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Close the thread
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickClose: function (ev) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ this.close();
+ },
+ /**
+ * Fold/unfold the thread window
+ *
+ * @private
+ */
+ _onClickFold: function () {
+ if (!config.device.isMobile) {
+ this.toggleFold();
+ }
+ },
+ /**
+ * Called when the composer is clicked -> forces focus on input even if
+ * jquery's blockUI is enabled.
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onComposerClick: function (ev) {
+ if ($(ev.target).closest('a, button').length) {
+ return;
+ }
+ this._focusInput();
+ },
+ /**
+ * @private
+ */
+ _onDocumentViewerClose: function () {
+ this._focusInput();
+ },
+ /**
+ * @private
+ */
+ _onEscapePressed: function () {
+ if (!this.isFolded()) {
+ this.close();
+ }
+ },
+ /**
+ * Called when typing something on the composer of this thread window.
+ *
+ * @private
+ * @param {KeyboardEvent} ev
+ */
+ _onKeydown: function (ev) {
+ ev.stopPropagation(); // to prevent jquery's blockUI to cancel event
+ // ENTER key (avoid requiring jquery ui for external livechat)
+ if (ev.which === 13) {
+ var content = _.str.trim(this.$input.val());
+ var messageData = {
+ content: content,
+ attachment_ids: [],
+ partner_ids: [],
+ };
+ this.$input.val('');
+ if (content) {
+ this._postMessage(messageData);
+ }
+ }
+ },
+ /**
+ * @private
+ * @param {KeyboardEvent} ev
+ */
+ _onKeypress: function (ev) {
+ ev.stopPropagation(); // to prevent jquery's blockUI to cancel event
+ },
+ /**
+ * When a thread window is clicked on, we want to give the focus to the main
+ * input. An exception is made when the user is selecting something.
+ *
+ * @private
+ */
+ _onThreadWindowClicked: function () {
+ var selectObj = window.getSelection();
+ if (selectObj.anchorOffset === selectObj.focusOffset) {
+ this.$input.focus();
+ }
+ },
+});
+
+return AbstractThreadWindow;
+
+});