diff --git a/addons/mail/static/src/core/common/messaging_service.js b/addons/mail/static/src/core/common/messaging_service.js index 10e1288e780..72a1a9e9c61 100644 --- a/addons/mail/static/src/core/common/messaging_service.js +++ b/addons/mail/static/src/core/common/messaging_service.js @@ -27,8 +27,6 @@ export class Messaging { this.store = services["mail.store"]; this.rpc = services.rpc; this.orm = services.orm; - /** @type {import("@mail/core/common/channel_member_service").ChannelMemberService} */ - this.channelMemberService = services["discuss.channel.member"]; /** @type {import("@mail/core/common/attachment_service").AttachmentService} */ this.attachmentService = services["mail.attachment"]; this.notificationService = services.notification; @@ -40,11 +38,8 @@ export class Messaging { this.messageService = services["mail.message"]; /** @type {import("@mail/core/common/persona_service").PersonaService} */ this.personaService = services["mail.persona"]; - /** @type {import("@mail/core/common/out_of_focus_service").OutOfFocusService} */ - this.outOfFocusService = services["mail.out_of_focus"]; this.router = services.router; this.bus = services.bus_service; - this.presence = services.presence; this.isReady = new Deferred(); this.imStatusService = services.im_status; const user = services.user; @@ -158,14 +153,6 @@ export class Messaging { // ------------------------------------------------------------------------- handleNotification(notifications) { - const channelsLeft = new Set( - notifications.reduce((channelIds, notification) => { - if (notification.type === "discuss.channel/leave") { - channelIds.push(notification.payload.id); - } - return channelIds; - }, []) - ); for (const notif of notifications) { switch (notif.type) { case "mail.activity/updated": @@ -176,17 +163,6 @@ export class Messaging { this.store.activityCounter--; } break; - case "discuss.channel/new_message": - if (channelsLeft.has(notif.payload.id)) { - // Do not handle new message notification if the channel - // was just left. This issue occurs because the - // "discuss.channel/leave" and the - // "discuss.channel/new_message" notifications come from - // the bus as a batch. - break; - } - this._handleNotificationNewMessage(notif); - break; case "discuss.channel/leave": { const thread = this.threadService.insert({ @@ -446,87 +422,6 @@ export class Messaging { } } - async _handleNotificationNewMessage(notif) { - const { id, message: messageData } = notif.payload; - let channel = this.store.threads[createLocalId("discuss.channel", id)]; - if (!channel || !channel.type) { - const [channelData] = await this.rpc("/discuss/channel/info", { channel_id: id }); - channel = this.threadService.insert({ - model: "discuss.channel", - type: channelData.channel.channel_type, - ...channelData, - }); - } - if (!channel.is_pinned) { - this.threadService.pin(channel); - } - - removeFromArrayWithPredicate(channel.messages, ({ id }) => id === messageData.temporary_id); - delete this.store.messages[messageData.temporary_id]; - messageData.temporary_id = null; - if ("parentMessage" in messageData && messageData.parentMessage.body) { - messageData.parentMessage.body = markup(messageData.parentMessage.body); - } - const data = Object.assign(messageData, { - body: markup(messageData.body), - }); - const message = this.messageService.insert({ - ...data, - res_id: channel.id, - model: channel.model, - }); - if (!channel.messages.includes(message)) { - if (!channel.loadNewer) { - channel.messages.push(message); - } else if (channel.state === "loading") { - channel.pendingNewMessages.push(message); - } - if (message.isSelfAuthored) { - channel.seen_message_id = message.id; - } else { - if (notif.id > this.store.initBusId) { - channel.message_unread_counter++; - } - if (message.isNeedaction) { - const inbox = this.store.discuss.inbox; - if (!inbox.messages.includes(message)) { - inbox.messages.push(message); - if (notif.id > this.store.initBusId) { - inbox.counter++; - } - } - if (!channel.needactionMessages.includes(message)) { - channel.needactionMessages.push(message); - if (notif.id > this.store.initBusId) { - channel.message_needaction_counter++; - } - } - } - } - } - if (channel.chatPartnerId !== this.store.odoobot?.id) { - if (!this.presence.isOdooFocused() && channel.isChatChannel) { - this.outOfFocusService.notify(message, channel); - } - - if (channel.type !== "channel" && !this.store.guest) { - // disabled on non-channel threads and - // on "channel" channels for performance reasons - this.threadService.markAsFetched(channel); - } - } - if ( - !channel.loadNewer && - !message.isSelfAuthored && - channel.composer.isFocused && - channel.newestPersistentMessage && - !this.store.guest && - channel.newestPersistentMessage === channel.newestMessage - ) { - this.threadService.markAsRead(channel); - } - } - _handleNotificationRecordInsert(notif) { if (notif.payload.Thread) { this.threadService.insert(notif.payload.Thread); @@ -652,7 +547,6 @@ export class Messaging { export const messagingService = { dependencies: [ "mail.store", - "discuss.channel.member", "rpc", "orm", "user", @@ -660,13 +554,11 @@ export const messagingService = { "bus_service", "im_status", "notification", - "presence", "mail.attachment", "mail.user_settings", "mail.thread", "mail.message", "mail.persona", - "mail.out_of_focus", ], start(env, services) { const messaging = new Messaging(env, services); diff --git a/addons/mail/static/src/core/web/messaging_service_patch.js b/addons/mail/static/src/core/web/messaging_service_patch.js index 9025f0d1b66..52fd4b37325 100644 --- a/addons/mail/static/src/core/web/messaging_service_patch.js +++ b/addons/mail/static/src/core/web/messaging_service_patch.js @@ -1,7 +1,6 @@ /* @odoo-module */ import { Messaging, messagingService } from "@mail/core/common/messaging_service"; -import { createLocalId } from "@mail/utils/common/misc"; import { _t } from "@web/core/l10n/translation"; import { patch } from "@web/core/utils/patch"; @@ -42,18 +41,6 @@ patch(Messaging.prototype, "mail/core/web", { } this._super(data); }, - async _handleNotificationNewMessage(notif) { - await this._super(notif); - const channel = this.store.threads[createLocalId("discuss.channel", notif.payload.id)]; - const message = this.store.messages[notif.payload.message.id]; - if ( - !this.ui.isSmall && - channel.correspondent !== this.store.odoobot && - !message.isSelfAuthored - ) { - this.chatWindowService.insert({ thread: channel }); - } - }, }); patch(messagingService, "mail/core/web", { diff --git a/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js b/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js new file mode 100644 index 00000000000..eadd2ff4eae --- /dev/null +++ b/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js @@ -0,0 +1,152 @@ +/* @odoo-module */ + +import { removeFromArrayWithPredicate } from "@mail/utils/common/arrays"; +import { createLocalId } from "@mail/utils/common/misc"; + +import { markup, reactive } from "@odoo/owl"; + +import { registry } from "@web/core/registry"; + +export class DiscussCoreCommon { + constructor(env, services) { + Object.assign(this, { + busService: services.bus_service, + env, + presence: services.presence, + rpc: services.rpc, + }); + /** @type {import("@mail/core/common/message_service").MessageService} */ + this.messageService = services["mail.message"]; + /** @type {import("@mail/core/common/messaging_service").Messaging} */ + this.messagingService = services["mail.messaging"]; + /** @type {import("@mail/core/common/out_of_focus_service").OutOfFocusService} */ + this.outOfFocusService = services["mail.out_of_focus"]; + /** @type {import("@mail/core/common/thread_service").ThreadService} */ + this.threadService = services["mail.thread"]; + /** @type {import("@mail/core/common/store_service").Store} */ + this.store = services["mail.store"]; + } + + setup() { + this.messagingService.isReady.then(() => { + this.busService.addEventListener("notification", ({ detail: notifications }) => { + // Do not handle new message notification if the channel was just left. This issue + // occurs because the "discuss.channel/leave" and the "discuss.channel/new_message" + // notifications come from the bus as a batch. + const channelsLeft = new Set( + notifications + .filter(({ type }) => type === "discuss.channel/leave") + .map(({ payload }) => payload.id) + ); + for (const notif of notifications.filter( + ({ payload, type }) => + type === "discuss.channel/new_message" && !channelsLeft.has(payload.id) + )) { + this._handleNotificationNewMessage(notif); + } + }); + this.busService.start(); + }); + } + + async _handleNotificationNewMessage(notif) { + const { id, message: messageData } = notif.payload; + let channel = this.store.threads[createLocalId("discuss.channel", id)]; + if (!channel || !channel.type) { + const [channelData] = await this.rpc("/discuss/channel/info", { channel_id: id }); + channel = this.threadService.insert({ + model: "discuss.channel", + type: channelData.channel.channel_type, + ...channelData, + }); + } + if (!channel.is_pinned) { + this.threadService.pin(channel); + } + removeFromArrayWithPredicate(channel.messages, ({ id }) => id === messageData.temporary_id); + delete this.store.messages[messageData.temporary_id]; + messageData.temporary_id = null; + if ("parentMessage" in messageData && messageData.parentMessage.body) { + messageData.parentMessage.body = markup(messageData.parentMessage.body); + } + const data = Object.assign(messageData, { + body: markup(messageData.body), + }); + const message = this.messageService.insert({ + ...data, + res_id: channel.id, + model: channel.model, + }); + if (!channel.messages.includes(message)) { + if (!channel.loadNewer) { + channel.messages.push(message); + } else if (channel.state === "loading") { + channel.pendingNewMessages.push(message); + } + if (message.isSelfAuthored) { + channel.seen_message_id = message.id; + } else { + if (notif.id > this.store.initBusId) { + channel.message_unread_counter++; + } + if (message.isNeedaction) { + const inbox = this.store.discuss.inbox; + if (!inbox.messages.includes(message)) { + inbox.messages.push(message); + if (notif.id > this.store.initBusId) { + inbox.counter++; + } + } + if (!channel.needactionMessages.includes(message)) { + channel.needactionMessages.push(message); + if (notif.id > this.store.initBusId) { + channel.message_needaction_counter++; + } + } + } + } + } + if (channel.chatPartnerId !== this.store.odoobot?.id) { + if (!this.presence.isOdooFocused() && channel.isChatChannel) { + this.outOfFocusService.notify(message, channel); + } + + if (channel.type !== "channel" && !this.store.guest) { + // disabled on non-channel threads and + // on "channel" channels for performance reasons + this.threadService.markAsFetched(channel); + } + } + if ( + !channel.loadNewer && + !message.isSelfAuthored && + channel.composer.isFocused && + channel.newestPersistentMessage && + !this.store.guest && + channel.newestPersistentMessage === channel.newestMessage + ) { + this.threadService.markAsRead(channel); + } + this.env.bus.trigger("discuss.channel/new_message", { channel, message }); + } +} + +export const discussCoreCommon = { + dependencies: [ + "bus_service", + "mail.message", + "mail.messaging", + "mail.out_of_focus", + "mail.store", + "mail.thread", + "presence", + "rpc", + ], + start(env, services) { + const discussCoreCommon = reactive(new DiscussCoreCommon(env, services)); + discussCoreCommon.setup(); + return discussCoreCommon; + }, +}; + +registry.category("services").add("discuss.core.common", discussCoreCommon); diff --git a/addons/mail/static/src/discuss/core/web/discuss_core_web_service.js b/addons/mail/static/src/discuss/core/web/discuss_core_web_service.js new file mode 100644 index 00000000000..c4ec289391f --- /dev/null +++ b/addons/mail/static/src/discuss/core/web/discuss_core_web_service.js @@ -0,0 +1,41 @@ +/* @odoo-module */ + +import { reactive } from "@odoo/owl"; + +import { registry } from "@web/core/registry"; + +export class DiscussCoreWeb { + constructor(env, services) { + Object.assign(this, { env, ui: services.ui }); + /** @type {import("@mail/core/common/chat_window_service").ChatWindowService} */ + this.chatWindowService = services["mail.chat_window"]; + /** @type {import("@mail/core/common/store_service").Store} */ + this.store = services["mail.store"]; + } + + setup() { + this.env.bus.addEventListener( + "discuss.channel/new_message", + ({ detail: { channel, message } }) => { + if ( + !this.ui.isSmall && + channel.correspondent !== this.store.odoobot && + !message.isSelfAuthored + ) { + this.chatWindowService.insert({ thread: channel }); + } + } + ); + } +} + +export const discussCoreWeb = { + dependencies: ["mail.chat_window", "mail.store", "ui"], + start(env, services) { + const discussCoreWeb = reactive(new DiscussCoreWeb(env, services)); + discussCoreWeb.setup(); + return discussCoreWeb; + }, +}; + +registry.category("services").add("discuss.core.web", discussCoreWeb); diff --git a/addons/mail/static/tests/helpers/discuss/core/common/setup_manager_patch.js b/addons/mail/static/tests/helpers/discuss/core/common/setup_manager_patch.js new file mode 100644 index 00000000000..d12c5599edc --- /dev/null +++ b/addons/mail/static/tests/helpers/discuss/core/common/setup_manager_patch.js @@ -0,0 +1,15 @@ +/* @odoo-module */ + +import { discussCoreCommon } from "@mail/discuss/core/common/discuss_core_common_service"; +import { setupManager } from "@mail/../tests/helpers/webclient_setup"; + +import { patch } from "@web/core/utils/patch"; + +patch(setupManager, "discuss/core/common", { + setupServices(...args) { + return { + ...this._super(...args), + "discuss.core.common": discussCoreCommon, + }; + }, +}); diff --git a/addons/mail/static/tests/helpers/discuss/core/web/setup_manager_patch.js b/addons/mail/static/tests/helpers/discuss/core/web/setup_manager_patch.js new file mode 100644 index 00000000000..08cf8a8ef00 --- /dev/null +++ b/addons/mail/static/tests/helpers/discuss/core/web/setup_manager_patch.js @@ -0,0 +1,15 @@ +/* @odoo-module */ + +import { discussCoreWeb } from "@mail/discuss/core/web/discuss_core_web_service"; +import { setupManager } from "@mail/../tests/helpers/webclient_setup"; + +import { patch } from "@web/core/utils/patch"; + +patch(setupManager, "discuss/core/web", { + setupServices(...args) { + return { + ...this._super(...args), + "discuss.core.web": discussCoreWeb, + }; + }, +});