summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-06-15 18:12:21 +0000
committerSébastien Theys <seb@odoo.com>2020-06-15 18:12:21 +0000
commit3fea5b2136627c1d7e83b2a5e111952fe3eb10a1 (patch)
tree5eefad07972a87ce4689b83260f981bc87aadad4 /addons/mail/static/src/js/thread_windows/abstract_thread_window.js
parent0a82848606151ef60eb6523e9c4c161171f214e7 (diff)
[REF] mail, *: refactor messaging with OWL
This commit is a significant rewriting of client-side discuss, chatter, chat window, and messaging menu using OWL. The behavior should be broadly the same, with some slight functional changes here and there. From a technical standpoint, the code of messaging is mainly organized in 2 main groups of modules: - models, which are logical entities that depict the client-side state of messaging as a whole. - components, which are in charge of displaying information from models. This refactoring also introduces new JS guidelines regarding folder structure (/static) and naming rules for JS modules. Community PR: https://github.com/odoo/odoo/pull/39023 Enterprise PR: https://github.com/odoo/enterprise/pull/6249 Task-1914207 This PR is a collaborative work by Alexandre, Julien, Sébastien and Xavier, with the precious help of Lucas to speed it up towards the end. closes odoo/odoo#39023 Related: odoo/enterprise#6249 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Co-authored-by: Alexandre Kühn <aku@odoo.com> Co-authored-by: Julien Giannone <jgi@odoo.com> Co-authored-by: Lucas Perais <lpe@odoo.com> Co-authored-by: Sébastien Theys <seb@odoo.com> Co-authored-by: Xavier Dubuc <xdu@odoo.com>
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.js519
1 files changed, 0 insertions, 519 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
deleted file mode 100644
index 7e39f9c7e2e..00000000000
--- a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
+++ /dev/null
@@ -1,519 +0,0 @@
-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;
-
-/**
- * This is an abstract widget for rendering thread windows.
- *
- * It contains logic that are shared between mail.ThreadWindow and
- * im_livechat.WebsiteLivechatWindow.
- *
- * 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 im_livechat.WebsiteLivechatWindow 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_composer_text_field': '_onComposerClick',
- 'click .o_mail_thread': '_onThreadWindowClicked',
- 'keydown .o_composer_text_field': '_onKeydown',
- 'keypress .o_composer_text_field': '_onKeypress',
- },
- FOLD_ANIMATION_DURATION: 200, // duration in ms for (un)fold transition
- HEIGHT_OPEN: '400px', // height in px of thread window when open
- HEIGHT_FOLDED: '34px', // height, in px, of thread window when folded
- /**
- * 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 {mail.model.AbstractThread} [thread=null] the thread that this
- * thread window is linked to. If not set, it is the "blank" thread
- * window.
- * @param {Object} [options={}]
- * @param {mail.model.AbstractThread} [options.thread]
- */
- init: function (parent, thread, options) {
- this._super(parent);
-
- this.options = _.defaults(options || {}, {
- autofocus: true,
- displayStars: true,
- displayReplyIcons: false,
- displayNotificationIcons: false,
- placeholder: _t("Say something"),
- });
-
- this._hidden = false;
- this._thread = thread || null;
-
- this._debouncedOnScroll = _.debounce(this._onScroll.bind(this), 100);
-
- if (!this.hasThread()) {
- // internal fold state of thread window without any thread
- this._folded = false;
- }
- },
- start: function () {
- var self = this;
- this.$input = this.$('.o_composer_text_field');
- this.$header = this.$('.o_thread_window_header');
- var options = {
- displayMarkAsRead: false,
- displayStars: this.options.displayStars,
- };
- if (this._thread && this._thread._type === 'document_thread') {
- options.displayDocumentLinks = false;
- }
- this._threadWidget = new ThreadWidget(this, options);
-
- // animate the (un)folding of thread windows
- this.$el.css({transition: 'height ' + this.FOLD_ANIMATION_DURATION + 'ms linear'});
- if (this.isFolded()) {
- this.$el.css('height', this.HEIGHT_FOLDED);
- } else if (this.options.autofocus) {
- this._focusInput();
- }
- if (!config.device.isMobile) {
- var margin_dir = _t.database.parameters.direction === "rtl" ? "margin-left" : "margin-right";
- this.$el.css(margin_dir, $.position.scrollbarWidth());
- }
- var def = this._threadWidget.replace(this.$('.o_thread_window_content')).then(function () {
- self._threadWidget.$el.on('scroll', self, self._debouncedOnScroll);
- });
- return Promise.all([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 ID of the thread window, which is equivalent to the ID of the
- * thread related to this window
- *
- * @returns {integer|string}
- */
- getID: function () {
- return this._getThreadID();
- },
- /**
- * @returns {mail.model.Thread|undefined}
- */
- getThread: function () {
- if (!this.hasThread()) {
- return undefined;
- }
- return this._thread;
- },
- /**
- * Get the status of the thread, such as the im status of a DM chat
- * ('online', 'offline', etc.). If this window has no thread, returns
- * `undefined`.
- *
- * @returns {string|undefined}
- */
- getThreadStatus: function () {
- if (!this.hasThread()) {
- return undefined;
- }
- return this._thread.getStatus();
- },
- /**
- * Get the title of the thread window, which usually contains the name of
- * the thread.
- *
- * @returns {string}
- */
- getTitle: function () {
- if (!this.hasThread()) {
- return _t("Undefined");
- }
- return this._thread.getTitle();
- },
- /**
- * Get the unread counter of the related thread. If there are no thread
- * linked to this window, returns 0.
- *
- * @returns {integer}
- */
- getUnreadCounter: function () {
- if (!this.hasThread()) {
- return 0;
- }
- return this._thread.getUnreadCounter();
- },
- /**
- * 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.
- *
- * @returns {boolean}
- */
- hasThread: function () {
- return !! this._thread;
- },
- /**
- * Tells whether the bottom of the thread in the thread window is visible
- * or not.
- *
- * @returns {boolean}
- */
- isAtBottom: function () {
- return this._threadWidget.isAtBottom();
- },
- /**
- * State whether the related thread is folded or not. If there are no
- * thread related to this window, it means this is the "blank" thread
- * window, therefore we use the internal folded state.
- *
- * @returns {boolean}
- */
- isFolded: function () {
- if (!this.hasThread()) {
- return this._folded;
- }
- return this._thread.isFolded();
- },
- /**
- * 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;
- },
- /**
- * States whether the input of the thread window should be displayed or not.
- * By default, any thread window with a thread needs a composer.
- *
- * @returns {boolean}
- */
- needsComposer: function () {
- return this.hasThread();
- },
- /**
- * Render the thread window
- */
- render: function () {
- this.renderHeader();
- if (this.hasThread()) {
- this._threadWidget.render(this._thread, { displayLoadMore: false });
- }
- },
- /**
- * Render the header of this thread window.
- * This is useful when some information on the header have be updated such
- * as the status or the title of the thread that have changed.
- *
- * @private
- */
- renderHeader: function () {
- var options = this._getHeaderRenderingOptions();
- this.$header.html(
- QWeb.render('mail.AbstractThreadWindow.HeaderContent', options));
- },
- /**
- * Scroll to the bottom of the thread in the thread window
- */
- scrollToBottom: function () {
- this._threadWidget.scrollToBottom();
- },
- /**
- * Toggle the fold state of this thread window. Also update the fold state
- * of the thread model. If the boolean parameter `folded` is provided, it
- * folds/unfolds the window when it is set/unset.
- *
- * @param {boolean} [folded] if not a boolean, toggle the fold state.
- * Otherwise, fold/unfold the window if set/unset.
- */
- 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();
- if (this.options.autofocus) {
- this._focusInput();
- }
- }
- var height = this.isFolded() ? this.HEIGHT_FOLDED : this.HEIGHT_OPEN;
- this.$el.css({height: height});
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * 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.getThreadStatus(),
- thread: this.getThread(),
- title: this.getTitle(),
- unreadCounter: this.getUnreadCounter(),
- widget: this,
- };
- },
- /**
- * 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.
- *
- * @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();
- },
- /**
- * Tells whether there is focus on this thread. Note that a thread that has
- * the focus means the input has focus.
- *
- * @private
- * @returns {boolean}
- */
- _hasFocus: function () {
- return this.$input.is(':focus');
- },
- /**
- * Post a message on this thread window, and auto-scroll to the bottom of
- * the thread.
- *
- * @private
- * @param {Object} messageData
- */
- _postMessage: function (messageData) {
- var self = this;
- if (!this.hasThread()) {
- return;
- }
- this._thread.postMessage(messageData)
- .then(function () {
- self._threadWidget.scrollToBottom();
- });
- },
- /**
- * Update the fold state of the thread.
- *
- * This function is called when toggling the fold state of this window.
- * If there is no thread linked to this window, it means this is the
- * "blank" thread window, therefore we use the internal state 'folded'
- *
- * @private
- * @param {boolean} folded
- */
- _updateThreadFoldState: function (folded) {
- if (this.hasThread()) {
- this._thread.fold(folded);
- } else {
- this._folded = folded;
- this.updateVisualFoldState();
- }
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Close the thread window.
- * Mark the thread as read if the thread window was open.
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onClickClose: function (ev) {
- ev.stopPropagation();
- ev.preventDefault();
- if (
- this.hasThread() &&
- this._thread.getUnreadCounter() > 0 &&
- !this.isFolded()
- ) {
- this._thread.markAsRead();
- }
- this.close();
- },
- /**
- * Fold/unfold the thread window.
- * Also mark the thread as read.
- *
- * @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
- },
- /**
- * @private
- */
- _onScroll: function () {
- if (this.hasThread() && this.isAtBottom()) {
- this._thread.markAsRead();
- }
- },
- /**
- * 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;
-
-});