[IMP] mail: make messaging and models available in models without env
Part of task-2582313
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -235,6 +235,9 @@ export class DiscussSidebar extends Component {
|
||||
*/
|
||||
_onHideAddingItem(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!this.discuss) {
|
||||
return;
|
||||
}
|
||||
this.discuss.clearIsAddingItem();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,79 +2,81 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.DiscussSidebar" owl="1">
|
||||
<div name="root" class="o_DiscussSidebar">
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupMailbox">
|
||||
<t t-foreach="orderedMailboxes" t-as="mailbox" t-key="mailbox.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="mailbox.localId"
|
||||
/>
|
||||
<div class="o_DiscussSidebar">
|
||||
<t t-if="discuss" name="root">
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupMailbox">
|
||||
<t t-foreach="orderedMailboxes" t-as="mailbox" t-key="mailbox.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="mailbox.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<hr class="o_DiscussSidebar_separator"/>
|
||||
<t t-if="env.models['mail.thread'].all(thread => thread.isPinned and thread.model === 'mail.channel').length > 19">
|
||||
<input class="o_DiscussSidebar_quickSearch" t-on-input="_onInputQuickSearch" placeholder="Quick search..." t-ref="quickSearchInput" t-esc="discuss.sidebarQuickSearchValue"/>
|
||||
</t>
|
||||
</div>
|
||||
<hr class="o_DiscussSidebar_separator"/>
|
||||
<t t-if="env.models['mail.thread'].all(thread => thread.isPinned and thread.model === 'mail.channel').length > 19">
|
||||
<input class="o_DiscussSidebar_quickSearch" t-on-input="_onInputQuickSearch" placeholder="Quick search..." t-ref="quickSearchInput" t-esc="discuss.sidebarQuickSearchValue"/>
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupChannel">
|
||||
<div class="o_DiscussSidebar_groupHeader">
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle o-clickable" t-on-click="_onClickChannelTitle">
|
||||
Channels
|
||||
</div>
|
||||
<div class="o-autogrow"/>
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupHeaderItemAdd fa fa-plus" t-on-click="_onClickChannelAdd" title="Add or join a channel"/>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_list">
|
||||
<t t-if="discuss.isAddingChannel">
|
||||
<div class="o_DiscussSidebar_item o_DiscussSidebar_itemNew">
|
||||
<AutocompleteInput
|
||||
class="o_DiscussSidebar_itemNewInput"
|
||||
customClass="'o_DiscussSidebar_newChannelAutocompleteSuggestions'"
|
||||
isFocusOnMount="true"
|
||||
isHtml="true"
|
||||
placeholder="FIND_OR_CREATE_CHANNEL"
|
||||
select="_onAddChannelAutocompleteSelect"
|
||||
source="_onAddChannelAutocompleteSource"
|
||||
t-on-o-hide="_onHideAddingItem"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="quickSearchOrderedAndPinnedMultiUserChannels" t-as="channel" t-key="channel.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="channel.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupChat">
|
||||
<div class="o_DiscussSidebar_groupHeader">
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle">
|
||||
Direct Messages
|
||||
</div>
|
||||
<div class="o-autogrow"/>
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupHeaderItemAdd fa fa-plus" t-on-click="_onClickChatAdd" title="Start a conversation"/>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_list">
|
||||
<t t-if="discuss.isAddingChat">
|
||||
<div class="o_DiscussSidebar_item o_DiscussSidebar_itemNew">
|
||||
<AutocompleteInput
|
||||
class="o_DiscussSidebar_itemNewInput"
|
||||
isFocusOnMount="true"
|
||||
placeholder="'Find or start a conversation...'"
|
||||
select="_onAddChatAutocompleteSelect"
|
||||
source="_onAddChatAutocompleteSource"
|
||||
t-on-o-hide="_onHideAddingItem"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="quickSearchPinnedAndOrderedChats" t-as="chat" t-key="chat.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="chat.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupChannel">
|
||||
<div class="o_DiscussSidebar_groupHeader">
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle o-clickable" t-on-click="_onClickChannelTitle">
|
||||
Channels
|
||||
</div>
|
||||
<div class="o-autogrow"/>
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupHeaderItemAdd fa fa-plus" t-on-click="_onClickChannelAdd" title="Add or join a channel"/>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_list">
|
||||
<t t-if="discuss.isAddingChannel">
|
||||
<div class="o_DiscussSidebar_item o_DiscussSidebar_itemNew">
|
||||
<AutocompleteInput
|
||||
class="o_DiscussSidebar_itemNewInput"
|
||||
customClass="'o_DiscussSidebar_newChannelAutocompleteSuggestions'"
|
||||
isFocusOnMount="true"
|
||||
isHtml="true"
|
||||
placeholder="FIND_OR_CREATE_CHANNEL"
|
||||
select="_onAddChannelAutocompleteSelect"
|
||||
source="_onAddChannelAutocompleteSource"
|
||||
t-on-o-hide="_onHideAddingItem"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="quickSearchOrderedAndPinnedMultiUserChannels" t-as="channel" t-key="channel.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="channel.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupChat">
|
||||
<div class="o_DiscussSidebar_groupHeader">
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle">
|
||||
Direct Messages
|
||||
</div>
|
||||
<div class="o-autogrow"/>
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupHeaderItemAdd fa fa-plus" t-on-click="_onClickChatAdd" title="Start a conversation"/>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_list">
|
||||
<t t-if="discuss.isAddingChat">
|
||||
<div class="o_DiscussSidebar_item o_DiscussSidebar_itemNew">
|
||||
<AutocompleteInput
|
||||
class="o_DiscussSidebar_itemNewInput"
|
||||
isFocusOnMount="true"
|
||||
placeholder="'Find or start a conversation...'"
|
||||
select="_onAddChatAutocompleteSelect"
|
||||
source="_onAddChatAutocompleteSource"
|
||||
t-on-o-hide="_onHideAddingItem"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="quickSearchPinnedAndOrderedChats" t-as="chat" t-key="chat.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="chat.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
+4
-21
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}.`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
-7
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user