diff --git a/addons/mail/static/src/components/attachment_image/attachment_image.xml b/addons/mail/static/src/components/attachment_image/attachment_image.xml
index 65643900d17..0b0b76d3fa5 100644
--- a/addons/mail/static/src/components/attachment_image/attachment_image.xml
+++ b/addons/mail/static/src/components/attachment_image/attachment_image.xml
@@ -20,15 +20,14 @@
-
-
diff --git a/addons/mail/static/src/models/attachment_image.js b/addons/mail/static/src/models/attachment_image.js
index eb345d7905e..fd61611f49a 100644
--- a/addons/mail/static/src/models/attachment_image.js
+++ b/addons/mail/static/src/models/attachment_image.js
@@ -3,15 +3,36 @@
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 { isEventHandled, markEventHandled } from '@mail/utils/utils';
registerModel({
name: 'AttachmentImage',
identifyingFields: ['attachmentList', 'attachment'],
recordMethods: {
/**
- * Opens the attachment viewer when clicking on viewable attachment.
+ * Called when clicking on download icon.
+ *
+ * @param {MouseEvent} ev
*/
- onClickImage() {
+ onClickDownload(ev) {
+ markEventHandled(ev, 'AttachmentImage.onClickDownload');
+ if (!this.exists()) {
+ return;
+ }
+ this.attachment.download();
+ },
+ /**
+ * Opens the attachment viewer when clicking on viewable attachment.
+ *
+ * @param {MouseEvent} ev
+ */
+ onClickImage(ev) {
+ if (isEventHandled(ev, 'AttachmentImage.onClickDownload')) {
+ return;
+ }
+ if (isEventHandled(ev, 'AttachmentImage.onClickUnlink')) {
+ return;
+ }
if (!this.attachment || !this.attachment.isViewable) {
return;
}
@@ -26,8 +47,8 @@ registerModel({
* @param {MouseEvent} ev
*/
onClickUnlink(ev) {
- ev.stopPropagation(); // prevents from opening viewer
- if (!this.attachment) {
+ markEventHandled(ev, 'AttachmentImage.onClickUnlink');
+ if (!this.exists()) {
return;
}
if (this.attachmentList.composerViewOwner) {
@@ -36,6 +57,16 @@ registerModel({
this.update({ attachmentDeleteConfirmDialog: insertAndReplace() });
}
},
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeHasDownloadButton() {
+ if (!this.attachment || !this.attachmentList) {
+ return clear();
+ }
+ return !this.attachmentList.composerViewOwner && !this.attachment.isUploading;
+ },
/**
* @private
* @returns {number}
@@ -99,6 +130,13 @@ registerModel({
readonly: true,
required: true,
}),
+ /**
+ * Determines whether `this` should display a download button.
+ */
+ hasDownloadButton: attr({
+ compute: '_computeHasDownloadButton',
+ default: false,
+ }),
/**
* Determines the max height of this attachment image in px.
*/
diff --git a/addons/mail/static/tests/qunit_suite_tests/components/message_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/message_tests.js
index 86439f030ff..45a4d875ad8 100644
--- a/addons/mail/static/tests/qunit_suite_tests/components/message_tests.js
+++ b/addons/mail/static/tests/qunit_suite_tests/components/message_tests.js
@@ -698,6 +698,36 @@ QUnit.test('prevent attachment delete on non-authored message in channels', asyn
);
});
+QUnit.test('allow attachment image download on message', async function (assert) {
+ assert.expect(1);
+
+ const pyEnv = await startServer();
+ const mailChannelId1 = pyEnv['mail.channel'].create({});
+ const irAttachmentId1 = pyEnv['ir.attachment'].create({
+ name: "Blah.jpg",
+ mimetype: 'image/jpeg',
+ });
+ pyEnv['mail.message'].create({
+ attachment_ids: [irAttachmentId1],
+ body: '
Test
',
+ model: 'mail.channel',
+ res_id: mailChannelId1,
+ });
+ const { openDiscuss } = await start({
+ discuss: {
+ context: {
+ active_id: mailChannelId1,
+ },
+ },
+ });
+ await openDiscuss();
+ assert.containsOnce(
+ document.body,
+ '.o_AttachmentImage_actionDownload',
+ "should have download attachment button"
+ );
+});
+
QUnit.test('subtype description should be displayed if it is different than body', async function (assert) {
assert.expect(2);