diff options
Diffstat (limited to 'addons/mail/static/src/components')
10 files changed, 316 insertions, 2 deletions
diff --git a/addons/mail/static/src/components/chatter/chatter.xml b/addons/mail/static/src/components/chatter/chatter.xml index 53550da516d..911851106ad 100644 --- a/addons/mail/static/src/components/chatter/chatter.xml +++ b/addons/mail/static/src/components/chatter/chatter.xml @@ -13,6 +13,7 @@ class="o_Chatter_composer" t-att-class="{ 'o-bordered': chatter.hasExternalBorder }" composerLocalId="chatter.thread.composer.localId" + hasFollowers="true" hasMentionSuggestionsBelowPosition="true" isCompact="false" isExpandable="true" diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js index 510a5ac6038..bc46cd72d1d 100644 --- a/addons/mail/static/src/components/composer/composer.js +++ b/addons/mail/static/src/components/composer/composer.js @@ -3,6 +3,7 @@ odoo.define('mail/static/src/components/composer/composer.js', function (require const components = { AttachmentList: require('mail/static/src/components/attachment_list/attachment_list.js'), + ComposerSuggestedRecipientList: require('mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js'), DropZone: require('mail/static/src/components/drop_zone/drop_zone.js'), EmojisPopover: require('mail/static/src/components/emojis_popover/emojis_popover.js'), FileUploader: require('mail/static/src/components/file_uploader/file_uploader.js'), @@ -190,7 +191,6 @@ class Composer extends Component { } return; } - // TODO: take suggested recipients into account (task-2283356) await this.composer.postMessage(); // TODO: we might need to remove trigger and use the store to wait for the post rpc to be done // task-2252858 diff --git a/addons/mail/static/src/components/composer/composer.scss b/addons/mail/static/src/components/composer/composer.scss index 7ba50371577..37250a2ffd7 100644 --- a/addons/mail/static/src/components/composer/composer.scss +++ b/addons/mail/static/src/components/composer/composer.scss @@ -13,7 +13,7 @@ &.o-has-current-partner-avatar { grid-template-columns: 50px 1fr; - padding: map-get($spacers, 0) map-get($spacers, 3) map-get($spacers, 1) map-get($spacers, 1); + padding: map-get($spacers, 3) map-get($spacers, 3) map-get($spacers, 4) map-get($spacers, 1); &:not(.o-has-footer) { padding-bottom: 20px; diff --git a/addons/mail/static/src/components/composer/composer.xml b/addons/mail/static/src/components/composer/composer.xml index 61694e5aaf1..be947b72b3f 100644 --- a/addons/mail/static/src/components/composer/composer.xml +++ b/addons/mail/static/src/components/composer/composer.xml @@ -46,6 +46,9 @@ </t> </b> </small> + <ComposerSuggestedRecipientList + threadLocalId="composer.thread.localId" + /> </t> </div> </t> diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js new file mode 100644 index 00000000000..cbc215a3b7e --- /dev/null +++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js @@ -0,0 +1,163 @@ +odoo.define('mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js', function (require) { +'use strict'; + +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { FormViewDialog } = require('web.view_dialogs'); +const { ComponentAdapter } = require('web.OwlCompatibility'); + +const { Component } = owl; +const { useRef } = owl.hooks; + +class FormViewDialogComponentAdapter extends ComponentAdapter { + + renderWidget() { + // Ensure the dialog is properly reconstructed. Without this line, it is + // impossible to open the dialog again after having it closed a first + // time, because the DOM of the dialog has disappeared. + return this.willStart(); + } + +} + +const components = { + FormViewDialogComponentAdapter, +}; + +class ComposerSuggestedRecipient extends Component { + + constructor(...args) { + super(...args); + this.id = _.uniqueId('o_ComposerSuggestedRecipient_'); + + useStore(props => { + const suggestedRecipientInfo = this.env.models['mail.suggested_recipient_info'].get(props.suggestedRecipientLocalId); + const partner = suggestedRecipientInfo && suggestedRecipientInfo.partner; + return { + partner: partner && partner.__state, + suggestedRecipientInfo: suggestedRecipientInfo && suggestedRecipientInfo.__state, + }; + }); + /** + * Form view dialog class. Useful to reference it in the template. + */ + this.FormViewDialog = FormViewDialog; + /** + * Reference of the checkbox. Useful to know whether it was checked or + * not, to properly update the corresponding state in the record or to + * prompt the user with the partner creation dialog. + */ + this._checkboxRef = useRef('checkbox'); + /** + * Reference of the partner creation dialog. Useful to open it, for + * compatibility with old code. + */ + this._dialogRef = useRef('dialog'); + /** + * Whether the dialog is currently open. `_dialogRef` cannot be trusted + * to know if the dialog is open due to manually calling `open` and + * potential out of sync with component adapter. + */ + this._isDialogOpen = false; + this._onDialogSaved = this._onDialogSaved.bind(this); + } + + mounted() { + this._update(); + } + + patched() { + this._update(); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {string|undefined} + */ + get ADD_AS_RECIPIENT_AND_FOLLOWER_REASON() { + if (!this.suggestedRecipientInfo) { + return undefined; + } + return this.env._t(_.str.sprintf( + "Add as recipient and follower (reason: %s)", + this.suggestedRecipientInfo.reason + )); + } + + /** + * @returns {string} + */ + get PLEASE_COMPLETE_CUSTOMER_S_INFORMATION() { + return this.env._t("Please complete customer's information"); + } + + /** + * @returns {mail.suggested_recipient_info} + */ + get suggestedRecipientInfo() { + return this.env.models['mail.suggested_recipient_info'].get(this.props.suggestedRecipientInfoLocalId); + } + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _update() { + if (this._checkboxRef.el && this.suggestedRecipientInfo) { + this._checkboxRef.el.checked = this.suggestedRecipientInfo.isSelected; + } + } + + //-------------------------------------------------------------------------- + // Handler + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onChangeCheckbox() { + const isChecked = this._checkboxRef.el.checked; + this.suggestedRecipientInfo.update({ isSelected: isChecked }); + if (!this.suggestedRecipientInfo.partner) { + // Recipients must always be partners. On selecting a suggested + // recipient that does not have a partner, the partner creation form + // should be opened. + if (isChecked && this._dialogRef && !this._isDialogOpen) { + this._isDialogOpen = true; + this._dialogRef.comp.widget.on('closed', this, () => { + this._isDialogOpen = false; + }); + this._dialogRef.comp.widget.open(); + } + } + } + + /** + * @private + */ + _onDialogSaved() { + const thread = this.suggestedRecipientInfo && this.suggestedRecipientInfo.thread; + if (!thread) { + return; + } + thread.fetchAndUpdateSuggestedRecipients(); + } +} + +Object.assign(ComposerSuggestedRecipient, { + components, + props: { + suggestedRecipientInfoLocalId: String, + }, + template: 'mail.ComposerSuggestedRecipient', +}); + +return ComposerSuggestedRecipient; + +}); diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.scss b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.scss new file mode 100644 index 00000000000..c8b6b5adaba --- /dev/null +++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.scss @@ -0,0 +1,5 @@ +// Dirty fix: clear modal-body padding, otherwise it create space inside the +// suggested_recipient list. +.o_ComposerSuggestedRecipient .modal-body { + padding: 0; +} diff --git a/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml new file mode 100644 index 00000000000..ae65b2bb77c --- /dev/null +++ b/addons/mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + <t t-name="mail.ComposerSuggestedRecipient" owl="1"> + <div class="o_ComposerSuggestedRecipient" t-att-title="ADD_AS_RECIPIENT_AND_FOLLOWER_REASON"> + <t t-if="suggestedRecipientInfo"> + <div class="custom-control custom-checkbox"> + <input t-attf-id="{{ id }}_checkbox" class="custom-control-input" type="checkbox" t-att-checked="suggestedRecipientInfo.isSelected ? 'checked' : undefined" t-on-change="_onChangeCheckbox" t-ref="checkbox" /> + <label class="custom-control-label" t-attf-for="{{ id }}_checkbox"> + <t t-if="suggestedRecipientInfo.name"> + <t t-esc="suggestedRecipientInfo.name"/> + </t> + <t t-if="suggestedRecipientInfo.email"> + (<t t-esc="suggestedRecipientInfo.email"/>) + </t> + </label> + </div> + <t t-if="!suggestedRecipientInfo.partner"> + <FormViewDialogComponentAdapter + Component="FormViewDialog" + params="{ + context: { + active_id: suggestedRecipientInfo.thread.id, + active_model: 'mail.compose.message', + default_email: suggestedRecipientInfo.email, + default_name: suggestedRecipientInfo.name, + force_email: true, + ref: 'compound_context', + }, + disable_multiple_selection: true, + on_saved: _onDialogSaved, + res_id: false, + res_model: 'res.partner', + title: PLEASE_COMPLETE_CUSTOMER_S_INFORMATION, + }" + t-ref="dialog" + /> + </t> + </t> + </div> + </t> +</templates> diff --git a/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js new file mode 100644 index 00000000000..330072960bd --- /dev/null +++ b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js @@ -0,0 +1,72 @@ +odoo.define('mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.js', function (require) { +'use strict'; + +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { Component } = owl; +const { useState } = owl.hooks; + +const components = { + ComposerSuggestedRecipient: require('mail/static/src/components/composer_suggested_recipient/composer_suggested_recipient.js'), +}; + +class ComposerSuggestedRecipientList extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + this.state = useState({ + hasShowMoreButton: false, + }); + + useStore(props => { + const thread = this.env.models['mail.thread'].get(props.threadLocalId); + return { + thread: thread ? thread.__state : undefined, + }; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {mail.thread} + */ + get thread() { + return this.env.models['mail.thread'].get(this.props.threadLocalId); + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onClickShowLess(ev) { + this.state.hasShowMoreButton = false; + } + + /** + * @private + */ + _onClickShowMore(ev) { + this.state.hasShowMoreButton = true; + } + +} + +Object.assign(ComposerSuggestedRecipientList, { + components, + props: { + threadLocalId: String, + }, + template: 'mail.ComposerSuggestedRecipientList', +}); + +return ComposerSuggestedRecipientList; +}); diff --git a/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.scss b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.scss new file mode 100644 index 00000000000..4e1b4dd76ea --- /dev/null +++ b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.scss @@ -0,0 +1,3 @@ +.o_ComposerSuggestedRecipientList { + margin-bottom: map-get($spacers, 2); +} diff --git a/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.xml b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.xml new file mode 100644 index 00000000000..c78570dbe24 --- /dev/null +++ b/addons/mail/static/src/components/composer_suggested_recipient_list/composer_suggested_recipient_list.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + <t t-name="mail.ComposerSuggestedRecipientList" owl="1"> + <div class="o_ComposerSuggestedRecipientList"> + <t t-if="thread"> + <t t-foreach="state.hasShowMoreButton ? thread.suggestedRecipientInfoList : thread.suggestedRecipientInfoList.slice(0,3)" t-as="recipientInfo" t-key="recipientInfo.localId"> + <ComposerSuggestedRecipient + suggestedRecipientInfoLocalId="recipientInfo.localId" + /> + </t> + <t t-if="thread.suggestedRecipientInfoList.length > 3"> + <t t-if="!state.hasShowMoreButton" > + <button class="o_ComposerSuggestedRecipientList_showMore btn btn-sm btn-link" t-on-click="_onClickShowMore"> + Show more + </button> + </t> + <t t-else=""> + <button class="o_ComposerSuggestedRecipientList_showLess btn btn-sm btn-link" t-on-click="_onClickShowLess"> + Show less + </button> + </t> + </t> + </t> + </div> + </t> +</templates> |
