From 5d0b8cdfc946fadccb69c63adba1c33aebc46a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Theys?= Date: Mon, 16 Aug 2021 14:44:38 +0000 Subject: [PATCH] [IMP] mail: make messaging and models available in models without env Part of task-2582313 --- .../static/src/models/partner/partner.js | 2 +- .../src/components/composer/composer.js | 6 + .../static/src/components/discuss/discuss.js | 3 + .../discuss_sidebar/discuss_sidebar.js | 3 + .../discuss_sidebar/discuss_sidebar.xml | 144 +++++++++--------- addons/mail/static/src/env/test_env.js | 25 +-- addons/mail/static/src/js/main.js | 29 ++-- addons/mail/static/src/model/model_field.js | 38 ++--- addons/mail/static/src/model/model_manager.js | 62 +++++--- .../chat_window_manager.js | 5 +- .../mail/static/src/models/chatter/chatter.js | 2 +- .../mail/static/src/models/discuss/discuss.js | 21 +-- .../mail/static/src/models/message/message.js | 17 ++- .../static/src/models/messaging/messaging.js | 13 +- .../messaging_initializer.js | 7 - .../models/messaging_menu/messaging_menu.js | 5 +- .../messaging_notification_handler.js | 7 - addons/mail/static/src/models/model/model.js | 13 +- .../src/models/thread_cache/thread_cache.js | 36 +++-- .../src/models/thread_view/thread_view.js | 5 +- addons/mail/static/src/utils/test_utils.js | 30 ++-- 21 files changed, 245 insertions(+), 228 deletions(-) diff --git a/addons/hr_holidays/static/src/models/partner/partner.js b/addons/hr_holidays/static/src/models/partner/partner.js index 5a6c42d1c54..bff5c2e727c 100644 --- a/addons/hr_holidays/static/src/models/partner/partner.js +++ b/addons/hr_holidays/static/src/models/partner/partner.js @@ -32,7 +32,7 @@ registerInstancePatchModel('mail.partner', 'hr_holidays/static/src/models/partne if (!this.outOfOfficeDateEnd) { return clear(); } - if (!this.env.messaging.locale.language) { + if (!this.messaging.locale || !this.messaging.locale.language) { return clear(); } const currentDate = new Date(); diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js index 4491e211ce9..f1bbee2fedc 100644 --- a/addons/mail/static/src/components/composer/composer.js +++ b/addons/mail/static/src/components/composer/composer.js @@ -112,6 +112,9 @@ export class Composer extends Component { * @returns {boolean} */ get hasFooter() { + if (!this.composer) { + return false; + } return ( this.props.hasThreadTyping || this.composer.attachments.length > 0 || @@ -209,6 +212,9 @@ export class Composer extends Component { if (this.contains(ev.target)) { return; } + if (!this.composer) { + return; + } this.composer.discard(); } diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js index 86cb132dcf0..7427914e647 100644 --- a/addons/mail/static/src/components/discuss/discuss.js +++ b/addons/mail/static/src/components/discuss/discuss.js @@ -37,6 +37,9 @@ export class Discuss extends Component { } patched() { + if (!this.discuss) { + return; + } if (this.discuss.thread) { this.trigger('o-push-state-action-manager'); } diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js index fd83f800910..3645e842eeb 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.js @@ -235,6 +235,9 @@ export class DiscussSidebar extends Component { */ _onHideAddingItem(ev) { ev.stopPropagation(); + if (!this.discuss) { + return; + } this.discuss.clearIsAddingItem(); } diff --git a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml index 4f9c10e5de2..d0efa9bfdf8 100644 --- a/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml +++ b/addons/mail/static/src/components/discuss_sidebar/discuss_sidebar.xml @@ -2,79 +2,81 @@ -
-
- - +
+ +
+ + + +
+
+ + -
-
- - +
+
+
+ Channels +
+
+
+
+
+ +
+ +
+
+ + + +
+
+
+
+
+ Direct Messages +
+
+
+
+
+ +
+ +
+
+ + + +
+
-
-
-
- Channels -
-
-
-
-
- -
- -
-
- - - -
-
-
-
-
- Direct Messages -
-
-
-
-
- -
- -
-
- - - -
-
diff --git a/addons/mail/static/src/env/test_env.js b/addons/mail/static/src/env/test_env.js index 3bfecb10a54..b8d69aecfae 100644 --- a/addons/mail/static/src/env/test_env.js +++ b/addons/mail/static/src/env/test_env.js @@ -9,9 +9,8 @@ const { EventBus } = owl.core; * @param {Object} [providedEnv={}] * @returns {Object} */ -function addMessagingToEnv(providedEnv = {}) { - const env = Object.assign(providedEnv); - +export function addMessagingToEnv(providedEnv = {}) { + const env = { ...providedEnv }; /** * Registry of models. */ @@ -31,12 +30,6 @@ function addMessagingToEnv(providedEnv = {}) { }, }, (env.browser && env.browser.Notification) || {}), }, env.browser), - destroyMessaging() { - if (env.modelManager) { - env.modelManager.deleteAll(); - env.messaging = undefined; - } - }, disableAnimation: true, isMessagingInitialized() { if (!this.messaging) { @@ -52,13 +45,10 @@ function addMessagingToEnv(providedEnv = {}) { */ isQUnitTest: true, loadingBaseDelayDuration: providedEnv.loadingBaseDelayDuration || 0, - messaging: undefined, messagingCreatedPromise: makeDeferred(), messagingInitializedDeferred: makeDeferred(), messagingBus: new EventBus(), - modelManager: undefined, }); - return env; } @@ -66,10 +56,8 @@ function addMessagingToEnv(providedEnv = {}) { * @param {Object} [providedEnv={}] * @returns {Object} */ -function addTimeControlToEnv(providedEnv = {}) { - - let env = Object.assign({}, providedEnv); - +export function addTimeControlToEnv(providedEnv = {}) { + const env = { ...providedEnv }; if (!env.browser) { env.browser = {}; } @@ -126,8 +114,3 @@ function addTimeControlToEnv(providedEnv = {}) { }); return env; } - -export { - addMessagingToEnv, - addTimeControlToEnv, -} diff --git a/addons/mail/static/src/js/main.js b/addons/mail/static/src/js/main.js index 2caa1cccf17..dd6e35cb125 100644 --- a/addons/mail/static/src/js/main.js +++ b/addons/mail/static/src/js/main.js @@ -33,33 +33,19 @@ async function createMessaging() { await env.session.is_bound; env.modelManager.start(); - /** - * Create the messaging singleton record. - */ - env.messaging = env.models['mail.messaging'].create(); } -/** - * Registry of models. - */ -env.models = {}; /** * Environment keys used in messaging. */ Object.assign(env, { autofetchPartnerImStatus: true, - destroyMessaging() { - if (env.modelManager) { - env.modelManager.deleteAll(); - env.messaging = undefined; - } - }, disableAnimation: false, isMessagingInitialized() { - if (!this.messaging) { + if (!this.modelManager.messaging) { return false; } - return this.messaging.isInitialized; + return this.modelManager.messaging.isInitialized; }, /** * States whether the environment is in QUnit test or not. @@ -69,7 +55,6 @@ Object.assign(env, { */ isQUnitTest: false, loadingBaseDelayDuration: 400, - messaging: undefined, messagingBus: new EventBus(), /** * Promise which becomes resolved when messaging is created. @@ -80,5 +65,15 @@ Object.assign(env, { messagingCreatedPromise: createMessaging(), modelManager: new ModelManager(env), }); +Object.defineProperty(env, 'messaging', { + get() { + return this.modelManager.messaging; + }, +}); +Object.defineProperty(env, 'models', { + get() { + return this.modelManager.models; + }, +}); serviceRegistry.add('messaging', MessagingService); diff --git a/addons/mail/static/src/model/model_field.js b/addons/mail/static/src/model/model_field.js index 33c29619c20..d7069f8d655 100644 --- a/addons/mail/static/src/model/model_field.js +++ b/addons/mail/static/src/model/model_field.js @@ -16,11 +16,11 @@ export class ModelField { constructor({ compute, default: def, - env, fieldName, fieldType, inverse, isCausal = false, + modelManager, readonly = false, related, relationType, @@ -39,10 +39,6 @@ export class ModelField { * set a value by default. */ this.default = def; - /** - * The messaging env. - */ - this.env = env; /** * Name of the field in the definition of fields on model. */ @@ -68,6 +64,10 @@ export class ModelField { * relation is removed, the related record is automatically deleted. */ this.isCausal = isCausal; + /** + * Reference to the manager of this field. + */ + this.modelManager = modelManager; /** * Determines whether the field is read only. Read only field * can't be updated once the record is created. @@ -482,8 +482,8 @@ export class ModelField { * @returns {boolean} whether the value changed for the current field */ _setRelationCreate(record, data, options) { - const OtherModel = this.env.models[this.to]; - const other = this.env.modelManager._create(OtherModel, data); + const OtherModel = this.modelManager.models[this.to]; + const other = this.modelManager._create(OtherModel, data); return this._setRelationLink(record, other, options); } @@ -500,8 +500,8 @@ export class ModelField { * @returns {boolean} whether the value changed for the current field */ _setRelationInsert(record, data, options) { - const OtherModel = this.env.models[this.to]; - const other = this.env.modelManager._insert(OtherModel, data); + const OtherModel = this.modelManager.models[this.to]; + const other = this.modelManager._insert(OtherModel, data); return this._setRelationLink(record, other, options); } @@ -517,8 +517,8 @@ export class ModelField { * @returns {boolean} whether the value changed for the current field */ _setRelationInsertAndReplace(record, data, options) { - const OtherModel = this.env.models[this.to]; - const newValue = this.env.modelManager._insert(OtherModel, data); + const OtherModel = this.modelManager.models[this.to]; + const newValue = this.modelManager._insert(OtherModel, data); return this._setRelationReplace(record, newValue, options); } @@ -569,7 +569,7 @@ export class ModelField { otherRecords.add(recordToLink); // link current record to other records if (hasToUpdateInverse) { - this.env.modelManager._update( + this.modelManager._update( recordToLink, { [this.inverse]: link(record) }, { allowWriteReadonly: true, hasToUpdateInverse: false } @@ -605,7 +605,7 @@ export class ModelField { record.__values[this.fieldName] = recordToLink; // link current record to other record if (hasToUpdateInverse) { - this.env.modelManager._update( + this.modelManager._update( recordToLink, { [this.inverse]: link(record) }, { allowWriteReadonly: true, hasToUpdateInverse: false } @@ -713,7 +713,7 @@ export class ModelField { if (!otherRecord) { throw Error(`Record ${record.localId} cannot update undefined relational field ${this.fieldName}.`); } - this.env.modelManager._update(otherRecord, data); + this.modelManager._update(otherRecord, data); return false; } @@ -756,9 +756,9 @@ export class ModelField { } // apply causality if (this.isCausal) { - this.env.modelManager._delete(recordToUnlink); + this.modelManager._delete(recordToUnlink); } else { - this.env.modelManager._update( + this.modelManager._update( recordToUnlink, { [this.inverse]: unlink(record) }, { allowWriteReadonly: true, hasToUpdateInverse: false } @@ -800,9 +800,9 @@ export class ModelField { } // apply causality if (this.isCausal) { - this.env.modelManager._delete(otherRecord); + this.modelManager._delete(otherRecord); } else { - this.env.modelManager._update( + this.modelManager._update( otherRecord, { [this.inverse]: unlink(record) }, { allowWriteReadonly: true, hasToUpdateInverse: false } @@ -822,7 +822,7 @@ export class ModelField { * @throws {Error} if record does not satisfy related model */ _verifyRelationalValue(record) { - const OtherModel = this.env.models[this.to]; + const OtherModel = this.modelManager.models[this.to]; if (!OtherModel.get(record.localId, { isCheckingInheritance: true })) { throw Error(`Record ${record.localId} is not valid for relational field ${this.fieldName}.`); } diff --git a/addons/mail/static/src/model/model_manager.js b/addons/mail/static/src/model/model_manager.js index 0ea0d02bab3..709b95b8ef4 100644 --- a/addons/mail/static/src/model/model_manager.js +++ b/addons/mail/static/src/model/model_manager.js @@ -4,7 +4,7 @@ import { registry } from '@mail/model/model_core'; import { ModelField } from '@mail/model/model_field'; import { Listener } from '@mail/model/model_listener'; import { patchClassMethods, patchInstanceMethods } from '@mail/utils/utils'; -import { unlinkAll } from '@mail/model/model_field_command'; +import { link, unlinkAll } from '@mail/model/model_field_command'; /** * Object that manage models and records, notably their update cycle: whenever @@ -83,6 +83,14 @@ export class ModelManager { * iterate all localId to be able to find which are using it. */ this._localIdsObservedByListener = new Map(); + /** + * The messaging singleton associated to this model manager. + */ + this._messaging = undefined; + /** + * All generated models. Keys are model name, values are model class. + */ + this.models = {}; /** * Set of records that have been updated during the current update * cycle. @@ -98,7 +106,11 @@ export class ModelManager { /** * Generate the models. */ - Object.assign(this.env.models, this._generateModels()); + this.models = this._generateModels(); + /** + * Create the messaging singleton record. + */ + this.models['mail.messaging'].create(); } //-------------------------------------------------------------------------- @@ -150,18 +162,6 @@ export class ModelManager { this._flushUpdateCycle(); } - /** - * Delete all records. - */ - deleteAll() { - for (const Model of Object.values(this.env.models)) { - for (const record of Object.values(Model.__records)) { - this._delete(record); - } - } - this._flushUpdateCycle(); - } - /** * Returns whether the given record still exists. * @@ -232,7 +232,7 @@ export class ModelManager { return; } // support for inherited models (eg. relation targeting `mail.model`) - for (const SubModel of Object.values(this.env.models)) { + for (const SubModel of Object.values(this.models)) { if (!(SubModel.prototype instanceof Model)) { continue; } @@ -260,6 +260,20 @@ export class ModelManager { return res; } + /** + * Returns the messaging singleton associated to this model manager. + * + * @returns {mail.messaging} + */ + get messaging() { + if (!this.models || !this._messaging) { + return undefined; + } + // Use "get" specifically to ensure the record still exists and to + // ensure listeners are properly notified of this access. + return this.get(this.models['mail.messaging'], this._messaging.localId); + } + /** * Removes a listener, with the same object reference as given to `startListening`. * Removing the listener effectively makes its `onChange` function no longer @@ -605,6 +619,12 @@ export class ModelManager { * expected to be found on every record. */ const record = new Model({ valid: true }); + if (Model.modelName === 'mail.messaging') { + if (this._messaging) { + throw Error('messaging should be a singleton'); + } + this._messaging = record; + } Object.assign(record, { // The messaging env. env: this.env, @@ -651,6 +671,7 @@ export class ModelManager { data2[field.fieldName] = field.default; } } + data2.messaging = link(this._messaging); this._update(record, data2, { allowWriteReadonly: true }); for (const field of Model.__fieldList) { if (field.compute) { @@ -724,6 +745,9 @@ export class ModelManager { this._markListenerToNotify(listener); } delete Model.__records[record.localId]; + if (record === this._messaging) { + this._messaging = undefined; + } } /** @@ -812,6 +836,12 @@ export class ModelManager { // Make environment accessible from Model. const Model = generatable.factory(Models); Model.env = this.env; + const modelManager = this; + Object.defineProperty(Model, 'messaging', { + get() { + return modelManager.messaging; + }, + }); /** * Contains all records. key is local id, while value is the record. */ @@ -900,7 +930,6 @@ export class ModelManager { {}, relFunc(Model.modelName, { inverse: field.fieldName }), { - env: this.env, fieldName: `_inverse_${Model.modelName}/${field.fieldName}`, modelManager: this, } @@ -983,7 +1012,6 @@ export class ModelManager { // Make fields aware of their field name. for (const [fieldName, fieldData] of Object.entries(Model.fields)) { Model.fields[fieldName] = new ModelField(Object.assign({}, fieldData, { - env: this.env, fieldName, modelManager: this, })); diff --git a/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js b/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js index 76f64350830..80b6aad84cb 100644 --- a/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js +++ b/addons/mail/static/src/models/chat_window_manager/chat_window_manager.js @@ -297,7 +297,7 @@ function factory(dependencies) { */ _computeVisual() { let visual = JSON.parse(JSON.stringify(BASE_VISUAL)); - if (!this.messaging) { + if (!this.messaging || !this.messaging.device) { return visual; } const device = this.messaging.device; @@ -391,9 +391,6 @@ function factory(dependencies) { lastVisible: many2one('mail.chat_window', { compute: '_computeLastVisible', }), - messaging: one2one('mail.messaging', { - inverse: 'chatWindowManager', - }), newMessageChatWindow: one2one('mail.chat_window', { compute: '_computeNewMessageChatWindow', }), diff --git a/addons/mail/static/src/models/chatter/chatter.js b/addons/mail/static/src/models/chatter/chatter.js index d393a1d57b3..ef6ff2805f6 100644 --- a/addons/mail/static/src/models/chatter/chatter.js +++ b/addons/mail/static/src/models/chatter/chatter.js @@ -161,6 +161,7 @@ function factory(dependencies) { _onThreadIsLoadingAttachmentsChanged() { if (!this.thread || !this.thread.isLoadingAttachments) { this._stopAttachmentsLoading(); + this.update({ isShowingAttachmentsLoading: false }); return; } if (this._isPreparingAttachmentsLoading || this.isShowingAttachmentsLoading) { @@ -186,7 +187,6 @@ function factory(dependencies) { _stopAttachmentsLoading() { this.env.browser.clearTimeout(this._attachmentsLoaderTimeout); this._attachmentsLoaderTimeout = null; - this.update({ isShowingAttachmentsLoading: false }); this._isPreparingAttachmentsLoading = false; } diff --git a/addons/mail/static/src/models/discuss/discuss.js b/addons/mail/static/src/models/discuss/discuss.js index 72c74a60596..95f19eb6dc6 100644 --- a/addons/mail/static/src/models/discuss/discuss.js +++ b/addons/mail/static/src/models/discuss/discuss.js @@ -2,7 +2,7 @@ import { registerNewModel } from '@mail/model/model_core'; import { attr, many2one, one2many, one2one } from '@mail/model/model_field'; -import { clear, create, link, replace, unlink, unlinkAll, update } from '@mail/model/model_field_command'; +import { clear, create, link, unlink, unlinkAll, update } from '@mail/model/model_field_command'; function factory(dependencies) { @@ -343,23 +343,9 @@ function factory(dependencies) { * @returns {mail.thread|undefined} */ _computeThread() { - let thread = this.thread; - if (this.env.messaging && - this.env.messaging.inbox && - this.env.messaging.device.isMobile && - this.activeMobileNavbarTabId === 'mailbox' && - this.initActiveId !== 'mail.box_inbox' && - !thread - ) { - // After loading Discuss from an arbitrary tab other then 'mailbox', - // switching to 'mailbox' requires to also set its inner-tab ; - // by default the 'inbox'. - return replace(this.env.messaging.inbox); - } - if (!thread || !thread.isPinned) { + if (!this.thread || !this.thread.isPinned) { return unlink(); } - return; } /** @@ -454,9 +440,6 @@ function factory(dependencies) { menu_id: attr({ default: null, }), - messaging: one2one('mail.messaging', { - inverse: 'discuss', - }), renamingThreads: one2many('mail.thread'), /** * The message that is currently selected as being replied to in Inbox. diff --git a/addons/mail/static/src/models/message/message.js b/addons/mail/static/src/models/message/message.js index ad72589d50c..f90a1ba89e0 100644 --- a/addons/mail/static/src/models/message/message.js +++ b/addons/mail/static/src/models/message/message.js @@ -55,7 +55,7 @@ function factory(dependencies) { data2.email_from = data.email_from; } if ('history_partner_ids' in data) { - data2.isHistory = data.history_partner_ids.includes(this.env.messaging.currentPartner.id); + data2.isHistory = data.history_partner_ids.includes(this.messaging.currentPartner.id); } if ('id' in data) { data2.id = data.id; @@ -171,6 +171,9 @@ function factory(dependencies) { limit, }, }, { shadow: true }); + if (!this.messaging) { + return; + } const messages = this.env.models['mail.message'].insert(messagesData.map( messageData => this.env.models['mail.message'].convertData(messageData) )); @@ -427,14 +430,14 @@ function factory(dependencies) { */ _computeThreads() { const threads = []; - if (this.isHistory) { - threads.push(this.env.messaging.history); + if (this.isHistory && this.messaging.history) { + threads.push(this.messaging.history); } - if (this.isNeedaction) { - threads.push(this.env.messaging.inbox); + if (this.isNeedaction && this.messaging.inbox) { + threads.push(this.messaging.inbox); } - if (this.isStarred) { - threads.push(this.env.messaging.starred); + if (this.isStarred && this.messaging.starred) { + threads.push(this.messaging.starred); } if (this.originThread) { threads.push(this.originThread); diff --git a/addons/mail/static/src/models/messaging/messaging.js b/addons/mail/static/src/models/messaging/messaging.js index dfe9c712abf..39b1ef2f6e9 100644 --- a/addons/mail/static/src/models/messaging/messaging.js +++ b/addons/mail/static/src/models/messaging/messaging.js @@ -179,10 +179,17 @@ function factory(dependencies) { } Messaging.fields = { + /** + * Inverse of the messaging field present on all models. This field + * therefore contains all existing records. + */ + allRecords: one2many('mail.model', { + inverse: 'messaging', + isCausal: true, + }), cannedResponses: one2many('mail.canned_response'), chatWindowManager: one2one('mail.chat_window_manager', { default: create(), - inverse: 'messaging', isCausal: true, readonly: true, }), @@ -201,7 +208,6 @@ function factory(dependencies) { }), discuss: one2one('mail.discuss', { default: create(), - inverse: 'messaging', isCausal: true, readonly: true, }), @@ -223,7 +229,6 @@ function factory(dependencies) { }), initializer: one2one('mail.messaging_initializer', { default: create(), - inverse: 'messaging', isCausal: true, readonly: true, }), @@ -245,7 +250,6 @@ function factory(dependencies) { }), messagingMenu: one2one('mail.messaging_menu', { default: create(), - inverse: 'messaging', isCausal: true, readonly: true, }), @@ -256,7 +260,6 @@ function factory(dependencies) { }), notificationHandler: one2one('mail.messaging_notification_handler', { default: create(), - inverse: 'messaging', isCausal: true, readonly: true, }), diff --git a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js index d217b36a505..b3e07be88eb 100644 --- a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js @@ -1,7 +1,6 @@ /** @odoo-module **/ import { registerNewModel } from '@mail/model/model_core'; -import { one2one } from '@mail/model/model_field'; import { executeGracefully } from '@mail/utils/utils'; import { create, link, insert } from '@mail/model/model_field_command'; @@ -237,12 +236,6 @@ function factory(dependencies) { } - MessagingInitializer.fields = { - messaging: one2one('mail.messaging', { - inverse: 'initializer', - }), - }; - MessagingInitializer.modelName = 'mail.messaging_initializer'; return MessagingInitializer; diff --git a/addons/mail/static/src/models/messaging_menu/messaging_menu.js b/addons/mail/static/src/models/messaging_menu/messaging_menu.js index f66c2d33306..bf7731a66b8 100644 --- a/addons/mail/static/src/models/messaging_menu/messaging_menu.js +++ b/addons/mail/static/src/models/messaging_menu/messaging_menu.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { registerNewModel } from '@mail/model/model_core'; -import { attr, one2one } from '@mail/model/model_field'; +import { attr } from '@mail/model/model_field'; function factory(dependencies) { @@ -96,9 +96,6 @@ function factory(dependencies) { isOpen: attr({ default: false, }), - messaging: one2one('mail.messaging', { - inverse: 'messagingMenu', - }), }; MessagingMenu.modelName = 'mail.messaging_menu'; diff --git a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js index e3fd9ecc383..8226f8ced20 100644 --- a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js +++ b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js @@ -1,7 +1,6 @@ /** @odoo-module **/ import { registerNewModel } from '@mail/model/model_core'; -import { one2one } from '@mail/model/model_field'; import { decrement, increment, insert, link } from '@mail/model/model_field_command'; import { htmlToTextContentInline } from '@mail/js/utils'; @@ -712,12 +711,6 @@ function factory(dependencies) { } - MessagingNotificationHandler.fields = { - messaging: one2one('mail.messaging', { - inverse: 'notificationHandler', - }), - }; - MessagingNotificationHandler.modelName = 'mail.messaging_notification_handler'; return MessagingNotificationHandler; diff --git a/addons/mail/static/src/models/model/model.js b/addons/mail/static/src/models/model/model.js index d8f2bc31b9d..1b91b9aedcf 100644 --- a/addons/mail/static/src/models/model/model.js +++ b/addons/mail/static/src/models/model/model.js @@ -2,6 +2,7 @@ import { registerNewModel } from '@mail/model/model_core'; import { RecordDeletedError } from '@mail/model/model_errors'; +import { many2one } from '@mail/model/model_field'; /** * This function generates a class that represent a model. Instances of such @@ -242,7 +243,17 @@ function factory() { * Note: fields of super-class are automatically inherited, therefore a * sub-class should (re-)define fields without copying ancestors' fields. */ - Model.fields = {}; + Model.fields = { + /** + * States the messaging singleton. Automatically assigned by the model + * manager at creation. + */ + messaging: many2one('mail.messaging', { + inverse: 'allRecords', + required: true, + readonly: true, + }), + }; /** * Name of the model. Important to refer to appropriate model class diff --git a/addons/mail/static/src/models/thread_cache/thread_cache.js b/addons/mail/static/src/models/thread_cache/thread_cache.js index b7aec867806..cbb61b130ed 100644 --- a/addons/mail/static/src/models/thread_cache/thread_cache.js +++ b/addons/mail/static/src/models/thread_cache/thread_cache.js @@ -27,14 +27,17 @@ function factory(dependencies) { let fetchedMessages; let success; try { - fetchedMessages = await this.async(() => this._loadMessages({ + fetchedMessages = await this._loadMessages({ extraDomain: [['id', '<', Math.min(...messageIds)]], limit, - })); + }); success = true; - } catch (e) { + } catch (e) { success = false; } + if (!this.exists()) { + return; + } if (success) { if (fetchedMessages.length < limit) { this.update({ isAllHistoryLoaded: true }); @@ -254,20 +257,23 @@ function factory(dependencies) { const context = this.env.session.user_context; let messages; try { - messages = await this.async(() => - this.env.models['mail.message'].performRpcMessageFetch( - domain, - limit, - context, - ) + messages = await this.env.models['mail.message'].performRpcMessageFetch( + domain, + limit, + context, ); - } catch(e) { - this.update({ - hasLoadingFailed: true, - isLoading: false, - }); + } catch (e) { + if (this.exists()) { + this.update({ + hasLoadingFailed: true, + isLoading: false, + }); + } throw e; } + if (!this.exists()) { + return; + } this.update({ fetchedMessages: link(messages), hasLoadingFailed: false, @@ -339,7 +345,7 @@ function factory(dependencies) { if (!this.hasToLoadMessages) { return; } - const fetchedMessages = await this.async(() => this._loadMessages()); + const fetchedMessages = await this._loadMessages(); for (const threadView of this.threadViews) { threadView.addComponentHint('messages-loaded', { fetchedMessages }); } diff --git a/addons/mail/static/src/models/thread_view/thread_view.js b/addons/mail/static/src/models/thread_view/thread_view.js index d154cf36e51..fd84ee4e840 100644 --- a/addons/mail/static/src/models/thread_view/thread_view.js +++ b/addons/mail/static/src/models/thread_view/thread_view.js @@ -94,11 +94,14 @@ function factory(dependencies) { if (!this.thread) { return; } + if (!this.messaging.device) { + return; + } // Actually in mobile there is a send button, so we need there 'enter' to allow new line. // Hence, we want to use a different shortcut 'ctrl/meta enter' to send for small screen // size with a non-mailing channel. // here send will be done on clicking the button or using the 'ctrl/meta enter' shortcut. - if (this.env.messaging.device.isMobile) { + if (this.messaging.device.isMobile) { return ['ctrl-enter', 'meta-enter']; } return ['enter']; diff --git a/addons/mail/static/src/utils/test_utils.js b/addons/mail/static/src/utils/test_utils.js index eec266d2601..cfe33f18ac8 100644 --- a/addons/mail/static/src/utils/test_utils.js +++ b/addons/mail/static/src/utils/test_utils.js @@ -536,8 +536,8 @@ async function start(param0 = {}) { destroyCallbacks.forEach(callback => callback({ widget })); this._super(...arguments); legacyUnpatch(widget); - if (testEnv) { - testEnv.destroyMessaging(); + if (testEnv && testEnv.modelManager.messaging) { + testEnv.modelManager.messaging.delete(); } } }); @@ -587,8 +587,8 @@ async function start(param0 = {}) { destroyCallbacks.forEach(callback => callback({ widget })); this._super(...arguments); legacyUnpatch(widget); - if (testEnv) { - testEnv.destroyMessaging(); + if (testEnv && testEnv.modelManager.messaging) { + testEnv.modelManager.messaging.delete(); } } }); @@ -603,13 +603,26 @@ async function start(param0 = {}) { delete widget.destroy; destroyCallbacks.forEach(callback => callback({ widget })); parent.destroy(); - if (testEnv) { - testEnv.destroyMessaging(); + if (testEnv && testEnv.modelManager.messaging) { + testEnv.modelManager.messaging.delete(); } }, }); } + // get the final test env after execution of createView/createWebClient/addMockEnvironment testEnv = Component.env; + const modelManager = new ModelManager(testEnv); + Object.assign(testEnv, { modelManager }); + Object.defineProperty(testEnv, 'messaging', { + get() { + return this.modelManager.messaging; + }, + }); + Object.defineProperty(testEnv, 'models', { + get() { + return this.modelManager.models; + }, + }); /** * Returns a promise resolved after the expected event is received. * @@ -668,12 +681,7 @@ async function start(param0 = {}) { */ await env.session.is_bound; - testEnv.modelManager = new ModelManager(testEnv); testEnv.modelManager.start(); - /** - * Create the messaging singleton record. - */ - testEnv.messaging = testEnv.models['mail.messaging'].create(); testEnv.messaging.start().then(() => testEnv.messagingInitializedDeferred.resolve() );