Drag Files Here
diff --git a/addons/mail/static/src/components/message_action_list/message_action_list.js b/addons/mail/static/src/components/message_action_list/message_action_list.js
index f02e11768c8..be8c6c34603 100644
--- a/addons/mail/static/src/components/message_action_list/message_action_list.js
+++ b/addons/mail/static/src/components/message_action_list/message_action_list.js
@@ -13,7 +13,6 @@ export class MessageActionList extends Component {
setup() {
super.setup();
useRefToModel({ fieldName: 'actionReactionRef', modelName: 'MessageActionList', refName: 'actionReaction' });
- this.ADD_A_REACTION = this.env._t("Add a Reaction");
}
/**
diff --git a/addons/mail/static/src/components/message_action_list/message_action_list.xml b/addons/mail/static/src/components/message_action_list/message_action_list.xml
index 5432e69fbaa..96dfb5722b5 100644
--- a/addons/mail/static/src/components/message_action_list/message_action_list.xml
+++ b/addons/mail/static/src/components/message_action_list/message_action_list.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/addons/mail/static/src/models/drop_zone_view.js b/addons/mail/static/src/models/drop_zone_view.js
index e36171be3a7..8f4d2e2da01 100644
--- a/addons/mail/static/src/models/drop_zone_view.js
+++ b/addons/mail/static/src/models/drop_zone_view.js
@@ -1,11 +1,95 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
-import { one } from '@mail/model/model_field';
+import { attr, one } from '@mail/model/model_field';
+import { decrement, increment } from '@mail/model/model_field_command';
registerModel({
name: 'DropZoneView',
identifyingFields: [['attachmentBoxViewOwner', 'composerViewOwner']],
+ recordMethods: {
+ /**
+ * Shows a visual drop effect when dragging inside the dropzone.
+ *
+ * @param {DragEvent} ev
+ */
+ onDragenter(ev) {
+ if (!this.exists()) {
+ return;
+ }
+ ev.preventDefault();
+ if (this.dragCount === 0) {
+ this.update({ isDraggingInside: true });
+ }
+ this.update({ dragCount: increment() });
+ },
+ /**
+ * Hides the visual drop effect when dragging outside the dropzone.
+ *
+ * @param {DragEvent} ev
+ */
+ onDragleave(ev) {
+ if (!this.exists()) {
+ return;
+ }
+ this.update({ dragCount: decrement() });
+ if (this.dragCount === 0) {
+ this.update({ isDraggingInside: false });
+ }
+ },
+ /**
+ * Prevents default (from the template) in order to receive the drop event.
+ * The drop effect cursor works only when set on dragover.
+ *
+ * @param {DragEvent} ev
+ */
+ onDragover(ev) {
+ ev.preventDefault();
+ ev.dataTransfer.dropEffect = 'copy';
+ },
+ /**
+ * Trigger callback 'props.onDropzoneFilesDropped' with event when new files are dropped
+ * on the dropzone, and then removes the visual drop effect.
+ *
+ * The parents should handle this event to process the files as they wish,
+ * such as uploading them.
+ *
+ * @param {DragEvent} ev
+ */
+ async onDrop(ev) {
+ if (!this.exists()) {
+ return;
+ }
+ ev.preventDefault();
+ this.update({ isDraggingInside: false });
+ if (this._isDragSourceExternalFile(ev.dataTransfer)) {
+ if (this.attachmentBoxViewOwner) {
+ await this.attachmentBoxViewOwner.fileUploader.uploadFiles(ev.dataTransfer.files);
+ }
+ if (this.composerViewOwner) {
+ await this.composerViewOwner.fileUploader.uploadFiles(ev.dataTransfer.files);
+ }
+ }
+ },
+ /**
+ * Making sure that dragging content is external files.
+ * Ignoring other content dragging like text.
+ *
+ * @private
+ * @param {DataTransfer} dataTransfer
+ * @returns {boolean}
+ */
+ _isDragSourceExternalFile(dataTransfer) {
+ const dragDataType = dataTransfer.types;
+ if (dragDataType.constructor === window.DOMStringList) {
+ return dragDataType.contains('Files');
+ }
+ if (dragDataType.constructor === Array) {
+ return dragDataType.includes('Files');
+ }
+ return false;
+ },
+ },
fields: {
attachmentBoxViewOwner: one('AttachmentBoxView', {
inverse: 'dropZoneView',
@@ -15,5 +99,19 @@ registerModel({
inverse: 'dropZoneView',
readonly: true,
}),
+ /**
+ * Counts how many drag enter/leave happened on self and children. This
+ * ensures the drop effect stays active when dragging over a child.
+ */
+ dragCount: attr({
+ default: 0,
+ }),
+ /**
+ * Determines whether the user is dragging files over the dropzone.
+ * Useful to provide visual feedback in that case.
+ */
+ isDraggingInside: attr({
+ default: false,
+ }),
},
});
diff --git a/addons/mail/static/src/models/message_action_list.js b/addons/mail/static/src/models/message_action_list.js
index 61abef25228..63123dfb729 100644
--- a/addons/mail/static/src/models/message_action_list.js
+++ b/addons/mail/static/src/models/message_action_list.js
@@ -72,6 +72,13 @@ registerModel({
onClickToggleStar(ev) {
this.message.toggleStar();
},
+ /**
+ * @private
+ * @returns {string}
+ */
+ _computeAddReactionText() {
+ return this.env._t("Add a Reaction");
+ },
/**
* @private
* @returns {boolean}
@@ -103,6 +110,9 @@ registerModel({
* States the reference to the reaction action in the component.
*/
actionReactionRef: attr(),
+ addReactionText: attr({
+ compute: '_computeAddReactionText',
+ }),
deleteConfirmDialog: one('Dialog', {
inverse: 'messageActionListOwnerAsDeleteConfirm',
isCausal: true,
diff --git a/addons/mail/static/src/models/message_list_view.js b/addons/mail/static/src/models/message_list_view.js
index 7505ec19c2e..c6fc1e566bd 100644
--- a/addons/mail/static/src/models/message_list_view.js
+++ b/addons/mail/static/src/models/message_list_view.js
@@ -2,6 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
+import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'MessageListView',
@@ -16,6 +17,16 @@ registerModel({
}
return this.component.root.el;
},
+ /**
+ * @private
+ * @returns {boolean|FieldCommand}
+ */
+ _computeHasScrollAdjust() {
+ if (this.threadViewOwner.threadViewer.chatter) {
+ return this.threadViewOwner.threadViewer.chatter.hasMessageListScrollAdjust;
+ }
+ return clear();
+ },
/***
* @private
* @returns {boolean}
@@ -38,6 +49,10 @@ registerModel({
* States the OWL component of this message list view
*/
component: attr(),
+ hasScrollAdjust: attr({
+ compute: '_computeHasScrollAdjust',
+ default: true,
+ }),
/**
* States whether the message list scroll position is at the end of
* the message list. Depending of the message list order, this could be
diff --git a/addons/mail/static/src/models/messaging_menu.js b/addons/mail/static/src/models/messaging_menu.js
index 5f9c0751fd7..a838d4d755d 100644
--- a/addons/mail/static/src/models/messaging_menu.js
+++ b/addons/mail/static/src/models/messaging_menu.js
@@ -122,6 +122,7 @@ registerModel({
activeTabId: attr({
default: 'all',
}),
+ component: attr(),
/**
* States the counter of this messaging menu. The counter is an integer
* value to give to the current user an estimate of how many things
diff --git a/addons/mail/static/src/models/use_drag_visible_drop_zone.js b/addons/mail/static/src/models/use_drag_visible_drop_zone.js
index b89d6ef0a7c..7b48ad29118 100644
--- a/addons/mail/static/src/models/use_drag_visible_drop_zone.js
+++ b/addons/mail/static/src/models/use_drag_visible_drop_zone.js
@@ -11,7 +11,7 @@ registerModel({
_created() {
document.addEventListener('dragenter', this._onDragenterListener, true);
document.addEventListener('dragleave', this._onDragleaveListener, true);
- document.addEventListener('drop', this._onDropListener, true);
+ document.addEventListener('drop', this._onDropListener);
// Thoses Events prevent the browser to open or download the file if
// it's dropped outside of the dropzone
@@ -21,7 +21,7 @@ registerModel({
_willDelete() {
document.removeEventListener('dragenter', this._onDragenterListener, true);
document.removeEventListener('dragleave', this._onDragleaveListener, true);
- document.removeEventListener('drop', this._onDropListener, true);
+ document.removeEventListener('drop', this._onDropListener);
window.removeEventListener('dragover', ev => ev.preventDefault());
window.removeEventListener('drop', ev => ev.preventDefault());
diff --git a/addons/mail/static/tests/qunit_mobile_suite_tests/components/discuss_mobile_mailbox_selection_tests.js b/addons/mail/static/tests/qunit_mobile_suite_tests/components/discuss_mobile_mailbox_selection_tests.js
index 9197595eed3..d1dcdb860e2 100644
--- a/addons/mail/static/tests/qunit_mobile_suite_tests/components/discuss_mobile_mailbox_selection_tests.js
+++ b/addons/mail/static/tests/qunit_mobile_suite_tests/components/discuss_mobile_mailbox_selection_tests.js
@@ -48,13 +48,13 @@ QUnit.test('select another mailbox', async function (assert) {
);
assert.containsOnce(
document.body,
- `.o_DiscussMobileMailboxSelection_button[
+ `.o_DiscussMobileMailboxSelectionItem[
data-mailbox-local-id="${messaging.starred.localId}"
]`,
"should have a button to open starred mailbox"
);
- await click(`.o_DiscussMobileMailboxSelection_button[
+ await click(`.o_DiscussMobileMailboxSelectionItem[
data-mailbox-local-id="${messaging.starred.localId}"]
`);
assert.containsOnce(
@@ -106,7 +106,7 @@ QUnit.test('auto-select "Inbox" when discuss had channel as active thread', asyn
"'mailbox' tab should be selected after click on mailbox tab"
);
assert.hasClass(
- document.querySelector(`.o_DiscussMobileMailboxSelection_button[data-mailbox-local-id="${
+ document.querySelector(`.o_DiscussMobileMailboxSelectionItem[data-mailbox-local-id="${
messaging.inbox.localId
}"]`),
'o-active',