From b9914a9bcf8be59eeec9523c5ba728e95f19306b Mon Sep 17 00:00:00 2001 From: Du-ma Date: Thu, 17 Apr 2025 16:37:26 +0300 Subject: [PATCH] [MIG] web_send_message_popup: Migration to 18.0 --- web_send_message_popup/__manifest__.py | 6 +- .../src/chatter/web/chatter_patch.esm.js | 183 ++++++++++++++++++ .../static/src/models/chatter/chatter.esm.js | 62 ------ 3 files changed, 185 insertions(+), 66 deletions(-) create mode 100644 web_send_message_popup/static/src/chatter/web/chatter_patch.esm.js delete mode 100644 web_send_message_popup/static/src/models/chatter/chatter.esm.js diff --git a/web_send_message_popup/__manifest__.py b/web_send_message_popup/__manifest__.py index 934c6733f..3283b74cd 100644 --- a/web_send_message_popup/__manifest__.py +++ b/web_send_message_popup/__manifest__.py @@ -2,14 +2,12 @@ # License AGPL-3.0 or later (http://gnu.org/licenses/agpl). { "name": "Web Send Message as Popup", - "version": "16.0.1.0.0", + "version": "18.0.1.0.0", "author": "Camptocamp, Odoo Community Association (OCA)", "maintainer": "Camptocamp", "license": "AGPL-3", "category": "Hidden", "depends": ["web", "mail"], "website": "https://github.com/OCA/web", - "assets": { - "web.assets_backend": ["web_send_message_popup/static/src/models/**/*.js"] - }, + "assets": {"web.assets_backend": ["web_send_message_popup/static/src/**/*.js"]}, } diff --git a/web_send_message_popup/static/src/chatter/web/chatter_patch.esm.js b/web_send_message_popup/static/src/chatter/web/chatter_patch.esm.js new file mode 100644 index 000000000..69fdefe36 --- /dev/null +++ b/web_send_message_popup/static/src/chatter/web/chatter_patch.esm.js @@ -0,0 +1,183 @@ +import {EventBus, markup, toRaw} from "@odoo/owl"; +import {Chatter} from "@mail/chatter/web_portal/chatter"; +import {SIGNATURE_CLASS} from "@html_editor/main/signature_plugin"; +import {_t} from "@web/core/l10n/translation"; +import {browser} from "@web/core/browser/browser"; +import {childNodes} from "@html_editor/utils/dom_traversal"; +import {parseHTML} from "@html_editor/utils/html"; +import {patch} from "@web/core/utils/patch"; +import {prettifyMessageContent} from "@mail/utils/common/format"; +import {renderToElement} from "@web/core/utils/render"; +import {rpc} from "@web/core/network/rpc"; +import {wrapInlinesInBlocks} from "@html_editor/utils/dom"; + +patch(Chatter.prototype, { + setup() { + super.setup(...arguments); + this.fullComposerBus = new EventBus(); + this.isFullComposerOpen = false; + }, + toggleComposer(mode = false) { + if (mode === "message") { + this.closeSearch(); + this.state.composerType = false; + if (!this.state.thread.id) { + this.props.saveRecord?.(); + } + this.openFullComposer(); + return; + } + return super.toggleComposer(...arguments); + }, + // A rough composer function copy of `onClickFullComposer` + async openFullComposer() { + const newPartners = this.state.thread.suggestedRecipients.filter( + (recipient) => recipient.checked && !recipient.persona + ); + if (newPartners.length) { + const recipientEmails = []; + const recipientAdditionalValues = {}; + newPartners.forEach((recipient) => { + recipientEmails.push(recipient.email); + recipientAdditionalValues[recipient.email] = + recipient.create_values || {}; + }); + const partners = await rpc("/mail/partner/from_email", { + emails: recipientEmails, + additional_values: recipientAdditionalValues, + }); + for (const index in partners) { + const partnerData = partners[index]; + const persona = this.store.Persona.insert({ + ...partnerData, + type: "partner", + }); + const email = recipientEmails[index]; + const recipient = this.state.thread.suggestedRecipients.find( + (rec) => rec.email === email + ); + Object.assign(recipient, {persona}); + } + } + const body = this.state.thread.composer.text; + const validMentions = this.store.getMentionsFromText(body, { + mentionedChannels: this.state.thread.composer.mentionedChannels, + mentionedPartners: this.state.thread.composer.mentionedPartners, + }); + let default_body = await prettifyMessageContent(body, validMentions); + if (!default_body) { + const composer = toRaw(this.state.thread.composer); + composer.emailAddSignature = true; + } + default_body = this.formatDefaultBodyForFullComposer( + default_body, + this.state.thread.composer.emailAddSignature + ? markup(this.store.self.signature) + : "" + ); + const action = { + name: _t("Compose Email"), + type: "ir.actions.act_window", + res_model: "mail.compose.message", + view_mode: "form", + views: [[false, "form"]], + target: "new", + context: { + default_attachment_ids: this.state.thread.composer.attachments.map( + (attachment) => attachment.id + ), + default_body, + default_email_add_signature: false, + default_model: this.state.thread.model, + default_partner_ids: this.state.thread.suggestedRecipients + .filter((recipient) => recipient.checked) + .map((recipient) => recipient.persona.id), + default_res_ids: [this.state.thread.id], + default_subtype_xmlid: "mail.mt_comment", + mail_post_autofollow: this.state.thread.hasWriteAccess, + }, + }; + const options = { + onClose: (...args) => { + const accidentalDiscard = !args.length; + const isDiscard = accidentalDiscard || args[0]?.special; + if (!isDiscard && this.state.thread.model === "mail.box") { + this.notifySendFromMailbox(); + } + if (accidentalDiscard) { + this.fullComposerBus.trigger("ACCIDENTAL_DISCARD", { + onAccidentalDiscard: (isEmpty) => { + if (!isEmpty) { + this.saveContent(); + this.restoreContent(); + } + }, + }); + } else { + this.clear(); + } + this.onCloseFullComposerCallback(); + this.isFullComposerOpen = false; + this.fullComposerBus = new EventBus(); + }, + props: {fullComposerBus: this.fullComposerBus}, + }; + await this.env.services.action.doAction(action, options); + this.isFullComposerOpen = true; + }, + // Method copied not from the composer file but the composer_patch one + formatDefaultBodyForFullComposer(defaultBody, signature = "") { + const fragment = parseHTML(document, defaultBody); + if (!fragment.firstChild) { + fragment.append(document.createElement("BR")); + } + if (signature) { + const signatureEl = renderToElement("html_editor.Signature", { + signature, + signatureClass: SIGNATURE_CLASS, + }); + fragment.append(signatureEl); + } + const container = document.createElement("DIV"); + container.append(...childNodes(fragment)); + wrapInlinesInBlocks(container, {baseContainerNodeName: "DIV"}); + return container.innerHTML; + }, + // Copied and modified methods from composer + notifySendFromMailbox() { + this.env.services.notification.add( + _t('Message posted on "%s"', this.state.thread.displayName), + {type: "info"} + ); + }, + saveContent() { + const composer = toRaw(this.state.thread.composer); + const onSaveContent = (text, emailAddSignature) => { + browser.localStorage.setItem( + composer.localId, + JSON.stringify({emailAddSignature, text}) + ); + }; + if (this.isFullComposerOpen) { + this.fullComposerBus.trigger("SAVE_CONTENT", {onSaveContent}); + } else { + onSaveContent(composer.text, true); + } + }, + restoreContent() { + const composer = toRaw(this.state.thread.composer); + try { + const config = JSON.parse(browser.localStorage.getItem(composer.localId)); + if (config.text) { + composer.emailAddSignature = config.emailAddSignature; + composer.text = config.text; + } + } catch { + browser.localStorage.removeItem(composer.localId); + } + }, + clear() { + this.state.thread.composer.clear(); + browser.localStorage.removeItem(this.state.thread.composer.localId); + }, +}); diff --git a/web_send_message_popup/static/src/models/chatter/chatter.esm.js b/web_send_message_popup/static/src/models/chatter/chatter.esm.js deleted file mode 100644 index 748afc110..000000000 --- a/web_send_message_popup/static/src/models/chatter/chatter.esm.js +++ /dev/null @@ -1,62 +0,0 @@ -/** @odoo-module **/ - -import {clear} from "@mail/model/model_field_command"; -import {escapeAndCompactTextContent} from "@mail/js/utils"; -import {registerPatch} from "@mail/model/model_core"; - -registerPatch({ - name: "Chatter", - recordMethods: { - // Fn overwrite - onClickSendMessage() { - if (this.composerView) { - // Change `isLog` to false since this should only be possible when you - // press "Log Note" first, otherwise this won't hurt. - this.composerView.composer.update({isLog: false}); - // Open the full composer with `composerView` because it carries through - // the composer options. - this.composerView.openFullComposer(); - // Clear the `composerView` since we don't need it no more. - this.update({composerView: clear()}); - return; - } - this.openFullComposer(); - }, - async openFullComposer() { - // Rough copy of composer view function `openFullComposer`. - // Get composer from thread. - // We access data from the composer since history still is saved there. - // e.g. open and close "Log note". - const composer = this.thread.composer; - const context = { - default_attachment_ids: composer.attachments.map((att) => att.id), - default_body: escapeAndCompactTextContent(composer.textInputContent), - default_is_log: false, - default_model: this.threadModel, - default_partner_ids: composer.recipients.map((partner) => partner.id), - default_res_id: this.threadId, - mail_post_autofollow: this.thread.hasWriteAccess, - }; - const action = { - type: "ir.actions.act_window", - name: this.env._t("Compose Email"), - res_model: "mail.compose.message", - view_mode: "form", - views: [[false, "form"]], - target: "new", - context, - }; - const options = { - on_close: () => { - if (composer.exists()) { - composer._reset(); - if (composer.activeThread) { - composer.activeThread.fetchData(["messages"]); - } - } - }, - }; - await this.env.services.action.doAction(action, options); - }, - }, -});