[IMP] mail, *: introduce identifyingMode and remove identifyingFields

* = calendar, hr, im_livechat, rating, snailmail, website_livechat

This simplifies definition and overrides and opens up new identification
opportunities.

Part of task-2741982

closes odoo/odoo#98166

Related: odoo/enterprise#30470
Signed-off-by: Louis Wicket (wil) <wil@odoo.com>
This commit is contained in:
Sébastien Theys
2022-08-17 11:21:40 +02:00
committed by Louis Wicket (wil)
parent 62520fbefe
commit 60c87dd426
171 changed files with 390 additions and 321 deletions
@@ -9,7 +9,6 @@ import { getLangTimeFormat } from 'web.time';
registerModel({
name: 'calendar.event',
identifyingFields: ['id'],
recordMethods: {
_computeFormattedStart() {
if (!this.start) {
@@ -25,6 +24,7 @@ registerModel({
compute: '_computeFormattedStart',
}),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
+1 -1
View File
@@ -6,7 +6,6 @@ import { clear, insert } from '@mail/model/model_field_command';
registerModel({
name: 'Employee',
identifyingFields: ['id'],
modelMethods: {
/**
* @param {Object} data
@@ -164,6 +163,7 @@ registerModel({
* Unique identifier for this employee.
*/
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'Chatbot',
identifyingFields: ['publicLivechatGlobalOwner'],
recordMethods: {
/**
* @private
@@ -157,6 +156,7 @@ registerModel({
default: 3500, // in milliseconds
}),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
identifying: true,
inverse: 'chatbot',
readonly: true,
required: true,
@@ -5,9 +5,9 @@ import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'ChatbotStep',
identifyingFields: ['chabotOwner'],
fields: {
chabotOwner: one('Chatbot', {
identifying: true,
inverse: 'currentStep',
readonly: true,
required: true,
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { addFields, patchIdentifyingFields, patchRecordMethods } from '@mail/model/model_core';
import { addFields, patchRecordMethods } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
// ensure that the model definition is loaded before the patch
@@ -8,15 +8,12 @@ import '@mail/models/discuss_sidebar_category';
addFields('DiscussSidebarCategory', {
discussAsLivechat: one('Discuss', {
identifying: true,
inverse: 'categoryLivechat',
readonly: true,
}),
});
patchIdentifyingFields('DiscussSidebarCategory', identifyingFields => {
identifyingFields[0].push('discussAsLivechat');
});
patchRecordMethods('DiscussSidebarCategory', {
/**
* @override
@@ -11,7 +11,6 @@ import { get_cookie, set_cookie, unaccent } from 'web.utils';
registerModel({
name: 'LivechatButtonView',
identifyingFields: ['publicLivechatGlobalOwner'],
lifecycleHooks: {
_created() {
this.update({ widget: this.env.services.public_livechat_service.mountLivechatButton() });
@@ -609,6 +608,7 @@ registerModel({
compute: '_computeOpenChatDebounced',
}),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
identifying: true,
inverse: 'livechatButtonView',
readonly: true,
required: true,
@@ -5,9 +5,9 @@ import { attr } from '@mail/model/model_field';
registerModel({
name: 'LivechatOperator',
identifyingFields: ['id'],
fields: {
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -8,7 +8,6 @@ import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'PublicLivechat',
identifyingFields: ['publicLivechatGlobalOwner'],
lifecycleHooks: {
_created() {
this.update({
@@ -93,6 +92,7 @@ registerModel({
}),
legacyPublicLivechat: attr(),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
identifying: true,
inverse: 'publicLivechat',
readonly: true,
required: true,
@@ -7,7 +7,6 @@ import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'PublicLivechatFeedbackView',
identifyingFields: ['publicLivechatGlobalOwner'],
lifecycleHooks: {
_created() {
this.update({
@@ -35,6 +34,7 @@ registerModel({
},
fields: {
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
identifying: true,
inverse: 'feedbackView',
readonly: true,
required: true,
@@ -10,7 +10,6 @@ import { get_cookie, Markup, set_cookie } from 'web.utils';
registerModel({
name: 'PublicLivechatGlobal',
identifyingFields: ['messaging'],
lifecycleHooks: {
_created() {
// History tracking
@@ -9,7 +9,6 @@ import utils from 'web.utils';
registerModel({
name: 'PublicLivechatGlobalNotificationHandler',
identifyingFields: ['publicLivechatGlobalOwner'],
lifecycleHooks: {
_created() {
this.env.services['bus_service'].addChannel(this.messaging.publicLivechatGlobal.publicLivechat.uuid);
@@ -96,6 +95,7 @@ registerModel({
},
fields: {
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
identifying: true,
inverse: 'notificationHandler',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'PublicLivechatMessage',
identifyingFields: ['id'],
recordMethods: {
/**
* @private
@@ -25,6 +24,7 @@ registerModel({
}),
data: attr(),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -7,7 +7,6 @@ import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'PublicLivechatView',
identifyingFields: ['publicLivechatWindowOwner'],
lifecycleHooks: {
_created() {
this.update({
@@ -20,6 +19,7 @@ registerModel({
},
fields: {
publicLivechatWindowOwner: one('PublicLivechatWindow', {
identifying: true,
inverse: 'publicLivechatView',
readonly: true,
required: true,
@@ -8,7 +8,6 @@ import { insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'PublicLivechatWindow',
identifyingFields: ['livechatButtonViewOwner'],
lifecycleHooks: {
_created() {
this.update({
@@ -43,6 +42,7 @@ registerModel({
default: null,
}),
livechatButtonViewOwner: one('LivechatButtonView', {
identifying: true,
inverse: 'chatWindow',
readonly: true,
required: true,
+5 -22
View File
@@ -250,23 +250,6 @@ function assertNameIsAvailableOnRecords(name, modelDefinition) {
}
}
/**
* Applies the provided function to the identifying fields of the model
* specified by the `modelName`.
*
* @param {string} modelName The name of the model to which to apply the patch.
* @param {function} patch The function to be applied on the identifying fields.
*/
export function patchIdentifyingFields(modelName, patch) {
addContextToErrors(() => {
if (!registry.has(modelName)) {
throw new Error(`model must be registered before being patched.`);
}
assertIsFunction(patch);
}, `Cannot patch identifyingFields on model "${modelName}": `);
patch(registry.get(modelName).get('identifyingFields'));
}
/**
* Overrides the lifecycle hooks of the model specified by the `modelName`.
*
@@ -343,7 +326,7 @@ export function patchRecordMethods(modelName, recordMethods) {
/**
* @param {Object} definition The JSON definition of the model to register.
* @param {Object} [definition.fields]
* @param {string[]} definition.identifyingFields
* @param {string} [definition.identifyingMode='and']
* @param {Object} [definition.lifecycleHooks]
* @param {Object} [definition.modelGetters] Deprecated; use fields instead.
* @param {Object} [definition.modelMethods]
@@ -352,19 +335,19 @@ export function patchRecordMethods(modelName, recordMethods) {
* @param {Object} [definition.recordGetters] Deprecated; use fields instead.
* @param {Object} [definition.recordMethods]
*/
export function registerModel({ fields, identifyingFields, lifecycleHooks, modelGetters, modelMethods, name, onChanges, recordGetters, recordMethods }) {
export function registerModel({ fields, identifyingMode = 'and', lifecycleHooks, modelGetters, modelMethods, name, onChanges, recordGetters, recordMethods }) {
if (!name) {
throw new Error("Model is lacking a name.");
}
if (!identifyingFields) {
throw new Error(`Cannot register model "${name}": definition is lacking identifying fields.`);
if (!identifyingMode) {
throw new Error(`Cannot register model "${name}": definition is lacking identifying mode.`);
}
if (registry.has(name)) {
throw new Error(`Cannot register model "${name}": model has already been registered.`);
}
registry.set(name, new Map([
['name', name],
['identifyingFields', identifyingFields],
['identifyingMode', identifyingMode],
['lifecycleHooks', new Map()],
['modelMethods', new Map()],
['modelGetters', new Map()],
@@ -18,6 +18,7 @@ export class ModelField {
default: def,
fieldName,
fieldType,
identifying = false,
inverse,
isCausal = false,
readonly = false,
@@ -52,6 +53,10 @@ export class ModelField {
* 2. 'relation': fields that relate to some other records.
*/
this.fieldType = fieldType;
/**
* Determines whether this field is an identifying field for its model.
*/
this.identifying = identifying;
/**
* This prop only makes sense in a relational field. This contains
* the name of the field name in the inverse relation. This may not
+96 -60
View File
@@ -197,8 +197,7 @@ export class ModelManager {
for (const model of Object.values(this.models)) {
delete model.__fieldList;
delete model.__fieldMap;
delete model.__identifyingFields;
delete model.__identifyingFieldsFlattened;
delete model.__identifyingFieldNames;
delete model.__records;
delete model.__requiredFieldsList;
delete model.fields;
@@ -418,7 +417,7 @@ export class ModelManager {
}
/**
* Adds fields, methods, getters, and identifyingFields from the model
* Adds fields, methods, getters, and identifyingMode from the model
* definition to the model, then registers it in `this.models`.
*
* @private
@@ -439,17 +438,7 @@ export class ModelManager {
model.fields = {};
// Contains all records. key is local id, while value is the record.
model.__records = {};
model.__identifyingFields = definition.get('identifyingFields');
const identifyingFieldsFlattened = new Set();
for (const identifyingElement of model.__identifyingFields) {
const identifyingFields = typeof identifyingElement === 'string'
? [identifyingElement]
: identifyingElement;
for (const identifyingField of identifyingFields) {
identifyingFieldsFlattened.add(identifyingField);
}
}
model.__identifyingFieldsFlattened = identifyingFieldsFlattened;
model.identifyingMode = definition.get('identifyingMode');
this._listenersObservingAllByModel.set(model, new Map());
this.models[model.name] = model;
}
@@ -476,6 +465,7 @@ export class ModelManager {
'compute',
'default',
'fieldType',
'identifying',
'readonly',
'related',
'required',
@@ -492,6 +482,7 @@ export class ModelManager {
'compute',
'default',
'fieldType',
'identifying',
'inverse',
'isCausal',
'readonly',
@@ -579,6 +570,17 @@ export class ModelManager {
*/
_checkProcessedFieldsOnModels() {
for (const model of Object.values(this.models)) {
switch (model.identifyingMode) {
case 'and':
break;
case 'xor':
if (model.__identifyingFieldNames.size === 0) {
throw new Error(`No identifying fields has been specified for 'xor' identifying mode on ${model}`);
}
break;
default:
throw new Error(`Unsupported identifying mode "${model.identifyingMode}" on ${model}. Must be one of 'and' or 'xor'.`);
}
for (const field of model.__fieldList) {
const fieldName = field.fieldName;
if (!(['attribute', 'relation'].includes(field.fieldType))) {
@@ -617,7 +619,7 @@ export class ModelManager {
throw new Error(`${field} on ${model} has its inverse ${inverseField} on ${relatedModel} referring to an invalid model (model(${inverseField.to})).`);
}
}
for (const identifyingField of model.__identifyingFieldsFlattened) {
for (const identifyingField of model.__identifyingFieldNames) {
const field = model.__fieldMap[identifyingField];
if (!field) {
throw new Error(`Identifying field "${identifyingField}" is not a field on ${model}.`);
@@ -966,51 +968,81 @@ export class ModelManager {
* @returns {string}
*/
_getRecordIndex(model, data) {
return `${model.name}(${model.__identifyingFields.map(identifyingElement => {
const fieldName = typeof identifyingElement === 'string'
? identifyingElement
: identifyingElement.reduce((fieldName, currentFieldName) => {
const fieldValue = data[currentFieldName] !== undefined
? data[currentFieldName]
: model.__fieldMap[currentFieldName].default;
const parts = [];
switch (model.identifyingMode) {
case 'and':
for (const fieldName of model.__identifyingFieldNames) {
const fieldValue = this._getValueFromDataOrFromDefault(model, fieldName, data);
if (fieldValue === undefined) {
return fieldName;
throw new Error(`Identifying field "${fieldName}" is lacking a value on ${model} with 'and' identifying mode`);
}
parts.push(this._getRecordIndexFromField(model, fieldName, fieldValue));
}
break;
case 'xor': {
const [fieldName, fieldValue] = [...model.__identifyingFieldNames].reduce(([fieldName, fieldValue], currentFieldName) => {
const currentFieldValue = this._getValueFromDataOrFromDefault(model, currentFieldName, data);
if (currentFieldValue === undefined) {
return [fieldName, fieldValue];
}
if (fieldName) {
throw new Error(`Identifying element on ${model} with "or" value should have only one of the conditional values given in data. Currently have both ${fieldName} and ${currentFieldName}.`);
throw new Error(`Identifying field on ${model} with 'xor' identifying mode should have only one of the conditional values given in data. Currently have both "${fieldName}" and "${currentFieldName}".`);
}
return currentFieldName;
}, undefined);
if (!fieldName) {
throw new Error(`Identifying element "${identifyingElement}" on ${model} is lacking a value.`);
return [currentFieldName, currentFieldValue];
}, [undefined, undefined]);
parts.push(this._getRecordIndexFromField(model, fieldName, fieldValue));
break;
}
const fieldValue = data[fieldName] !== undefined
? data[fieldName]
: model.__fieldMap[fieldName].default;
if (fieldValue === undefined) {
throw new Error(`Identifying field "${fieldName}" on ${model} is lacking a value.`);
}
const relationTo = model.__fieldMap[fieldName].to;
if (!relationTo) {
return `${fieldName}: ${fieldValue}`;
}
const OtherModel = this.models[relationTo];
if (fieldValue instanceof OtherModel) {
return `${fieldName}: ${this._getRecordIndex(OtherModel, fieldValue)}`;
}
const fieldValue2 = model.__fieldMap[fieldName].convertToFieldCommandList(fieldValue)[0];
if (!(fieldValue2 instanceof FieldCommand)) {
throw new Error(`Identifying element "${model}/${fieldName}" is expecting a command for relational field.`);
}
if (!['link', 'insert', 'replace', 'insert-and-replace'].includes(fieldValue2._name)) {
throw new Error(`Identifying element "${model}/${fieldName}" is expecting a command of type "insert-and-replace", "replace", "insert" or "link". "${fieldValue2._name}" was given instead.`);
}
const relationValue = fieldValue2._value;
if (!relationValue) {
throw new Error(`Identifying element "${model}/${fieldName}" is lacking a relation value.`);
}
return `${fieldName}: ${this._getRecordIndex(OtherModel, relationValue)}`;
}).join(', ')})`;
}
return `${model.name}(${parts.join(', ')})`;
}
/**
* Returns the part of the record index that comes from the given fieldName
* with the given fieldValue.
*
* @param {Object} model
* @param {string} fieldName
* @param {any} fieldValue
* @returns {string}
*/
_getRecordIndexFromField(model, fieldName, fieldValue) {
const relationTo = model.__fieldMap[fieldName].to;
if (!relationTo) {
return `${fieldName}: ${fieldValue}`;
}
const OtherModel = this.models[relationTo];
if (fieldValue instanceof OtherModel) {
return `${fieldName}: ${this._getRecordIndex(OtherModel, fieldValue)}`;
}
const fieldValue2 = model.__fieldMap[fieldName].convertToFieldCommandList(fieldValue)[0];
if (!(fieldValue2 instanceof FieldCommand)) {
throw new Error(`Identifying element "${model}/${fieldName}" is expecting a command for relational field.`);
}
if (!['link', 'insert', 'replace', 'insert-and-replace'].includes(fieldValue2._name)) {
throw new Error(`Identifying element "${model}/${fieldName}" is expecting a command of type "insert-and-replace", "replace", "insert" or "link". "${fieldValue2._name}" was given instead.`);
}
const relationValue = fieldValue2._value;
if (!relationValue) {
throw new Error(`Identifying element "${model}/${fieldName}" is lacking a relation value.`);
}
return `${fieldName}: ${this._getRecordIndex(OtherModel, relationValue)}`;
}
/**
* Returns the value for the given fieldName from the provided data if it is
* defined or from default value of this field otherwise.
*
* @param {Object} model
* @param {string} fieldName
* @param {Object} data
* @returns {any}
*/
_getValueFromDataOrFromDefault(model, fieldName, data) {
if (data[fieldName] !== undefined) {
return data[fieldName];
}
return model.__fieldMap[fieldName].default;
}
/**
@@ -1051,7 +1083,7 @@ export class ModelManager {
fieldName: `_inverse_${model}/${field.fieldName}`,
// Allows the inverse of an identifying field to be
// automatically generated.
isCausal: model.__identifyingFieldsFlattened.has(field.fieldName),
isCausal: field.identifying,
},
));
return inverseField;
@@ -1230,8 +1262,12 @@ export class ModelManager {
model.__requiredFieldsList = model.__fieldList.filter(
field => field.required
);
// Add field accessors.
for (const fieldName of Object.keys(model.__fieldMap)) {
model.__identifyingFieldNames = new Set();
for (const [fieldName, field] of Object.entries(model.__fieldMap)) {
if (field.identifying) {
model.__identifyingFieldNames.add(fieldName);
}
// Add field accessors.
Object.defineProperty(model.prototype, fieldName, {
get: function getFieldValue() { // this is bound to record
const field = model.__fieldMap[fieldName];
@@ -1290,10 +1326,10 @@ export class ModelManager {
// Always update identifying fields first because updating other relational field will
// trigger update of inverse field, which will require this identifying fields to be set
// beforehand to properly detect whether this is already linked or not on the inverse.
if (model.__identifyingFieldsFlattened.has(a) && !model.__identifyingFieldsFlattened.has(b)) {
if (model.__identifyingFieldNames.has(a) && !model.__identifyingFieldNames.has(b)) {
return -1;
}
if (!model.__identifyingFieldsFlattened.has(a) && model.__identifyingFieldsFlattened.has(b)) {
if (!model.__identifyingFieldNames.has(a) && model.__identifyingFieldNames.has(b)) {
return 1;
}
return 0;
+1 -1
View File
@@ -8,7 +8,6 @@ const { markup } = owl;
registerModel({
name: 'Activity',
identifyingFields: ['id'],
modelMethods: {
/**
* @param {Object} data
@@ -289,6 +288,7 @@ registerModel({
}),
icon: attr(),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'ActivityBoxView',
identifyingFields: ['chatter'],
recordMethods: {
/**
* Handles click on activity box title.
@@ -32,6 +31,7 @@ registerModel({
isCausal: true,
}),
chatter: one('Chatter', {
identifying: true,
inverse: 'activityBoxView',
readonly: true,
required: true,
@@ -7,7 +7,6 @@ import { OnChange } from '@mail/model/model_onchange';
registerModel({
name: 'ActivityGroup',
identifyingFields: ['irModel'],
modelMethods: {
convertData(data) {
return {
@@ -45,6 +44,7 @@ registerModel({
}),
domain: attr(),
irModel: one('ir.model', {
identifying: true,
inverse: 'activityGroup',
readonly: true,
required: true,
@@ -7,7 +7,6 @@ import session from 'web.session';
registerModel({
name: 'ActivityGroupView',
identifyingFields: ['activityMenuViewOwner', 'activityGroup'],
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -78,11 +77,13 @@ registerModel({
},
fields: {
activityGroup: one('ActivityGroup', {
identifying: true,
inverse: 'activityGroupViews',
readonly: true,
required: true,
}),
activityMenuViewOwner: one('ActivityMenuView', {
identifying: true,
inverse: 'activityGroupViews',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { clear, replace } from '@mail/model/model_field_command';
registerModel({
name: 'ActivityMarkDonePopoverContentView',
identifyingFields: ['popoverViewOwner'],
recordMethods: {
/**
* Handles blur on this feedback textarea.
@@ -105,6 +104,7 @@ registerModel({
default: '',
}),
popoverViewOwner: one('PopoverView', {
identifying: true,
inverse: 'activityMarkDonePopoverContentView',
readonly: true,
required: true,
@@ -8,7 +8,6 @@ import session from 'web.session';
registerModel({
name: 'ActivityMenuView',
identifyingFields: [],
lifecycleHooks: {
_created() {
this.fetchData();
@@ -5,13 +5,13 @@ import { attr, many } from '@mail/model/model_field';
registerModel({
name: 'ActivityType',
identifyingFields: ['id'],
fields: {
activities: many('Activity', {
inverse: 'type',
}),
displayName: attr(),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -9,7 +9,6 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'ActivityView',
identifyingFields: ['activityBoxView', 'activity'],
recordMethods: {
/**
* Handles the click on a link inside the activity.
@@ -169,11 +168,13 @@ registerModel({
},
fields: {
activity: one('Activity', {
identifying: true,
inverse: 'activityViews',
required: true,
readonly: true,
}),
activityBoxView: one('ActivityBoxView', {
identifying: true,
inverse: 'activityViews',
readonly: true,
required: true,
+1 -1
View File
@@ -6,7 +6,6 @@ import { clear, insert, replace } from '@mail/model/model_field_command';
registerModel({
name: 'Attachment',
identifyingFields: ['id'],
modelMethods: {
/**
* @static
@@ -330,6 +329,7 @@ registerModel({
}),
filename: attr(),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'AttachmentBoxView',
identifyingFields: ['chatter'],
recordMethods: {
/**
* Handles click on the "add attachment" button.
@@ -35,6 +34,7 @@ registerModel({
readonly: true,
}),
chatter: one('Chatter', {
identifying: true,
inverse: 'attachmentBoxView',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'AttachmentCard',
identifyingFields: ['attachmentList', 'attachment'],
recordMethods: {
/**
* Opens the attachment viewer when clicking on viewable attachment.
@@ -49,6 +48,7 @@ registerModel({
* Determines the attachment of this card.
*/
attachment: one('Attachment', {
identifying: true,
readonly: true,
required: true,
}),
@@ -60,6 +60,7 @@ registerModel({
* States the attachmentList displaying this card.
*/
attachmentList: one('AttachmentList', {
identifying: true,
inverse: 'attachmentCards',
readonly: true,
required: true,
@@ -7,7 +7,6 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'AttachmentDeleteConfirmView',
identifyingFields: ['dialogOwner'],
recordMethods: {
/**
* Returns whether the given html element is inside this attachment delete confirm view.
@@ -84,6 +83,7 @@ registerModel({
}),
component: attr(),
dialogOwner: one('Dialog', {
identifying: true,
inverse: 'attachmentDeleteConfirmView',
readonly: true,
required: true,
@@ -7,7 +7,6 @@ import { isEventHandled, markEventHandled } from '@mail/utils/utils';
registerModel({
name: 'AttachmentImage',
identifyingFields: ['attachmentList', 'attachment'],
recordMethods: {
/**
* Called when clicking on download icon.
@@ -115,6 +114,7 @@ registerModel({
* Determines the attachment of this attachment image..
*/
attachment: one('Attachment', {
identifying: true,
readonly: true,
required: true,
}),
@@ -126,6 +126,7 @@ registerModel({
* States the attachmentList displaying this attachment image.
*/
attachmentList: one('AttachmentList', {
identifying: true,
inverse: 'attachmentImages',
readonly: true,
required: true,
@@ -6,7 +6,7 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'AttachmentList',
identifyingFields: [['composerViewOwner', 'messageViewOwner', 'attachmentBoxViewOwner']],
identifyingMode: 'xor',
recordMethods: {
/**
* Select the next attachment.
@@ -137,6 +137,7 @@ registerModel({
* Link with a AttachmentBoxView to handle attachments.
*/
attachmentBoxViewOwner: one('AttachmentBoxView', {
identifying: true,
inverse: 'attachmentList',
readonly: true,
}),
@@ -171,6 +172,7 @@ registerModel({
* Link with a composer view to handle attachments.
*/
composerViewOwner: one('ComposerView', {
identifying: true,
inverse: 'attachmentList',
readonly: true,
}),
@@ -220,6 +222,7 @@ registerModel({
* Link with a message view to handle attachments.
*/
messageViewOwner: one('MessageView', {
identifying: true,
inverse: 'attachmentList',
readonly: true,
}),
@@ -6,7 +6,6 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'AttachmentViewer',
identifyingFields: ['dialogOwner'],
recordMethods: {
/**
* Close the dialog with this attachment viewer.
@@ -249,6 +248,7 @@ registerModel({
* Determines the dialog displaying this attachment viewer.
*/
dialogOwner: one('Dialog', {
identifying: true,
inverse: 'attachmentViewer',
isCausal: true,
readonly: true,
@@ -10,7 +10,7 @@ import { attr, one } from "@mail/model/model_field";
registerModel({
name: "AttachmentViewerViewable",
identifyingFields: [["attachmentOwner"]],
identifyingMode: 'xor',
recordMethods: {
download() {
return this.attachmentOwner.download();
@@ -94,6 +94,7 @@ registerModel({
},
fields: {
attachmentOwner: one("Attachment", {
identifying: true,
inverse: 'attachmentViewerViewable',
readonly: true,
}),
@@ -6,12 +6,7 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'AutocompleteInputView',
identifyingFields: [[
'chatWindowOwnerAsNewMessage',
'discussSidebarCategoryOwnerAsAddingItem',
'discussViewOwnerAsMobileAddItemHeader',
'messagingMenuOwnerAsMobileNewMessageInput',
]],
identifyingMode: 'xor',
recordMethods: {
/**
* @param {FocusEvent} ev
@@ -92,6 +87,7 @@ registerModel({
},
fields: {
chatWindowOwnerAsNewMessage: one('ChatWindow', {
identifying: true,
inverse: 'newMessageAutocompleteInputView',
readonly: true,
}),
@@ -101,10 +97,12 @@ registerModel({
default: '',
}),
discussSidebarCategoryOwnerAsAddingItem: one('DiscussSidebarCategory', {
identifying: true,
inverse: 'addingItemAutocompleteInputView',
readonly: true,
}),
discussViewOwnerAsMobileAddItemHeader: one('DiscussView', {
identifying: true,
inverse: 'mobileAddItemHeaderAutocompleteInputView',
readonly: true,
}),
@@ -117,6 +115,7 @@ registerModel({
default: false,
}),
messagingMenuOwnerAsMobileNewMessageInput: one('MessagingMenu', {
identifying: true,
inverse: 'mobileNewMessageAutocompleteInputView',
readonly: true,
}),
@@ -6,7 +6,6 @@ import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'CallActionListView',
identifyingFields: ['callMainView'],
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -150,6 +149,7 @@ registerModel({
default: '',
}),
callMainView: one('CallMainView', {
identifying: true,
inverse: 'callActionListView',
readonly: true,
required: true,
@@ -5,7 +5,6 @@ import { registerModel } from '@mail/model/model_core';
registerModel({
name: 'CallDemoView',
identifyingFields: ['welcomeView'],
recordMethods: {
/**
* Stops recording user's microphone.
@@ -174,6 +173,7 @@ registerModel({
* States the welcome view containing this media preview.
*/
welcomeView: one('WelcomeView', {
identifying: true,
inverse: 'callDemoView',
readonly: true,
}),
@@ -5,7 +5,6 @@ import { one } from '@mail/model/model_field';
registerModel({
name: 'CallInviteRequestPopup',
identifyingFields: ['thread'],
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -35,6 +34,7 @@ registerModel({
},
fields: {
thread: one('Thread', {
identifying: true,
inverse: 'callInviteRequestPopup',
readonly: true,
required: true,
@@ -8,7 +8,6 @@ import { isEventHandled, markEventHandled } from '@mail/utils/utils';
registerModel({
name: 'CallMainView',
identifyingFields: ['callView'],
recordMethods: {
/**
* Finds a tile layout and dimensions that respects param0.aspectRatio while maximizing
@@ -163,6 +162,7 @@ registerModel({
readonly: true,
}),
callView: one('CallView', {
identifying: true,
inverse: 'callMainView',
required: true,
readonly: true,
@@ -6,14 +6,15 @@ import { one } from '@mail/model/model_field';
registerModel({
name: 'CallMainViewTile',
identifyingFields: ['callMainViewOwner', 'channelMember'],
fields: {
callMainViewOwner: one('CallMainView', {
identifying: true,
inverse: 'mainTiles',
readonly: true,
required: true,
}),
channelMember: one('ChannelMember', {
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'CallOptionMenu',
identifyingFields: ['callActionListView'],
recordMethods: {
/**
* @param {Event} ev
@@ -67,6 +66,7 @@ registerModel({
*/
component: attr(),
callActionListView: one('CallActionListView', {
identifying: true,
inverse: 'callOptionMenu',
readonly: true,
required: true,
@@ -9,7 +9,7 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'CallParticipantCard',
identifyingFields: [['sidebarViewTileOwner', 'mainViewTileOwner']],
identifyingMode: 'xor',
recordMethods: {
/**
* @param {Event} ev
@@ -168,6 +168,7 @@ registerModel({
compute: '_computeInboundConnectionTypeText',
}),
mainViewTileOwner: one('CallMainViewTile', {
identifying: true,
inverse: 'participantCard',
readonly: true,
}),
@@ -203,6 +204,7 @@ registerModel({
inverse: 'callParticipantCards',
}),
sidebarViewTileOwner: one('CallSidebarViewTile', {
identifying: true,
inverse: 'participantCard',
readonly: true,
}),
@@ -6,7 +6,6 @@ import { clear, replace } from '@mail/model/model_field_command';
registerModel({
name: 'CallParticipantVideoView',
identifyingFields: ['callParticipantCardOwner'],
recordMethods: {
/**
* Plays the video as some browsers may not support or block autoplay.
@@ -38,6 +37,7 @@ registerModel({
},
fields: {
callParticipantCardOwner: one('CallParticipantCard', {
identifying: true,
inverse: 'callParticipantVideoView',
readonly: true,
required: true,
@@ -7,7 +7,6 @@ import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'CallSettingsMenu',
identifyingFields: ['userSetting'],
lifecycleHooks: {
_created() {
browser.addEventListener('keydown', this._onKeyDown);
@@ -83,6 +82,7 @@ registerModel({
default: false,
}),
userSetting: one('UserSetting', {
identifying: true,
inverse: 'callSettingsMenu',
readonly: true,
}),
@@ -6,7 +6,6 @@ import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'CallSidebarView',
identifyingFields: ['callView'],
recordMethods: {
/**
* @private
@@ -18,6 +17,7 @@ registerModel({
},
fields: {
callView: one('CallView', {
identifying: true,
inverse: 'callSidebarView',
readonly: true,
required: true,
@@ -6,14 +6,15 @@ import { one } from '@mail/model/model_field';
registerModel({
name: 'CallSidebarViewTile',
identifyingFields: ['callSidebarViewOwner', 'channelMember'],
fields: {
callSidebarViewOwner: one('CallSidebarView', {
identifying: true,
inverse: 'sidebarTiles',
readonly: true,
required: true,
}),
channelMember: one('ChannelMember', {
identifying: true,
readonly: true,
required: true,
}),
@@ -8,7 +8,6 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'CallSystrayMenu',
identifyingFields: ['rtc'],
recordMethods: {
/**
* @private
@@ -30,6 +29,7 @@ registerModel({
default: '',
}),
rtc: one('Rtc', {
identifying: true,
inverse: 'callSystrayMenu',
readonly: true,
required: true,
+1 -1
View File
@@ -9,7 +9,6 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'CallView',
identifyingFields: ['threadView'],
lifecycleHooks: {
_created() {
browser.addEventListener('fullscreenchange', this._onFullScreenChange);
@@ -229,6 +228,7 @@ registerModel({
* ThreadView on which the call view is attached.
*/
threadView: one('ThreadView', {
identifying: true,
inverse: 'callView',
readonly: true,
required: true,
@@ -7,7 +7,6 @@ import { cleanSearchTerm } from '@mail/utils/utils';
registerModel({
name: 'CannedResponse',
identifyingFields: ['id'],
modelMethods: {
/**
* Fetches canned responses matching the given search term to extend the
@@ -71,6 +70,7 @@ registerModel({
},
fields: {
id: attr({
identifying: true,
readonly: true,
required: true,
}),
+1 -1
View File
@@ -6,7 +6,6 @@ import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'Channel',
identifyingFields: ['id'],
recordMethods: {
async fetchChannelMembers() {
const channelData = await this.messaging.rpc({
@@ -92,6 +91,7 @@ registerModel({
}),
channel_type: attr(),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -7,7 +7,6 @@ import { cleanSearchTerm } from '@mail/utils/utils';
registerModel({
name: 'ChannelCommand',
identifyingFields: ['name'],
modelMethods: {
/**
* Fetches channel commands matching the given search term to extend the
@@ -128,6 +127,7 @@ registerModel({
* The keyword to use a specific command.
*/
name: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -9,7 +9,7 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'ChannelInvitationForm',
identifyingFields: [['chatWindow', 'popoverViewOwner']],
identifyingMode: 'xor',
recordMethods: {
/**
* Handles click on the "copy" button.
@@ -236,6 +236,7 @@ registerModel({
compute: '_computeAccessRestrictedToGroupText',
}),
chatWindow: one('ChatWindow', {
identifying: true,
inverse: 'channelInvitationForm',
readonly: true,
}),
@@ -271,6 +272,7 @@ registerModel({
* If set, this channel invitation form is content of related popover view.
*/
popoverViewOwner: one('PopoverView', {
identifying: true,
inverse: 'channelInvitationForm',
isCausal: true,
readonly: true,
@@ -6,7 +6,6 @@ import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'ChannelInvitationFormSelectablePartnerView',
identifyingFields: ['channelInvitationFormOwner', 'partner'],
recordMethods: {
/**
* @private
@@ -18,11 +17,13 @@ registerModel({
},
fields: {
channelInvitationFormOwner: one('ChannelInvitationForm', {
identifying: true,
inverse: 'selectablePartnerViews',
readonly: true,
required: true,
}),
partner: one('Partner', {
identifying: true,
inverse: 'channelInvitationFormSelectablePartnerViews',
readonly: true,
required: true,
@@ -5,14 +5,15 @@ import { one } from '@mail/model/model_field';
registerModel({
name: 'ChannelInvitationFormSelectedPartnerView',
identifyingFields: ['channelInvitationFormOwner', 'partner'],
fields: {
channelInvitationFormOwner: one('ChannelInvitationForm', {
identifying: true,
inverse: 'selectedPartnerViews',
readonly: true,
required: true,
}),
partner: one('Partner', {
identifying: true,
inverse: 'channelInvitationFormSelectedPartnerViews',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { clear, replace } from '@mail/model/model_field_command';
registerModel({
name: 'ChannelMember',
identifyingFields: ['id'],
recordMethods: {
/**
* @private
@@ -81,6 +80,7 @@ registerModel({
isCausal: true,
}),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -8,7 +8,7 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'ChannelMemberListCategoryView',
identifyingFields: [['channelMemberListViewOwnerAsOffline', 'channelMemberListViewOwnerAsOnline']],
identifyingMode: 'xor',
recordMethods: {
/**
* @private
@@ -77,10 +77,12 @@ registerModel({
required: true,
}),
channelMemberListViewOwnerAsOffline: one('ChannelMemberListView', {
identifying: true,
inverse: 'offlineCategoryView',
readonly: true,
}),
channelMemberListViewOwnerAsOnline: one('ChannelMemberListView', {
identifying: true,
inverse: 'onlineCategoryView',
readonly: true,
}),
@@ -6,7 +6,7 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'ChannelMemberListView',
identifyingFields: [['chatWindowOwner', 'threadViewOwner']],
identifyingMode: 'xor',
lifecycleHooks: {
_created() {
this.channel.fetchChannelMembers();
@@ -59,6 +59,7 @@ registerModel({
readonly: true,
}),
chatWindowOwner: one('ChatWindow', {
identifying: true,
inverse: 'channelMemberListView',
readonly: true,
}),
@@ -73,6 +74,7 @@ registerModel({
isCausal: true,
}),
threadViewOwner: one('ThreadView', {
identifying: true,
inverse: 'channelMemberListView',
readonly: true,
}),
@@ -3,11 +3,10 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
import { isEventHandled } from '@mail/utils/utils'
import { isEventHandled } from '@mail/utils/utils';
registerModel({
name: 'ChannelMemberView',
identifyingFields: ['channelMemberListCategoryViewOwner', 'channelMember'],
recordMethods: {
/**
* Handles click on channel member in the member list of this channel.
@@ -47,11 +46,13 @@ registerModel({
},
fields: {
channelMemberListCategoryViewOwner: one('ChannelMemberListCategoryView', {
identifying: true,
inverse: 'channelMemberViews',
readonly: true,
required: true,
}),
channelMember: one('ChannelMember', {
identifying: true,
inverse: 'channelMemberViews',
readonly: true,
required: true,
+3 -1
View File
@@ -7,7 +7,7 @@ import { isEventHandled, markEventHandled } from '@mail/utils/utils';
registerModel({
name: 'ChatWindow',
identifyingFields: ['manager', ['thread', 'managerAsNewMessage']],
identifyingMode: 'xor',
recordMethods: {
/**
* Close this chat window.
@@ -658,6 +658,7 @@ registerModel({
readonly: true,
}),
managerAsNewMessage: one('ChatWindowManager', {
identifying: true,
inverse: 'newMessageChatWindow',
readonly: true,
}),
@@ -681,6 +682,7 @@ registerModel({
* If no `Thread` is linked, `this` is considered "new message".
*/
thread: one('Thread', {
identifying: true,
inverse: 'chatWindow',
readonly: true,
}),
@@ -6,7 +6,6 @@ import { isEventHandled } from '@mail/utils/utils';
registerModel({
name: 'ChatWindowHeaderView',
identifyingFields: ['chatWindowOwner'],
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -28,6 +27,7 @@ registerModel({
},
fields: {
chatWindowOwner: one('ChatWindow', {
identifying: true,
inverse: 'chatWindowHeaderView',
readonly: true,
required: true,
@@ -44,7 +44,6 @@ const BASE_VISUAL = {
registerModel({
name: 'ChatWindowManager',
identifyingFields: ['messaging'],
recordMethods: {
/**
* Close all chat windows.
+1 -1
View File
@@ -23,7 +23,6 @@ const getMessageNextTemporaryId = (function () {
registerModel({
name: 'Chatter',
identifyingFields: ['id'],
recordMethods: {
focus() {
if (this.composerView) {
@@ -439,6 +438,7 @@ registerModel({
* of this record.
*/
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -7,7 +7,6 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'ChatterTopbar',
identifyingFields: ['chatter'],
recordMethods: {
/**
* @private
@@ -41,6 +40,7 @@ registerModel({
compute: '_computeAttachmentButtonText',
}),
chatter: one('Chatter', {
identifying: true,
inverse: 'topbar',
readonly: true,
required: true,
+1 -1
View File
@@ -9,7 +9,6 @@ import { OnChange } from '@mail/model/model_onchange';
*/
registerModel({
name: 'Clock',
identifyingFields: ['frequency'],
lifecycleHooks: {
_willDelete() {
this.messaging.browser.clearInterval(this.tickInterval);
@@ -62,6 +61,7 @@ registerModel({
* must be recomputed.
*/
frequency: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -8,9 +8,10 @@ import { one } from '@mail/model/model_field';
*/
registerModel({
name: 'ClockWatcher',
identifyingFields: [['activityViewOwner', 'messageViewOwner']],
identifyingMode: 'xor',
fields: {
activityViewOwner: one('ActivityView', {
identifying: true,
inverse: 'clockWatcher',
readonly: true,
}),
@@ -19,6 +20,7 @@ registerModel({
required: true,
}),
messageViewOwner: one('MessageView', {
identifying: true,
inverse: 'clockWatcher',
readonly: true,
}),
+3 -1
View File
@@ -7,7 +7,7 @@ import { sprintf } from '@web/core/utils/strings';
registerModel({
name: 'Composer',
identifyingFields: [['thread', 'messageViewInEditing']],
identifyingMode: 'xor',
recordMethods: {
/**
* @private
@@ -185,6 +185,7 @@ registerModel({
compute: '_computeMentionedPartners',
}),
messageViewInEditing: one('MessageView', {
identifying: true,
inverse: 'composerForEditing',
readonly: true,
}),
@@ -218,6 +219,7 @@ registerModel({
* States the thread which this composer represents the state (if any).
*/
thread: one('Thread', {
identifying: true,
inverse: 'composer',
readonly: true,
}),
@@ -5,13 +5,15 @@ import { many, one } from '@mail/model/model_field';
registerModel({
name: 'ComposerSuggestable',
identifyingFields: [['cannedResponse', 'channelCommand', 'partner', 'thread']],
identifyingMode: 'xor',
fields: {
cannedResponse: one('CannedResponse', {
identifying: true,
inverse: 'suggestable',
readonly: true,
}),
channelCommand: one('ChannelCommand', {
identifying: true,
inverse: 'suggestable',
readonly: true,
}),
@@ -24,10 +26,12 @@ registerModel({
isCausal: true,
}),
partner: one('Partner', {
identifying: true,
inverse: 'suggestable',
readonly: true,
}),
thread: one('Thread', {
identifying: true,
inverse: 'suggestable',
readonly: true,
}),
@@ -6,7 +6,6 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'ComposerSuggestedRecipientListView',
identifyingFields: ['composerViewOwner'],
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -63,6 +62,7 @@ registerModel({
isCausal: true,
}),
composerViewOwner: one('ComposerView', {
identifying: true,
inverse: 'composerSuggestedRecipientListView',
readonly: true,
required: true,
@@ -5,14 +5,15 @@ import { one } from '@mail/model/model_field';
registerModel({
name: 'ComposerSuggestedRecipientView',
identifyingFields: ['composerSuggestedRecipientListViewOwner', 'suggestedRecipientInfo'],
fields: {
composerSuggestedRecipientListViewOwner: one('ComposerSuggestedRecipientListView', {
identifying: true,
inverse: 'composerSuggestedRecipientViews',
readonly: true,
required: true,
}),
suggestedRecipientInfo: one('SuggestedRecipientInfo', {
identifying: true,
inverse: 'composerSuggestedRecipientViews',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'ComposerSuggestionListView',
identifyingFields: ['composerViewOwner'],
recordMethods: {
/**
* Sets the first suggestion as active. Main and extra records are
@@ -120,6 +119,7 @@ registerModel({
isCausal: true,
}),
composerViewOwner: one('ComposerView', {
identifying: true,
inverse: 'composerSuggestionListView',
readonly: true,
required: true,
@@ -11,9 +11,9 @@ import { insertAndReplace } from '@mail/model/model_field_command';
*/
registerModel({
name: 'ComposerSuggestionListViewExtraComposerSuggestionViewItem',
identifyingFields: ['composerSuggestionListViewOwner', 'suggestable'],
fields: {
composerSuggestionListViewOwner: one('ComposerSuggestionListView', {
identifying: true,
inverse: 'composerSuggestionListViewExtraComposerSuggestionViewItems',
readonly: true,
required: true,
@@ -26,6 +26,7 @@ registerModel({
required: true,
}),
suggestable: one('ComposerSuggestable', {
identifying: true,
inverse: 'composerSuggestionListViewExtraComposerSuggestionViewItems',
readonly: true,
required: true,
@@ -11,9 +11,9 @@ import { insertAndReplace } from '@mail/model/model_field_command';
*/
registerModel({
name: 'ComposerSuggestionListViewMainComposerSuggestionViewItem',
identifyingFields: ['composerSuggestionListViewOwner', 'suggestable'],
fields: {
composerSuggestionListViewOwner: one('ComposerSuggestionListView', {
identifying: true,
inverse: 'composerSuggestionListViewMainComposerSuggestionViewItems',
readonly: true,
required: true,
@@ -26,6 +26,7 @@ registerModel({
required: true,
}),
suggestable: one('ComposerSuggestable', {
identifying: true,
inverse: 'composerSuggestionListViewMainComposerSuggestionViewItems',
readonly: true,
required: true,
@@ -13,7 +13,7 @@ import { sprintf } from '@web/core/utils/strings';
*/
registerModel({
name: 'ComposerSuggestionView',
identifyingFields: [['composerSuggestionListViewExtraComposerSuggestionViewItemOwner', 'composerSuggestionListViewMainComposerSuggestionViewItemOwner']],
identifyingMode: 'xor',
recordMethods: {
/**
* @param {Event} ev
@@ -115,10 +115,12 @@ registerModel({
inverse: 'activeSuggestionView',
}),
composerSuggestionListViewExtraComposerSuggestionViewItemOwner: one('ComposerSuggestionListViewExtraComposerSuggestionViewItem', {
identifying: true,
inverse: 'composerSuggestionView',
readonly: true,
}),
composerSuggestionListViewMainComposerSuggestionViewItemOwner: one('ComposerSuggestionListViewMainComposerSuggestionViewItem', {
identifying: true,
inverse: 'composerSuggestionView',
readonly: true,
}),
@@ -12,7 +12,7 @@ import { url } from '@web/core/utils/urls';
registerModel({
name: 'ComposerView',
identifyingFields: [['threadView', 'messageViewInEditing', 'chatter']],
identifyingMode: 'xor',
lifecycleHooks: {
_created() {
document.addEventListener('click', this.onClickCaptureGlobal, true);
@@ -1377,6 +1377,7 @@ registerModel({
* States the chatter which this composer allows editing (if any).
*/
chatter: one('Chatter', {
identifying: true,
inverse: 'composerView',
readonly: true,
}),
@@ -1542,6 +1543,7 @@ registerModel({
* States the message view on which this composer allows editing (if any).
*/
messageViewInEditing: one('MessageView', {
identifying: true,
inverse: 'composerViewInEditing',
readonly: true,
}),
@@ -1609,6 +1611,7 @@ registerModel({
* States the thread view on which this composer allows editing (if any).
*/
threadView: one('ThreadView', {
identifying: true,
inverse: 'composerView',
readonly: true,
}),
+1 -1
View File
@@ -6,7 +6,6 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'Country',
identifyingFields: ['id'],
recordMethods: {
/**
* @private
@@ -25,6 +24,7 @@ registerModel({
compute: '_computeFlagUrl',
}),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'DeleteMessageConfirmView',
identifyingFields: ['dialogOwner'],
recordMethods: {
/**
* Returns whether the given html element is inside this delete message confirm view.
@@ -44,6 +43,7 @@ registerModel({
fields: {
component: attr(),
dialogOwner: one('Dialog', {
identifying: true,
inverse: 'deleteMessageConfirmView',
readonly: true,
required: true,
-1
View File
@@ -8,7 +8,6 @@ import { SIZES } from '@web/core/ui/ui_service';
registerModel({
name: 'Device',
identifyingFields: ['messaging'],
lifecycleHooks: {
_created() {
this._refresh();
+6 -7
View File
@@ -6,13 +6,7 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'Dialog',
identifyingFields: [[
'attachmentCardOwnerAsAttachmentDeleteConfirm',
'attachmentImageOwnerAsAttachmentDeleteConfirm',
'attachmentListOwnerAsAttachmentView',
'followerOwnerAsSubtypeList',
'messageActionListOwnerAsDeleteConfirm',
]],
identifyingMode: 'xor',
lifecycleHooks: {
_created() {
document.addEventListener('click', this._onClickGlobal, true);
@@ -188,6 +182,7 @@ registerModel({
},
fields: {
attachmentCardOwnerAsAttachmentDeleteConfirm: one('AttachmentCard', {
identifying: true,
inverse: 'attachmentDeleteConfirmDialog',
readonly: true,
}),
@@ -197,10 +192,12 @@ registerModel({
isCausal: true,
}),
attachmentImageOwnerAsAttachmentDeleteConfirm: one('AttachmentImage', {
identifying: true,
inverse: 'attachmentDeleteConfirmDialog',
readonly: true,
}),
attachmentListOwnerAsAttachmentView: one('AttachmentList', {
identifying: true,
inverse: 'attachmentListViewDialog',
readonly: true,
}),
@@ -225,6 +222,7 @@ registerModel({
isCausal: true,
}),
followerOwnerAsSubtypeList: one('Follower', {
identifying: true,
inverse: 'followerSubtypeListDialog',
readonly: true,
}),
@@ -243,6 +241,7 @@ registerModel({
readonly: true,
}),
messageActionListOwnerAsDeleteConfirm: one('MessageActionList', {
identifying: true,
inverse: 'deleteConfirmDialog',
readonly: true,
}),
@@ -5,7 +5,6 @@ import { many } from '@mail/model/model_field';
registerModel({
name: 'DialogManager',
identifyingFields: ['messaging'],
fields: {
// FIXME: dependent on implementation that uses insert order in relations!!
dialogs: many('Dialog', {
-1
View File
@@ -7,7 +7,6 @@ import { escape, sprintf } from '@web/core/utils/strings';
registerModel({
name: 'Discuss',
identifyingFields: ['messaging'],
recordMethods: {
clearIsAddingItem() {
this.update({
@@ -6,7 +6,6 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'DiscussPublicView',
identifyingFields: ['messaging'],
recordMethods: {
/**
* Creates and displays the thread view and clears the welcome view.
@@ -7,7 +7,7 @@ import { OnChange } from '@mail/model/model_onchange';
registerModel({
name: 'DiscussSidebarCategory',
identifyingFields: [['discussAsChannel', 'discussAsChat']],
identifyingMode: 'xor',
modelMethods: {
/**
* Performs the `set_res_users_settings` RPC on `res.users.settings`.
@@ -377,10 +377,12 @@ registerModel({
sum: 'categoryItems.categoryCounterContribution',
}),
discussAsChannel: one('Discuss', {
identifying: true,
inverse: 'categoryChannel',
readonly: true,
}),
discussAsChat: one('Discuss', {
identifying: true,
inverse: 'categoryChat',
readonly: true,
}),
@@ -8,7 +8,6 @@ import Dialog from 'web.Dialog';
registerModel({
name: 'DiscussSidebarCategoryItem',
identifyingFields: ['category', 'thread'],
recordMethods: {
/**
* @private
@@ -226,6 +225,7 @@ registerModel({
* Determines the discuss sidebar category displaying this item.
*/
category: one('DiscussSidebarCategory', {
identifying: true,
inverse: 'categoryItems',
readonly: true,
required: true,
@@ -288,6 +288,7 @@ registerModel({
* The related thread.
*/
thread: one('Thread', {
identifying: true,
inverse: 'discussSidebarCategoryItem',
readonly: true,
required: true,
@@ -6,7 +6,7 @@ import { clear, replace } from '@mail/model/model_field_command';
registerModel({
name: 'DiscussSidebarMailboxView',
identifyingFields: [['discussViewOwnerAsHistory', 'discussViewOwnerAsInbox', 'discussViewOwnerAsStarred']],
identifyingMode: 'xor',
recordMethods: {
/**
* @private
@@ -27,14 +27,17 @@ registerModel({
},
fields: {
discussViewOwnerAsHistory: one('DiscussView', {
identifying: true,
inverse: 'historyView',
readonly: true,
}),
discussViewOwnerAsInbox: one('DiscussView', {
identifying: true,
inverse: 'inboxView',
readonly: true,
}),
discussViewOwnerAsStarred: one('DiscussView', {
identifying: true,
inverse: 'starredView',
readonly: true,
}),
@@ -7,7 +7,6 @@ import { OnChange } from '@mail/model/model_onchange';
registerModel({
name: 'DiscussView',
identifyingFields: ['discuss'],
recordMethods: {
/**
* Handles click on the mobile "new channel" button.
@@ -136,6 +135,7 @@ registerModel({
*/
actionId: attr(),
discuss: one('Discuss', {
identifying: true,
inverse: 'discussView',
readonly: true,
required: true,
@@ -6,7 +6,7 @@ import { decrement, increment } from '@mail/model/model_field_command';
registerModel({
name: 'DropZoneView',
identifyingFields: [['chatterOwner', 'composerViewOwner']],
identifyingMode: 'xor',
recordMethods: {
/**
* Shows a visual drop effect when dragging inside the dropzone.
@@ -94,10 +94,12 @@ registerModel({
},
fields: {
chatterOwner: one('Chatter', {
identifying: true,
inverse: 'dropZoneView',
readonly: true,
}),
composerViewOwner: one('ComposerView', {
identifying: true,
inverse: 'dropZoneView',
readonly: true,
}),
+1 -1
View File
@@ -6,7 +6,6 @@ import { replace } from '@mail/model/model_field_command';
registerModel({
name: 'Emoji',
identifyingFields: ['unicode'],
recordMethods: {
/**
* @private
@@ -38,6 +37,7 @@ registerModel({
readonly: true,
}),
unicode: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -5,12 +5,12 @@ import { attr, many } from '@mail/model/model_field';
registerModel({
name: 'EmojiCategory',
identifyingFields: ['categoryName'],
fields: {
allEmojis: many('Emoji', {
inverse: 'emojiCategories',
}),
categoryName: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'EmojiCategoryBarView',
identifyingFields: ['emojiPickerViewOwner'],
recordMethods: {
/**
* @private
@@ -58,6 +57,7 @@ registerModel({
isCausal: true,
}),
emojiPickerViewOwner: one('EmojiPickerView', {
identifying: true,
inverse: 'emojiCategoryBarView',
readonly: true,
required: true,
@@ -6,9 +6,8 @@ import { replace } from '@mail/model/model_field_command';
registerModel({
name: 'EmojiCategoryView',
identifyingFields: ['emojiCategoryBarViewOwner', 'emojiCategory'],
recordMethods: {
/**
/**
* @param {MouseEvent} ev
*/
onClick() {
@@ -29,11 +28,13 @@ registerModel({
},
fields: {
emojiCategory: one('EmojiCategory', {
identifying: true,
inverse: 'emojiCategoryViews',
readonly: true,
required: true,
}),
emojiCategoryBarViewOwner: one('EmojiCategoryBarView', {
identifying: true,
inverse: 'emojiCategoryViews',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'EmojiGridView',
identifyingFields: ['emojiPickerViewOwner'],
recordMethods: {
/**
* @private
@@ -25,6 +24,7 @@ registerModel({
},
fields: {
emojiPickerViewOwner: one('EmojiPickerView', {
identifying: true,
inverse: 'emojiGridView',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'EmojiPickerView',
identifyingFields: ['popoverViewOwner'],
fields: {
emojiCategoryBarView: one('EmojiCategoryBarView', {
default: insertAndReplace(),
@@ -23,6 +22,7 @@ registerModel({
isCausal: true,
}),
popoverViewOwner: one('PopoverView', {
identifying: true,
inverse: 'emojiPickerView',
readonly: true,
required: true,
@@ -6,7 +6,6 @@ import { insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'EmojiRegistry',
identifyingFields: ['messaging'],
fields: {
allEmojis: many('Emoji', {
default: insertAndReplace([
+2 -1
View File
@@ -5,7 +5,6 @@ import { attr, one } from '@mail/model/model_field';
registerModel({
name: 'EmojiView',
identifyingFields: ['emojiGridView', 'emoji'],
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -35,11 +34,13 @@ registerModel({
},
fields: {
emoji: one('Emoji', {
identifying: true,
inverse: 'emojiViews',
readonly: true,
required: true,
}),
emojiGridView: one('EmojiGridView', {
identifying: true,
inverse: 'emojiViews',
readonly: true,
required: true,
@@ -16,7 +16,7 @@ const getAttachmentNextTemporaryId = (function () {
registerModel({
name: 'FileUploader',
identifyingFields: [['activityView', 'attachmentBoxView', 'chatterOwner', 'composerView']],
identifyingMode: 'xor',
recordMethods: {
openBrowserFileUploader() {
this.fileInput.click();
@@ -188,18 +188,22 @@ registerModel({
},
fields: {
activityView: one('ActivityView', {
identifying: true,
inverse: 'fileUploader',
readonly: true,
}),
attachmentBoxView: one('AttachmentBoxView', {
identifying: true,
inverse: 'fileUploader',
readonly: true,
}),
chatterOwner: one('Chatter', {
identifying: true,
inverse: 'fileUploader',
readonly: true,
}),
composerView: one('ComposerView', {
identifying: true,
inverse: 'fileUploader',
readonly: true,
}),
@@ -6,7 +6,6 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'FollowButtonView',
identifyingFields: [['chatterOwner']],
recordMethods: {
/**
* @private
@@ -64,6 +63,7 @@ registerModel({
},
fields: {
chatterOwner: one('Chatter', {
identifying: true,
inverse: 'followButtonView',
readonly: true,
}),
+1 -1
View File
@@ -6,7 +6,6 @@ import { clear, insert, insertAndReplace, link, replace, unlink } from '@mail/mo
registerModel({
name: 'Follower',
identifyingFields: ['id'],
modelMethods: {
/**
* @param {Object} data
@@ -184,6 +183,7 @@ registerModel({
isCausal: true,
}),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'FollowerListMenuView',
identifyingFields: [['chatterOwner']],
recordMethods: {
hide() {
this.update({ isDropdownOpen: false });
@@ -48,6 +47,7 @@ registerModel({
},
fields: {
chatterOwner: one('Chatter', {
identifying: true,
inverse: 'followerListMenuView',
readonly: true,
}),
@@ -5,7 +5,6 @@ import { attr, many } from '@mail/model/model_field';
registerModel({
name: 'FollowerSubtype',
identifyingFields: ['id'],
modelMethods: {
/**
* @param {Object} data
@@ -43,6 +42,7 @@ registerModel({
isCausal: true,
}),
id: attr({
identifying: true,
readonly: true,
required: true,
}),
@@ -6,7 +6,6 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
registerModel({
name: 'FollowerSubtypeList',
identifyingFields: ['dialogOwner'],
recordMethods: {
/**
* Returns whether the given html element is inside this follower subtype list.
@@ -68,6 +67,7 @@ registerModel({
* States the dialog displaying this follower subtype list.
*/
dialogOwner: one('Dialog', {
identifying: true,
inverse: 'followerSubtypeList',
isCausal: true,
readonly: true,
@@ -5,7 +5,6 @@ import { one } from '@mail/model/model_field';
registerModel({
name: 'FollowerSubtypeView',
identifyingFields: ['followerSubtypeListOwner', 'subtype'],
recordMethods: {
/**
* Called when clicking on cancel button.
@@ -25,11 +24,13 @@ registerModel({
related: 'followerSubtypeListOwner.follower',
}),
followerSubtypeListOwner: one('FollowerSubtypeList', {
identifying: true,
inverse: 'followerSubtypeViews',
readonly: true,
required: true,
}),
subtype: one('FollowerSubtype', {
identifying: true,
inverse: 'followerSubtypeViews',
readonly: true,
required: true,

Some files were not shown because too many files have changed in this diff Show More