diff options
| author | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
|---|---|---|
| committer | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
| commit | 3fea5b2136627c1d7e83b2a5e111952fe3eb10a1 (patch) | |
| tree | 5eefad07972a87ce4689b83260f981bc87aadad4 /addons/mail/static/src/js/thread_windows/abstract_thread_window.js | |
| parent | 0a82848606151ef60eb6523e9c4c161171f214e7 (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.js | 519 |
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; - -}); |
