[IMP] mail, snailmail: convert legacy dialog with t-ref to model
In preparation to using Owl 2 in discuss code. task-2738648 closes odoo/odoo#83598 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
committed by
Sébastien Theys
parent
ef9429231c
commit
0df4b0029b
@@ -22,7 +22,6 @@
|
||||
<AttachmentList
|
||||
className="'o_attachmentBox_attachmentList'"
|
||||
localId="attachmentBoxView.attachmentList.localId"
|
||||
onAttachmentRemoved="attachmentBoxView.onAttachmentRemoved"
|
||||
/>
|
||||
</t>
|
||||
<button class="o_AttachmentBox_buttonAdd btn btn-link" type="button" t-on-click="attachmentBoxView.onClickAddAttachment">
|
||||
|
||||
@@ -277,7 +277,7 @@ QUnit.test('remove attachment should ask for confirmation', async function (asse
|
||||
res_id: 100,
|
||||
res_model: 'res.partner',
|
||||
});
|
||||
const { createChatterContainerComponent } = await this.start();
|
||||
const { createChatterContainerComponent } = await this.start({ hasDialog: true });
|
||||
await createChatterContainerComponent({
|
||||
isAttachmentBoxVisibleInitially: true,
|
||||
threadId: 100,
|
||||
|
||||
@@ -29,13 +29,7 @@ export class AttachmentCard extends Component {
|
||||
}
|
||||
|
||||
Object.assign(AttachmentCard, {
|
||||
props: {
|
||||
localId: String,
|
||||
onAttachmentRemoved: {
|
||||
type: Function,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
props: { localId: String },
|
||||
template: 'mail.AttachmentCard',
|
||||
});
|
||||
|
||||
|
||||
@@ -56,13 +56,6 @@
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachmentCard.hasDeleteConfirmDialog">
|
||||
<AttachmentDeleteConfirm
|
||||
attachmentLocalId="attachmentCard.attachment.localId"
|
||||
onAttachmentRemoved="props.onAttachmentRemoved"
|
||||
onClosed="attachmentCard.onDeleteConfirmDialogClosed"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
+10
-59
@@ -1,80 +1,31 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model/use_component_to_model';
|
||||
|
||||
import Dialog from 'web.OwlDialog';
|
||||
|
||||
const { Component, escape } = owl;
|
||||
const { Component } = owl;
|
||||
|
||||
export class AttachmentDeleteConfirm extends Component {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Attachment}
|
||||
* @override
|
||||
*/
|
||||
get attachment() {
|
||||
return this.messaging && this.messaging.models['Attachment'].get(this.props.attachmentLocalId);
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component', modelName: 'AttachmentDeleteConfirmView' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
* @returns {AttachmentDeleteConfirmView}
|
||||
*/
|
||||
getBody() {
|
||||
return _.str.sprintf(
|
||||
this.env._t(`Do you really want to delete "%s"?`),
|
||||
escape(this.attachment.displayName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getTitle() {
|
||||
return this.env._t("Confirmation");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickCancel() {
|
||||
this.root.comp._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _onClickOk() {
|
||||
await this.attachment.remove();
|
||||
this.root.comp._close();
|
||||
if (this.props.onAttachmentRemoved) {
|
||||
this.props.onAttachmentRemoved({
|
||||
attachmentLocalId: this.props.attachmentLocalId,
|
||||
});
|
||||
}
|
||||
get attachmentDeleteConfirmView() {
|
||||
return this.messaging && this.messaging.models['AttachmentDeleteConfirmView'].get(this.props.localId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(AttachmentDeleteConfirm, {
|
||||
components: { Dialog },
|
||||
props: {
|
||||
attachmentLocalId: String,
|
||||
onAttachmentRemoved: {
|
||||
type: Function,
|
||||
optional: true,
|
||||
},
|
||||
onClosed: {
|
||||
type: Function,
|
||||
optional: true,
|
||||
}
|
||||
},
|
||||
props: { localId: String },
|
||||
template: 'mail.AttachmentDeleteConfirm',
|
||||
});
|
||||
|
||||
|
||||
+11
-6
@@ -1,12 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.AttachmentDeleteConfirm" owl="1">
|
||||
<Dialog contentClass="'o_AttachmentDeleteConfirm'" title="getTitle()" size="'medium'" onClosed="props.onClosed" t-ref="root">
|
||||
<p class="o_AttachmentDeleteConfirm_mainText" t-esc="getBody()"/>
|
||||
<t t-set-slot="buttons">
|
||||
<button class="o_AttachmentDeleteConfirm_confirmButton btn btn-primary" t-on-click="_onClickOk">Ok</button>
|
||||
<button class="o_AttachmentDeleteConfirm_cancelButton btn btn-secondary" t-on-click="_onClickCancel">Cancel</button>
|
||||
<div class="o_AttachmentDeleteConfirm card bg-white" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-if="attachmentDeleteConfirmView">
|
||||
<h4 class="m-3">Confirmation</h4>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<p class="o_AttachmentDeleteConfirm_mainText mx-3 mb-3" t-esc="attachmentDeleteConfirmView.body"/>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<div class="o_AttachmentDeleteConfirm_buttons mx-3 mb-3">
|
||||
<button class="o_AttachmentDeleteConfirm_confirmButton btn btn-primary me-2" t-on-click="attachmentDeleteConfirmView.onClickOk">Ok</button>
|
||||
<button class="o_AttachmentDeleteConfirm_cancelButton btn btn-secondary me-2" t-on-click="attachmentDeleteConfirmView.onClickCancel">Cancel</button>
|
||||
</div>
|
||||
</t>
|
||||
</Dialog>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -25,13 +25,7 @@ export class AttachmentImage extends Component {
|
||||
}
|
||||
|
||||
Object.assign(AttachmentImage, {
|
||||
props: {
|
||||
localId: String,
|
||||
onAttachmentRemoved: {
|
||||
type: Function,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
props: { localId: String },
|
||||
template: 'mail.AttachmentImage',
|
||||
});
|
||||
|
||||
|
||||
@@ -29,13 +29,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachmentImage.hasDeleteConfirmDialog">
|
||||
<AttachmentDeleteConfirm
|
||||
attachmentLocalId="attachmentImage.attachment.localId"
|
||||
onAttachmentRemoved="props.onAttachmentRemoved"
|
||||
onClosed="attachmentImage.onDeleteConfirmDialogClosed"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -16,13 +16,7 @@ export class AttachmentList extends Component {
|
||||
}
|
||||
|
||||
Object.assign(AttachmentList, {
|
||||
props: {
|
||||
localId: String,
|
||||
onAttachmentRemoved: {
|
||||
type: Function,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
props: { localId: String },
|
||||
template: 'mail.AttachmentList',
|
||||
});
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<t t-if="attachmentList">
|
||||
<div class="o_AttachmentList_partialList o_AttachmentList_partialListImages d-flex flex-grow-1 flex-wrap">
|
||||
<t t-foreach="attachmentList.attachmentImages" t-as="attachmentImage" t-key="attachmentImage.localId">
|
||||
<AttachmentImage className="'o_AttachmentList_attachment my-1 me-1 mw-100'" localId="attachmentImage.localId" onAttachmentRemoved="props.onAttachmentRemoved"/>
|
||||
<AttachmentImage className="'o_AttachmentList_attachment my-1 me-1 mw-100'" localId="attachmentImage.localId"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_AttachmentList_partialList o_AttachmentList_partialListNonImages d-flex flex-grow-1 flex-wrap justify-content-start m-1">
|
||||
<t t-foreach="attachmentList.attachmentCards" t-as="attachmentCard" t-key="attachmentCard.localId">
|
||||
<AttachmentCard className="'o_AttachmentList_attachment my-1 me-1 mw-100'" localId="attachmentCard.localId" onAttachmentRemoved="props.onAttachmentRemoved"/>
|
||||
<AttachmentCard className="'o_AttachmentList_attachment my-1 me-1 mw-100'" localId="attachmentCard.localId"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -272,6 +272,7 @@ QUnit.test('clicking on the delete attachment button multiple times should do th
|
||||
assert.expect(2);
|
||||
|
||||
const { createMessageComponent } = await this.start({
|
||||
hasDialog: true,
|
||||
async mockRPC(route, args) {
|
||||
if (route === '/mail/attachment/delete') {
|
||||
assert.step('attachment_unlink');
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
import Dialog from 'web.OwlDialog';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class DeleteMessageConfirm extends Component {
|
||||
@@ -13,24 +12,20 @@ export class DeleteMessageConfirm extends Component {
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.title = this.env._t("Confirmation");
|
||||
useComponentToModel({ fieldName: 'component', modelName: 'DeleteMessageConfirmView' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {MessageActionList}
|
||||
* @returns {DeleteMessageConfirmView}
|
||||
*/
|
||||
get messageActionList() {
|
||||
return this.messaging && this.messaging.models['MessageActionList'].get(this.props.localId);
|
||||
get deleteMessageConfirmView() {
|
||||
return this.messaging && this.messaging.models['DeleteMessageConfirmView'].get(this.props.localId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(DeleteMessageConfirm, {
|
||||
components: {
|
||||
Dialog,
|
||||
},
|
||||
props: {
|
||||
localId: String,
|
||||
},
|
||||
props: { localId: String },
|
||||
template: 'mail.DeleteMessageConfirm',
|
||||
});
|
||||
|
||||
|
||||
+14
-11
@@ -1,18 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.DeleteMessageConfirm" owl="1">
|
||||
<Dialog contentClass="'o_DeleteMessageConfirm ' + className" title="title" onClosed="messageActionList and messageActionList.onDeleteConfirmDialogClosed" t-ref="root">
|
||||
<t t-if="messageActionList">
|
||||
<p>Are you sure you want to delete this message?</p>
|
||||
<blockquote class="o_DeleteMessageConfirm_blockquote">
|
||||
<Message localId="messageActionList.messageViewForDelete.localId"/>
|
||||
<div class="o_DeleteMessageConfirm card bg-white" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-if="deleteMessageConfirmView">
|
||||
<h4 class="m-3">Confirmation</h4>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<p class="mx-3 mb-3">Are you sure you want to delete this message?</p>
|
||||
<blockquote class="o_DeleteMessageConfirm_blockquote mx-3 mb-3">
|
||||
<Message localId="deleteMessageConfirmView.messageView.localId"/>
|
||||
</blockquote>
|
||||
<small t-if="!messageActionList.message.originThread or messageActionList.message.originThread.model !== 'mail.channel'">Pay attention: The followers of this document who were notified by email will still be able to read the content of this message and reply to it.</small>
|
||||
<t t-set-slot="buttons">
|
||||
<button class="btn btn-primary" t-on-click="messageActionList.onClickConfirmDelete">Delete</button>
|
||||
<button class="btn btn-secondary" t-on-click="root.comp._close()">Cancel</button>
|
||||
</t>
|
||||
<small t-if="!deleteMessageConfirmView.message.originThread or deleteMessageConfirmView.message.originThread.model !== 'mail.channel'" class="mx-3 mb-3">Pay attention: The followers of this document who were notified by email will still be able to read the content of this message and reply to it.</small>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<div class="o_DeleteMessageConfirm_buttons mx-3 mb-3">
|
||||
<button class="btn btn-primary me-2" t-on-click="deleteMessageConfirmView.onClickDelete">Delete</button>
|
||||
<button class="btn btn-secondary me-2" t-on-click="deleteMessageConfirmView.onClickCancel">Cancel</button>
|
||||
</div>
|
||||
</t>
|
||||
</Dialog>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
z-index: $zindex-modal;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_Dialog {
|
||||
background-color: rgba($black, 0.7);
|
||||
.o_Dialog_componentLargeSize {
|
||||
min-width: Min(95vw, $o-modal-lg);
|
||||
max-width: 95vw;
|
||||
}
|
||||
|
||||
.o_Dialog_componentMediumSize {
|
||||
min-width: Min(95vw, $o-modal-md);
|
||||
max-width: 95vw;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.Dialog" owl="1">
|
||||
<div class="o_Dialog" t-attf-class="{{ className }}" t-ref="root">
|
||||
<div class="o_Dialog" t-attf-class="{{ className }}" t-att-style="dialog and dialog.style" t-ref="root">
|
||||
<t t-if="dialog">
|
||||
<t
|
||||
t-component="constructor.components[dialog.componentName]"
|
||||
className="'o_Dialog_component'"
|
||||
t-props="{ localId: dialog.record.localId }"
|
||||
t-props="{
|
||||
className: `o_Dialog_component ${dialog.componentClassName}`,
|
||||
localId: dialog.record.localId,
|
||||
}"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -153,7 +153,7 @@ export class Message extends Component {
|
||||
this.messageView.messageActionList &&
|
||||
(
|
||||
this.messageView.messageActionList.reactionPopoverView ||
|
||||
this.messageView.messageActionList.showDeleteConfirm
|
||||
this.messageView.messageActionList.deleteConfirmDialog
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -543,15 +543,6 @@ export class Message extends Component {
|
||||
this.messageView.message.author.openProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickFailure(ev) {
|
||||
markEventHandled(ev, 'Message.ClickFailure');
|
||||
this.messageView.message.openResendAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
</t>
|
||||
<t t-if="threadView and messageView.message.originThread and messageView.message.originThread === threadView.thread and messageView.message.notifications.length > 0">
|
||||
<t t-if="messageView.message.failureNotifications.length > 0">
|
||||
<span class="o_Message_notificationIconClickable o-error" t-on-click="_onClickFailure">
|
||||
<span class="o_Message_notificationIconClickable o-error" t-on-click="messageView.onClickFailure">
|
||||
<i name="failureIcon" class="o_Message_notificationIcon fa fa-envelope"/>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
@@ -654,7 +654,7 @@ QUnit.test('only show messaging seen indicator if authored by me, after last see
|
||||
QUnit.test('allow attachment delete on authored message', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const { createMessageComponent } = await this.start();
|
||||
const { createMessageComponent } = await this.start({ hasDialog: true });
|
||||
const message = this.messaging.models['Message'].create({
|
||||
attachments: insertAndReplace({
|
||||
filename: "BLAH.jpg",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<span t-if="messageActionList.hasMarkAsReadIcon" class="o_MessageActionList_action o_MessageActionList_actionMarkRead p-2 fa fa-lg fa-check" title="Mark as Read" t-on-click="messageActionList.onClickMarkAsRead"/>
|
||||
<span t-if="messageActionList.message.canBeDeleted" class="o_MessageActionList_action o_MessageActionList_actionEdit p-2 fa fa-lg fa-pencil" title="Edit" t-on-click="messageActionList.onClickEdit"/>
|
||||
<span t-if="messageActionList.message.canBeDeleted" class="o_MessageActionList_action o_MessageActionList_actionDelete p-2 fa fa-lg fa-trash" title="Delete" t-on-click="messageActionList.onClickDelete"/>
|
||||
<DeleteMessageConfirm t-if="messageActionList.showDeleteConfirm" localId="messageActionList.localId"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -986,7 +986,7 @@ QUnit.test("delete all attachments of message without content should no longer d
|
||||
res_id: 11,
|
||||
}
|
||||
);
|
||||
const { createThreadViewComponent } = await this.start();
|
||||
const { createThreadViewComponent } = await this.start({ hasDialog: true });
|
||||
const threadViewer = this.messaging.models['ThreadViewer'].create({
|
||||
hasThreadView: true,
|
||||
qunitTest: insertAndReplace(),
|
||||
@@ -1046,7 +1046,7 @@ QUnit.test('delete all attachments of a message with some text content should st
|
||||
res_id: 11,
|
||||
},
|
||||
);
|
||||
const { createThreadViewComponent } = await this.start();
|
||||
const { createThreadViewComponent } = await this.start({ hasDialog: true });
|
||||
const threadViewer = this.messaging.models['ThreadViewer'].create({
|
||||
hasThreadView: true,
|
||||
qunitTest: insertAndReplace(),
|
||||
@@ -1113,7 +1113,7 @@ QUnit.test('delete all attachments of a message with tracking fields should stil
|
||||
new_value: "New name",
|
||||
old_value: "Old name",
|
||||
});
|
||||
const { createThreadViewComponent } = await this.start();
|
||||
const { createThreadViewComponent } = await this.start({ hasDialog: true });
|
||||
const threadViewer = this.messaging.models['ThreadViewer'].create({
|
||||
hasThreadView: true,
|
||||
qunitTest: insertAndReplace(),
|
||||
|
||||
@@ -8,13 +8,6 @@ registerModel({
|
||||
name: 'AttachmentBoxView',
|
||||
identifyingFields: ['chatter'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* Handles attachment removed event.
|
||||
*/
|
||||
onAttachmentRemoved() {
|
||||
// FIXME Could be changed by spying attachments count (task-2252858)
|
||||
this.component.trigger('o-attachments-changed');
|
||||
},
|
||||
/**
|
||||
* Handles click on the "add attachment" button.
|
||||
*/
|
||||
|
||||
@@ -34,18 +34,9 @@ registerModel({
|
||||
this.component.trigger('o-attachment-removed', { attachmentLocalId: this.attachment.localId });
|
||||
this.attachment.remove();
|
||||
} else {
|
||||
this.update({ hasDeleteConfirmDialog: true });
|
||||
this.update({ attachmentDeleteConfirmDialog: insertAndReplace() });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Synchronizes the `hasDeleteConfirmDialog` when the dialog is closed.
|
||||
*/
|
||||
onDeleteConfirmDialogClosed() {
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
this.update({ hasDeleteConfirmDialog: false });
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
/**
|
||||
@@ -55,6 +46,10 @@ registerModel({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
attachmentDeleteConfirmDialog: one('Dialog', {
|
||||
inverse: 'attachmentCardOwnerAsAttachmentDeleteConfirm',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* States the attachmentList displaying this card.
|
||||
*/
|
||||
@@ -67,11 +62,5 @@ registerModel({
|
||||
* States the OWL component of this attachment card.
|
||||
*/
|
||||
component: attr(),
|
||||
/**
|
||||
* Determines the status of the delete confirm dialog (open/closed).
|
||||
*/
|
||||
hasDeleteConfirmDialog: attr({
|
||||
default: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear, replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'AttachmentDeleteConfirmView',
|
||||
identifyingFields: ['dialogOwner'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* Returns whether the given html element is inside this attachment delete confirm view.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
containsElement(element) {
|
||||
return Boolean(this.component && this.component.root.el && this.component.root.el.contains(element));
|
||||
},
|
||||
onClickCancel() {
|
||||
this.dialogOwner.delete();
|
||||
},
|
||||
async onClickOk() {
|
||||
await this.attachment.remove();
|
||||
if (this.chatter && this.chatter.component) {
|
||||
this.chatter.component.trigger('o-attachments-changed');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeAttachment() {
|
||||
if (this.dialogOwner && this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm) {
|
||||
return replace(this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachment);
|
||||
}
|
||||
if (this.dialogOwner && this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm) {
|
||||
return replace(this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachment);
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeBody() {
|
||||
return _.str.sprintf(this.env._t(`Do you really want to delete "%s"?`), this.attachment.displayName);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeChatter() {
|
||||
if (
|
||||
this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm &&
|
||||
this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner &&
|
||||
this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter
|
||||
) {
|
||||
return replace(this.dialogOwner.attachmentCardOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter);
|
||||
}
|
||||
if (
|
||||
this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm &&
|
||||
this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner &&
|
||||
this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter
|
||||
) {
|
||||
return replace(this.dialogOwner.attachmentImageOwnerAsAttachmentDeleteConfirm.attachmentList.attachmentBoxViewOwner.chatter);
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
attachment: one('Attachment', {
|
||||
compute: '_computeAttachment',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
body: attr({
|
||||
compute: '_computeBody',
|
||||
}),
|
||||
chatter: one('Chatter', {
|
||||
compute: '_computeChatter',
|
||||
}),
|
||||
component: attr(),
|
||||
dialogOwner: one('Dialog', {
|
||||
inverse: 'attachmentDeleteConfirmView',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -34,18 +34,9 @@ registerModel({
|
||||
this.component.trigger('o-attachment-removed', { attachmentLocalId: this.attachment.localId });
|
||||
this.attachment.remove();
|
||||
} else {
|
||||
this.update({ hasDeleteConfirmDialog: true });
|
||||
this.update({ attachmentDeleteConfirmDialog: insertAndReplace() });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Synchronize the `hasDeleteConfirmDialog` when the dialog is closed.
|
||||
*/
|
||||
onDeleteConfirmDialogClosed() {
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
this.update({ hasDeleteConfirmDialog: false });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {number}
|
||||
@@ -97,6 +88,10 @@ registerModel({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
attachmentDeleteConfirmDialog: one('Dialog', {
|
||||
inverse: 'attachmentImageOwnerAsAttachmentDeleteConfirm',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* States the attachmentList displaying this attachment image.
|
||||
*/
|
||||
@@ -109,12 +104,6 @@ registerModel({
|
||||
* States the OWL component of this attachment image.
|
||||
*/
|
||||
component: attr(),
|
||||
/**
|
||||
* Determines the status of the delete confirm dialog (open/closed).
|
||||
*/
|
||||
hasDeleteConfirmDialog: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determines the max height of this attachment image in px.
|
||||
*/
|
||||
|
||||
@@ -459,7 +459,7 @@ registerModel({
|
||||
async updateMessage() {
|
||||
const composer = this.composer;
|
||||
if (!composer.textInputContent) {
|
||||
this.messageViewInEditing.messageActionList.update({ showDeleteConfirm: true });
|
||||
this.messageViewInEditing.messageActionList.update({ deleteConfirmDialog: insertAndReplace() });
|
||||
return;
|
||||
}
|
||||
const escapedAndCompactContent = escapeAndCompactTextContent(composer.textInputContent);
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'DeleteMessageConfirmView',
|
||||
identifyingFields: ['dialogOwner'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* Returns whether the given html element is inside this delete message confirm view.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
containsElement(element) {
|
||||
return Boolean(this.component && this.component.root.el && this.component.root.el.contains(element));
|
||||
},
|
||||
onClickCancel() {
|
||||
this.dialogOwner.delete();
|
||||
},
|
||||
onClickDelete() {
|
||||
this.message.updateContent({
|
||||
attachment_ids: [],
|
||||
body: '',
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeMessage() {
|
||||
return replace(this.dialogOwner.messageActionListOwnerAsDeleteConfirm.message);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {MessageView}
|
||||
*/
|
||||
_computeMessageView() {
|
||||
return this.message ? insertAndReplace({ message: replace(this.message) }) : clear();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
component: attr(),
|
||||
dialogOwner: one('Dialog', {
|
||||
inverse: 'deleteMessageConfirmView',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
message: one('Message', {
|
||||
compute: '_computeMessage',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Determines the message view that this delete message confirm view
|
||||
* will use to display this message.
|
||||
*/
|
||||
messageView: one('MessageView', {
|
||||
compute: '_computeMessageView',
|
||||
inverse: 'deleteMessageConfirmViewOwner',
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -7,8 +7,11 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
|
||||
registerModel({
|
||||
name: 'Dialog',
|
||||
identifyingFields: [[
|
||||
'attachmentCardOwnerAsAttachmentDeleteConfirm',
|
||||
'attachmentImageOwnerAsAttachmentDeleteConfirm',
|
||||
'attachmentListOwnerAsAttachmentView',
|
||||
'followerOwnerAsSubtypeList',
|
||||
'messageActionListOwnerAsDeleteConfirm',
|
||||
]],
|
||||
recordMethods: {
|
||||
/**
|
||||
@@ -18,6 +21,19 @@ registerModel({
|
||||
hasElementInContent(element) {
|
||||
return Boolean(this.record && this.record.containsElement(element));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeAttachmentDeleteConfirmView() {
|
||||
if (this.attachmentCardOwnerAsAttachmentDeleteConfirm) {
|
||||
return insertAndReplace();
|
||||
}
|
||||
if (this.attachmentImageOwnerAsAttachmentDeleteConfirm) {
|
||||
return insertAndReplace();
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
@@ -28,6 +44,29 @@ registerModel({
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_computeBackgroundOpacity() {
|
||||
if (this.attachmentViewer) {
|
||||
return 0.7;
|
||||
}
|
||||
return 0.5;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeComponentClassName() {
|
||||
if (this.attachmentDeleteConfirmView) {
|
||||
return 'o_Dialog_componentMediumSize align-self-start mt-5';
|
||||
}
|
||||
if (this.deleteMessageConfirmView) {
|
||||
return 'o_Dialog_componentLargeSize align-self-start mt-5';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {string|FieldCommand}
|
||||
@@ -36,20 +75,30 @@ registerModel({
|
||||
if (this.attachmentViewer) {
|
||||
return 'AttachmentViewer';
|
||||
}
|
||||
if (this.attachmentDeleteConfirmView) {
|
||||
return 'AttachmentDeleteConfirm';
|
||||
}
|
||||
if (this.deleteMessageConfirmView) {
|
||||
return 'DeleteMessageConfirm';
|
||||
}
|
||||
if (this.followerSubtypeList) {
|
||||
return 'FollowerSubtypeList';
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeDeleteMessageConfirmView() {
|
||||
return this.messageActionListOwnerAsDeleteConfirm ? insertAndReplace() : clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeFollowerSubtypeList() {
|
||||
if (this.followerOwnerAsSubtypeList) {
|
||||
return insertAndReplace();
|
||||
}
|
||||
return clear();
|
||||
return this.followerOwnerAsSubtypeList ? insertAndReplace() : clear();
|
||||
},
|
||||
/**
|
||||
/**
|
||||
@@ -84,12 +133,38 @@ registerModel({
|
||||
if (this.attachmentViewer) {
|
||||
return replace(this.attachmentViewer);
|
||||
}
|
||||
if (this.attachmentDeleteConfirmView) {
|
||||
return replace(this.attachmentDeleteConfirmView);
|
||||
}
|
||||
if (this.deleteMessageConfirmView) {
|
||||
return replace(this.deleteMessageConfirmView);
|
||||
}
|
||||
if (this.followerSubtypeList) {
|
||||
return replace(this.followerSubtypeList);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeStyle() {
|
||||
return `background-color: rgba(0, 0, 0, ${this.backgroundOpacity});`;
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
attachmentCardOwnerAsAttachmentDeleteConfirm: one('AttachmentCard', {
|
||||
inverse: 'attachmentDeleteConfirmDialog',
|
||||
readonly: true,
|
||||
}),
|
||||
attachmentDeleteConfirmView: one('AttachmentDeleteConfirmView', {
|
||||
compute: '_computeAttachmentDeleteConfirmView',
|
||||
inverse: 'dialogOwner',
|
||||
isCausal: true,
|
||||
}),
|
||||
attachmentImageOwnerAsAttachmentDeleteConfirm: one('AttachmentImage', {
|
||||
inverse: 'attachmentDeleteConfirmDialog',
|
||||
readonly: true,
|
||||
}),
|
||||
attachmentListOwnerAsAttachmentView: one('AttachmentList', {
|
||||
inverse: 'attachmentListViewDialog',
|
||||
readonly: true,
|
||||
@@ -99,10 +174,21 @@ registerModel({
|
||||
inverse: 'dialogOwner',
|
||||
isCausal: true,
|
||||
}),
|
||||
backgroundOpacity: attr({
|
||||
compute: '_computeBackgroundOpacity',
|
||||
}),
|
||||
componentClassName: attr({
|
||||
compute: '_computeComponentClassName',
|
||||
}),
|
||||
componentName: attr({
|
||||
compute: '_computeComponentName',
|
||||
required: true,
|
||||
}),
|
||||
deleteMessageConfirmView: one('DeleteMessageConfirmView', {
|
||||
compute: '_computeDeleteMessageConfirmView',
|
||||
inverse: 'dialogOwner',
|
||||
isCausal: true,
|
||||
}),
|
||||
followerOwnerAsSubtypeList: one('Follower', {
|
||||
inverse: 'followerSubtypeListDialog',
|
||||
readonly: true,
|
||||
@@ -121,6 +207,10 @@ registerModel({
|
||||
inverse: 'dialogs',
|
||||
readonly: true,
|
||||
}),
|
||||
messageActionListOwnerAsDeleteConfirm: one('MessageActionList', {
|
||||
inverse: 'deleteConfirmDialog',
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Content of dialog that is directly linked to a record that models
|
||||
* a UI component, such as AttachmentViewer. These records must be
|
||||
@@ -132,5 +222,8 @@ registerModel({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
style: attr({
|
||||
compute: '_computeStyle',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -16,22 +16,12 @@ registerModel({
|
||||
onClick(ev) {
|
||||
markEventHandled(ev, 'MessageActionList.Click');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickConfirmDelete(ev) {
|
||||
this.message.updateContent({
|
||||
body: '',
|
||||
attachment_ids: [],
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickDelete(ev) {
|
||||
this.update({ showDeleteConfirm: true });
|
||||
this.update({ deleteConfirmDialog: insertAndReplace() });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -82,13 +72,6 @@ registerModel({
|
||||
onClickToggleStar(ev) {
|
||||
this.message.toggleStar();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
onDeleteConfirmDialogClosed(ev) {
|
||||
this.update({ showDeleteConfirm: false });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
@@ -114,21 +97,16 @@ registerModel({
|
||||
)
|
||||
);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {MessageView}
|
||||
*/
|
||||
_computeMessageViewForDelete() {
|
||||
return this.message
|
||||
? insertAndReplace({ message: replace(this.message) })
|
||||
: clear();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
/**
|
||||
* States the reference to the reaction action in the component.
|
||||
*/
|
||||
actionReactionRef: attr(),
|
||||
deleteConfirmDialog: one('Dialog', {
|
||||
inverse: 'messageActionListOwnerAsDeleteConfirm',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Determines whether this message action list has mark as read icon.
|
||||
*/
|
||||
@@ -155,15 +133,6 @@ registerModel({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Determines the message view that this message action list will use to
|
||||
* display this message in this delete confirmation dialog.
|
||||
*/
|
||||
messageViewForDelete: one('MessageView', {
|
||||
compute: '_computeMessageViewForDelete',
|
||||
inverse: 'messageActionListWithDelete',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Determines the reaction popover that is active on this message action list.
|
||||
*/
|
||||
@@ -171,11 +140,5 @@ registerModel({
|
||||
inverse: 'messageActionListOwnerAsReaction',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Determines whether to show the message delete-confirm dialog.
|
||||
*/
|
||||
showDeleteConfirm: attr({
|
||||
default: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
import { markEventHandled } from '@mail/utils/utils';
|
||||
|
||||
registerModel({
|
||||
name: 'MessageView',
|
||||
identifyingFields: [['threadView', 'messageActionListWithDelete'], 'message'],
|
||||
identifyingFields: [['threadView', 'deleteMessageConfirmViewOwner'], 'message'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* Briefly highlights the message.
|
||||
@@ -20,6 +21,13 @@ registerModel({
|
||||
}, 2000),
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickFailure(ev) {
|
||||
markEventHandled(ev, 'Message.ClickFailure');
|
||||
this.message.openResendAction();
|
||||
},
|
||||
onComponentUpdate() {
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
@@ -110,9 +118,7 @@ registerModel({
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeMessageActionList() {
|
||||
return (!this.messageActionListWithDelete)
|
||||
? insertAndReplace()
|
||||
: clear();
|
||||
return this.deleteMessageConfirmViewOwner ? clear() : insertAndReplace();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -153,6 +159,14 @@ registerModel({
|
||||
inverse: 'messageViewInEditing',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* States the delete message confirm view that is displaying this
|
||||
* message view.
|
||||
*/
|
||||
deleteMessageConfirmViewOwner: one('DeleteMessageConfirmView', {
|
||||
inverse: 'messageView',
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines whether this message view should be highlighted at next
|
||||
* render. Scrolls into view and briefly highlights it.
|
||||
@@ -189,15 +203,6 @@ registerModel({
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* States the message action list that is displaying this message view
|
||||
* in its delete confirmation view.
|
||||
*/
|
||||
messageActionListWithDelete: one('MessageActionList', {
|
||||
inverse: 'messageViewForDelete',
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines the message that is displayed by this message view.
|
||||
*/
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Message } from '@mail/components/message/message';
|
||||
|
||||
import { patch } from 'web.utils';
|
||||
|
||||
const { useState } = owl;
|
||||
|
||||
patch(Message.prototype, 'snailmail/static/src/components/message/message.js', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
this._super();
|
||||
this._onDialogClosedSnailmailError = this._onDialogClosedSnailmailError.bind(this);
|
||||
this.snailmailState = useState({
|
||||
// Determine if the error dialog is displayed.
|
||||
hasDialog: false,
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onClickFailure() {
|
||||
if (this.messageView.message.message_type === 'snailmail') {
|
||||
/**
|
||||
* Messages from snailmail are considered to have at most one
|
||||
* notification. The failure type of the whole message is considered
|
||||
* to be the same as the one from that first notification, and the
|
||||
* click action will depend on it.
|
||||
*/
|
||||
switch (this.messageView.message.notifications[0].failure_type) {
|
||||
case 'sn_credit':
|
||||
// URL only used in this component, not received at init
|
||||
this.messaging.fetchSnailmailCreditsUrl();
|
||||
this.snailmailState.hasDialog = true;
|
||||
break;
|
||||
case 'sn_error':
|
||||
this.snailmailState.hasDialog = true;
|
||||
break;
|
||||
case 'sn_fields':
|
||||
this.messageView.message.openMissingFieldsLetterAction();
|
||||
break;
|
||||
case 'sn_format':
|
||||
this.messageView.message.openFormatLetterAction();
|
||||
break;
|
||||
case 'sn_price':
|
||||
this.snailmailState.hasDialog = true;
|
||||
break;
|
||||
case 'sn_trial':
|
||||
// URL only used in this component, not received at init
|
||||
this.messaging.fetchSnailmailCreditsUrlTrial();
|
||||
this.snailmailState.hasDialog = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this._super(...arguments);
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onDialogClosedSnailmailError() {
|
||||
this.snailmailState.hasDialog = false;
|
||||
},
|
||||
});
|
||||
@@ -16,12 +16,6 @@
|
||||
<t t-else="">$0</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//*[@name='rootCondition']" position="inside">
|
||||
<t t-if="snailmailState.hasDialog">
|
||||
<SnailmailError messageLocalId="messageView.message.localId" onClosed="_onDialogClosedSnailmailError"/>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<!--
|
||||
It was decided that the information displayed for snailmail messages
|
||||
has to be different than for standard messages, see task-1907998.
|
||||
|
||||
@@ -281,6 +281,7 @@ QUnit.test('No Price Available', async function (assert) {
|
||||
res_partner_id: 12,
|
||||
});
|
||||
const { createThreadViewComponent } = await this.start({
|
||||
hasDialog: true,
|
||||
async mockRPC(route, args) {
|
||||
if (args.method === 'cancel_letter' && args.model === 'mail.message' && args.args[0][0] === 10) {
|
||||
assert.step(args.method);
|
||||
@@ -373,6 +374,7 @@ QUnit.test('Credit Error', async function (assert) {
|
||||
res_partner_id: 12,
|
||||
});
|
||||
const { createThreadViewComponent } = await this.start({
|
||||
hasDialog: true,
|
||||
async mockRPC(route, args) {
|
||||
if (args.method === 'send_letter' && args.model === 'mail.message' && args.args[0][0] === 10) {
|
||||
assert.step(args.method);
|
||||
@@ -470,6 +472,7 @@ QUnit.test('Trial Error', async function (assert) {
|
||||
res_partner_id: 12,
|
||||
});
|
||||
const { createThreadViewComponent } = await this.start({
|
||||
hasDialog: true,
|
||||
async mockRPC(route, args) {
|
||||
if (args.method === 'send_letter' && args.model === 'mail.message' && args.args[0][0] === 10) {
|
||||
assert.step(args.method);
|
||||
|
||||
@@ -1,90 +1,32 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
import Dialog from 'web.OwlDialog';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
class SnailmailError extends Component {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
export class SnailmailError extends Component {
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
* @override
|
||||
*/
|
||||
get hasCreditsError() {
|
||||
return (
|
||||
this.notification.failure_type === 'sn_credit' ||
|
||||
this.notification.failure_type === 'sn_trial'
|
||||
);
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component', modelName: 'SnailmailErrorView' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Message}
|
||||
* @returns {SnailmailErrorView}
|
||||
*/
|
||||
get message() {
|
||||
return this.messaging && this.messaging.models['Message'].get(this.props.messageLocalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Notification}
|
||||
*/
|
||||
get notification() {
|
||||
// Messages from snailmail are considered to have at most one notification.
|
||||
return this.message.notifications[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get title() {
|
||||
return this.env._t("Failed letter");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickCancelLetter() {
|
||||
this.root.comp._close();
|
||||
this.message.cancelLetter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickClose() {
|
||||
this.root.comp._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickResendLetter() {
|
||||
this.root.comp._close();
|
||||
this.message.resendLetter();
|
||||
get snailmailErrorView() {
|
||||
return this.messaging && this.messaging.models['SnailmailErrorView'].get(this.props.localId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(SnailmailError, {
|
||||
components: { Dialog },
|
||||
props: {
|
||||
messageLocalId: String,
|
||||
onClosed: {
|
||||
type: Function,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
props: { localId: String },
|
||||
template: 'snailmail.SnailmailError',
|
||||
});
|
||||
|
||||
registerMessagingComponent(SnailmailError);
|
||||
|
||||
export default SnailmailError;
|
||||
|
||||
@@ -2,60 +2,62 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="snailmail.SnailmailError" owl="1">
|
||||
<Dialog contentClass="'o_SnailmailError'" title="title" size="'medium'" onClosed="props.onClosed" t-ref="root">
|
||||
<t t-if="message and notification">
|
||||
<t t-if="notification.failure_type === 'sn_credit'">
|
||||
<p class="o_SnailmailError_contentCredit">
|
||||
<div class="o_SnailmailError card bg-white" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-if="snailmailErrorView">
|
||||
<h4 class="m-3">Failed letter</h4>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<t t-if="snailmailErrorView.notification.failure_type === 'sn_credit'">
|
||||
<p class="o_SnailmailError_contentCredit mx-3 mb-3">
|
||||
The letter could not be sent due to insufficient credits on your IAP account.
|
||||
</p>
|
||||
<t t-if="messaging.snailmail_credits_url">
|
||||
<div class="text-right">
|
||||
<div class="text-right mx-3 mb-3">
|
||||
<a class="btn btn-link" t-att-href="messaging.snailmail_credits_url" target="_blank">
|
||||
<i class="fa fa-arrow-right"/> Buy credits
|
||||
</a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="notification.failure_type === 'sn_trial'">
|
||||
<p class="o_SnailmailError_contentTrial">
|
||||
<t t-elif="snailmailErrorView.notification.failure_type === 'sn_trial'">
|
||||
<p class="o_SnailmailError_contentTrial mx-3 mb-3">
|
||||
You need credits on your IAP account to send a letter.
|
||||
</p>
|
||||
<t t-if="messaging.snailmail_credits_url_trial">
|
||||
<div class="text-right">
|
||||
<div class="text-right mx-3 mb-3">
|
||||
<a class="btn btn-link" t-att-href="messaging.snailmail_credits_url_trial">
|
||||
<i class="fa fa-arrow-right"/> Buy credits
|
||||
</a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="notification.failure_type === 'sn_price'">
|
||||
<p class="o_SnailmailError_contentPrice">
|
||||
<t t-elif="snailmailErrorView.notification.failure_type === 'sn_price'">
|
||||
<p class="o_SnailmailError_contentPrice mx-3 mb-3">
|
||||
The country to which you want to send the letter is not supported by our service.
|
||||
</p>
|
||||
</t>
|
||||
<t t-elif="notification.failure_type === 'sn_error'">
|
||||
<p class="o_SnailmailError_contentError">
|
||||
<t t-elif="snailmailErrorView.notification.failure_type === 'sn_error'">
|
||||
<p class="o_SnailmailError_contentError mx-3 mb-3">
|
||||
An unknown error occurred. Please contact our <a href="https://www.odoo.com/help" target="new">support</a> for further assistance.
|
||||
</p>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="buttons">
|
||||
<t t-if="hasCreditsError">
|
||||
<button class="o_SnailmailError_resendLetterButton btn btn-primary" t-on-click="_onClickResendLetter">Re-send letter</button>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<div class="o_SnailmailError_buttons mx-3 mb-3">
|
||||
<t t-if="snailmailErrorView.hasCreditsError">
|
||||
<button class="o_SnailmailError_resendLetterButton btn btn-primary me-2" t-on-click="snailmailErrorView.onClickResendLetter">Re-send letter</button>
|
||||
</t>
|
||||
<button class="o_SnailmailError_cancelLetterButton btn"
|
||||
<button class="o_SnailmailError_cancelLetterButton btn me-2"
|
||||
t-att-class="{
|
||||
'btn-primary': !hasCreditsError,
|
||||
'btn-secondary': hasCreditsError,
|
||||
'btn-primary': !snailmailErrorView.hasCreditsError,
|
||||
'btn-secondary': snailmailErrorView.hasCreditsError,
|
||||
}"
|
||||
t-on-click="_onClickCancelLetter"
|
||||
t-on-click="snailmailErrorView.onClickCancelLetter"
|
||||
>
|
||||
Cancel letter
|
||||
</button>
|
||||
<button class="o_SnailmailError_closeButton btn btn-secondary" t-on-click="_onClickClose">Close</button>
|
||||
</t>
|
||||
<button class="o_SnailmailError_closeButton btn btn-secondary me-2" t-on-click="snailmailErrorView.onClickClose">Close</button>
|
||||
</div>
|
||||
</t>
|
||||
</Dialog>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addFields, addRecordMethods, patchIdentifyingFields, patchRecordMethods } from '@mail/model/model_core';
|
||||
import { one } from '@mail/model/model_field';
|
||||
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/dialog/dialog';
|
||||
|
||||
patchIdentifyingFields('Dialog', identifyingFields => {
|
||||
identifyingFields[0].push('messageViewOwnerAsSnailmailError');
|
||||
});
|
||||
|
||||
addFields('Dialog', {
|
||||
messageViewOwnerAsSnailmailError: one('MessageView', {
|
||||
inverse: 'snailmailErrorDialog',
|
||||
readonly: true,
|
||||
}),
|
||||
snailmailErrorView: one('SnailmailErrorView', {
|
||||
compute: '_computeSnailmailErrorView',
|
||||
inverse: 'dialogOwner',
|
||||
isCausal: true,
|
||||
}),
|
||||
});
|
||||
|
||||
addRecordMethods('Dialog', {
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeSnailmailErrorView() {
|
||||
if (this.messageViewOwnerAsSnailmailError) {
|
||||
return insertAndReplace();
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
});
|
||||
|
||||
patchRecordMethods('Dialog', {
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeComponentClassName() {
|
||||
if (this.snailmailErrorView) {
|
||||
return 'o_Dialog_componentMediumSize align-self-start mt-5';
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeComponentName() {
|
||||
if (this.snailmailErrorView) {
|
||||
return 'SnailmailError';
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeRecord() {
|
||||
if (this.snailmailErrorView) {
|
||||
return replace(this.snailmailErrorView);
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addFields, patchRecordMethods } from '@mail/model/model_core';
|
||||
import { one } from '@mail/model/model_field';
|
||||
import { insertAndReplace } from '@mail/model/model_field_command';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/message_view/message_view';
|
||||
|
||||
addFields('MessageView', {
|
||||
snailmailErrorDialog: one('Dialog', {
|
||||
inverse: 'messageViewOwnerAsSnailmailError',
|
||||
isCausal: true,
|
||||
}),
|
||||
});
|
||||
|
||||
patchRecordMethods('MessageView', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onClickFailure() {
|
||||
if (this.message.message_type === 'snailmail') {
|
||||
/**
|
||||
* Messages from snailmail are considered to have at most one
|
||||
* notification. The failure type of the whole message is considered
|
||||
* to be the same as the one from that first notification, and the
|
||||
* click action will depend on it.
|
||||
*/
|
||||
switch (this.message.notifications[0].failure_type) {
|
||||
case 'sn_credit':
|
||||
// URL only used in this component, not received at init
|
||||
this.messaging.fetchSnailmailCreditsUrl();
|
||||
this.update({ snailmailErrorDialog: insertAndReplace() });
|
||||
break;
|
||||
case 'sn_error':
|
||||
this.update({ snailmailErrorDialog: insertAndReplace() });
|
||||
break;
|
||||
case 'sn_fields':
|
||||
this.message.openMissingFieldsLetterAction();
|
||||
break;
|
||||
case 'sn_format':
|
||||
this.message.openFormatLetterAction();
|
||||
break;
|
||||
case 'sn_price':
|
||||
this.update({ snailmailErrorDialog: insertAndReplace() });
|
||||
break;
|
||||
case 'sn_trial':
|
||||
// URL only used in this component, not received at init
|
||||
this.messaging.fetchSnailmailCreditsUrlTrial();
|
||||
this.update({ snailmailErrorDialog: insertAndReplace() });
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this._super(...arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'SnailmailErrorView',
|
||||
identifyingFields: ['dialogOwner'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* Returns whether the given html element is inside this snailmail error view.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
containsElement(element) {
|
||||
return Boolean(this.component && this.component.root.el && this.component.root.el.contains(element));
|
||||
},
|
||||
onClickCancelLetter() {
|
||||
this.message.cancelLetter();
|
||||
this.dialogOwner.delete();
|
||||
},
|
||||
onClickClose() {
|
||||
this.dialogOwner.delete();
|
||||
},
|
||||
onClickResendLetter() {
|
||||
this.message.resendLetter();
|
||||
this.dialogOwner.delete();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeHasCreditsError() {
|
||||
return Boolean(
|
||||
this.notification &&
|
||||
(
|
||||
this.notification.failure_type === 'sn_credit' ||
|
||||
this.notification.failure_type === 'sn_trial'
|
||||
)
|
||||
);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeMessage() {
|
||||
return replace(this.dialogOwner.messageViewOwnerAsSnailmailError.message);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeNotification() {
|
||||
return replace(this.message.notifications[0]);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
component: attr(),
|
||||
dialogOwner: one('Dialog', {
|
||||
inverse: 'snailmailErrorView',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
hasCreditsError: attr({
|
||||
compute: '_computeHasCreditsError',
|
||||
}),
|
||||
message: one('Message', {
|
||||
compute: '_computeMessage',
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Messages from snailmail are considered to have at most one notification.
|
||||
*/
|
||||
notification: one('Notification', {
|
||||
compute: '_computeNotification',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user