[REF] mail: introduce Record.one() relational field on model.

This allow storing only the local id internally, and there are
automatic `get`/`set` to get the related record. This prepares
support of record deletion that would automatically delete
all relational fields, in a follow-up PR.

```js
class Message {
    author = one("Persona");
}
```

Relational fields can be used to uniquely identify records:

```js
class ChatWindow {
    static id = "thread";
    thread = one("Thread");
}
```

Part-of: odoo/odoo#134884
This commit is contained in:
Alexandre Kühn
2023-09-21 23:54:21 +00:00
parent 6ed7d952c4
commit 355b586970
32 changed files with 164 additions and 175 deletions
@@ -28,10 +28,10 @@ threadActionsRegistry.add("open-hr-profile", {
const orm = useService("orm");
const store = useService("mail.store");
let employeeId;
if (!component.thread?.correspondent?.employeeId && component.thread?.chatPartnerId) {
if (!component.thread?.correspondent?.employeeId && component.thread?.chatPartner) {
const employees = await orm.silent.searchRead(
"hr.employee",
[["user_partner_id", "=", component.thread.chatPartnerId]],
[["user_partner_id", "=", component.thread.chatPartner.id]],
["id"]
);
employeeId = employees[0]?.id;
@@ -39,7 +39,7 @@ export class Attachment extends Record {
id;
mimetype;
name;
originThreadLocalId;
originThread = Record.one("Thread");
res_name;
type;
/** @type {string} */
@@ -48,16 +48,10 @@ export class Attachment extends Record {
url;
/** @type {boolean} */
uploading;
/** @type {import("@mail/core/common/message_model").Message} */
message;
message = Record.one("Message");
/** @type {string} */
create_date;
/** @type {import("@mail/core/common/thread_model").Thread} */
get originThread() {
return this._store.Thread.records[this.originThreadLocalId];
}
get isDeletable() {
return true;
}
@@ -43,11 +43,10 @@ export class AttachmentService {
const threadData = Array.isArray(data.originThread)
? data.originThread[0][1]
: data.originThread;
this.store.Thread.insert({
attachment.originThread = this.store.Thread.insert({
model: threadData.model,
id: threadData.id,
});
attachment.originThreadLocalId = this.store.Thread.localId(threadData);
const thread = attachment.originThread;
if (attachment.notIn(thread.attachments)) {
thread.attachments.push(attachment);
@@ -59,7 +58,7 @@ export class AttachmentService {
/**
* Remove the given attachment globally.
*
* @param {Attachment} attachment
* @param {import("@mail/core/common/attachment_model").Attachment} attachment
*/
remove(attachment) {
if (attachment.tmpUrl) {
@@ -6,8 +6,8 @@ import { Record } from "@mail/core/common/record";
* @class ChannelMember
* @typedef Data
* @property {number} id
* @property {string} personaLocalId
* @property {number} threadId
* @property {import("@mail/core/common/persona_model").Persona} persona
* @property {import("@mail/core/common/thread_model").Thread} thread
*/
export class ChannelMember extends Record {
static id = "id";
@@ -34,25 +34,9 @@ export class ChannelMember extends Record {
/** @type {number} */
id;
personaLocalId;
rtcSessionId;
threadId;
get persona() {
return this._store.Persona.records[this.personaLocalId];
}
set persona(persona) {
this.personaLocalId = persona?.localId;
}
get rtcSession() {
return this._store.RtcSession.get(this.rtcSessionId);
}
get thread() {
return this._store.Thread.get({ model: "discuss.channel", id: this.threadId });
}
persona = Record.one("Persona");
rtcSession = Record.one("RtcSession");
thread = Record.one("Thread");
/**
* @returns {string}
@@ -25,13 +25,16 @@ export class ChannelMemberService {
channelId: memberData.persona.guest ? memberData.channel.id : null,
});
}
member.threadId = memberData.threadId ?? member.threadId ?? memberData.channel?.id;
if (member.threadId && !member.thread) {
this.store.Thread.insert({
id: member.threadId,
let thread = memberData.thread ?? member.thread;
if (!thread && memberData.channel?.id) {
thread = this.store.Thread.insert({
id: memberData.channel.id,
model: "discuss.channel",
});
}
if (thread && !member.thread) {
member.thread = thread;
}
switch (command) {
case "insert":
{
@@ -6,7 +6,7 @@
<div t-if="chatWindowService.hidden.length > 0 and !ui.isSmall" t-ref="hiddenMenu" class="o-mail-ChatWindow-hiddenMenuContainer position-fixed bottom-0">
<t t-call="mail.ChatWindowHiddenMenu"/>
</div>
<t t-foreach="chatWindowService.visible" t-as="chatWindow" t-key="chatWindow.threadLocalId">
<t t-foreach="chatWindowService.visible" t-as="chatWindow" t-key="chatWindow.thread?.localId">
<ChatWindow chatWindow="chatWindow" right="CHAT_WINDOW_END_GAP_WIDTH + chatWindow_index * (CHAT_WINDOW_WIDTH + CHAT_WINDOW_INBETWEEN_WIDTH * 2)"/>
</t>
</div>
@@ -21,7 +21,7 @@
</t>
<t t-set-slot="default">
<ul class="m-0 p-0 overflow-auto" role="menu" t-ref="list">
<li t-foreach="[...chatWindowService.hidden].reverse()" t-as="chatWindow" t-key="chatWindow.threadLocalId" class="o-mail-ChatWindowHiddenMenu-item dropdown-item p-0" t-att-class="{ 'border-bottom' : !chatWindow_last }" role="menuitem">
<li t-foreach="[...chatWindowService.hidden].reverse()" t-as="chatWindow" t-key="chatWindow.thread?.localId" class="o-mail-ChatWindowHiddenMenu-item dropdown-item p-0" t-att-class="{ 'border-bottom' : !chatWindow_last }" role="menuitem">
<ChatWindow chatWindow="chatWindow"/>
</li>
</ul>
@@ -8,7 +8,7 @@ import { _t } from "@web/core/l10n/translation";
/** @typedef {{ thread?: import("@mail/core/common/thread_model").Thread, folded?: boolean, replaceNewMessageChatWindow?: boolean }} ChatWindowData */
export class ChatWindow extends Record {
static id = "threadLocalId";
static id = "thread";
/** @type {ChatWindow[]} */
static records = [];
/** @returns {ChatWindow} */
@@ -24,7 +24,7 @@ export class ChatWindow extends Record {
* @returns {ChatWindow}
*/
static insert(data = {}) {
const chatWindow = this.records.find((c) => c.threadLocalId === data.thread?.localId);
const chatWindow = this.records.find((c) => c.thread?.eq(data.thread));
if (!chatWindow) {
const chatWindow = this.new(data);
Object.assign(chatWindow, { thread: data.thread });
@@ -57,20 +57,11 @@ export class ChatWindow extends Record {
return chatWindow;
}
/** @type {import("@mail/core/common/thread_model").Thread.localId} */
threadLocalId;
thread = Record.one("Thread");
autofocus = 0;
folded = false;
hidden = false;
get thread() {
return this._store.Thread.records[this.threadLocalId];
}
set thread(thread) {
this.threadLocalId = thread?.localId;
}
get displayName() {
return this.thread?.displayName ?? _t("New message");
}
@@ -7,7 +7,7 @@ import { OR, Record } from "@mail/core/common/record";
*/
export class Composer extends Record {
static id = OR("threadLocalId", "messageLocalId");
static id = OR("thread", "message");
/** @returns {Composer} */
static new(data) {
return super.new(data);
@@ -57,8 +57,7 @@ export class Composer extends Record {
/** @type {import("@mail/core/common/attachment_model").Attachment[]} */
attachments = [];
/** @type {import("@mail/core/common/message_model").Message.localId} */
messageLocalId;
message = Record.one("Message");
/** @type {RawMentions} */
rawMentions = {
partnerIds: new Set(),
@@ -68,8 +67,7 @@ export class Composer extends Record {
cannedResponseIds = new Set();
/** @type {string} */
textInputContent;
/** @type {import("@mail/core/common/thread_model").Thread.localId} */
threadLocalId;
thread = Record.one("Thread");
/** @type {{ start: number, end: number, direction: "forward" | "backward" | "none"}}*/
selection = {
start: 0,
@@ -79,26 +77,6 @@ export class Composer extends Record {
/** @type {boolean} */
forceCursorMove;
isFocused = false;
/** @type {import("@mail/core/common/message_model").Message} */
get message() {
return this._store.Message.records[this.messageLocalId];
}
/** @param {import("@mail/core/common/message_model").Message} */
set message(newMessage) {
this.messageLocalId = newMessage?.localId;
}
/** @type {import("@mail/core/common/thread_model").Thread} */
get thread() {
return this._store.Thread.records[this.threadLocalId];
}
/** @param {import("@mail/core/common/thread_model").Thread} */
set thread(newThread) {
this.threadLocalId = newThread?.localId;
}
}
Composer.register();
@@ -37,14 +37,12 @@ export class Follower extends Record {
return follower;
}
/** @type {import("@mail/core/common/thread_model").Thread} */
followedThread;
followedThread = Record.one("Thread");
/** @type {number} */
id;
/** @type {boolean} */
isActive;
/** @type {import("@mail/core/common/persona_model").Persona} */
partner;
partner = Record.one("Persona");
/**
* @returns {boolean}
@@ -23,7 +23,7 @@ export class LinkPreviewConfirmDelete extends Component {
}
get message() {
return this.store.Message.get(this.props.linkPreview.message_id);
return this.props.linkPreview.message;
}
onClickOk() {
@@ -18,6 +18,8 @@ export class LinkPreview extends Record {
*/
static insert(data) {
const message = this.store.Message.get(data.message_id);
data.message = message;
delete data.message_id;
let linkPreview = message?.linkPreviews.find((lp) => lp.id === data.id);
if (linkPreview) {
return Object.assign(linkPreview, data);
@@ -30,8 +32,7 @@ export class LinkPreview extends Record {
/** @type {number} */
id;
/** @type {number} */
message_id;
message = Record.one("Message");
/** @type {string} */
image_mimetype;
/** @type {string} */
@@ -42,8 +42,7 @@ export class Message extends Record {
/** @type {Object[]} */
attachments = [];
/** @type {import("@mail/core/common/persona_model").Persona} */
author;
author = Record.one("Persona");
/** @type {string} */
body;
/** @type {string} */
@@ -64,8 +63,7 @@ export class Message extends Record {
needaction_partner_ids = [];
/** @type {number[]} */
history_partner_ids = [];
/** @type {Message|undefined} */
parentMessage;
parentMessage = Record.one("Message");
/** @type {MessageReactions[]} */
reactions = [];
/** @type {import("@mail/core/common/notification_model").Notification[]} */
@@ -3,7 +3,7 @@
import { AND, Record } from "@mail/core/common/record";
export class MessageReactions extends Record {
static id = AND("messageId", "content");
static id = AND("message", "content");
/** @returns {MessageReactions} */
static new(data) {
return super.new(data);
@@ -48,7 +48,7 @@ export class MessageReactions extends Record {
Object.assign(reaction, {
count: data.count,
content: data.content,
messageId: data.message.id,
message: data.message,
personaLocalIds: reaction.personaLocalIds.filter(
(localId) => !personasToUnlink.has(localId)
),
@@ -62,8 +62,7 @@ export class MessageReactions extends Record {
count;
/** @type {number[]} */
personaLocalIds = [];
/** @type {number} */
messageId;
message = Record.one("Message");
/** @type {import("@mail/core/common/persona_model").Persona[]} */
get personas() {
@@ -182,7 +182,7 @@ export class MessageService {
{
action: "remove",
content: reaction.content,
message_id: reaction.messageId,
message_id: reaction.message.id,
},
{ silent: true }
);
@@ -284,7 +284,7 @@ export class MessageService {
replaceArrayWithCompare(
message.notifications,
notifications.map((notification) =>
this.store.Notification.insert({ ...notification, messageId: message.id })
this.store.Notification.insert({ ...notification, message })
)
);
replaceArrayWithCompare(
@@ -342,7 +342,7 @@ export class MessageService {
updateNotification(notification, data) {
Object.assign(notification, {
messageId: data.messageId,
message: data.message,
notification_status: data.notification_status,
notification_type: data.notification_type,
failure_type: data.failure_type,
@@ -390,10 +390,10 @@ export class MessageService {
group.notifications = group.notifications.filter(
({ id }) => !notificationIdsToRemove.has(id)
);
group.lastMessageId = group.notifications[0]?.message.id;
group.lastMessage = group.notifications[0]?.message;
for (const notification of group.notifications) {
if (group.lastMessageId < notification.message.id) {
group.lastMessageId = notification.message.id;
if (group.lastMessage?.id < notification.message?.id) {
group.lastMessage = notification.message;
}
}
group.resIds.add(data.resId);
@@ -51,8 +51,7 @@ export class NotificationGroup extends Record {
modelName;
/** @type {string} */
resModel;
/** @type {number} */
lastMessageId;
lastMessage = Record.one("Message");
/** @type {Set<number>} */
resIds = new Set();
/** @type {'sms' | 'email'} */
@@ -66,10 +65,6 @@ export class NotificationGroup extends Record {
return _t("An error occurred when sending an email");
}
get lastMessage() {
return this._store.Message.get(this.lastMessageId);
}
get datetime() {
return this.lastMessage?.datetime;
}
@@ -29,20 +29,14 @@ export class Notification extends Record {
/** @type {number} */
id;
/** @type {number} */
messageId;
message = Record.one("Message");
/** @type {string} */
notification_status;
/** @type {string} */
notification_type;
/** @type {string} */
failure_type;
/** @type {import("@mail/core/common/persona_model").Persona} */
persona;
get message() {
return this._store.Message.get(this.messageId);
}
persona = Record.one("Persona");
get isFailure() {
return ["exception", "bounce"].includes(this.notification_status);
@@ -35,8 +35,6 @@ export class Persona extends Record {
return persona;
}
/** @type {string} */
localId;
/** @type {number} */
id;
/** @type {boolean | undefined} */
@@ -110,7 +110,7 @@ export class PersonaService {
}
return b.lastInterestDateTime.ts - a.lastInterestDateTime.ts;
})
.map((thread) => thread.chatPartnerId);
.map((thread) => thread.chatPartner?.id);
}
}
+22 -2
View File
@@ -5,6 +5,7 @@ import { registry } from "@web/core/registry";
export const modelRegistry = registry.category("discuss.model");
const ONE_SYM = Symbol("one");
const OR_SYM = Symbol("or");
const AND_SYM = Symbol("and");
@@ -31,7 +32,7 @@ export class Record {
}
static localId(data) {
let idStr;
if (typeof data === "object") {
if (typeof data === "object" && data !== null) {
idStr = this._localId(this.id, data);
} else {
idStr = data; // non-object data => single id
@@ -40,6 +41,10 @@ export class Record {
}
static _localId(expr, data, { brackets = false } = {}) {
if (!Array.isArray(expr)) {
if (this.Class.__rels__.has(expr)) {
// relational field (note: optional when OR)
return `(${data[expr]?.localId})`;
}
return data[expr];
}
const vals = [];
@@ -79,13 +84,28 @@ export class Record {
}
return record;
}
/**
* @template {keyof import("model ").Models} M
* @param {M} modelName
* @returns {import("models").Models[M]}
*/
static one(modelName) {
return ONE_SYM;
}
/**
* @param {Object} data
* @returns {Record}
*/
static insert(data) {}
/**
* Raw relational values of the record, each of which contains object id(s)
* rather than the record(s). This allows data in store and models being normalized,
* which eases handling relations notably in when a record gets deleted.
*
* @type {Map<string, any>}
*/
__rels__ = new Map();
/** @type {import("@mail/core/common/store_service").Store} */
_store;
/**
@@ -7,7 +7,7 @@ import { reactive } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { debounce } from "@web/core/utils/timing";
import { modelRegistry } from "./record";
import { modelRegistry, Record } from "./record";
export class Store {
/** @type {typeof import("@mail/core/web/activity_model").Activity} */
@@ -187,9 +187,43 @@ export const storeService = {
// work-around: make an object whose prototype is the class, so that static props become
// instance props.
const entry = Object.assign(Object.create(Model), { env, store: res });
entry.Class = Model;
// Produce another class with changed prototype, so that there are automatic get/set on relational fields
const Class = {
[Model.name]: class extends Model {
static __rels__ = new Set();
constructor() {
super();
for (const name of this.constructor.__rels__) {
// Relational fields contain symbols for detection in original class.
// This constructor is called on genuine records:
// - 'one' fields => undefined
const newVal = undefined;
this[name] = newVal;
this.__rels__.set(name, newVal);
}
}
},
}[Model.name];
entry.Class = Class;
entry.records = JSON.parse(JSON.stringify(Model.records));
res[name] = entry;
// Detect relational fields with a dummy record and setup getter/setters on them
const obj = new Model();
for (const [name, val] of Object.entries(obj)) {
if (val !== Record.one()) {
continue;
}
Class.__rels__.add(name);
Object.defineProperty(Class.prototype, name, {
get() {
const localId = this.__rels__.get(name);
return res.get(localId);
},
set(record) {
this.__rels__.set(name, record?.localId);
},
});
}
}
onChange(res.Thread, "records", () => res.updateBusSubscription());
services.ui.bus.addEventListener("resize", () => {
@@ -24,6 +24,6 @@ export class ThreadIcon extends Component {
}
get chatPartner() {
return this.store.Persona.get({ type: "partner", id: this.props.thread.chatPartnerId });
return this.props.thread.chatPartner;
}
}
@@ -101,21 +101,18 @@ export class Thread extends Record {
areAttachmentsLoaded = false;
/** @type {import("@mail/core/common/attachment_model").Attachment[]} */
attachments = [];
/** @type {integer} */
activeRtcSessionId;
activeRtcSession = Record.one("RtcSession");
/** @type {object|undefined} */
channel;
/** @type {import("@mail/core/common/channel_member_model").ChannelMember[]} */
channelMembers = [];
/** @type {Object<number, import("@mail/discuss/call/common/rtc_session_model").RtcSession>} */
rtcSessions = {};
invitingRtcSessionId;
rtcInvitingSession = Record.one("RtcSession");
/** @type {Set<number>} */
invitedMemberIds = new Set();
/** @type {integer} */
chatPartnerId;
/** @type {import("@mail/core/common/composer_model").Composer} */
composer;
chatPartner = Record.one("Persona");
composer = Record.one("Composer");
counter = 0;
/** @type {string} */
customName;
@@ -123,8 +120,7 @@ export class Thread extends Record {
description;
/** @type {Set<import("@mail/core/common/follower_model").Follower>} */
followers = new Set();
/** @type {import("@mail/core/common/follower_model").Follower} */
selfFollower;
selfFollower = Record.one("Follower");
/** @type {integer|undefined} */
followersCount;
isAdmin = false;
@@ -133,8 +129,7 @@ export class Thread extends Record {
isLoadingAttachments = false;
isLoadedDeferred = new Deferred();
isLoaded = false;
/** @type {import("@mail/core/common/attachment_model").Attachment} */
mainAttachment;
mainAttachment = Record.one("Attachment");
memberCount = 0;
message_needaction_counter = 0;
message_unread_counter = 0;
@@ -197,8 +192,7 @@ export class Thread extends Record {
canPostOnReadonly;
/** @type {String} */
last_interest_dt;
/** @type {number} */
lastServerMessageId;
lastServerMessage = Record.one("Message");
/** @type {Boolean} */
is_editable;
@@ -211,14 +205,6 @@ export class Thread extends Record {
});
}
get activeRtcSession() {
return this._store.RtcSession.get(this.activeRtcSessionId);
}
set activeRtcSession(session) {
this.activeRtcSessionId = session?.id;
}
get areAllMembersLoaded() {
return this.memberCount === this.channelMembers.length;
}
@@ -276,12 +262,8 @@ export class Thread extends Record {
}
get displayName() {
if (this.type === "chat" && this.chatPartnerId) {
return (
this.customName ||
this._store.Persona.get({ type: "partner", id: this.chatPartnerId })
.nameOrDisplayName
);
if (this.type === "chat" && this.chatPartner) {
return this.customName || this.chatPartner.nameOrDisplayName;
}
if (this.type === "group" && !this.name) {
const listFormatter = new Intl.ListFormat(
@@ -471,10 +453,6 @@ export class Thread extends Record {
return this.memberCount - this.channelMembers.length;
}
get rtcInvitingSession() {
return this._store.RtcSession.get(this.invitingRtcSessionId);
}
get hasNeedactionMessages() {
return this.needactionMessages.length > 0;
}
@@ -78,7 +78,7 @@ export class ThreadService {
thread.memberCount = results["memberCount"];
for (const channelMember of channelMembers) {
if (channelMember.persona || channelMember.partner) {
this.store.ChannelMember.insert({ ...channelMember, threadId: thread.id });
this.store.ChannelMember.insert({ ...channelMember, thread });
}
}
}
@@ -533,7 +533,7 @@ export class ThreadService {
return;
}
return Object.values(this.store.Thread.records).find(
(thread) => thread.type === "chat" && thread.chatPartnerId === partner.id
(thread) => thread.type === "chat" && thread.chatPartner?.eq(partner)
);
}
@@ -707,7 +707,12 @@ export class ThreadService {
if (serverData.channel && "message_unread_counter" in serverData.channel) {
thread.message_unread_counter = serverData.channel.message_unread_counter;
}
thread.lastServerMessageId = serverData.last_message_id ?? thread.lastServerMessageId;
const lastServerMessageId = serverData.last_message_id ?? thread.lastServerMessage?.id;
if (thread.lastServerMessage?.id !== lastServerMessageId) {
thread.lastServerMessage = this.store.Message.insert({
id: lastServerMessageId,
});
}
if (thread.model === "discuss.channel" && serverData.channel) {
nullifyClearCommands(serverData.channel);
thread.channel = assignDefined(thread.channel ?? {}, serverData.channel);
@@ -730,7 +735,7 @@ export class ThreadService {
(serverData.channel.channelMembers[0][1].length === 1 &&
member.persona?.eq(thread._store.user))
) {
thread.chatPartnerId = member.persona.id;
thread.chatPartner = member.persona;
}
}
}
@@ -166,9 +166,7 @@ patch(ThreadService.prototype, {
thread.suggestedRecipients = recipients;
},
async leaveChannel(channel) {
const chatWindow = this.store.ChatWindow.records.find(
(c) => c.threadLocalId === channel.localId
);
const chatWindow = this.store.ChatWindow.records.find((c) => c.thread?.eq(channel));
if (chatWindow) {
this.chatWindowService.close(chatWindow);
}
@@ -249,9 +247,7 @@ patch(ThreadService.prototype, {
follower.delete();
},
unpin(thread) {
const chatWindow = this.store.ChatWindow.records.find(
(c) => c.threadLocalId === thread.localId
);
const chatWindow = this.store.ChatWindow.records.find((c) => c.thread?.eq(thread));
if (chatWindow) {
this.chatWindowService.close(chatWindow);
}
@@ -290,7 +290,9 @@ export class Rtc {
thread,
record: data.rtc_inviting_session,
});
thread.invitingRtcSessionId = data.rtc_inviting_session.id;
thread.rtcInvitingSession = this.store.RtcSession.insert({
id: data.rtc_inviting_session.id,
});
if (!this.store.ringingThreads.includes(thread.localId)) {
this.store.ringingThreads.push(thread.localId);
}
@@ -298,7 +300,7 @@ export class Rtc {
if ("rtcInvitingSession" in data) {
if (Array.isArray(data.rtcInvitingSession)) {
if (data.rtcInvitingSession[0][0] === "unlink") {
thread.invitingRtcSessionId = undefined;
thread.rtcInvitingSession = undefined;
removeFromArray(this.store.ringingThreads, thread.localId);
}
return;
@@ -307,7 +309,9 @@ export class Rtc {
thread,
record: data.rtcInvitingSession,
});
thread.invitingRtcSessionId = data.rtcInvitingSession.id;
thread.rtcInvitingSession = this.store.RtcSession.insert({
id: data.rtcInvitingSession.id,
});
this.store.ringingThreads.push(thread.localId);
}
if ("rtcSessions" in data) {
@@ -29,7 +29,7 @@ export class RtcSession extends Record {
}
if (channelMember) {
const channelMemberRecord = this.store.ChannelMember.insert(channelMember);
channelMemberRecord.rtcSessionId = session.id;
channelMemberRecord.rtcSession = session;
session.channelMemberId = channelMemberRecord.id;
if (channelMemberRecord.thread) {
channelMemberRecord.thread.rtcSessions[session.id] = session;
@@ -103,7 +103,7 @@ export class ChannelInvitation extends Component {
async onClickInvite() {
if (this.props.thread.type === "chat") {
await this.discussCoreCommonService.startChat([
this.props.thread.chatPartnerId,
this.props.thread.chatPartner?.id,
...this.state.selectedPartners.map((partner) => partner.id),
]);
} else {
@@ -120,13 +120,13 @@ export class ChannelInvitation extends Component {
} else if (this.props.thread.type === "group") {
return _t("Invite to Group Chat");
} else if (this.props.thread.type === "chat") {
if (this.props.thread.chatPartnerId === this.store.self.id) {
if (this.props.thread.chatPartner?.eq(this.store.self)) {
if (this.state.selectedPartners.length === 0) {
return _t("Invite");
}
if (this.state.selectedPartners.length === 1) {
const alreadyChat = Object.values(this.store.Thread.records).some(
(thread) => thread.chatPartnerId === this.state.selectedPartners[0].id
const alreadyChat = Object.values(this.store.Thread.records).some((thread) =>
thread.chatPartner?.eq(this.state.selectedPartners[0])
);
if (alreadyChat) {
return _t("Go to conversation");
@@ -269,7 +269,7 @@ export class DiscussCoreCommon {
}
}
}
if (channel.chatPartnerId !== this.store.odoobot?.id) {
if (!channel.chatPartner?.eq(this.store.odoobot)) {
if (
!this.presence.isOdooFocused() &&
channel.isChatChannel &&
@@ -15,7 +15,7 @@ patch(Thread.prototype, {
);
}
if (this.type === "chat") {
return `/web/image/res.partner/${this.chatPartnerId}/avatar_128`;
return `/web/image/res.partner/${this.chatPartner.id}/avatar_128`;
}
return super.imgUrl;
},
@@ -44,14 +44,14 @@ commandProviderRegistry.add("mail.partner", {
if (!options.searchValue) {
mentionedChannels.slice(0, 3).map((channel) => {
if (channel.type === "chat") {
displayedPartnerIds.add(channel.chatPartnerId);
displayedPartnerIds.add(channel.chatPartner.id);
}
commands.push({
Component: DiscussCommand,
async action() {
switch (channel.type) {
case "chat":
threadService.openChat({ partnerId: channel.chatPartnerId });
threadService.openChat({ partnerId: channel.chatPartner.id });
break;
case "group":
threadService.open(channel);
@@ -90,8 +90,8 @@ export class DiscussCoreWeb {
if (data.serverFoldState && thread && data.serverFoldState !== thread.state) {
thread.state = data.serverFoldState;
if (thread.state === "closed") {
const chatWindow = this.store.ChatWindow.records.find(
(chatWindow) => chatWindow.threadLocalId === thread.localId
const chatWindow = this.store.ChatWindow.records.find((chatWindow) =>
chatWindow.thread?.eq(thread)
);
if (chatWindow) {
this.chatWindowService.close(chatWindow);
+20
View File
@@ -0,0 +1,20 @@
/* add this file in jsconfig.json, in typeRoots array */
declare module "models" {
import { Attachment } from "@mail/core/common/attachment_model";
import { Composer } from "@mail/core/common/composer_model";
import { Follower } from "@mail/core/common/follower_model";
import { Message } from "@mail/core/common/message_model";
import { Persona } from "@mail/core/common/persona_model";
import { RtcSession } from "@mail/discuss/call/common/rtc_session_model";
import { Thread } from "@mail/core/common/thread_model";
export interface Models {
"Attachment": Attachment,
"Composer": Composer,
"Follower": Follower,
"Message": Message,
"Persona": Persona,
"RtcSession": RtcSession,
"Thread": Thread,
}
}