[IMP] mail: Allowing drag/drop files/images on mail composer
By this commit user can upload attachments by drag and drop in chatter composer. This feature can be used from discuss UI, chat windows and form view chatter. Before this commit chatter unlinks the attachment if another attachment uploaded with same name. Criteria for removing duplicate attachment is changed with this commit. Now it will remove the attachment based on name and size of attachment. The reason for this change is, when you upload image with same name from different directory with old condition it was not possible to attach more then one image/file of same name. Related to task #32469 Closes #18914
This commit is contained in:
committed by
Alexandre Kühn
parent
db0f80ee53
commit
fb051d3420
@@ -28,6 +28,8 @@ var BasicComposer = Widget.extend({
|
||||
'focusout .o_composer_button_emoji': '_onEmojiButtonFocusout',
|
||||
'click .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageClick',
|
||||
'focus .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageFocus',
|
||||
'dragover .o_file_drop_zone_container': '_onFileDragover',
|
||||
'drop .o_file_drop_zone_container': '_onFileDrop',
|
||||
'input .o_input': '_onInput',
|
||||
'keydown .o_composer_input textarea': '_onKeydown',
|
||||
'keyup .o_composer_input': '_onKeyup',
|
||||
@@ -124,9 +126,19 @@ var BasicComposer = Widget.extend({
|
||||
// Mention
|
||||
this._mentionManager.prependTo(this.$('.o_composer'));
|
||||
|
||||
// Drag-Drop files
|
||||
// Allowing body to detect dragenter and dragleave for display
|
||||
var $body = $('body');
|
||||
this._dropZoneNS = _.uniqueId('o_dz_'); // For event namespace used when multiple chat window is open
|
||||
$body.on('dragleave.' + this._dropZoneNS, this._onBodyFileDragLeave.bind(this));
|
||||
$body.on("dragover." + this._dropZoneNS, this._onBodyFileDragover.bind(this));
|
||||
$body.on("drop." + this._dropZoneNS, this._onBodyFileDrop.bind(this));
|
||||
return this._super();
|
||||
},
|
||||
destroy: function () {
|
||||
$("body").off('dragleave.' + this._dropZoneNS);
|
||||
$("body").off('dragover.' + this._dropZoneNS);
|
||||
$("body").off('drop.' + this._dropZoneNS);
|
||||
$(window).off(this.fileuploadID);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
@@ -253,6 +265,24 @@ var BasicComposer = Widget.extend({
|
||||
_hideEmojis: function () {
|
||||
this._$emojisContainer.remove();
|
||||
},
|
||||
/**
|
||||
* Making sure that dragging content is external files.
|
||||
* Ignoring other content draging like text.
|
||||
*
|
||||
* @private
|
||||
* @param {DataTransfer} dataTransfer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDragSourceExternalFile: function (dataTransfer) {
|
||||
var DragDataType = dataTransfer.types;
|
||||
if (DragDataType.constructor === DOMStringList) {
|
||||
return DragDataType.contains('Files');
|
||||
}
|
||||
if (DragDataType.constructor === Array) {
|
||||
return DragDataType.indexOf('Files') !== -1;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} search
|
||||
@@ -377,6 +407,67 @@ var BasicComposer = Widget.extend({
|
||||
command: commands.length > 0 ? commands[0].name : undefined,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Allowing to upload attachment with file selector as well drag drop feature.
|
||||
*
|
||||
* @private
|
||||
* @param {Array<File>} params.files
|
||||
* @param {boolean} params.submitForm [optional]
|
||||
*/
|
||||
_processAttachmentChange: function (params) {
|
||||
var self = this,
|
||||
attachments = this.get('attachment_ids'),
|
||||
files = params.files,
|
||||
submitForm = params.submitForm;
|
||||
_.each(files, function (file) {
|
||||
var attachment = _.findWhere(attachments, {
|
||||
name: file.name,
|
||||
size: file.size
|
||||
});
|
||||
// if the files already exits, delete the file before upload
|
||||
if (attachment) {
|
||||
self._attachmentDataSet.unlink([attachment.id]);
|
||||
attachments = _.without(attachments, attachment);
|
||||
}
|
||||
});
|
||||
var $form = this.$('form.o_form_binary_form');
|
||||
if (submitForm) {
|
||||
$form.submit();
|
||||
this._$attachmentButton.prop('disabled', true);
|
||||
} else {
|
||||
var data = new FormData($form[0]);
|
||||
_.each(files, function (file) {
|
||||
// removing existing key with blank data and appending again with file info
|
||||
// In safari, existing key will not be updated when append with new file.
|
||||
data.delete("ufile");
|
||||
data.append("ufile", file, file.name);
|
||||
$.ajax({
|
||||
url: $form.attr("action"),
|
||||
type: "POST",
|
||||
enctype: 'multipart/form-data',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: data,
|
||||
success: function (result) {
|
||||
var $el = $(result);
|
||||
$.globalEval($el.contents().text());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
var uploadAttachments = _.map(files, function (file){
|
||||
return {
|
||||
id: 0,
|
||||
name: file.name,
|
||||
filename: file.name,
|
||||
url: '',
|
||||
upload: true,
|
||||
mimetype: '',
|
||||
};
|
||||
});
|
||||
attachments = attachments.concat(uploadAttachments);
|
||||
this.set('attachment_ids', attachments);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -419,33 +510,10 @@ var BasicComposer = Widget.extend({
|
||||
* @param {jQuery.Event} ev
|
||||
*/
|
||||
_onAttachmentChange: function (ev) {
|
||||
var self = this;
|
||||
var files = ev.target.files;
|
||||
var attachments = this.get('attachment_ids');
|
||||
|
||||
_.each(files, function (file){
|
||||
var attachment = _.findWhere(attachments, {name: file.name});
|
||||
// if the files already exits, delete the file before upload
|
||||
if (attachment){
|
||||
self._attachmentDataSet.unlink([attachment.id]);
|
||||
attachments = _.without(attachments, attachment);
|
||||
}
|
||||
this._processAttachmentChange({
|
||||
files: ev.currentTarget.files,
|
||||
submitForm: true
|
||||
});
|
||||
|
||||
this.$('form.o_form_binary_form').submit();
|
||||
this._$attachmentButton.prop('disabled', true);
|
||||
var uploadAttachments = _.map(files, function (file){
|
||||
return {
|
||||
id: 0,
|
||||
name: file.name,
|
||||
filename: file.name,
|
||||
url: '',
|
||||
upload: true,
|
||||
mimetype: '',
|
||||
};
|
||||
});
|
||||
attachments = attachments.concat(uploadAttachments);
|
||||
this.set('attachment_ids', attachments);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -497,6 +565,7 @@ var BasicComposer = Widget.extend({
|
||||
name: file.name || file.filename,
|
||||
filename: file.filename,
|
||||
mimetype: file.mimetype,
|
||||
size: file.size,
|
||||
url: session.url('/web/content', { id: file.id, download: true }),
|
||||
});
|
||||
}
|
||||
@@ -517,6 +586,46 @@ var BasicComposer = Widget.extend({
|
||||
attachmentViewer.appendTo($('body'));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onBodyFileDragLeave: function (ev) {
|
||||
// On every dragenter chain created with parent child element
|
||||
// That's why dragleave is fired every time when a child elemnt is hovered
|
||||
// so here we hide dropzone based on mouse position
|
||||
if (ev.originalEvent.clientX <= 0
|
||||
|| ev.originalEvent.clientY <= 0
|
||||
|| ev.originalEvent.clientX >= window.innerWidth
|
||||
|| ev.originalEvent.clientY >= window.innerHeight
|
||||
) {
|
||||
this.$(".o_file_drop_zone_container").addClass("d-none");
|
||||
}
|
||||
},
|
||||
/**
|
||||
* When user start dragging on element drop area will be visible to drop selected files.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onBodyFileDragover: function (ev) {
|
||||
ev.preventDefault();
|
||||
if (this._isDragSourceExternalFile(ev.originalEvent.dataTransfer)) {
|
||||
this.$(".o_file_drop_zone_container").removeClass("d-none");
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onBodyFileDrop: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.$(".o_file_drop_zone_container").addClass("d-none");
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickAddAttachment: function () {
|
||||
this.$('input.o_input_file').click();
|
||||
this.$input.focus();
|
||||
@@ -573,6 +682,32 @@ var BasicComposer = Widget.extend({
|
||||
_onEmojiImageFocus: function () {
|
||||
clearTimeout(this._hideEmojisTimeout);
|
||||
},
|
||||
/**
|
||||
* Setting drop Effect to copy so when mouse pointer on dropzone
|
||||
* cursor icon changed to copy ('+')
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onFileDragover: function (ev) {
|
||||
ev.originalEvent.dataTransfer.dropEffect = "copy";
|
||||
},
|
||||
/**
|
||||
* Called when user drop selected files on drop area
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onFileDrop: function (ev) {
|
||||
ev.preventDefault();
|
||||
// FIX: In case multiple chat windows are opened, and file droped in one of them
|
||||
// at that time, other chat windows are still displaing drop areas so here hide them all with $ selector
|
||||
$(".o_file_drop_zone_container").addClass("d-none");
|
||||
if (this._isDragSourceExternalFile(ev.originalEvent.dataTransfer)) {
|
||||
var files = ev.originalEvent.dataTransfer.files;
|
||||
this._processAttachmentChange({ files: files });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Called when the input in the composer changes
|
||||
*
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
}
|
||||
|
||||
.o_thread_composer {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
margin: 1px 0;
|
||||
@@ -111,6 +110,7 @@
|
||||
.o_composer {
|
||||
border-radius: 2px;
|
||||
textarea {
|
||||
padding-top: $o-mail-chatter-gap !important;
|
||||
padding-right: $grid-gutter-width; // avoid overlaps betwenn textarea and o_composer_button_full_composer
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
flex: 1 0 100%;
|
||||
}
|
||||
.o_composer, .o_composer_subject {
|
||||
padding: $o-mail-chatter-gap $o-mail-chatter-gap 0;
|
||||
padding: 0 $o-mail-chatter-gap 0;
|
||||
}
|
||||
.o_composer_send {
|
||||
margin-top: $o-mail-chatter-gap;
|
||||
@@ -167,6 +167,10 @@
|
||||
.o_composer_input {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
textarea {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
}
|
||||
.o_composer_text_field {
|
||||
flex: 1 1 50%;
|
||||
@@ -230,6 +234,21 @@
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.o_file_drop_zone_container {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
color: $o-brand-primary;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 2px dashed $o-brand-primary;
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.o_composer_attachments_list:not(:empty) {
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
}
|
||||
|
||||
.o_thread_composer {
|
||||
.o_file_drop_zone_container {
|
||||
top: 35px;
|
||||
}
|
||||
.o_composer {
|
||||
.o_composer_input {
|
||||
width: 80%;
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<div t-name="mail.Composer" t-attf-class="o_thread_composer #{widget.extended ? 'o_thread_composer_extended' : (widget.notInline ? '' : 'o_chat_inline_composer')} #{widget.isMini ? 'o_chat_mini_composer' : ''}">
|
||||
<div t-if="widget.isMini || !widget.notInline" class="o_file_drop_zone_container d-none">
|
||||
<h4>Drag Files Here <span class="fa fa-download"/></h4>
|
||||
</div>
|
||||
<div class="o_composer_container">
|
||||
<img t-if="widget.avatarURL" height="36" width="36" class="o_thread_message_sidebar o_chatter_avatar rounded-circle" t-att-src="widget.avatarURL" alt="Avatar"/>
|
||||
<div t-if="widget.extended" class="o_composer_subject">
|
||||
<input tabindex="1" type="text" placeholder="Subject" class="o_input"/>
|
||||
</div>
|
||||
<div t-attf-class="o_composer #{widget.extended ? 'o_extended_composer' : ''}">
|
||||
<div t-if="widget.notInline" class="o_file_drop_zone_container d-none">
|
||||
<h4>Drag Files Here <span class="fa fa-download"/></h4>
|
||||
</div>
|
||||
<div class="o_composer_input">
|
||||
<textarea class="o_input o_composer_text_field" tabindex="2" placeholder="Write something..."/>
|
||||
<div class="o_chatter_composer_tools">
|
||||
|
||||
@@ -1177,7 +1177,8 @@ class Binary(http.Controller):
|
||||
args.append({
|
||||
'filename': filename,
|
||||
'mimetype': ufile.content_type,
|
||||
'id': attachment.id
|
||||
'id': attachment.id,
|
||||
'size': attachment.file_size
|
||||
})
|
||||
return out % (json.dumps(callback), json.dumps(args))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user