[FIX] mail: properly compare discuss models with toRaw
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) <seb@odoo.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) })
|
||||
);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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} */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export class Composer extends Component {
|
||||
);
|
||||
useEffect(
|
||||
(rThread, cThread) => {
|
||||
if (cThread && rThread === cThread) {
|
||||
if (cThread && cThread.eq(rThread)) {
|
||||
this.state.autofocus++;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<div class="o-mail-Composer-sidebarMain flex-shrink-0" t-if="!compact and props.sidebar">
|
||||
<img class="o-mail-Composer-avatar o_avatar rounded" t-att-src="threadService.avatarUrl(store.self, props.composer.thread)" alt="Avatar of user"/>
|
||||
</div>
|
||||
<div class="o-mail-Composer-coreHeader text-truncate small p-2" t-if="props.composer.thread and props.messageToReplyTo?.thread === props.composer.thread">
|
||||
<div class="o-mail-Composer-coreHeader text-truncate small p-2" t-if="props.composer.thread and props.messageToReplyTo?.thread?.eq(props.composer.thread)">
|
||||
<span class="cursor-pointer" t-on-click="() => env.messageHighlight?.highlightMessage(props.messageToReplyTo.message, props.composer.thread)">
|
||||
Replying to <b t-esc="props.messageToReplyTo.message.author.name"/>
|
||||
</span>
|
||||
<span t-if="props.messageToReplyTo.message.originThread !== props.composer.thread">
|
||||
<span t-if="props.messageToReplyTo.message.originThread.notEq(props.composer.thread)">
|
||||
on: <b><t t-esc="props.messageToReplyTo.message.originThread.displayName"/></b>
|
||||
</span>
|
||||
<i class="fa fa-lg fa-times-circle rounded-circle p-0 ms-1 cursor-pointer" title="Stop replying" t-on-click="() => props.messageToReplyTo.cancel()"/>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { Record } from "@mail/core/common/record";
|
||||
|
||||
/**
|
||||
* @typedef {{partnerIds: Set<number>, threadIds: Set<number>}} 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;
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<div class="overflow-auto bg-view d-flex flex-grow-1" t-ref="core">
|
||||
<div class="d-flex flex-column flex-grow-1">
|
||||
<Thread thread="thread" t-key="thread.localId" jumpPresent="state.jumpThreadPresent" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo"/>
|
||||
<Composer t-if="thread.type !== 'mailbox' or messageToReplyTo.thread === thread" t-key="thread.localId" composer="thread.composer" autofocus="true" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo" onDiscardCallback="() => messageToReplyTo.cancel()" onPostCallback.bind="() => this.state.jumpThreadPresent++" dropzoneRef="contentRef" type="messageToReplyTo?.message ? (messageToReplyTo.message.isNote ? 'note' : 'message') : undefined"/>
|
||||
<Composer t-if="thread.type !== 'mailbox' or thread.eq(messageToReplyTo.thread)" t-key="thread.localId" composer="thread.composer" autofocus="true" messageEdition="messageEdition" messageToReplyTo="messageToReplyTo" onDiscardCallback="() => messageToReplyTo.cancel()" onPostCallback.bind="() => this.state.jumpThreadPresent++" dropzoneRef="contentRef" type="messageToReplyTo?.message ? (messageToReplyTo.message.isNote ? 'note' : 'message') : undefined"/>
|
||||
</div>
|
||||
<div t-if="threadActions.activeAction?.componentCondition" t-attf-class="{{ threadActions.activeAction.panelOuterClass }}" class="h-100 border-start o-mail-Discuss-inspector">
|
||||
<t t-component="threadActions.activeAction.component" thread="thread" t-props="threadActions.activeAction.componentProps"/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<i class="fa fa-calendar-o"/>
|
||||
</span>
|
||||
</div>
|
||||
<div t-if="message.originThread === props.thread and message.notifications.length > 0" t-att-class="{ 'ms-2': !isAlignedRight }">
|
||||
<div t-if="message.originThread?.eq(props.thread) and message.notifications.length > 0" t-att-class="{ 'ms-2': !isAlignedRight }">
|
||||
<span class="o-mail-Message-notification cursor-pointer" t-att-class="message.failureNotifications.length > 0 ? 'text-danger' : text-600" role="button" tabindex="0" t-on-click="onClickNotification">
|
||||
<i t-att-class="message.notifications[0].icon" role="img" aria-label="Delivery failure"/> <span t-if="message.notifications[0].label" t-out="message.notifications[0].label"/>
|
||||
</span>
|
||||
@@ -140,7 +140,7 @@
|
||||
<button t-if="canReplyTo" class="btn px-1 py-0 rounded-0" t-on-click.stop="onClickReplyTo" tabindex="1" title="Reply" aria-label="Reply"><i class="fa fa-lg fa-reply"/></button>
|
||||
<button t-if="canToggleStar" class="btn px-1 py-0 rounded-0" t-on-click="() => messageService.toggleStar(props.message)" tabindex="0" title="Mark as Todo" aria-label="Mark as Todo"><i class="fa fa-lg" t-att-class="message.isStarred ? 'fa-star o-mail-Message-starred' : 'fa-star-o'"/></button>
|
||||
<button t-if="isInInbox" class="btn px-1 py-0 rounded-0" t-on-click.stop="() => messageService.setDone(props.message)" title="Mark as Read" aria-label="Mark as Read"><i class="fa fa-lg fa-check"/></button>
|
||||
<Dropdown t-if="message.reactions.length or editable or deletable or (canAddReaction and isInInbox) or (props.thread?.model === 'discuss.channel' and store.user)" onStateChanged="state => this.state.expandOptions = state.open" position="props.thread?.newestMessage === props.message ? 'top-start' : 'bottom-start'" togglerClass="`btn p-0 ${ state.expandOptions ? 'bg-200' : '' }`" menuClass="'d-flex flex-column py-0 o-mail-Message-moreMenu'" class="'d-flex rounded-0'" title="expandText">
|
||||
<Dropdown t-if="message.reactions.length or editable or deletable or (canAddReaction and isInInbox) or (props.thread?.model === 'discuss.channel' and store.user)" onStateChanged="state => this.state.expandOptions = state.open" position="props.message.eq(props.thread?.newestMessage) ? 'top-start' : 'bottom-start'" togglerClass="`btn p-0 ${ state.expandOptions ? 'bg-200' : '' }`" menuClass="'d-flex flex-column py-0 o-mail-Message-moreMenu'" class="'d-flex rounded-0'" title="expandText">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="btn px-1 py-0 fa fa-lg fa-ellipsis-h rounded-0" t-att-class="{ 'order-1': props.isInChatWindow }" tabindex="1"/>
|
||||
</t>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<div class="d-flex h-100" t-on-keydown="onKeydown" t-ref="root">
|
||||
<div class="d-flex overflow-auto flex-column bg-100 p-2 h-100 border-end">
|
||||
<t t-foreach="props.message.reactions" t-as="reaction" t-key="reaction.content">
|
||||
<button class="btn p-1 rounded-2 mx-2 py-0 d-flex align-items-center" t-att-class="{ 'bg-200 border-primary': reaction === state.reaction }" t-att-title="getEmojiShortcode(reaction)" t-att-data-reaction="reaction" t-on-click="() => state.reaction = reaction">
|
||||
<button class="btn p-1 rounded-2 mx-2 py-0 d-flex align-items-center" t-att-class="{ 'bg-200 border-primary': reaction.eq(state.reaction) }" t-att-title="getEmojiShortcode(reaction)" t-att-data-reaction="reaction" t-on-click="() => state.reaction = reaction">
|
||||
<span class="mx-1 fs-2" t-esc="reaction.content"/>
|
||||
<span class="mx-1 pe-2" t-att-class="{ 'text-primary': reaction === state.reaction }" t-esc="reaction.count"/>
|
||||
<span class="mx-1 pe-2" t-att-class="{ 'text-primary': reaction.eq(state.reaction) }" t-esc="reaction.count"/>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="d-flex flex-grow-1 align-items-center">
|
||||
<span class="mx-2 text-truncate fs-6" t-esc="persona.name"/>
|
||||
<div class="flex-grow-1"/>
|
||||
<button t-if="store.self === persona" class="btn btn-light fa fa-trash rounded-pill o-bg-inherit border-0" title="Remove" t-on-click.stop="() => this.messageService.removeReaction(state.reaction)"/>
|
||||
<button t-if="persona.eq(store.self)" class="btn btn-light fa fa-trash rounded-pill o-bg-inherit border-0" title="Remove" t-on-click.stop="() => this.messageService.removeReaction(state.reaction)"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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} */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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} */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 `<span class="text-muted" title="${escape(partner.email)}">me</span>`;
|
||||
}
|
||||
const text = partner.email ? partner.emailWithoutDomain : partner.name;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<t t-if="message.isSelfAuthored">
|
||||
<i class="fa fa-mail-reply me-1"/>You:
|
||||
</t>
|
||||
<t t-elif="message.author and (message.author.id !== thread.correspondent?.id or message.author.type !== thread.correspondent?.type)">
|
||||
<t t-elif="message.author and !message.author?.eq(thread.correspondent)">
|
||||
<t t-esc="message.author.name"/>:
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<i class="fa fa-times fa-stack-1x" t-att-class="{ 'fa-lg': !isSmall }"/>
|
||||
</div>
|
||||
</button>
|
||||
<t t-if="props.thread === rtc.state.channel" t-set="callText">Disconnect</t>
|
||||
<t t-if="props.thread.eq(rtc.state.channel)" t-set="callText">Disconnect</t>
|
||||
<t t-else="" t-set="callText">Join Call</t>
|
||||
<button class="btn d-flex m-1 border-0 rounded-circle shadow-none"
|
||||
t-att-aria-label="callText"
|
||||
|
||||
@@ -49,7 +49,7 @@ export class CallParticipantCard extends Component {
|
||||
if (!this.rtcSession) {
|
||||
return false;
|
||||
}
|
||||
return this.rtcSession?.id !== this.rtc.state.selfSession?.id;
|
||||
return !this.rtcSession?.eq(this.rtc.state.selfSession);
|
||||
}
|
||||
|
||||
get rtcSession() {
|
||||
@@ -67,7 +67,7 @@ export class CallParticipantCard extends Component {
|
||||
get showConnectionState() {
|
||||
return Boolean(
|
||||
this.isOfActiveCall &&
|
||||
!(this.rtcSession.channelMember?.persona.localId === this.store.self?.localId) &&
|
||||
!this.rtcSession.channelMember?.persona.eq(this.store.self) &&
|
||||
!HIDDEN_CONNECTION_STATES.has(this.rtcSession.connectionState)
|
||||
);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export class CallParticipantCard extends Component {
|
||||
}
|
||||
if (this.rtcSession) {
|
||||
const channel = this.rtcSession.channel;
|
||||
if (channel.activeRtcSession === this.rtcSession) {
|
||||
if (this.rtcSession.eq(channel.activeRtcSession)) {
|
||||
channel.activeRtcSession = undefined;
|
||||
} else {
|
||||
channel.activeRtcSession = this.rtcSession;
|
||||
|
||||
@@ -349,7 +349,7 @@ export class Rtc {
|
||||
*/
|
||||
endCall(channel = this.state.channel) {
|
||||
channel.rtcInvitingSessionId = undefined;
|
||||
if (this.state.channel === channel) {
|
||||
if (channel.eq(this.state.channel)) {
|
||||
this.clear();
|
||||
this.soundEffectsService.play("channel-leave");
|
||||
}
|
||||
@@ -539,7 +539,7 @@ export class Rtc {
|
||||
return;
|
||||
}
|
||||
this.state.hasPendingRequest = true;
|
||||
const isActiveCall = Boolean(this.state.channel && this.state.channel === channel);
|
||||
const isActiveCall = channel.eq(this.state.channel);
|
||||
if (this.state.channel) {
|
||||
await this.leaveCall(this.state.channel);
|
||||
}
|
||||
@@ -623,7 +623,7 @@ export class Rtc {
|
||||
return;
|
||||
}
|
||||
for (const session of Object.values(this.state.channel.rtcSessions)) {
|
||||
if (session.peerConnection || session.id === this.state.selfSession.id) {
|
||||
if (session.peerConnection || session.eq(this.state.selfSession)) {
|
||||
continue;
|
||||
}
|
||||
this.log(session, "init call", { step: "init call" });
|
||||
@@ -788,7 +788,7 @@ export class Rtc {
|
||||
this.state.logs.set("selfSessionId", this.state.selfSession?.id);
|
||||
this.state.logs.set("hasTURN", hasTurn(this.state.iceServers));
|
||||
const channelProxy = reactive(this.state.channel, () => {
|
||||
if (channel !== this.state.channel) {
|
||||
if (channel.notEq(this.state.channel)) {
|
||||
throw new Error("channel has changed");
|
||||
}
|
||||
if (this.state.channel) {
|
||||
@@ -1150,7 +1150,7 @@ export class Rtc {
|
||||
}
|
||||
}
|
||||
for (const session of Object.values(this.state.channel.rtcSessions)) {
|
||||
if (session.id === this.state.selfSession.id) {
|
||||
if (session.eq(this.state.selfSession)) {
|
||||
continue;
|
||||
}
|
||||
await this.updateRemote(session, "video");
|
||||
@@ -1353,7 +1353,7 @@ export class Rtc {
|
||||
this.state.audioTrack = audioTrack;
|
||||
await this.linkVoiceActivation();
|
||||
for (const session of Object.values(this.state.channel.rtcSessions)) {
|
||||
if (session.id === this.state.selfSession.id) {
|
||||
if (session.eq(this.state.selfSession)) {
|
||||
continue;
|
||||
}
|
||||
await this.updateRemote(session, "audio");
|
||||
@@ -1438,7 +1438,7 @@ export class Rtc {
|
||||
deleteSession(id) {
|
||||
const session = this.store.rtcSessions[id];
|
||||
if (session) {
|
||||
if (this.state.selfSession && session.id === this.state.selfSession.id) {
|
||||
if (this.state.selfSession && session.eq(this.state.selfSession)) {
|
||||
this.endCall();
|
||||
}
|
||||
delete this.store.threads[createLocalId("discuss.channel", session.channelId)]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { Record } from "@mail/core/common/record";
|
||||
import { createLocalId } from "@mail/utils/common/misc";
|
||||
|
||||
export class RtcSession {
|
||||
export class RtcSession extends Record {
|
||||
// Server data
|
||||
channelId;
|
||||
channelMemberId;
|
||||
|
||||
@@ -13,7 +13,7 @@ threadActionsRegistry
|
||||
condition(component) {
|
||||
return (
|
||||
component.thread?.allowCalls &&
|
||||
component.thread !== component.rtc.state.channel &&
|
||||
!component.thread?.eq(component.rtc.state.channel) &&
|
||||
!component.props.chatWindow?.hidden
|
||||
);
|
||||
},
|
||||
@@ -43,7 +43,7 @@ threadActionsRegistry
|
||||
name: _t("Show Call Settings"),
|
||||
nameActive: _t("Hide Call Settings"),
|
||||
sequence(component) {
|
||||
return component.props.chatWindow && component.thread === component.rtc.state.channel
|
||||
return component.props.chatWindow && component.thread?.eq(component.rtc.state.channel)
|
||||
? 6
|
||||
: 60;
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div
|
||||
t-if="Object.keys(props.thread.rtcSessions).length > 0"
|
||||
class="fa fa-volume-up ms-1 me-3"
|
||||
t-att-class="{ 'text-danger': rtc.state.channel?.id === props.thread.id }"
|
||||
t-att-class="{ 'text-danger': props.thread.eq(rtc.state.channel) }"
|
||||
/>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -79,7 +79,7 @@ export class ChannelInvitation extends Component {
|
||||
}
|
||||
|
||||
onClickSelectablePartner(partner) {
|
||||
if (this.state.selectedPartners.includes(partner)) {
|
||||
if (partner.in(this.state.selectedPartners)) {
|
||||
const index = this.state.selectedPartners.indexOf(partner);
|
||||
if (index !== -1) {
|
||||
this.state.selectedPartners.splice(index, 1);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<t name="selectablePartnerDetail">
|
||||
<span class="flex-grow-1 mx-2 text-truncate text-start fs-6" t-esc="selectablePartner.name"/>
|
||||
</t>
|
||||
<input class="form-check-input flex-shrink-0" type="checkbox" t-att-checked="state.selectedPartners.includes(selectablePartner) ? 'checked' : undefined"/>
|
||||
<input class="form-check-input flex-shrink-0" type="checkbox" t-att-checked="selectablePartner.in(state.selectedPartners) ? 'checked' : undefined"/>
|
||||
</div>
|
||||
</t>
|
||||
<div t-if="state.selectablePartners.length === 0">No user found that is not already a member of this channel.</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ChannelMemberList extends Component {
|
||||
if (this.store.inPublicPage) {
|
||||
return false;
|
||||
}
|
||||
if (member.persona === this.store.self) {
|
||||
if (member.persona?.eq(this.store.self)) {
|
||||
return false;
|
||||
}
|
||||
if (member.persona.type === "guest") {
|
||||
|
||||
@@ -240,7 +240,7 @@ export class DiscussCoreCommon {
|
||||
res_id: channel.id,
|
||||
model: channel.model,
|
||||
});
|
||||
if (!channel.messages.includes(message)) {
|
||||
if (message.notIn(channel.messages)) {
|
||||
if (!channel.loadNewer) {
|
||||
channel.messages.push(message);
|
||||
} else if (channel.state === "loading") {
|
||||
@@ -254,13 +254,13 @@ export class DiscussCoreCommon {
|
||||
}
|
||||
if (message.isNeedaction) {
|
||||
const inbox = this.store.discuss.inbox;
|
||||
if (!inbox.messages.includes(message)) {
|
||||
if (message.notIn(inbox.messages)) {
|
||||
inbox.messages.push(message);
|
||||
if (notif.id > this.store.initBusId) {
|
||||
inbox.counter++;
|
||||
}
|
||||
}
|
||||
if (!channel.needactionMessages.includes(message)) {
|
||||
if (message.notIn(channel.needactionMessages)) {
|
||||
channel.needactionMessages.push(message);
|
||||
if (notif.id > this.store.initBusId) {
|
||||
channel.message_needaction_counter++;
|
||||
@@ -288,9 +288,8 @@ export class DiscussCoreCommon {
|
||||
!channel.loadNewer &&
|
||||
!message.isSelfAuthored &&
|
||||
channel.composer.isFocused &&
|
||||
channel.newestPersistentMessage &&
|
||||
!this.store.guest &&
|
||||
channel.newestPersistentMessage === channel.newestMessage
|
||||
channel.newestPersistentMessage?.eq(channel.newestMessage)
|
||||
) {
|
||||
this.threadService.markAsRead(channel);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ partnerCompareRegistry.add(
|
||||
"discuss.members",
|
||||
(p1, p2, { thread }) => {
|
||||
if (thread?.model === "discuss.channel") {
|
||||
const isMember1 = thread.channelMembers.some((member) => member.persona === p1);
|
||||
const isMember2 = thread.channelMembers.some((member) => member.persona === p2);
|
||||
const isMember1 = thread.channelMembers.some((member) => p1.eq(member.persona));
|
||||
const isMember2 = thread.channelMembers.some((member) => p2.eq(member.persona));
|
||||
if (isMember1 && !isMember2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export class DiscussCoreWeb {
|
||||
if (this.ui.isSmall || message.isSelfAuthored) {
|
||||
return;
|
||||
}
|
||||
if (channel.correspondent === this.store.odoobot && this.store.odoobotOnboarding) {
|
||||
if (channel.correspondent?.eq(this.store.odoobot) && this.store.odoobotOnboarding) {
|
||||
// this cancels odoobot onboarding auto-opening of chat window
|
||||
this.store.odoobotOnboarding = false;
|
||||
return;
|
||||
|
||||
@@ -28,7 +28,7 @@ export class PinnedMessagesPanel extends Component {
|
||||
this.messagePinService.fetchPinnedMessages(this.props.thread);
|
||||
});
|
||||
onWillUpdateProps(async (nextProps) => {
|
||||
if (nextProps.thread.id !== this.props.thread.id) {
|
||||
if (nextProps.thread.notEq(this.props.thread)) {
|
||||
this.messagePinService.fetchPinnedMessages(nextProps.thread);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ export class Typing {
|
||||
getTypingMembers(channel) {
|
||||
return [...(this.memberIdsByChannelId.get(channel.id) ?? new Set())]
|
||||
.map((id) => this.channelMemberService.insert({ id }))
|
||||
.filter((member) => member.persona !== this.storeService.self);
|
||||
.filter((member) => !member.persona?.eq(this.storeService.self));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -245,7 +245,7 @@ export function useMessageHighlight(duration = 2000) {
|
||||
* @param {import("@mail/core/thread_model").Thread} thread
|
||||
*/
|
||||
async highlightMessage(message, thread) {
|
||||
if (message.originThread.localId !== thread.localId) {
|
||||
if (thread.notEq(message.originThread)) {
|
||||
return;
|
||||
}
|
||||
await threadService.loadAround(thread, message.id);
|
||||
@@ -407,7 +407,7 @@ export function useMessageToReplyTo() {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isNotSelected(thread, message) {
|
||||
return this.thread === thread && this.message !== message;
|
||||
return thread.eq(this.thread) && message.notEq(this.message);
|
||||
},
|
||||
/**
|
||||
* @param {import("@mail/core/common/thread_model").Thread} thread
|
||||
@@ -415,7 +415,7 @@ export function useMessageToReplyTo() {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSelected(thread, message) {
|
||||
return this.thread === thread && this.message === message;
|
||||
return thread.eq(this.thread) && message.eq(this.message);
|
||||
},
|
||||
/** @type {import("@mail/core/common/message_model").Message|null} */
|
||||
message: null,
|
||||
@@ -426,7 +426,7 @@ export function useMessageToReplyTo() {
|
||||
* @param {import("@mail/core/common/message_model").Message} message
|
||||
*/
|
||||
toggle(thread, message) {
|
||||
if (this.message === message) {
|
||||
if (message.eq(this.message)) {
|
||||
this.cancel();
|
||||
} else {
|
||||
Object.assign(this, { message, thread });
|
||||
|
||||
Reference in New Issue
Block a user