[IMP] mail, rating: turn computes and relateds readonly by default

Automatically add readonly on computed and related fields. This commit
required to rewrite some preexisting fields to make them readonly.
Task-2955927.

closes odoo/odoo#98541

Related: odoo/enterprise#30625
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Louis Wicket (wil)
2022-08-23 17:55:22 +02:00
parent d3905b5bb7
commit a8c73f285f
63 changed files with 217 additions and 309 deletions
+11 -5
View File
@@ -67,7 +67,7 @@ class Channel(models.Model):
groups='base.group_user')
rtc_session_ids = fields.One2many('mail.channel.rtc.session', 'channel_id', groups="base.group_system")
is_member = fields.Boolean('Is Member', compute='_compute_is_member', search='_search_is_member')
member_count = fields.Integer(string="Member Count", compute='_compute_member_count', compute_sudo=True, help="Excluding guests from count.")
member_count = fields.Integer(string="Member Count", compute='_compute_member_count', compute_sudo=True)
group_ids = fields.Many2many(
'res.groups', string='Auto Subscription',
help="Members of those groups will automatically added as followers. "
@@ -192,7 +192,7 @@ class Channel(models.Model):
self.env['mail.channel.member'].sudo()._search(user_domain)
)]
@api.depends('channel_partner_ids')
@api.depends('channel_member_ids')
def _compute_member_count(self):
read_group_res = self.env['mail.channel.member']._read_group(domain=[('channel_id', 'in', self.ids)], fields=['channel_id'], groupby=['channel_id'])
member_count_by_channel_id = {item['channel_id'][0]: item['channel_id_count'] for item in read_group_res}
@@ -332,8 +332,11 @@ class Channel(models.Model):
# post 'channel left' message as root since the partner just unsubscribed from the channel
self.sudo().message_post(body=notification, subtype_xmlid="mail.mt_comment", author_id=partner.id)
self.env['bus.bus']._sendone(self, 'mail.channel/insert', {
'channel': [('insert-and-replace', {
'id': self.id,
'memberCount': self.member_count,
})],
'id': self.id,
'memberCount': self.member_count,
'members': [('insert-and-unlink', {'id': partner.id})],
})
return result
@@ -414,9 +417,12 @@ class Channel(models.Model):
'channel': member.channel_id.sudo().channel_info()[0],
}))
notifications.append((channel, 'mail.channel/insert', {
'channel': [('insert-and-replace', {
'id': channel.id,
'memberCount': channel.member_count,
})],
'id': channel.id,
'guestMembers': [('insert', guest_members_data)],
'memberCount': channel.member_count,
'members': [('insert', members_data)],
}))
if invite_to_rtc_call:
@@ -768,6 +774,7 @@ class Channel(models.Model):
channel_data = {
'channel_type': channel.channel_type,
'id': channel.id,
'memberCount': channel.member_count,
}
info = {
'avatarCacheKey': channel._get_avatar_cache_key(),
@@ -785,7 +792,6 @@ class Channel(models.Model):
}
# add last message preview (only used in mobile)
info['last_message_id'] = channel_last_message_ids.get(channel.id, False)
info['memberCount'] = channel.member_count
# find the channel member state, if logged user
if self.env.user and self.env.user.partner_id:
info['message_needaction_counter'] = channel.message_needaction_counter
@@ -43,7 +43,7 @@
record="discussView.discuss.threadView"
/>
</t>
<t t-if="!discussView.discuss.thread and (!messaging.device.isSmall or discussView.discuss.activeMobileNavbarTabId === 'mailbox')">
<t t-if="!discussView.discuss.activeThread and (!messaging.device.isSmall or discussView.discuss.activeMobileNavbarTabId === 'mailbox')">
<div class="o_Discuss_noThread d-flex flex-grow-1 align-items-center justify-content-center w-100 bg-white">
<h4 class="text-muted"><b><i>No conversation selected.</i></b></h4>
</div>
@@ -34,7 +34,7 @@ export class DiscussContainer extends Component {
await this.messaging.initializedPromise;
if (!this.discuss.isInitThreadHandled) {
this.discuss.update({ isInitThreadHandled: true });
if (!this.discuss.thread) {
if (!this.discuss.activeThread) {
this.discuss.openInitThread();
}
}
@@ -4,7 +4,7 @@
<t t-name="mail.DiscussMobileMailboxSelectionItem" owl="1">
<button class="o_DiscussMobileMailboxSelectionItem btn btn-secondary flex-grow-1 p-2"
t-att-class="{
'active o-active shadow-none': discussView.discuss.thread === mailbox.thread,
'active o-active shadow-none': discussView.discuss.activeThread === mailbox.thread,
}" t-attf-class="{{ className }}" t-on-click="() => discussView.onClickMobileMailboxSelectionItem(mailbox)" t-att-data-mailbox-local-id="mailbox.localId" type="button" t-ref="root"
>
<t t-esc="mailbox.name"/>
@@ -3,8 +3,8 @@
<t t-name="mail.DiscussSidebarMailbox" owl="1">
<button class="o_DiscussSidebarMailbox btn d-flex align-items-center py-1 px-0 border-0 rounded-0 fw-normal text-dark"
t-att-class="{
'bg-100': discussSidebarMailboxView.mailbox.thread !== messaging.discuss.thread,
'o-active bg-200': discussSidebarMailboxView.mailbox.thread === messaging.discuss.thread,
'bg-100': discussSidebarMailboxView.mailbox.thread !== messaging.discuss.activeThread,
'o-active bg-200': discussSidebarMailboxView.mailbox.thread === messaging.discuss.activeThread,
'o-starred-box': discussSidebarMailboxView.mailbox === messaging.starred,
}" t-attf-class="{{ className }}" t-on-click="discussSidebarMailboxView.mailbox.thread.onClick" t-att-data-mailbox-local-id="discussSidebarMailboxView.mailbox.localId" t-att-data-mailbox-name="discussSidebarMailboxView.mailbox.name"
t-ref="root"
@@ -140,12 +140,22 @@ export class ModelField {
* model name this relation refers to.
*/
this.to = to;
/**
* Automatically make identifying fields readonly (and required for AND
* identifying mode).
*/
if (this.identifying) {
this.readonly = true;
if (model.identifyingMode === 'and') {
this.required = true;
}
}
/**
* Automatically make computes and relateds readonly.
*/
if (this.compute || this.related) {
this.readonly = true;
}
}
/**
+16 -8
View File
@@ -491,17 +491,22 @@ export class ModelManager {
}
}
// 4. Computed field.
if (field.compute && !(typeof field.compute === 'string')) {
throw new Error(`Property "compute" of field(${fieldName}) on ${model} must be a string (instance method name).`);
}
if (field.compute && !(model.prototype[field.compute])) {
throw new Error(`Property "compute" of field(${fieldName}) on ${model} does not refer to an instance method of this model.`);
if (field.compute) {
if (!(typeof field.compute === 'string')) {
throw new Error(`Property "compute" of field(${fieldName}) on ${model} must be a string (instance method name).`);
}
if (!model.prototype[field.compute]) {
throw new Error(`Property "compute" of field(${fieldName}) on ${model} does not refer to an instance method of this model.`);
}
if ('readonly' in field) {
throw new Error(`Computed field(${fieldName}) on ${model} has unnecessary "readonly" attribute (readonly is implicit for computed fields).`);
}
}
// 5. Related field.
if (field.compute && field.related) {
throw new Error(`field(${fieldName}) on ${model} cannot be a related and compute field at the same time.`);
}
if (field.related) {
if (field.compute) {
throw new Error(`field(${fieldName}) on ${model} cannot be a related and compute field at the same time.`);
}
if (!(typeof field.related === 'string')) {
throw new Error(`Property "related" of field(${fieldName}) on ${model} has invalid format.`);
}
@@ -543,6 +548,9 @@ export class ModelManager {
) {
throw new Error(`Related field(${fieldName}) on ${model} has mismatched target model name with its related model field.`);
}
if ('readonly' in field) {
throw new Error(`Related field(${fieldName}) on ${model} has unnecessary "readonly" attribute (readonly is implicit for related fields).`);
}
}
}
}
@@ -95,7 +95,6 @@ registerModel({
fields: {
activityViewOwner: one('ActivityView', {
compute: '_computeActivityViewOwner',
readonly: true,
}),
component: attr(),
feedbackTextareaRef: attr(),
@@ -408,7 +408,6 @@ registerModel({
threadsAsAttachmentsInWebClientView: many('Thread', {
compute: '_computeThreadsAsAttachmentsInWebClientView',
inverse: 'attachmentsInWebClientView',
readonly: true,
}),
type: attr(),
/**
@@ -31,7 +31,6 @@ registerModel({
compute: '_computeAttachmentList',
inverse: 'attachmentBoxViewOwner',
isCausal: true,
readonly: true,
}),
chatter: one('Chatter', {
identifying: true,
@@ -72,7 +72,6 @@ registerModel({
fields: {
attachment: one('Attachment', {
compute: '_computeAttachment',
readonly: true,
required: true,
}),
body: attr({
@@ -42,7 +42,6 @@ registerModel({
}),
rtcSession: one('RtcSession', {
compute: '_computeRtcSession',
readonly: true,
}),
},
});
@@ -171,7 +171,6 @@ registerModel({
compute: '_computeCallSideBarView',
inverse: 'callView',
isCausal: true,
readonly: true,
}),
channel: one('Thread', {
related: 'threadView.thread',
+4 -4
View File
@@ -92,9 +92,10 @@ registerModel({
id: attr({
identifying: true,
}),
memberCount: attr({
related: 'thread.memberCount',
}),
/**
* States the number of members in this channel according to the server.
*/
memberCount: attr(),
orderedOfflineMembers: many('ChannelMember', {
inverse: 'channelAsOfflineMember',
sort: '_sortMembers',
@@ -107,7 +108,6 @@ registerModel({
compute: '_computeThread',
inverse: 'channel',
isCausal: true,
readonly: true,
required: true,
}),
/**
@@ -316,7 +316,6 @@ registerModel({
*/
thread: one('Thread', {
compute: '_computeThread',
readonly: true,
required: true,
}),
},
@@ -28,7 +28,6 @@ registerModel({
compute: '_computePersonaImStatusIconView',
inverse: 'channelInvitationFormSelectablePartnerViewOwner',
isCausal: true,
readonly: true,
}),
},
});
@@ -56,7 +56,6 @@ registerModel({
fields: {
channel: one('Channel', {
compute: '_computeChannel',
readonly: true,
}),
chatWindowOwner: one('ChatWindow', {
identifying: true,
@@ -63,7 +63,6 @@ registerModel({
compute: '_computePersonaImStatusIconView',
inverse: 'channelMemberViewOwner',
isCausal: true,
readonly: true,
}),
},
});
@@ -697,7 +697,6 @@ registerModel({
compute: '_computeThreadViewer',
inverse: 'chatWindow',
isCausal: true,
readonly: true,
required: true,
}),
/**
@@ -50,8 +50,7 @@ registerModel({
*
*/
closeAll() {
const chatWindows = this.allOrdered;
for (const chatWindow of chatWindows) {
for (const chatWindow of this.chatWindows) {
chatWindow.close();
}
},
@@ -140,16 +139,15 @@ registerModel({
* @param {ChatWindow} chatWindow2
*/
swap(chatWindow1, chatWindow2) {
const ordered = this.allOrdered;
const index1 = ordered.findIndex(chatWindow => chatWindow === chatWindow1);
const index2 = ordered.findIndex(chatWindow => chatWindow === chatWindow2);
const index1 = this.chatWindows.findIndex(chatWindow => chatWindow === chatWindow1);
const index2 = this.chatWindows.findIndex(chatWindow => chatWindow === chatWindow2);
if (index1 === -1 || index2 === -1) {
return;
}
const _newOrdered = [...this.allOrdered];
const _newOrdered = [...this.chatWindows];
_newOrdered[index1] = chatWindow2;
_newOrdered[index2] = chatWindow1;
this.update({ allOrdered: _newOrdered });
this.update({ chatWindows: _newOrdered });
for (const chatWindow of [chatWindow1, chatWindow2]) {
if (chatWindow.threadView) {
chatWindow.threadView.addComponentHint('adjust-scroll');
@@ -260,7 +258,7 @@ registerModel({
if (!this.messaging.device.isSmall && this.messaging.discuss.discussView) {
return visual;
}
if (!this.allOrdered.length) {
if (!this.chatWindows.length) {
return visual;
}
const relativeGlobalWindowWidth = this.messaging.device.globalWindowInnerWidth - this.startGapWidth - this.endGapWidth;
@@ -273,10 +271,10 @@ registerModel({
maxAmountWithoutHidden = 1;
maxAmountWithHidden = 1;
}
if (this.allOrdered.length <= maxAmountWithoutHidden) {
if (this.chatWindows.length <= maxAmountWithoutHidden) {
// all visible
for (let i = 0; i < this.allOrdered.length; i++) {
const chatWindow = this.allOrdered[i];
for (let i = 0; i < this.chatWindows.length; i++) {
const chatWindow = this.chatWindows[i];
const offset = this.startGapWidth + i * (this.chatWindowWidth + this.betweenGapWidth);
visual.visible.push({ chatWindow, offset });
}
@@ -284,24 +282,24 @@ registerModel({
} else if (maxAmountWithHidden > 0) {
// some visible, some hidden
for (let i = 0; i < maxAmountWithHidden; i++) {
const chatWindow = this.allOrdered[i];
const chatWindow = this.chatWindows[i];
const offset = this.startGapWidth + i * (this.chatWindowWidth + this.betweenGapWidth);
visual.visible.push({ chatWindow, offset });
}
if (this.allOrdered.length > maxAmountWithHidden) {
if (this.chatWindows.length > maxAmountWithHidden) {
visual.isHiddenMenuVisible = !this.messaging.device.isSmall;
visual.hiddenMenuOffset = visual.visible[maxAmountWithHidden - 1].offset
+ this.chatWindowWidth + this.betweenGapWidth;
}
for (let j = maxAmountWithHidden; j < this.allOrdered.length; j++) {
visual.hiddenChatWindows.push(this.allOrdered[j]);
for (let j = maxAmountWithHidden; j < this.chatWindows.length; j++) {
visual.hiddenChatWindows.push(this.chatWindows[j]);
}
visual.availableVisibleSlots = maxAmountWithHidden;
} else {
// all hidden
visual.isHiddenMenuVisible = !this.messaging.device.isSmall;
visual.hiddenMenuOffset = this.startGapWidth;
visual.hiddenChatWindows.push(...this.allOrdered);
visual.hiddenChatWindows.push(...this.chatWindows);
console.warn('cannot display any visible chat windows (screen is too small)');
visual.availableVisibleSlots = 0;
}
@@ -309,12 +307,6 @@ registerModel({
},
},
fields: {
/**
* List of ordered chat windows.
*/
allOrdered: many('ChatWindow', {
compute: '_computeAllOrdered',
}),
allOrderedHidden: many('ChatWindow', {
compute: '_computeAllOrderedHidden',
}),
+1 -2
View File
@@ -305,7 +305,7 @@ registerModel({
model: this.threadModel,
}),
});
this.thread.cache.update({ messages: link(message) });
this.thread.cache.update({ temporaryMessages: link(message) });
}
},
/**
@@ -484,7 +484,6 @@ registerModel({
compute: '_computeThreadViewer',
inverse: 'chatter',
isCausal: true,
readonly: true,
required: true,
}),
topbar: one('ChatterTopbar', {
+7 -14
View File
@@ -9,22 +9,17 @@ import { attr, many } from '@mail/model/model_field';
registerModel({
name: 'Clock',
lifecycleHooks: {
_created() {
// The date is set here rather than via a default value so that the
// date set at first is the time of the record creation, and not the
// time of the model initialization.
this.update({ date: new Date() });
},
_willDelete() {
this.messaging.browser.clearInterval(this.tickInterval);
},
},
recordMethods: {
/**
* The date is set by a compute rather than using a default value. Thus,
* the date set at first is the time of the record creation, and not the
* time of the model initialization.
*
* @private
* @returns {Date}
*/
_computeDate() {
return new Date();
},
/**
* @private
* @returns {integer}
@@ -52,9 +47,7 @@ registerModel({
* A Date object set to the current date at the time the record is
* created, then updated at every tick.
*/
date: attr({
compute: '_computeDate',
}),
date: attr(),
/**
* An integer representing the frequency in milliseconds at which `date`
* must be recomputed.
+20 -19
View File
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear, unlink } from '@mail/model/model_field_command';
import { clear } from '@mail/model/model_field_command';
import { sprintf } from '@web/core/utils/strings';
registerModel({
@@ -47,47 +47,47 @@ registerModel({
* @returns {Partner[]}
*/
_computeMentionedPartners() {
const unmentionedPartners = [];
const mentionedPartners = [];
// ensure the same mention is not used multiple times if multiple
// partners have the same name
const namesIndex = {};
for (const partner of this.mentionedPartners) {
for (const partner of this.rawMentionedPartners) {
const fromIndex = namesIndex[partner.name] !== undefined
? namesIndex[partner.name] + 1 :
0;
const index = this.textInputContent.indexOf(`@${partner.name}`, fromIndex);
if (index !== -1) {
namesIndex[partner.name] = index;
} else {
unmentionedPartners.push(partner);
if (index === -1) {
continue;
}
namesIndex[partner.name] = index;
mentionedPartners.push(partner);
}
return unlink(unmentionedPartners);
return mentionedPartners;
},
/**
* Detects if mentioned channels are still in the composer text input content
* and removes them if not.
*
* @private
* @returns {Partner[]}
* @returns {Channels[]}
*/
_computeMentionedChannels() {
const unmentionedChannels = [];
const mentionedChannels = [];
// ensure the same mention is not used multiple times if multiple
// channels have the same name
const namesIndex = {};
for (const channel of this.mentionedChannels) {
for (const channel of this.rawMentionedChannels) {
const fromIndex = namesIndex[channel.name] !== undefined
? namesIndex[channel.name] + 1 :
0;
const index = this.textInputContent.indexOf(`#${channel.name}`, fromIndex);
if (index !== -1) {
namesIndex[channel.name] = index;
} else {
unmentionedChannels.push(channel);
if (index === -1) {
continue;
}
namesIndex[channel.name] = index;
mentionedChannels.push(channel);
}
return unlink(unmentionedChannels);
return mentionedChannels;
},
/**
* @private
@@ -129,8 +129,8 @@ registerModel({
_reset() {
this.update({
attachments: clear(),
mentionedChannels: clear(),
mentionedPartners: clear(),
rawMentionedChannels: clear(),
rawMentionedPartners: clear(),
textInputContent: clear(),
textInputCursorEnd: clear(),
textInputCursorStart: clear(),
@@ -144,7 +144,6 @@ registerModel({
fields: {
activeThread: one('Thread', {
compute: '_computeActiveThread',
readonly: true,
required: true,
}),
/**
@@ -194,6 +193,8 @@ registerModel({
placeholder: attr({
compute: '_computePlaceholder',
}),
rawMentionedChannels: many('Thread'),
rawMentionedPartners: many('Partner'),
/**
* Determines the extra `Partner` (on top of existing followers)
* that will receive the message being composed by `this`, and that will
@@ -12,7 +12,7 @@ registerModel({
*/
setFirstSuggestionViewActive() {
const firstSuggestionView = this.suggestionViews[0];
this.update({ activeSuggestionView: firstSuggestionView });
this.update({ rawActiveSuggestionView: firstSuggestionView });
},
/**
* Sets the last suggestion as active. Main and extra records are
@@ -20,7 +20,7 @@ registerModel({
*/
setLastSuggestionViewActive() {
const { length, [length - 1]: lastSuggestionView } = this.suggestionViews;
this.update({ activeSuggestionView: lastSuggestionView });
this.update({ rawActiveSuggestionView: lastSuggestionView });
},
/**
* Sets the next suggestion as active. Main and extra records are
@@ -36,7 +36,7 @@ registerModel({
return;
}
const nextSuggestionView = this.suggestionViews[activeElementIndex + 1];
this.update({ activeSuggestionView: nextSuggestionView });
this.update({ rawActiveSuggestionView: nextSuggestionView });
},
/**
* Sets the previous suggestion as active. Main and extra records are
@@ -52,7 +52,7 @@ registerModel({
return;
}
const previousSuggestionView = this.suggestionViews[activeElementIndex - 1];
this.update({ activeSuggestionView: previousSuggestionView });
this.update({ rawActiveSuggestionView: previousSuggestionView });
},
/**
* Adapts the active suggestion it if the active suggestion is no longer
@@ -62,8 +62,8 @@ registerModel({
* @returns {FieldCommand}
*/
_computeActiveSuggestionView() {
if (this.suggestionViews.includes(this.activeSuggestionView)) {
return;
if (this.suggestionViews.includes(this.rawActiveSuggestionView)) {
return this.rawActiveSuggestionView;
}
const firstSuggestionView = this.suggestionViews[0];
return firstSuggestionView;
@@ -120,9 +120,9 @@ registerModel({
hasToScrollToActiveSuggestionView: attr({
default: false,
}),
rawActiveSuggestionView: one('ComposerSuggestionView'),
suggestionViews: many('ComposerSuggestionView', {
compute: '_computeSuggestionViews',
readonly: true,
})
},
});
@@ -20,7 +20,7 @@ registerModel({
*/
onClick(ev) {
ev.preventDefault();
this.composerSuggestionListViewOwner.update({ activeSuggestionView: this });
this.composerSuggestionListViewOwner.update({ rawActiveSuggestionView: this });
const composerViewOwner = this.composerSuggestionListViewOwner.composerViewOwner;
composerViewOwner.insertSuggestion();
composerViewOwner.closeSuggestions();
@@ -132,11 +132,9 @@ registerModel({
compute: '_computePersonaImStatusIconView',
inverse: 'composerSuggestionViewOwner',
isCausal: true,
readonly: true,
}),
suggestable: one('ComposerSuggestable', {
compute: '_computeSuggestable',
readonly: true,
required: true,
}),
/**
+23 -38
View File
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear, link, unlink } from '@mail/model/model_field_command';
import { clear, link } from '@mail/model/model_field_command';
import { addLink, escapeAndCompactTextContent, parseAndTransform } from '@mail/js/utils';
import { isEventHandled, markEventHandled } from '@mail/utils/utils';
@@ -144,10 +144,10 @@ registerModel({
// the mention will appear in the target channel, or be notified to
// the target partner.
if (this.composerSuggestionListView.activeSuggestionView.suggestable.thread) {
Object.assign(updateData, { mentionedChannels: link(this.composerSuggestionListView.activeSuggestionView.suggestable.thread) });
Object.assign(updateData, { rawMentionedChannels: link(this.composerSuggestionListView.activeSuggestionView.suggestable.thread) });
}
if (this.composerSuggestionListView.activeSuggestionView.suggestable.partner) {
Object.assign(updateData, { mentionedPartners: link(this.composerSuggestionListView.activeSuggestionView.suggestable.partner) });
Object.assign(updateData, { rawMentionedPartners: link(this.composerSuggestionListView.activeSuggestionView.suggestable.partner) });
}
this.composer.update(updateData);
for (const composerView of this.composer.composerViews) {
@@ -793,24 +793,10 @@ registerModel({
return clear();
}
if (this.threadView.threadViewer.discuss) {
return this.threadView.threadViewer.discuss.thread === this.messaging.inbox.thread;
return this.threadView.threadViewer.discuss.activeThread === this.messaging.inbox.thread;
}
return clear();
},
/**
* Clears the extra suggestions on closing mentions, and ensures
* the extra list does not contain any element already present in the
* main list, which is a requirement for the navigation process.
*
* @private
* @returns {FieldCommand}
*/
_computeExtraSuggestions() {
if (this.suggestionDelimiterPosition === undefined) {
return clear();
}
return unlink(this.mainSuggestions);
},
/**
* @private
* @returns {boolean|FieldCommand}
@@ -969,17 +955,6 @@ registerModel({
}
return clear();
},
/**
* Clears the main suggestions on closing mentions.
*
* @private
* @returns {Record[]}
*/
_computeMainSuggestions() {
if (this.suggestionDelimiterPosition === undefined) {
return clear();
}
},
/**
* @private
* @returns {string}
@@ -1018,7 +993,7 @@ registerModel({
this.messaging.device.isSmall ||
(
this.messaging.discuss.threadView === this.threadView &&
this.messaging.discuss.thread === this.messaging.inbox.thread
this.messaging.discuss.activeThread === this.messaging.inbox.thread
)
) {
return ['ctrl-enter', 'meta-enter'];
@@ -1356,7 +1331,18 @@ registerModel({
return {};
}
return clear();
}
},
/**
* @private
*/
_onSuggestionDelimiterPositionChanged() {
if (this.suggestionDelimiterPosition === undefined) {
this.update({
extraSuggestions: clear(),
mainSuggestions: clear(),
});
}
},
},
fields: {
/**
@@ -1366,7 +1352,6 @@ registerModel({
compute: '_computeAttachmentList',
inverse: 'composerViewOwner',
isCausal: true,
readonly: true,
}),
/**
* States the ref to the html node of the emojis button.
@@ -1423,9 +1408,7 @@ registerModel({
inverse: 'composerViewOwnerAsEmoji',
isCausal: true,
}),
extraSuggestions: many('ComposerSuggestable', {
compute: '_computeExtraSuggestions',
}),
extraSuggestions: many('ComposerSuggestable'),
fileUploader: one('FileUploader', {
default: {},
inverse: 'composerView',
@@ -1534,9 +1517,7 @@ registerModel({
textareaLastInputValue: attr({
default: "",
}),
mainSuggestions: many('ComposerSuggestable', {
compute: '_computeMainSuggestions',
}),
mainSuggestions: many('ComposerSuggestable'),
/**
* States the message view on which this composer allows editing (if any).
*/
@@ -1632,5 +1613,9 @@ registerModel({
dependencies: ['suggestionDelimiterPosition', 'suggestionModelName', 'suggestionSearchTerm', 'composer.activeThread'],
methodName: '_onChangeUpdateSuggestionList',
},
{
dependencies: ['suggestionDelimiterPosition'],
methodName: '_onSuggestionDelimiterPositionChanged',
},
],
});
@@ -48,7 +48,6 @@ registerModel({
}),
message: one('Message', {
compute: '_computeMessage',
readonly: true,
required: true,
}),
/**
@@ -59,7 +58,6 @@ registerModel({
compute: '_computeMessageView',
inverse: 'deleteMessageConfirmViewOwner',
isCausal: true,
readonly: true,
required: true,
}),
},
-2
View File
@@ -234,7 +234,6 @@ registerModel({
manager: one('DialogManager', {
compute: '_computeManager',
inverse: 'dialogs',
readonly: true,
}),
messageActionViewOwnerAsDeleteConfirm: one('MessageActionView', {
identifying: true,
@@ -248,7 +247,6 @@ registerModel({
record: one('Record', {
compute: '_computeRecord',
isCausal: true,
readonly: true,
required: true,
}),
style: attr({
+29 -40
View File
@@ -192,10 +192,28 @@ registerModel({
* @returns {string|undefined}
*/
_computeActiveId() {
if (!this.activeThread) {
return clear();
}
return this.threadToActiveId(this.activeThread);
},
/**
* Only mailboxes and pinned channels are allowed in Discuss.
*
* @private
* @returns {FieldCommand|Thread}
*/
_computeActiveThread() {
if (!this.thread) {
return clear();
}
return this.threadToActiveId(this.thread);
if (this.thread.channel && this.thread.isPinned) {
return this.thread;
}
if (this.thread.mailbox) {
return this.thread;
}
return clear();
},
/**
* @private
@@ -211,29 +229,19 @@ registerModel({
_computeAddChatInputPlaceholder() {
return this.env._t("Search user...");
},
/**
* @private
* @returns {string}
*/
_computeAddingChannelValue() {
if (!this.discussView) {
return "";
}
return this.addingChannelValue;
},
/**
* @private
* @returns {boolean}
*/
_computeHasThreadView() {
if (!this.thread || !this.discussView) {
if (!this.activeThread || !this.discussView) {
return false;
}
if (
this.messaging.device.isSmall &&
(
this.activeMobileNavbarTabId !== 'mailbox' ||
!this.thread.mailbox
!this.activeThread.mailbox
)
) {
return false;
@@ -281,24 +289,6 @@ registerModel({
_computeNotificationListView() {
return (this.messaging.device.isSmall && this.activeMobileNavbarTabId !== 'mailbox') ? {} : clear();
},
/**
* Only mailboxes and pinned channels are allowed in Discuss.
*
* @private
* @returns {FieldCommand|undefined}
*/
_computeThread() {
if (!this.thread) {
return clear();
}
if (this.thread.channel && this.thread.isPinned) {
return undefined;
}
if (this.thread.mailbox) {
return undefined;
}
return clear();
},
/**
* @private
* @returns {ThreadViewer}
@@ -308,7 +298,7 @@ registerModel({
hasMemberList: true,
hasThreadView: this.hasThreadView,
hasTopbar: true,
thread: this.thread ? this.thread : clear(),
thread: this.activeThread ? this.activeThread : clear(),
};
},
},
@@ -322,6 +312,12 @@ registerModel({
activeMobileNavbarTabId: attr({
default: 'mailbox',
}),
/**
* Determines the `Thread` that should be displayed by `this`.
*/
activeThread: one('Thread', {
compute: '_computeActiveThread',
}),
addChannelInputPlaceholder: attr({
compute: '_computeAddChannelInputPlaceholder',
}),
@@ -332,7 +328,6 @@ registerModel({
* Value that is used to create a channel from the sidebar.
*/
addingChannelValue: attr({
compute: '_computeAddingChannelValue',
default: "",
}),
/**
@@ -424,12 +419,7 @@ registerModel({
sidebarQuickSearchValue: attr({
default: "",
}),
/**
* Determines the `Thread` that should be displayed by `this`.
*/
thread: one('Thread', {
compute: '_computeThread',
}),
thread: one('Thread'),
/**
* States the `ThreadView` displaying `this.thread`.
*/
@@ -443,7 +433,6 @@ registerModel({
compute: '_computeThreadViewer',
inverse: 'discuss',
isCausal: true,
readonly: true,
required: true,
}),
},
@@ -72,7 +72,6 @@ registerModel({
* States the thread view linked to this discuss public view.
*/
threadView: one('ThreadView', {
readonly: true,
related: 'threadViewer.threadView',
}),
/**
@@ -53,7 +53,7 @@ registerModel({
* @returns {FieldCommand}
*/
_computeActiveItem() {
const channel = this.messaging.discuss.thread && this.messaging.discuss.thread.channel;
const channel = this.messaging.discuss.activeThread && this.messaging.discuss.activeThread.channel;
if (channel && this.supportedChannelTypes.includes(channel.channel_type)) {
return {
thread: channel.thread,
@@ -389,7 +389,6 @@ registerModel({
*/
filteredCategoryItems: many('DiscussSidebarCategoryItem', {
compute: '_computeFilteredCategoryItems',
readonly: true,
}),
/**
* Display name of the category.
@@ -465,7 +464,6 @@ registerModel({
supportedChannelTypes: attr({
compute: '_computeSupportedChannelTypes',
required: true,
readonly: true,
}),
},
onChanges: [
@@ -96,7 +96,7 @@ registerModel({
* @returns {boolean}
*/
_computeIsActive() {
return this.messaging.discuss && this.thread === this.messaging.discuss.thread;
return this.messaging.discuss && this.thread === this.messaging.discuss.activeThread;
},
/**
* @private
@@ -234,10 +234,8 @@ registerModel({
*/
categoryCounterContribution: attr({
compute: '_computeCategoryCounterContribution',
readonly: true,
}),
channel: one('Channel', {
readonly: true,
related: 'thread.channel',
}),
/**
@@ -40,7 +40,6 @@ registerModel({
}),
mailbox: one('Mailbox', {
compute: '_computeMailbox',
readonly: true,
required: true,
}),
},
@@ -111,7 +111,7 @@ registerModel({
/**
* @private
*/
_onDiscussThreadChanged() {
_onDiscussActiveThreadChanged() {
this.env.services.router.pushState({
action: this.discuss.discussView.actionId,
active_id: this.discuss.activeId,
@@ -169,8 +169,8 @@ registerModel({
},
onChanges: [
{
dependencies: ['discuss.thread'],
methodName: '_onDiscussThreadChanged',
dependencies: ['discuss.activeThread'],
methodName: '_onDiscussActiveThreadChanged',
},
],
});
-1
View File
@@ -24,7 +24,6 @@ registerModel({
emojiRegistry: one('EmojiRegistry', {
compute: '_computeEmojiRegistry',
inverse: 'allEmojis',
readonly: true,
required: true,
}),
emojiViews: many('EmojiView', {
@@ -26,7 +26,6 @@ registerModel({
emojiViews: many('EmojiView', {
compute: '_computeEmojiViews',
inverse: 'emojiGridView',
readonly: true,
isCausal: true,
}),
},
@@ -208,7 +208,6 @@ registerModel({
}),
thread: one('Thread', {
compute: '_computeThread',
readonly: true,
required: true,
})
},
@@ -90,12 +90,10 @@ registerModel({
fields: {
isNonCompactActionContribution: attr({
compute: '_computeIsNonCompactActionContribution',
readonly: true,
}),
messageActionListOwner: one('MessageActionList', {
compute: '_computeMessageActionListOwner',
inverse: 'messageActions',
readonly: true,
required: true,
}),
messageActionListOwnerAsDelete: one('MessageActionList', {
@@ -130,7 +128,6 @@ registerModel({
compute: '_computeMessageActionView',
inverse: 'messageAction',
isCausal: true,
readonly: true,
}),
/**
* States the listing sequence of the action inside of the aciton list.
@@ -166,14 +166,12 @@ registerModel({
}),
firstActionView: one('MessageActionView', {
compute: '_computeFirstActionView',
readonly: true,
}),
isCompact: attr({
default: true,
}),
lastActionView: one('MessageActionView', {
compute: '_computeLastActionView',
readonly: true,
}),
/**
* States the message on which this action message list operates.
@@ -186,7 +184,6 @@ registerModel({
isCausal: true,
}),
messageActionViews: many('MessageActionView', {
readonly: true,
related: 'messageActions.messageActionView',
sort: '_sortMessageActionViews',
}),
@@ -161,7 +161,7 @@ registerModel({
const textInputContent = htmlDoc.body.textContent;
this.update({
composerForEditing: {
mentionedPartners: this.message.recipients,
rawMentionedPartners: this.message.recipients,
textInputContent,
textInputCursorEnd: textInputContent.length,
textInputCursorStart: textInputContent.length,
@@ -434,7 +434,6 @@ registerModel({
compute: '_computeAttachmentList',
inverse: 'messageViewOwner',
isCausal: true,
readonly: true,
}),
authorTitleText: attr({
compute: '_computeAuthorTitleText',
@@ -578,7 +577,6 @@ registerModel({
compute: '_computeMessageActionList',
inverse: 'messageView',
isCausal: true,
readonly: true,
}),
/**
* Determines the message that is displayed by this message view.
@@ -586,7 +584,6 @@ registerModel({
message: one('Message', {
compute: '_computeMessage',
inverse: 'messageViews',
readonly: true,
required: true,
}),
/**
@@ -597,7 +594,6 @@ registerModel({
compute: '_computeMessageInReplyToView',
inverse: 'messageView',
isCausal: true,
readonly: true,
}),
messageListViewMessageViewItemOwner: one('MessageListViewMessageViewItem', {
identifying: true,
@@ -615,7 +611,6 @@ registerModel({
compute: '_computePersonaImStatusIconView',
inverse: 'messageViewOwner',
isCausal: true,
readonly: true,
}),
/**
* States whether this message view is the last one of its thread view.
+6 -15
View File
@@ -14,6 +14,7 @@ registerModel({
lifecycleHooks: {
_created() {
odoo.__DEBUG__.messaging = this;
this.refreshIsNotificationPermissionDefault();
},
_willDelete() {
this.env.bus.removeEventListener('window_focus', this._handleGlobalWindowFocus);
@@ -200,7 +201,10 @@ registerModel({
* provide an API to detect when this value changes.
*/
refreshIsNotificationPermissionDefault() {
this.update({ isNotificationPermissionDefault: this._computeIsNotificationPermissionDefault() });
const browserNotification = this.messaging.browser.Notification;
this.update({
isNotificationPermissionDefault: Boolean(browserNotification) && browserNotification.permission === 'default',
});
},
async startFetchImStatus() {
this.update({
@@ -276,14 +280,6 @@ registerModel({
}
return new EventBus();
},
/**
* @private
* @returns {boolean}
*/
_computeIsNotificationPermissionDefault() {
const browserNotification = this.messaging.browser.Notification;
return browserNotification ? browserNotification.permission === 'default' : false;
},
/**
* @private
* @returns {FieldCommand}
@@ -416,7 +412,6 @@ registerModel({
initializedPromise: attr({
compute: '_computeInitializedPromise',
required: true,
readonly: true,
}),
initializer: one('MessagingInitializer', {
default: {},
@@ -440,9 +435,7 @@ registerModel({
* 'default' state. This means it is allowed to make a request to the
* user to enable notifications.
*/
isNotificationPermissionDefault: attr({
compute: '_computeIsNotificationPermissionDefault',
}),
isNotificationPermissionDefault: attr(),
locale: one('Locale', {
default: {},
isCausal: true,
@@ -460,7 +453,6 @@ registerModel({
*/
messagingBus: attr({
compute: '_computeMessagingBus',
readonly: true,
required: true,
}),
messagingMenu: one('MessagingMenu', {
@@ -470,7 +462,6 @@ registerModel({
notificationHandler: one('MessagingNotificationHandler', {
compute: '_computeNotificationHandler',
isCausal: true,
readonly: true,
}),
outOfFocusUnreadMessageCounter: attr({
default: 0,
@@ -326,8 +326,8 @@ registerModel({
}
if (this.messaging.currentPartner && this.messaging.currentPartner.id === partner_id) {
channel.thread.update({
lastSeenByCurrentPartnerMessageId: last_message_id,
pendingSeenMessageId: undefined,
rawLastSeenByCurrentPartnerMessageId: last_message_id,
});
}
},
@@ -19,7 +19,7 @@ registerModel({
this.discuss.update({ activeMobileNavbarTabId: tabId });
if (
this.discuss.activeMobileNavbarTabId === 'mailbox' &&
(!this.discuss.thread || !this.discuss.thread.mailbox)
(!this.discuss.activeThread || !this.discuss.activeThread.mailbox)
) {
this.discuss.update({ thread: this.messaging.inbox.thread });
}
@@ -42,7 +42,6 @@ registerModel({
compute: '_computePersonaImStatusIconView',
inverse: 'notificationRequestViewOwner',
isCausal: true,
readonly: true,
}),
},
});
+11 -5
View File
@@ -340,10 +340,16 @@ registerModel({
},
/**
* @private
* @returns {string|undefined}
* @returns {string|FieldCommand}
*/
_computeDisplayName() {
return this.display_name || this.user && this.user.display_name;
if (this.display_name) {
return this.display_name;
}
if (this.user && this.user.displayName) {
return this.user.displayName;
}
return clear();
},
/**
* @private
@@ -364,7 +370,7 @@ registerModel({
* @returns {string|undefined}
*/
_computeNameOrDisplayName() {
return this.name || this.display_name;
return this.name || this.displayName;
},
},
fields: {
@@ -392,7 +398,8 @@ registerModel({
* And if a specific name format is required, it should be computed from
* relevant fields instead.
*/
display_name: attr({
display_name: attr(),
displayName: attr({
compute: '_computeDisplayName',
default: "",
}),
@@ -413,7 +420,6 @@ registerModel({
im_status: attr(),
isImStatusSet: attr({
compute: '_computeIsImStatusSet',
readonly: true,
}),
/**
* States whether this partner is online.
-2
View File
@@ -46,11 +46,9 @@ registerModel({
}),
im_status: attr({
compute: '_computeImStatus',
readonly: true,
}),
name: attr({
compute: '_computeName',
readonly: true,
}),
partner: one('Partner', {
identifying: true,
@@ -73,7 +73,6 @@ registerModel({
}),
persona: one('Persona', {
compute: '_computePersona',
readonly: true,
required: true,
}),
},
@@ -176,7 +176,6 @@ registerModel({
compute: '_computeActivityMarkDonePopoverContentView',
inverse: 'popoverViewOwner',
isCausal: true,
readonly: true,
}),
activityViewOwnerAsMarkDone: one('ActivityView', {
identifying: true,
@@ -196,7 +195,6 @@ registerModel({
compute: '_computeChannelInvitationForm',
inverse: 'popoverViewOwner',
isCausal: true,
readonly: true,
}),
/**
* States the OWL component of this popover view.
@@ -239,12 +237,10 @@ registerModel({
compute: '_computeEmojiPickerView',
inverse: 'popoverViewOwner',
isCausal: true,
readonly: true,
}),
manager: one('PopoverManager', {
compute: '_computeManager',
inverse: 'popoverViews',
readonly: true,
}),
/**
* If set, this popover view is owned by a message action view.
+9 -19
View File
@@ -99,9 +99,6 @@ registerModel({
});
data2.serverLastMessage = insert(messageData);
}
if ('memberCount' in data) {
data2.memberCount = data.memberCount;
}
if ('message_needaction_counter' in data) {
data2.message_needaction_counter = data.message_needaction_counter;
}
@@ -115,7 +112,7 @@ registerModel({
data2.public = data.public;
}
if ('seen_message_id' in data) {
data2.lastSeenByCurrentPartnerMessageId = data.seen_message_id || 0;
data2.rawLastSeenByCurrentPartnerMessageId = data.seen_message_id;
}
if ('uuid' in data) {
data2.uuid = data.uuid;
@@ -1355,15 +1352,15 @@ registerModel({
const firstMessage = this.orderedMessages[0];
if (
firstMessage &&
this.lastSeenByCurrentPartnerMessageId &&
this.lastSeenByCurrentPartnerMessageId < firstMessage.id
this.rawLastSeenByCurrentPartnerMessageId &&
this.rawLastSeenByCurrentPartnerMessageId < firstMessage.id
) {
// no deduction can be made if there is a gap
return this.lastSeenByCurrentPartnerMessageId;
return this.rawLastSeenByCurrentPartnerMessageId;
}
let lastSeenByCurrentPartnerMessageId = this.lastSeenByCurrentPartnerMessageId;
let lastSeenByCurrentPartnerMessageId = this.rawLastSeenByCurrentPartnerMessageId;
for (const message of this.orderedMessages) {
if (message.id <= this.lastSeenByCurrentPartnerMessageId) {
if (message.id <= this.rawLastSeenByCurrentPartnerMessageId) {
continue;
}
if (
@@ -1800,7 +1797,6 @@ registerModel({
compute: '_computeComposer',
inverse: 'thread',
isCausal: true,
readonly: true,
}),
correspondent: one('Partner', {
compute: '_computeCorrespondent',
@@ -1868,7 +1864,6 @@ registerModel({
compute: '_computeDiscussSidebarCategoryItem',
inverse: 'thread',
isCausal: true,
readonly: true,
}),
displayName: attr({
compute: '_computeDisplayName',
@@ -2096,12 +2091,6 @@ registerModel({
inverse: 'thread',
}),
mainAttachment: one('Attachment'),
/**
* States the number of members in this thread according to the server.
* Guests are excluded from the count.
* Only makes sense if this thread is a channel.
*/
memberCount: attr(),
members: many('Partner', {
sort: '_sortPartnerMembers',
}),
@@ -2155,12 +2144,10 @@ registerModel({
messagingAsRingingThread: one('Messaging', {
compute: '_computeMessagingAsRingingThread',
inverse: 'ringingThreads',
readonly: true,
}),
messagingMenuAsPinnedAndUnreadChannel: one('MessagingMenu', {
compute: '_computeMessagingMenuAsPinnedAndUnreadChannel',
inverse: 'pinnedAndUnreadChannels',
readonly: true,
}),
model: attr({
identifying: true,
@@ -2238,6 +2225,9 @@ registerModel({
*/
pendingSeenMessageId: attr(),
public: attr(),
rawLastSeenByCurrentPartnerMessageId: attr({
default: 0,
}),
/**
* If set, the current thread is the thread that hosts the current RTC call.
*/
+7 -11
View File
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear, link, unlink } from '@mail/model/model_field_command';
import { clear, link } from '@mail/model/model_field_command';
registerModel({
name: 'ThreadCache',
@@ -62,19 +62,13 @@ registerModel({
},
/**
* @private
* @returns {Message[]}
* @returns {FieldCommand|Message[]}
*/
_computeFetchedMessages() {
if (!this.thread) {
return clear();
}
const toUnlinkMessages = [];
for (const message of this.fetchedMessages) {
if (!this.thread.messages.includes(message)) {
toUnlinkMessages.push(message);
}
}
return unlink(toUnlinkMessages);
return this.rawFetchedMessages.filter(m => this.thread.messages.includes(m));
},
/**
* @private
@@ -120,7 +114,7 @@ registerModel({
message.id > this.lastFetchedMessage.id
);
}
return this.fetchedMessages.concat(newerMessages);
return [...this.fetchedMessages, ...this.temporaryMessages, ...newerMessages];
},
/**
* @private
@@ -219,7 +213,7 @@ registerModel({
return;
}
this.update({
fetchedMessages: link(messages),
rawFetchedMessages: link(messages),
hasLoadingFailed: false,
isLoaded: true,
isLoading: false,
@@ -356,6 +350,8 @@ registerModel({
orderedNonEmptyMessages: many('Message', {
compute: '_computeOrderedNonEmptyMessages',
}),
rawFetchedMessages: many('Message'),
temporaryMessages: many('Message'),
thread: one('Thread', {
identifying: true,
inverse: 'cache',
@@ -87,15 +87,12 @@ registerModel({
inlineLastNeedactionMessageAsOriginThreadBody: attr({
compute: '_computeInlineLastNeedactionMessageAsOriginThreadBody',
default: "",
readonly: true,
}),
isEmpty: attr({
compute: '_computeIsEmpty',
readonly: true,
}),
lastTrackingValue: one('TrackingValue', {
compute: '_computeLastTrackingValue',
readonly: true,
}),
/**
* Reference of the "mark as read" button. Useful to disable the
@@ -115,7 +112,6 @@ registerModel({
compute: '_computePersonaImStatusIconView',
inverse: 'threadNeedactionPreviewViewOwner',
isCausal: true,
readonly: true,
}),
thread: one('Thread', {
identifying: true,
@@ -85,15 +85,12 @@ registerModel({
inlineLastMessageBody: attr({
compute: '_computeInlineLastMessageBody',
default: "",
readonly: true,
}),
isEmpty: attr({
compute: '_computeIsEmpty',
readonly: true,
}),
lastTrackingValue: one('TrackingValue', {
compute: '_computeLastTrackingValue',
readonly: true,
}),
/**
* Reference of the "mark as read" button. Useful to disable the
@@ -113,7 +110,6 @@ registerModel({
compute: '_computePersonaImStatusIconView',
inverse: 'threadPreviewViewOwner',
isCausal: true,
readonly: true,
}),
thread: one('Thread', {
identifying: true,
+1 -6
View File
@@ -119,7 +119,7 @@ registerModel({
*/
_computeHasComposerThreadName() {
if (this.threadViewer.discuss) {
return this.threadViewer.discuss.thread === this.messaging.inbox.thread;
return this.threadViewer.discuss.activeThread === this.messaging.inbox.thread;
}
return clear();
},
@@ -448,7 +448,6 @@ registerModel({
lastMessageView: one('MessageView', {
compute: '_computeLastMessageView',
inverse: 'threadViewOwnerAsLastMessageView',
readonly: true,
}),
/**
* Most recent message in this ThreadView that has been shown to the
@@ -482,14 +481,12 @@ registerModel({
compute: '_computeCallView',
inverse: 'threadView',
isCausal: true,
readonly: true,
}),
/**
* Determines the `Thread` currently displayed by `this`.
*/
thread: one('Thread', {
inverse: 'threadViews',
readonly: true,
related: 'threadViewer.thread',
}),
/**
@@ -497,7 +494,6 @@ registerModel({
*/
threadCache: one('ThreadCache', {
inverse: 'threadViews',
readonly: true,
related: 'threadViewer.threadCache',
}),
threadCacheInitialScrollHeight: attr({
@@ -534,7 +530,6 @@ registerModel({
compute: '_computeTopbar',
inverse: 'threadView',
isCausal: true,
readonly: true,
}),
},
onChanges: [
@@ -484,7 +484,6 @@ registerModel({
avatarUrl: attr({
compute: '_computeAvatarUrl',
default: '',
readonly: true,
}),
/**
* Determines whether the guest name input needs to be focused.
@@ -583,7 +582,6 @@ registerModel({
*/
hasGuestNameChanged: attr({
compute: '_computeHasGuestNameChanged',
readonly: true,
}),
/**
* Determines whether description area should display on top bar.
@@ -63,7 +63,6 @@ registerModel({
*/
duration: attr({
compute: '_computeDuration',
readonly: true,
required: true,
}),
/**
-1
View File
@@ -121,7 +121,6 @@ registerModel({
*/
duration: attr({
compute: '_computeDuration',
readonly: true,
required: true,
}),
messagingOwnerAsFetchImStatusTimer: one('Messaging', {
+11 -3
View File
@@ -158,10 +158,16 @@ registerModel({
},
/**
* @private
* @returns {string|undefined}
* @returns {string|FieldCommand}
*/
_computeDisplayName() {
return this.display_name || this.partner && this.partner.display_name;
if (this.display_name) {
return this.display_name;
}
if (this.partner && this.partner.displayName) {
return this.partner.displayName;
}
return clear();
},
/**
* @private
@@ -184,8 +190,10 @@ registerModel({
* `share` field in python.
*/
isInternalUser: attr(),
display_name: attr({
display_name: attr(),
displayName: attr({
compute: '_computeDisplayName',
default: "",
}),
model: attr({
default: 'res.user',
@@ -151,7 +151,6 @@ registerModel({
*/
guestNameInputUniqueId: attr({
compute: '_computeGuestNameInputUniqueId',
readonly: true,
}),
/**
* Determines whether the guest's name has been updated.
@@ -161,7 +160,6 @@ registerModel({
*/
hasGuestNameChanged: attr({
compute: '_computeHasGuestNameChanged',
readonly: true,
}),
/**
* Determines whether the 'guestNameInput' should be focused the next
@@ -184,7 +182,6 @@ registerModel({
compute: '_computeCallDemoView',
inverse: 'welcomeView',
isCausal: true,
readonly: true,
}),
/**
* States the name the guest had when landing on the welcome view.
@@ -1003,6 +1003,7 @@ patch(MockServer.prototype, 'mail', {
const channelData = {
channel_type: channel.channel_type,
id: channel.id,
memberCount: channel.member_count,
};
const res = Object.assign({}, channel, {
last_message_id: lastMessageId,
@@ -90,7 +90,7 @@ QUnit.test('create', async function (assert) {
assert.strictEqual(channel.name, "General");
const partner = messaging.models['Partner'].findFromIdentifyingData({ id: 5 });
assert.ok(partner);
assert.strictEqual(partner.display_name, "Demo");
assert.strictEqual(partner.displayName, "Demo");
assert.strictEqual(partner.id, 5);
});
@@ -26,6 +26,5 @@ addRecordMethods('ThreadNeedactionPreviewView', {
addFields('ThreadNeedactionPreviewView', {
isRating: attr({
compute: '_computeIsRating',
readonly: true,
}),
});
@@ -26,6 +26,5 @@ addRecordMethods('ThreadPreviewView', {
addFields('ThreadPreviewView', {
isRating: attr({
compute: '_computeIsRating',
readonly: true,
}),
});
@@ -122,6 +122,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'channel',
'id': self.channel_general.id,
'memberCount': len(self.group_user.users | self.user_root),
},
'create_uid': self.user_root.id,
'custom_channel_name': False,
@@ -134,7 +135,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_general.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_general._channel_last_message_ids()),
'memberCount': len(self.group_user.users | self.user_root),
'message_needaction_counter': 0,
'message_unread_counter': 5,
'name': 'general',
@@ -150,6 +150,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'channel',
'id': self.channel_channel_public_1.id,
'memberCount': 5,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -163,7 +164,6 @@ class TestDiscussFullPerformance(TransactionCase):
'last_interest_dt': self.channel_channel_public_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_channel_public_1._channel_last_message_ids()),
'message_needaction_counter': 1,
'memberCount': 5,
'message_unread_counter': 0,
'name': 'public 1',
'public': 'public',
@@ -178,6 +178,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'channel',
'id': self.channel_channel_public_2.id,
'memberCount': 5,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -190,7 +191,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_channel_public_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_channel_public_2._channel_last_message_ids()),
'memberCount': 5,
'message_needaction_counter': 0,
'message_unread_counter': 0,
'name': 'public 2',
@@ -206,6 +206,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'channel',
'id': self.channel_channel_group_1.id,
'memberCount': 5,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -218,7 +219,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_channel_group_1._channel_last_message_ids()),
'memberCount': 5,
'message_needaction_counter': 0,
'message_unread_counter': 0,
'name': 'group 1',
@@ -234,6 +234,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'channel',
'id': self.channel_channel_group_2.id,
'memberCount': 5,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -246,7 +247,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_channel_group_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_channel_group_2._channel_last_message_ids()),
'memberCount': 5,
'message_needaction_counter': 0,
'message_unread_counter': 0,
'name': 'group 2',
@@ -262,6 +262,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'channel',
'id': self.channel_channel_private_1.id,
'memberCount': 5,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -274,7 +275,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_channel_private_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_channel_private_1._channel_last_message_ids()),
'memberCount': 5,
'message_needaction_counter': 0,
'message_unread_counter': 0,
'name': 'private 1',
@@ -290,6 +290,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'channel',
'id': self.channel_channel_private_2.id,
'memberCount': 5,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -302,7 +303,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_channel_private_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_channel_private_2._channel_last_message_ids()),
'memberCount': 5,
'message_needaction_counter': 0,
'message_unread_counter': 0,
'name': 'private 2',
@@ -318,6 +318,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'group',
'id': self.channel_group_1.id,
'memberCount': 2,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -331,7 +332,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': False,
'memberCount': 2,
'members': [
{
'active': True,
@@ -385,6 +385,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'chat',
'id': self.channel_chat_1.id,
'memberCount': 2,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -398,7 +399,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_chat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': False,
'memberCount': 2,
'members': [
{
'active': True,
@@ -452,6 +452,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'chat',
'id': self.channel_chat_2.id,
'memberCount': 2,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -465,7 +466,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_chat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': False,
'memberCount': 2,
'members': [
{
'active': True,
@@ -519,6 +519,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'chat',
'id': self.channel_chat_3.id,
'memberCount': 2,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -532,7 +533,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_chat_3.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': False,
'memberCount': 2,
'members': [
{
'active': True,
@@ -586,6 +586,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'chat',
'id': self.channel_chat_4.id,
'memberCount': 2,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -599,7 +600,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_chat_4.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': False,
'memberCount': 2,
'members': [
{
'active': True,
@@ -653,6 +653,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'livechat',
'id': self.channel_livechat_1.id,
'memberCount': 2,
},
'create_uid': self.env.user.id,
'custom_channel_name': False,
@@ -666,7 +667,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_livechat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_livechat_1._channel_last_message_ids()),
'memberCount': 2,
'livechat_visitor': {
'country': False,
'id': self.users[1].partner_id.id,
@@ -720,6 +720,7 @@ class TestDiscussFullPerformance(TransactionCase):
'channel': {
'channel_type': 'livechat',
'id': self.channel_livechat_2.id,
'memberCount': 2,
},
'create_uid': self.env.ref('base.public_user').id,
'custom_channel_name': False,
@@ -733,7 +734,6 @@ class TestDiscussFullPerformance(TransactionCase):
'is_pinned': True,
'last_interest_dt': self.channel_livechat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'last_message_id': next(res['message_id'] for res in self.channel_livechat_2._channel_last_message_ids()),
'memberCount': 2,
'livechat_visitor': {
'country': (self.env.ref('base.be').id, 'Belgium'),
'id': False,