From 0c3e66340dc6e354b2b84534d45ef0b991df8a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Thu, 24 Aug 2023 15:43:25 +0000 Subject: [PATCH] [FIX] mail: properly compare discuss models with `toRaw` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, discuss models were compared with object reference, e.g. `thread_1 === thread_2`. This is unsafe with `owl.reactive()`, because it wraps the objects in a Proxy, and different proxies may map to a same raw object. This commit fixes the issue by defining `.eq()` and `.in()` methods on discuss models, which makes comparison on raw value rather than proxy/reactive one. This PR also introduce "not" variants, e.g. `.notEq()`, which eases readability by having "not" and "eq" next to each other. These "not" variants must be used without optional chaining, otherwise this gives the opposite result. For example: ```js const [t1, t2] = [Thread.insert()]; t1 === t1 // true (OK) t1.eq(t1) // true (OK) t1.notEq(t1) // false (OK) t2?.notEq(t1) // undefined => falsy (notOk) !t2?.eq(t1) // true (OK) ``` closes odoo/odoo#133065 Signed-off-by: Sébastien Theys (seb) --- .../static/src/core/thread_model_patch.js | 4 +-- .../src/embed/chatbot/chatbot_service.js | 2 +- .../src/embed/core/autopopup_service.js | 4 +-- .../src/core/common/attachment_model.js | 3 ++- .../src/core/common/attachment_service.js | 19 ++++++-------- .../static/src/core/common/attachment_view.js | 8 +++--- .../src/core/common/canned_response_model.js | 4 ++- .../src/core/common/channel_member_model.js | 3 ++- .../src/core/common/channel_member_service.js | 2 +- .../static/src/core/common/chat_window.js | 4 +-- .../src/core/common/chat_window_model.js | 5 +++- .../src/core/common/chat_window_service.js | 2 +- .../mail/static/src/core/common/composer.js | 2 +- .../mail/static/src/core/common/composer.xml | 4 +-- .../static/src/core/common/composer_model.js | 5 +++- .../mail/static/src/core/common/discuss.xml | 2 +- .../static/src/core/common/follower_model.js | 6 +++-- .../src/core/common/link_preview_model.js | 5 +++- .../core/common/mail_core_common_service.js | 5 ++-- addons/mail/static/src/core/common/message.js | 6 ++--- .../mail/static/src/core/common/message.xml | 4 +-- .../static/src/core/common/message_model.js | 7 +++--- .../src/core/common/message_reaction_menu.xml | 6 ++--- .../src/core/common/message_reactions.js | 2 +- .../core/common/message_reactions_model.js | 4 ++- .../static/src/core/common/message_service.js | 13 +++++----- .../core/common/notification_group_model.js | 7 ++++-- .../src/core/common/notification_model.js | 5 +++- .../static/src/core/common/partner_compare.js | 4 +-- .../static/src/core/common/persona_model.js | 4 ++- addons/mail/static/src/core/common/record.js | 25 +++++++++++++++++++ .../src/core/common/scroll_position_model.js | 5 +++- .../src/core/common/suggestion_service.js | 2 +- addons/mail/static/src/core/common/thread.js | 4 +-- .../static/src/core/common/thread_model.js | 14 ++++++----- .../static/src/core/common/thread_service.js | 14 +++++------ .../static/src/core/web/activity_model.js | 5 +++- addons/mail/static/src/core/web/chatter.js | 2 +- .../src/core/web/mail_core_web_service.js | 6 ++--- .../static/src/core/web/messaging_menu.js | 12 ++++++--- .../static/src/core/web/messaging_menu.xml | 2 +- .../src/core/web/thread_service_patch.js | 8 +++--- .../static/src/discuss/call/common/call.js | 2 +- .../discuss/call/common/call_action_list.js | 2 +- .../discuss/call/common/call_action_list.xml | 2 +- .../call/common/call_participant_card.js | 6 ++--- .../src/discuss/call/common/rtc_service.js | 14 +++++------ .../discuss/call/common/rtc_session_model.js | 3 ++- .../src/discuss/call/common/thread_actions.js | 4 +-- .../web/discuss_sidebar_call_indicator.xml | 2 +- .../discuss/core/common/channel_invitation.js | 2 +- .../core/common/channel_invitation.xml | 2 +- .../core/common/channel_member_list.js | 2 +- .../common/discuss_core_common_service.js | 9 +++---- .../discuss/core/common/partner_compare.js | 4 +-- .../core/web/discuss_core_web_service.js | 2 +- .../common/pinned_messages_panel.js | 2 +- .../discuss/typing/common/typing_service.js | 2 +- addons/mail/static/src/utils/common/hooks.js | 8 +++--- 59 files changed, 186 insertions(+), 132 deletions(-) create mode 100644 addons/mail/static/src/core/common/record.js diff --git a/addons/im_livechat/static/src/core/thread_model_patch.js b/addons/im_livechat/static/src/core/thread_model_patch.js index 685040ef0d9..cd79dff07ea 100644 --- a/addons/im_livechat/static/src/core/thread_model_patch.js +++ b/addons/im_livechat/static/src/core/thread_model_patch.js @@ -40,9 +40,7 @@ patch(Thread.prototype, { // For livechat threads, the correspondent is the first // channel member that is not the operator. const orderedChannelMembers = [...this.channelMembers].sort((a, b) => a.id - b.id); - const isFirstMemberOperator = - orderedChannelMembers[0]?.persona.type === "partner" && - orderedChannelMembers[0]?.persona.id === this.operator.id; + const isFirstMemberOperator = orderedChannelMembers[0]?.persona.eq(this.operator); correspondent = isFirstMemberOperator ? orderedChannelMembers[1]?.persona : orderedChannelMembers[0]?.persona; diff --git a/addons/im_livechat/static/src/embed/chatbot/chatbot_service.js b/addons/im_livechat/static/src/embed/chatbot/chatbot_service.js index 00cc10f6968..4c220049d5a 100644 --- a/addons/im_livechat/static/src/embed/chatbot/chatbot_service.js +++ b/addons/im_livechat/static/src/embed/chatbot/chatbot_service.js @@ -261,7 +261,7 @@ export class ChatBotService { channel_uuid: this.livechatService.thread.uuid, }); this.currentStep.isEmailValid = success; - if (msg && !this.livechatService.thread.hasMessage(msg)) { + if (msg && !this.livechatService.thread.messages.some((m) => m.id === msg.id)) { this.livechatService.thread.messages.push( this.messageService.insert({ ...msg, body: markup(msg.body) }) ); diff --git a/addons/im_livechat/static/src/embed/core/autopopup_service.js b/addons/im_livechat/static/src/embed/core/autopopup_service.js index cc1a1f3fe0c..b3a7c2d4d19 100644 --- a/addons/im_livechat/static/src/embed/core/autopopup_service.js +++ b/addons/im_livechat/static/src/embed/core/autopopup_service.js @@ -58,9 +58,7 @@ export class AutopopupService { */ async shouldOpenChatWindow() { const thread = await this.threadService.getLivechatThread(); - return this.storeService.chatWindows.every( - (chatWindow) => chatWindow.thread.localId !== thread?.localId - ); + return this.storeService.chatWindows.every((cw) => !cw.thread?.eq(thread)); } get allowAutoPopup() { diff --git a/addons/mail/static/src/core/common/attachment_model.js b/addons/mail/static/src/core/common/attachment_model.js index 66554a80bab..42979862553 100644 --- a/addons/mail/static/src/core/common/attachment_model.js +++ b/addons/mail/static/src/core/common/attachment_model.js @@ -1,10 +1,11 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; import { assignDefined } from "@mail/utils/common/misc"; import { url } from "@web/core/utils/urls"; -export class Attachment { +export class Attachment extends Record { /** @type {import("@mail/core/common/store_service").Store} */ _store; accessToken; diff --git a/addons/mail/static/src/core/common/attachment_service.js b/addons/mail/static/src/core/common/attachment_service.js index e1168d57889..031d06b4544 100644 --- a/addons/mail/static/src/core/common/attachment_service.js +++ b/addons/mail/static/src/core/common/attachment_service.js @@ -63,7 +63,7 @@ export class AttachmentService { }); attachment.originThreadLocalId = createLocalId(threadData.model, threadData.id); const thread = attachment.originThread; - if (!thread.attachments.includes(attachment)) { + if (attachment.notIn(thread.attachments)) { thread.attachments.push(attachment); thread.attachments.sort((a1, a2) => (a1.id < a2.id ? 1 : -1)); } @@ -81,25 +81,20 @@ export class AttachmentService { } delete this.store.attachments[attachment.id]; if (attachment.originThread) { - removeFromArrayWithPredicate( - attachment.originThread.attachments, - ({ id }) => id === attachment.id + removeFromArrayWithPredicate(attachment.originThread.attachments, (att) => + att.eq(attachment) ); } for (const message of Object.values(this.store.messages)) { - removeFromArrayWithPredicate(message.attachments, ({ id }) => id === attachment.id); + removeFromArrayWithPredicate(message.attachments, (att) => att.eq(attachment)); if (message.composer) { - removeFromArrayWithPredicate( - message.composer.attachments, - ({ id }) => id === attachment.id + removeFromArrayWithPredicate(message.composer.attachments, (att) => + att.eq(attachment) ); } } for (const thread of Object.values(this.store.threads)) { - removeFromArrayWithPredicate( - thread.composer.attachments, - ({ id }) => id === attachment.id - ); + removeFromArrayWithPredicate(thread.composer.attachments, (att) => att.eq(attachment)); } } diff --git a/addons/mail/static/src/core/common/attachment_view.js b/addons/mail/static/src/core/common/attachment_view.js index 2e77c16eb0b..d04e87d941b 100644 --- a/addons/mail/static/src/core/common/attachment_view.js +++ b/addons/mail/static/src/core/common/attachment_view.js @@ -33,8 +33,8 @@ export class AttachmentView extends Component { } onClickNext() { - const index = this.state.thread.attachmentsInWebClientView.findIndex( - (attachment) => attachment.id === this.state.thread.mainAttachment.id + const index = this.state.thread.attachmentsInWebClientView.findIndex((attachment) => + attachment.eq(this.state.thread.mainAttachment) ); this.threadService.setMainAttachmentFromIndex( this.state.thread, @@ -43,8 +43,8 @@ export class AttachmentView extends Component { } onClickPrevious() { - const index = this.state.thread.attachmentsInWebClientView.findIndex( - (attachment) => attachment.id === this.state.thread.mainAttachment.id + const index = this.state.thread.attachmentsInWebClientView.findIndex((attachment) => + attachment.eq(this.state.thread.mainAttachment) ); this.threadService.setMainAttachmentFromIndex( this.state.thread, diff --git a/addons/mail/static/src/core/common/canned_response_model.js b/addons/mail/static/src/core/common/canned_response_model.js index 18dae3476b4..620b33bf620 100644 --- a/addons/mail/static/src/core/common/canned_response_model.js +++ b/addons/mail/static/src/core/common/canned_response_model.js @@ -1,6 +1,8 @@ /* @odoo-module */ -export class CannedResponse { +import { Record } from "@mail/core/common/record"; + +export class CannedResponse extends Record { /** @type {number} */ id; /** @type {string} */ diff --git a/addons/mail/static/src/core/common/channel_member_model.js b/addons/mail/static/src/core/common/channel_member_model.js index 2fcdcb6992f..7876d58a148 100644 --- a/addons/mail/static/src/core/common/channel_member_model.js +++ b/addons/mail/static/src/core/common/channel_member_model.js @@ -1,5 +1,6 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; import { createLocalId } from "@mail/utils/common/misc"; /** @@ -9,7 +10,7 @@ import { createLocalId } from "@mail/utils/common/misc"; * @property {string} personaLocalId * @property {number} threadId */ -export class ChannelMember { +export class ChannelMember extends Record { /** @type {number} */ id; personaLocalId; diff --git a/addons/mail/static/src/core/common/channel_member_service.js b/addons/mail/static/src/core/common/channel_member_service.js index 2dddf0e91f6..b4b50a507ad 100644 --- a/addons/mail/static/src/core/common/channel_member_service.js +++ b/addons/mail/static/src/core/common/channel_member_service.js @@ -54,7 +54,7 @@ export class ChannelMemberService { switch (command) { case "insert": { - if (member.thread && !member.thread.channelMembers.includes(member)) { + if (member.thread && member.notIn(member.thread.channelMembers)) { member.thread.channelMembers.push(member); } } diff --git a/addons/mail/static/src/core/common/chat_window.js b/addons/mail/static/src/core/common/chat_window.js index 1047d5e1924..a56d1cd865c 100644 --- a/addons/mail/static/src/core/common/chat_window.js +++ b/addons/mail/static/src/core/common/chat_window.js @@ -103,8 +103,8 @@ export class ChatWindow extends Component { this.close({ escape: true }); break; case "Tab": { - const index = this.chatWindowService.visible.findIndex( - (cw) => cw === this.props.chatWindow + const index = this.chatWindowService.visible.findIndex((cw) => + cw.eq(this.props.chatWindow) ); if (index === 0) { this.chatWindowService.visible[this.chatWindowService.visible.length - 1] diff --git a/addons/mail/static/src/core/common/chat_window_model.js b/addons/mail/static/src/core/common/chat_window_model.js index 7ca89453f4d..c69fafbed03 100644 --- a/addons/mail/static/src/core/common/chat_window_model.js +++ b/addons/mail/static/src/core/common/chat_window_model.js @@ -1,10 +1,12 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; + import { _t } from "@web/core/l10n/translation"; /** @typedef {{ thread?: import("@mail/core/common/thread_model").Thread, folded?: boolean, replaceNewMessageChatWindow?: boolean }} ChatWindowData */ -export class ChatWindow { +export class ChatWindow extends Record { /** @type {import("@mail/core/common/store_service").Store} */ _store; @@ -20,6 +22,7 @@ export class ChatWindow { * @returns {ChatWindow} */ constructor(store, data) { + super(); Object.assign(this, { thread: data.thread, _store: store, diff --git a/addons/mail/static/src/core/common/chat_window_service.js b/addons/mail/static/src/core/common/chat_window_service.js index 9dfa6acfd30..6fca4a347c9 100644 --- a/addons/mail/static/src/core/common/chat_window_service.js +++ b/addons/mail/static/src/core/common/chat_window_service.js @@ -158,7 +158,7 @@ export class ChatWindowService { swaped.hidden = false; swaped.folded = false; } - const index = this.store.chatWindows.findIndex((c) => c === chatWindow); + const index = this.store.chatWindows.findIndex((c) => c.eq(chatWindow)); if (index > -1) { this.store.chatWindows.splice(index, 1); } diff --git a/addons/mail/static/src/core/common/composer.js b/addons/mail/static/src/core/common/composer.js index 1814efbedcc..1d8052580ad 100644 --- a/addons/mail/static/src/core/common/composer.js +++ b/addons/mail/static/src/core/common/composer.js @@ -175,7 +175,7 @@ export class Composer extends Component { ); useEffect( (rThread, cThread) => { - if (cThread && rThread === cThread) { + if (cThread && cThread.eq(rThread)) { this.state.autofocus++; } }, diff --git a/addons/mail/static/src/core/common/composer.xml b/addons/mail/static/src/core/common/composer.xml index 613dc8d87db..6cd10748eca 100644 --- a/addons/mail/static/src/core/common/composer.xml +++ b/addons/mail/static/src/core/common/composer.xml @@ -17,11 +17,11 @@
Avatar of user
-
+
Replying to - + on: diff --git a/addons/mail/static/src/core/common/composer_model.js b/addons/mail/static/src/core/common/composer_model.js index 88a991500bd..915a7a7f36a 100644 --- a/addons/mail/static/src/core/common/composer_model.js +++ b/addons/mail/static/src/core/common/composer_model.js @@ -1,10 +1,12 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; + /** * @typedef {{partnerIds: Set, threadIds: Set}} RawMentions */ -export class Composer { +export class Composer extends Record { /** @type {import("@mail/core/common/attachment_model").Attachment[]} */ attachments = []; /** @type {import("@mail/core/common/message_model").Message} */ @@ -33,6 +35,7 @@ export class Composer { isFocused = false; constructor(store, data) { + super(); const { message, thread } = data; if (thread) { this.thread = thread; diff --git a/addons/mail/static/src/core/common/discuss.xml b/addons/mail/static/src/core/common/discuss.xml index 095dfa4a541..8c7d8dc6518 100644 --- a/addons/mail/static/src/core/common/discuss.xml +++ b/addons/mail/static/src/core/common/discuss.xml @@ -63,7 +63,7 @@
- +
diff --git a/addons/mail/static/src/core/common/follower_model.js b/addons/mail/static/src/core/common/follower_model.js index ae1c8b1949b..29c13fbcb6b 100644 --- a/addons/mail/static/src/core/common/follower_model.js +++ b/addons/mail/static/src/core/common/follower_model.js @@ -1,5 +1,7 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; + /** * @typedef Data * @property {import("@mail/core/common/thread_model").Thread} followedThread @@ -8,7 +10,7 @@ * @property {import("@mail/core/common/partner_model").Data} partner */ -export class Follower { +export class Follower extends Record { /** @type {import("@mail/core/common/thread_model").Thread} */ followedThread; /** @type {number} */ @@ -25,7 +27,7 @@ export class Follower { */ get isEditable() { const hasWriteAccess = this.followedThread ? this.followedThread.hasWriteAccess : false; - return this._store.user === this.partner + return this.partner.eq(this._store.user) ? this.followedThread.hasReadAccess : hasWriteAccess; } diff --git a/addons/mail/static/src/core/common/link_preview_model.js b/addons/mail/static/src/core/common/link_preview_model.js index 7a08d1a1f48..27109fc93de 100644 --- a/addons/mail/static/src/core/common/link_preview_model.js +++ b/addons/mail/static/src/core/common/link_preview_model.js @@ -1,6 +1,8 @@ /* @odoo-module */ -export class LinkPreview { +import { Record } from "@mail/core/common/record"; + +export class LinkPreview extends Record { /** @type {number} */ id; /** @type {Object} */ @@ -25,6 +27,7 @@ export class LinkPreview { * @returns {LinkPreview} */ constructor(data) { + super(); Object.assign(this, data); } diff --git a/addons/mail/static/src/core/common/mail_core_common_service.js b/addons/mail/static/src/core/common/mail_core_common_service.js index 126b734d907..2abbf940d36 100644 --- a/addons/mail/static/src/core/common/mail_core_common_service.js +++ b/addons/mail/static/src/core/common/mail_core_common_service.js @@ -54,9 +54,8 @@ export class MailCoreCommon { } delete this.store.messages[messageId]; if (message.originThread) { - removeFromArrayWithPredicate( - message.originThread.messages, - ({ id }) => id === message.id + removeFromArrayWithPredicate(message.originThread.messages, (msg) => + msg.eq(message) ); } this.env.bus.trigger("mail.message/delete", { message }); diff --git a/addons/mail/static/src/core/common/message.js b/addons/mail/static/src/core/common/message.js index ae5118a6b31..8ddd5afd0ed 100644 --- a/addons/mail/static/src/core/common/message.js +++ b/addons/mail/static/src/core/common/message.js @@ -107,7 +107,7 @@ export class Message extends Component { }); useEffect( (editingMessage) => { - if (editingMessage === this.props.message) { + if (this.props.message.eq(editingMessage)) { this.enterEditMode(); } }, @@ -125,7 +125,7 @@ export class Message extends Component { const reaction = this.message.reactions.find( ({ content, personas }) => content === emoji && - personas.find((persona) => persona === this.store.self) + personas.find((persona) => persona.eq(this.store.self)) ); if (!reaction) { this.messageService.react(this.message, emoji); @@ -260,7 +260,7 @@ export class Message extends Component { if (!this.props.thread) { return false; } - return this.message.originThread === this.props.thread; + return this.props.thread.eq(this.message.originThread); } get isInInbox() { diff --git a/addons/mail/static/src/core/common/message.xml b/addons/mail/static/src/core/common/message.xml index a66b398851e..822b210728a 100644 --- a/addons/mail/static/src/core/common/message.xml +++ b/addons/mail/static/src/core/common/message.xml @@ -57,7 +57,7 @@
-
+
@@ -140,7 +140,7 @@ - + diff --git a/addons/mail/static/src/core/common/message_model.js b/addons/mail/static/src/core/common/message_model.js index 093f727c288..ea8ed8cc47c 100644 --- a/addons/mail/static/src/core/common/message_model.js +++ b/addons/mail/static/src/core/common/message_model.js @@ -1,5 +1,6 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; import { htmlToTextContentInline } from "@mail/utils/common/format"; import { createLocalId } from "@mail/utils/common/misc"; @@ -12,7 +13,7 @@ import { url } from "@web/core/utils/urls"; const { DateTime } = luxon; -export class Message { +export class Message extends Record { /** @type {Object[]} */ attachments = []; /** @type {import("@mail/core/common/persona_model").Persona} */ @@ -114,7 +115,7 @@ export class Message { } get isSelfMentioned() { - return this.recipients.some((recipient) => recipient === this._store.self); + return this._store.self?.in(this.recipients); } get isHighlightedFromMention() { @@ -125,7 +126,7 @@ export class Message { if (!this.author || !this._store.self) { return false; } - return this.author.id === this._store.self.id && this.author.type === this._store.self.type; + return this.author.eq(this._store.self); } get isNeedaction() { diff --git a/addons/mail/static/src/core/common/message_reaction_menu.xml b/addons/mail/static/src/core/common/message_reaction_menu.xml index 9df6d09b0aa..906ef15d7fc 100644 --- a/addons/mail/static/src/core/common/message_reaction_menu.xml +++ b/addons/mail/static/src/core/common/message_reaction_menu.xml @@ -6,9 +6,9 @@
-
@@ -18,7 +18,7 @@
-
diff --git a/addons/mail/static/src/core/common/message_reactions.js b/addons/mail/static/src/core/common/message_reactions.js index b0ef6a22960..7e0dd4bdf2b 100644 --- a/addons/mail/static/src/core/common/message_reactions.js +++ b/addons/mail/static/src/core/common/message_reactions.js @@ -62,7 +62,7 @@ export class MessageReactions extends Component { } hasSelfReacted(reaction) { - return reaction.personas.includes(this.store.self); + return this.store.self.in(reaction.personas); } onClickReaction(reaction) { diff --git a/addons/mail/static/src/core/common/message_reactions_model.js b/addons/mail/static/src/core/common/message_reactions_model.js index 94195434bf4..de293989775 100644 --- a/addons/mail/static/src/core/common/message_reactions_model.js +++ b/addons/mail/static/src/core/common/message_reactions_model.js @@ -1,6 +1,8 @@ /* @odoo-module */ -export class MessageReactions { +import { Record } from "@mail/core/common/record"; + +export class MessageReactions extends Record { /** @type {string} */ content; /** @type {number} */ diff --git a/addons/mail/static/src/core/common/message_service.js b/addons/mail/static/src/core/common/message_service.js index d1d88fa513a..89510f03da6 100644 --- a/addons/mail/static/src/core/common/message_service.js +++ b/addons/mail/static/src/core/common/message_service.js @@ -64,9 +64,8 @@ export class MessageService { async delete(message) { if (message.isStarred) { this.store.discuss.starred.counter--; - removeFromArrayWithPredicate( - this.store.discuss.starred.messages, - ({ id }) => id === message.id + removeFromArrayWithPredicate(this.store.discuss.starred.messages, (msg) => + msg.eq(message) ); } message.body = ""; @@ -201,12 +200,12 @@ export class MessageService { const starred = this.store.discuss.starred; if (isStarred) { starred.counter++; - if (!starred.messages.includes(message)) { + if (message.notIn(starred.messages)) { starred.messages.push(message); } } else { starred.counter--; - removeFromArrayWithPredicate(starred.messages, ({ id }) => id === message.id); + removeFromArrayWithPredicate(starred.messages, (msg) => msg.eq(message)); } } @@ -458,7 +457,7 @@ export class MessageService { }) : undefined, }); - if (notification.message.author !== this.store.self) { + if (!notification.message.author?.eq(this.store.self)) { return; } const thread = notification.message.originThread; @@ -487,7 +486,7 @@ export class MessageService { } this.updateNotificationGroup(group, data); if (group.notifications.length === 0) { - removeFromArrayWithPredicate(this.store.notificationGroups, (gr) => gr.id === group.id); + removeFromArrayWithPredicate(this.store.notificationGroups, (gr) => gr.eq(group)); } return group; } diff --git a/addons/mail/static/src/core/common/notification_group_model.js b/addons/mail/static/src/core/common/notification_group_model.js index eb9d362adec..5412103b365 100644 --- a/addons/mail/static/src/core/common/notification_group_model.js +++ b/addons/mail/static/src/core/common/notification_group_model.js @@ -1,9 +1,11 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; + import { _t } from "@web/core/l10n/translation"; let nextId = 1; -export class NotificationGroup { +export class NotificationGroup extends Record { /** @type {import("@mail/core/common/notification_model").Notification[]} */ notifications = []; /** @type {string} */ @@ -20,11 +22,12 @@ export class NotificationGroup { _store; constructor(store) { + super(); this._store = store; this._store.notificationGroups.push(this); this.id = nextId++; // return reactive - return store.notificationGroups.find((group) => group === this); + return store.notificationGroups.find((group) => group.eq(this)); } get iconSrc() { diff --git a/addons/mail/static/src/core/common/notification_model.js b/addons/mail/static/src/core/common/notification_model.js index 7c21d302b66..90f73555e08 100644 --- a/addons/mail/static/src/core/common/notification_model.js +++ b/addons/mail/static/src/core/common/notification_model.js @@ -1,8 +1,10 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; + import { _t } from "@web/core/l10n/translation"; -export class Notification { +export class Notification extends Record { /** @type {number} */ id; /** @type {number} */ @@ -19,6 +21,7 @@ export class Notification { _store; constructor(store, data) { + super(); Object.assign(this, { id: data.id, _store: store, diff --git a/addons/mail/static/src/core/common/partner_compare.js b/addons/mail/static/src/core/common/partner_compare.js index cccaaa2feef..82e701c4a69 100644 --- a/addons/mail/static/src/core/common/partner_compare.js +++ b/addons/mail/static/src/core/common/partner_compare.js @@ -31,8 +31,8 @@ partnerCompareRegistry.add( (p1, p2, { thread }) => { if (thread) { const followerList = [...thread.followers]; - const isFollower1 = followerList.some((follower) => follower.partner === p1); - const isFollower2 = followerList.some((follower) => follower.partner === p2); + const isFollower1 = followerList.some((follower) => p1.eq(follower.partner)); + const isFollower2 = followerList.some((follower) => p2.eq(follower.partner)); if (isFollower1 && !isFollower2) { return -1; } diff --git a/addons/mail/static/src/core/common/persona_model.js b/addons/mail/static/src/core/common/persona_model.js index dfe58920155..b9a93a3363f 100644 --- a/addons/mail/static/src/core/common/persona_model.js +++ b/addons/mail/static/src/core/common/persona_model.js @@ -1,5 +1,7 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; + /** * @typedef {'offline' | 'bot' | 'online' | 'away' | 'im_partner' | undefined} ImStatus * @typedef Data @@ -10,7 +12,7 @@ * @property {ImStatus} im_status */ -export class Persona { +export class Persona extends Record { /** @type {string} */ localId; /** @type {number} */ diff --git a/addons/mail/static/src/core/common/record.js b/addons/mail/static/src/core/common/record.js new file mode 100644 index 00000000000..b63dbe633a0 --- /dev/null +++ b/addons/mail/static/src/core/common/record.js @@ -0,0 +1,25 @@ +/* @odoo-module */ + +import { toRaw } from "@odoo/owl"; + +export class Record { + /** @param {Record} record */ + eq(record) { + return toRaw(this) === toRaw(record); + } + + /** @param {Record} record */ + notEq(record) { + return !this.eq(record); + } + + /** @param {Record[]} list */ + in(list) { + return list.some((record) => record.eq(this)); + } + + /** @param {Record[]} list */ + notIn(list) { + return !this.in(list); + } +} diff --git a/addons/mail/static/src/core/common/scroll_position_model.js b/addons/mail/static/src/core/common/scroll_position_model.js index af6546af3f4..2060ae1adb6 100644 --- a/addons/mail/static/src/core/common/scroll_position_model.js +++ b/addons/mail/static/src/core/common/scroll_position_model.js @@ -1,12 +1,15 @@ /* @odoo-module */ -export class ScrollPosition { +import { Record } from "@mail/core/common/record"; + +export class ScrollPosition extends Record { /** @type {number|undefined} */ top; /** @type {number|undefined} */ left; constructor(top, left) { + super(); this.top = top; this.left = left; } diff --git a/addons/mail/static/src/core/common/suggestion_service.js b/addons/mail/static/src/core/common/suggestion_service.js index edc64dc7f1f..376b172daae 100644 --- a/addons/mail/static/src/core/common/suggestion_service.js +++ b/addons/mail/static/src/core/common/suggestion_service.js @@ -130,7 +130,7 @@ export class SuggestionService { const mainSuggestionList = []; const extraSuggestionList = []; for (const partner of partners) { - if (partner === this.store.odoobot) { + if (partner.eq(this.store.odoobot)) { // ignore archived partners (except OdooBot) continue; } diff --git a/addons/mail/static/src/core/common/thread.js b/addons/mail/static/src/core/common/thread.js index 032aa8dd57a..2fdb9f60f9b 100644 --- a/addons/mail/static/src/core/common/thread.js +++ b/addons/mail/static/src/core/common/thread.js @@ -159,7 +159,7 @@ export class Thread extends Component { } }); onWillUpdateProps((nextProps) => { - if (nextProps.thread !== this.props.thread) { + if (nextProps.thread.notEq(this.props.thread)) { this.lastJumpPresent = nextProps.jumpPresent; } this.threadService.fetchNewMessages(nextProps.thread); @@ -217,7 +217,7 @@ export class Thread extends Component { return false; } - if (msg.author !== prevMsg.author) { + if (!msg.author?.eq(prevMsg.author)) { return false; } if (msg.resModel !== prevMsg.resModel || msg.resId !== prevMsg.resId) { diff --git a/addons/mail/static/src/core/common/thread_model.js b/addons/mail/static/src/core/common/thread_model.js index b955961f5f8..c0e1a6b400b 100644 --- a/addons/mail/static/src/core/common/thread_model.js +++ b/addons/mail/static/src/core/common/thread_model.js @@ -1,5 +1,6 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; import { ScrollPosition } from "@mail/core/common/scroll_position_model"; import { createLocalId } from "@mail/utils/common/misc"; @@ -20,7 +21,7 @@ import { Deferred } from "@web/core/utils/concurrency"; * @property {boolean} checked */ -export class Thread { +export class Thread extends Record { /** @type {number} */ id; /** @type {string} */ @@ -135,6 +136,7 @@ export class Thread { is_editable; constructor(store, data) { + super(); Object.assign(this, { id: data.id, model: data.model, @@ -194,7 +196,7 @@ export class Thread { get allowCalls() { return ( ["chat", "channel", "group"].includes(this.type) && - this.correspondent !== this._store.odoobot + !this.correspondent?.eq(this._store.odoobot) ); } @@ -317,8 +319,8 @@ export class Thread { } get hasSelfAsMember() { - return this.channelMembers.some( - (channelMember) => channelMember.persona === this._store.self + return this.channelMembers.some((channelMember) => + channelMember.persona?.eq(this._store.self) ); } @@ -326,7 +328,7 @@ export class Thread { * @param {import("@mail/core/common/message_model").Message} message */ hasMessage(message) { - return this.messages.some(({ id }) => id === message.id); + return message.in(this.messages); } get invitationLink() { @@ -377,7 +379,7 @@ export class Thread { } const lastMessageSeenByAllId = Math.min(...otherLastSeenMessageIds); const orderedSelfSeenMessages = this.persistentMessages.filter((message) => { - return message.author === this._store.self && message.id <= lastMessageSeenByAllId; + return message.author?.eq(this._store.self) && message.id <= lastMessageSeenByAllId; }); if (!orderedSelfSeenMessages || orderedSelfSeenMessages.length === 0) { return false; diff --git a/addons/mail/static/src/core/common/thread_service.js b/addons/mail/static/src/core/common/thread_service.js index 19354f7e8e7..a6a68fb683a 100644 --- a/addons/mail/static/src/core/common/thread_service.js +++ b/addons/mail/static/src/core/common/thread_service.js @@ -273,10 +273,10 @@ export class ThreadService { // feed needactions // same for needaction messages, special case for mailbox: // kinda "fetch new/more" with needactions on many origin threads at once - if (thread === this.store.discuss.inbox) { + if (thread.eq(this.store.discuss.inbox)) { for (const message of fetched) { const thread = message.originThread; - if (!thread.needactionMessages.includes(message)) { + if (message.notIn(thread.needactionMessages)) { thread.needactionMessages.unshift(message); } } @@ -367,7 +367,7 @@ export class ThreadService { }); if (!thread.isLoaded) { thread.messages.push(message); - if (message.isNeedaction && !thread.needactionMessages.includes(message)) { + if (message.isNeedaction && message.notIn(thread.needactionMessages)) { thread.needactionMessages.push(message); } } @@ -741,9 +741,9 @@ export class ThreadService { continue; } if ( - member.persona.id !== thread._store.user?.id || + member.persona.notEq(thread._store.user) || (serverData.channel.channelMembers[0][1].length === 1 && - member.persona.id === thread._store.user?.id) + member.persona?.eq(thread._store.user)) ) { thread.chatPartnerId = member.persona.id; } @@ -944,7 +944,7 @@ export class ThreadService { } const data = await this.rpc(this.getMessagePostRoute(thread), params); if (thread.type !== "chatter") { - removeFromArrayWithPredicate(thread.messages, ({ id }) => id === tmpMsg.id); + removeFromArrayWithPredicate(thread.messages, (msg) => msg.eq(tmpMsg)); delete this.store.messages[tmpMsg.id]; } if (!data) { @@ -961,7 +961,7 @@ export class ThreadService { const message = this.messageService.insert( Object.assign(data, { body: markup(data.body) }) ); - if (!thread.messages.some(({ id }) => id === message.id)) { + if (message.notIn(thread.messages)) { thread.messages.push(message); } if (!message.isEmpty && this.store.hasLinkPreviewFeature) { diff --git a/addons/mail/static/src/core/web/activity_model.js b/addons/mail/static/src/core/web/activity_model.js index 3f5aa655a5c..f33c0dfa8f7 100644 --- a/addons/mail/static/src/core/web/activity_model.js +++ b/addons/mail/static/src/core/web/activity_model.js @@ -1,5 +1,7 @@ /* @odoo-module */ +import { Record } from "@mail/core/common/record"; + /** * @typedef Data * @property {string} activity_category @@ -30,7 +32,7 @@ * @property {[number, string]} write_uid */ -export class Activity { +export class Activity extends Record { /** @type {string} */ activity_category; /** @type {[number, string]} */ @@ -94,6 +96,7 @@ export class Activity { * @returns {Activity} */ constructor(store, id) { + super(); Object.assign(this, { id, _store: store, diff --git a/addons/mail/static/src/core/web/chatter.js b/addons/mail/static/src/core/web/chatter.js index a5b04544195..c6c07e46acd 100644 --- a/addons/mail/static/src/core/web/chatter.js +++ b/addons/mail/static/src/core/web/chatter.js @@ -228,7 +228,7 @@ export class Chatter extends Component { } allFollowers.push(...this.state.thread.followers); const followers = allFollowers.slice(0, 5).map(({ partner }) => { - if (partner === this.store.self) { + if (partner.eq(this.store.self)) { return `me`; } const text = partner.email ? partner.emailWithoutDomain : partner.name; diff --git a/addons/mail/static/src/core/web/mail_core_web_service.js b/addons/mail/static/src/core/web/mail_core_web_service.js index acc56f06332..084401a1e18 100644 --- a/addons/mail/static/src/core/web/mail_core_web_service.js +++ b/addons/mail/static/src/core/web/mail_core_web_service.js @@ -73,12 +73,12 @@ export class MailCoreWeb { const data = Object.assign(payload, { body: markup(payload.body) }); const message = this.messageService.insert(data); const inbox = this.store.discuss.inbox; - if (!inbox.messages.includes(message)) { + if (message.notIn(inbox.messages)) { inbox.messages.push(message); inbox.counter++; } const thread = message.originThread; - if (!thread.needactionMessages.includes(message)) { + if (message.notIn(thread.needactionMessages)) { thread.needactionMessages.push(message); thread.message_needaction_counter++; } @@ -112,7 +112,7 @@ export class MailCoreWeb { removeFromArray(message.needaction_partner_ids, partnerIndex); removeFromArrayWithPredicate(inbox.messages, ({ id }) => id === messageId); const history = this.store.discuss.history; - if (!history.messages.includes(message)) { + if (message.notIn(history.messages)) { history.messages.push(message); } } diff --git a/addons/mail/static/src/core/web/messaging_menu.js b/addons/mail/static/src/core/web/messaging_menu.js index ea04c76337a..73503412078 100644 --- a/addons/mail/static/src/core/web/messaging_menu.js +++ b/addons/mail/static/src/core/web/messaging_menu.js @@ -118,10 +118,16 @@ export class MessagingMenu extends Component { * * In each group, thread with most recent message comes first */ - if (a.correspondent === this.store.odoobot && b.correspondent !== this.store.odoobot) { + if ( + a.correspondent?.eq(this.store.odoobot) && + !b.correspondent?.eq(this.store.odoobot) + ) { return 1; } - if (b.correspondent === this.store.odoobot && a.correspondent !== this.store.odoobot) { + if ( + b.correspondent?.eq(this.store.odoobot) && + !a.correspondent?.eq(this.store.odoobot) + ) { return -1; } if (a.hasNeedactionMessages && !b.hasNeedactionMessages) { @@ -233,7 +239,7 @@ export class MessagingMenu extends Component { }); // Close the related chat window as having both the form view // and the chat window does not look good. - this.store.chatWindows.find(({ thr }) => thr === thread)?.close(); + this.store.chatWindows.find(({ thr }) => thr?.eq(thread))?.close(); } else { this.threadService.open(thread); } diff --git a/addons/mail/static/src/core/web/messaging_menu.xml b/addons/mail/static/src/core/web/messaging_menu.xml index 4f28bf3e641..07babf23d81 100644 --- a/addons/mail/static/src/core/web/messaging_menu.xml +++ b/addons/mail/static/src/core/web/messaging_menu.xml @@ -74,7 +74,7 @@ You: - + : diff --git a/addons/mail/static/src/core/web/thread_service_patch.js b/addons/mail/static/src/core/web/thread_service_patch.js index b20f816caa2..c4de853b01c 100644 --- a/addons/mail/static/src/core/web/thread_service_patch.js +++ b/addons/mail/static/src/core/web/thread_service_patch.js @@ -5,7 +5,7 @@ import { ThreadService, threadService } from "@mail/core/common/thread_service"; import { parseEmail } from "@mail/js/utils"; import { createLocalId } from "@mail/utils/common/misc"; -import { markup, toRaw } from "@odoo/owl"; +import { markup } from "@odoo/owl"; import { _t } from "@web/core/l10n/translation"; import { patch } from "@web/core/utils/patch"; @@ -93,7 +93,7 @@ patch(ThreadService.prototype, { followedThread: thread, ...followerData, }); - if (toRaw(follower) !== toRaw(thread.selfFollower)) { + if (follower.notEq(thread.selfFollower)) { thread.followers.add(follower); } } @@ -195,7 +195,7 @@ patch(ThreadService.prototype, { followedThread: thread, ...data, }); - if (toRaw(follower) !== toRaw(thread.selfFollower)) { + if (follower.notEq(thread.selfFollower)) { thread.followers.add(follower); } } @@ -229,7 +229,7 @@ patch(ThreadService.prototype, { [follower.partner.id], ]); const thread = follower.followedThread; - if (toRaw(follower) === toRaw(thread.selfFollower)) { + if (follower.eq(thread.selfFollower)) { thread.selfFollower = undefined; } else { thread.followers.delete(follower); diff --git a/addons/mail/static/src/discuss/call/common/call.js b/addons/mail/static/src/discuss/call/common/call.js index 937ac132bbf..04a5ac7e1a6 100644 --- a/addons/mail/static/src/discuss/call/common/call.js +++ b/addons/mail/static/src/discuss/call/common/call.js @@ -56,7 +56,7 @@ export class Call extends Component { } get isActiveCall() { - return Boolean(this.props.thread.id === this.rtc.state?.channel?.id); + return Boolean(this.props.thread.eq(this.rtc.state?.channel)); } get minimized() { diff --git a/addons/mail/static/src/discuss/call/common/call_action_list.js b/addons/mail/static/src/discuss/call/common/call_action_list.js index 6104ec148d1..30c8dce7907 100644 --- a/addons/mail/static/src/discuss/call/common/call_action_list.js +++ b/addons/mail/static/src/discuss/call/common/call_action_list.js @@ -57,7 +57,7 @@ export class CallActionList extends Component { } get isOfActiveCall() { - return Boolean(this.props.thread.id === this.rtc.state?.channel?.id); + return Boolean(this.props.thread.eq(this.rtc.state?.channel)); } get isSmall() { diff --git a/addons/mail/static/src/discuss/call/common/call_action_list.xml b/addons/mail/static/src/discuss/call/common/call_action_list.xml index 3771c50cf04..7c83a957e54 100644 --- a/addons/mail/static/src/discuss/call/common/call_action_list.xml +++ b/addons/mail/static/src/discuss/call/common/call_action_list.xml @@ -75,7 +75,7 @@
- Disconnect + Disconnect Join Call