diff --git a/addons/mail/static/src/js/attachment_box.js b/addons/mail/static/src/js/attachment_box.js
index b42e8aeca95..f58c64f26e7 100644
--- a/addons/mail/static/src/js/attachment_box.js
+++ b/addons/mail/static/src/js/attachment_box.js
@@ -13,46 +13,43 @@ var AttachmentBox = Widget.extend({
events: {
"click .o_attachment_download": "_onAttachmentDownload",
"click .o_attachment_view": "_onAttachmentView",
+ "click .o_attachment_delete_cross": "_onDeleteAttachment",
+ "click .o_upload_attachments_button": "_onUploadAttachments",
+ "change .o_chatter_attachment_form .o_form_binary_form": "_onAddAttachment",
},
/**
* @override
* @param {string} record.model
* @param {Number} record.res_id
+ * @param {Object[]} attachments
*/
- init: function (parent, record) {
+ init: function (parent, record, attachments) {
this._super.apply(this, arguments);
+ this.fileuploadId = _.uniqueId('oe_fileupload');
+ $(window).on(this.fileuploadId, this._onUploaded.bind(this));
this.currentResID = record.res_id;
this.currentResModel = record.model;
- this.attachmentIDs = {};
+ this.attachmentIDs = attachments;
this.imageList = {};
this.otherList = {};
+
+ _.each(attachments, function (attachment) {
+ // required for compatibility with the chatter templates.
+ attachment.url = '/web/content/' + attachment.id + '?download=true';
+ attachment.filename = attachment.datas_fname || 'unnamed';
+ });
+ var sortedAttachments = _.partition(attachments, function (att) {
+ return att.mimetype && att.mimetype.split('/')[0] === 'image';
+ });
+ this.imageList = sortedAttachments[0];
+ this.otherList = sortedAttachments[1];
},
/**
* @override
*/
- willStart: function () {
- var self = this;
- var domain = [
- ['res_id', '=', this.currentResID],
- ['res_model', '=', this.currentResModel],
- ];
- return $.when(this._super.apply(this, arguments), this._rpc({
- model: 'ir.attachment',
- method: 'search_read',
- domain: domain,
- }).then(function (result) {
- self.attachmentIDs = result;
- _.each(result, function (attachment) {
- attachment.url = '/web/content/' + attachment.id + '?download=true';
- // required for compatibility with the chatter templates.
- attachment.filename = attachment.datas_fname || 'unnamed';
- });
- var sortedAttachments = _.partition(result, function (att) {
- return att.mimetype && att.mimetype.split('/')[0] === 'image';
- });
- self.imageList = sortedAttachments[0];
- self.otherList = sortedAttachments[1];
- }));
+ destroy: function () {
+ $(window).off(this.fileuploadId);
+ this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
@@ -60,8 +57,8 @@ var AttachmentBox = Widget.extend({
//--------------------------------------------------------------------------
/**
- * @param {Object} record
- */
+ * @param {Object} record
+ */
update: function (record) {
this.currentResID = record.res_id;
this.currentResModel = record.model;
@@ -71,6 +68,19 @@ var AttachmentBox = Widget.extend({
// Handlers
//--------------------------------------------------------------------------
+ /**
+ * Method triggered when user click on 'add attachment' and select a file
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onAddAttachment: function (ev) {
+ var $input = $(ev.currentTarget).find('input.o_input_file');
+ if ($input.val() !== '') {
+ var $binaryForm = this.$('form.o_form_binary_form');
+ $binaryForm.submit();
+ }
+ },
/**
* @private
* @param {MouseEvent} ev
@@ -92,6 +102,33 @@ var AttachmentBox = Widget.extend({
attachmentViewer.appendTo($('body'));
}
},
+ /**
+ * Opens File Explorer dialog if all fields are valid and record is saved
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onUploadAttachments: function (ev) {
+ this.$('input.o_input_file').click();
+ },
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onDeleteAttachment: function (ev) {
+ ev.stopPropagation();
+ var $target = $(ev.currentTarget);
+ this.trigger_up('delete_attachment', {
+ attachmentId: $target.data('id'),
+ attachmentName: $target.data('name')
+ });
+ },
+ /**
+ * @private
+ */
+ _onUploaded: function() {
+ this.trigger_up('reload_attachment_box');
+ },
});
return AttachmentBox;
diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js
index 000353d1219..da23521f65f 100644
--- a/addons/mail/static/src/js/chatter.js
+++ b/addons/mail/static/src/js/chatter.js
@@ -4,6 +4,7 @@ odoo.define('mail.Chatter', function (require) {
var Activity = require('mail.Activity');
var AttachmentBox = require('mail.AttachmentBox');
var ChatterComposer = require('mail.composer.Chatter');
+var Dialog = require('web.Dialog');
var Followers = require('mail.Followers');
var ThreadField = require('mail.ThreadField');
var mailUtils = require('mail.utils');
@@ -13,25 +14,28 @@ var config = require('web.config');
var core = require('web.core');
var Widget = require('web.Widget');
+var _t = core._t;
var QWeb = core.qweb;
// The purpose of this widget is to display the chatter area below the form view
//
-// It instanciates the optional mail_thread, mail_activity and mail_followers widgets.
+// It instantiates the optional mail_thread, mail_activity and mail_followers widgets.
// It Ensures that those widgets are appended at the right place, and allows them to communicate
// with each other.
-// It synchronizes the rendering of those widgets (as they may be asynchronous), to limitate
+// It synchronizes the rendering of those widgets (as they may be asynchronous), to mitigate
// the flickering when switching between records
var Chatter = Widget.extend({
template: 'mail.Chatter',
custom_events: {
+ delete_attachment: '_onDeleteAttachment',
discard_record_changes: '_onDiscardRecordChanges',
+ reload_attachment_box: '_onReloadAttachmentBox',
reload_mail_fields: '_onReloadMailFields',
},
events: {
'click .o_chatter_button_new_message': '_onOpenComposerMessage',
'click .o_chatter_button_log_note': '_onOpenComposerNote',
- 'click .o_chatter_button_attachment': '_onOpenAttachments',
+ 'click .o_chatter_button_attachment': '_onClickAttachmentButton',
'click .o_chatter_button_schedule_activity': '_onScheduleActivity',
},
supportedFieldTypes: ['one2many'],
@@ -52,15 +56,19 @@ var Chatter = Widget.extend({
this._super.apply(this, arguments);
this._setState(record);
- this._dp = new concurrency.DropPrevious();
+ this.attachments = {};
+ this.fields = {};
+ this._areAttachmentsLoaded = false;
+ this._disableAttachmentBox = !!options.disable_attachment_box;
+ this._dp = new concurrency.DropPrevious();
+ this._isAttachmentBoxOpen = false;
// mention: get the prefetched partners and use them as mention suggestions
// if there is a follower widget, the followers will be added to the
// suggestions as well once fetched
this._mentionPartnerSuggestions = this.call('mail_service', 'getMentionPartnerSuggestions');
this._mentionSuggestions = this._mentionPartnerSuggestions;
- this.fields = {};
if (mailFields.mail_activity) {
this.fields.activity = new Activity(this, mailFields.mail_activity, record, options);
}
@@ -74,16 +82,17 @@ var Chatter = Widget.extend({
this.hasLogButton = options.display_log_button || nodeOptions.display_log_button;
this.postRefresh = nodeOptions.post_refresh || 'never';
}
- this.attachmentBoxOpened = false;
},
/**
* @override
*/
start: function () {
this._$topbar = this.$('.o_chatter_topbar');
- this.$('.o_topbar_right_area').append(QWeb.render('mail.chatter.Attachment.Button', {
- count: this.record.data.message_attachment_count || 0,
- }));
+ if(!this._disableAttachmentBox) {
+ this.$('.o_topbar_right_area').append(QWeb.render('mail.chatter.Attachment.Button', {
+ count: this.record.data.message_attachment_count || 0,
+ }));
+ }
// render and append the buttons
this._$topbar.prepend(QWeb.render('mail.chatter.Buttons', {
newMessageButton: !!this.fields.thread,
@@ -115,6 +124,8 @@ var Chatter = Widget.extend({
if (this.record.res_id !== record.res_id) {
this._closeComposer(true);
this._closeAttachments();
+ this._areAttachmentsLoaded = false;
+ this.attachments = {};
}
// update the state
@@ -155,13 +166,12 @@ var Chatter = Widget.extend({
/**
* @private
- * @param {boolean} force
*/
_closeAttachments: function () {
if (this.fields.attachments) {
this.$('.o_chatter_button_attachment').removeClass('o_active_attach');
this.fields.attachments.destroy();
- this.attachmentBoxOpened = false;
+ this._isAttachmentBoxOpen = false;
}
},
/**
@@ -231,22 +241,46 @@ var Chatter = Widget.extend({
_enableComposer: function () {
this.$(".o_composer_button_send").prop('disabled', false);
},
+ /*
+ * @private
+ * @return {Deferred}
+ */
+ _fetchAttachments: function () {
+ var self = this;
+ var domain = [
+ ['res_id', '=', this.record.res_id],
+ ['res_model', '=', this.record.model],
+ ];
+ return this._rpc({
+ model: 'ir.attachment',
+ method: 'search_read',
+ domain: domain,
+ fields: ['id', 'name', 'datas_fname', 'mimetype'],
+ }).then(function (result) {
+ self._areAttachmentsLoaded = true;
+ self.attachments = result;
+ });
+
+ },
/**
* @private
*/
- _openAttachments: function () {
- var self = this;
- this.fields.attachments = new AttachmentBox(this, this.record);
-
+ _openAttachmentBox: function () {
+ if (this.fields.attachments) {
+ this._closeAttachments();
+ }
+ this.fields.attachments = new AttachmentBox(this, this.record, this.attachments);
var $anchor = this.$('.o_chatter_topbar');
if (this._composer) {
- $anchor = this.$('.o_thread_composer');
+ var $anchor = this.$('.o_thread_composer');
+ } else {
+ var $anchor = this.$('.o_chatter_topbar');
}
- this.fields.attachments.insertAfter($anchor).then(function () {
- self.$el.addClass('o_chatter_composer_active');
- self.$('.o_chatter_button_attachment').addClass('o_active_attach');
- });
- this.attachmentBoxOpened = true;
+ this.fields.attachments.insertAfter($anchor);
+ this.$el.addClass('o_chatter_composer_active');
+ this.$('.o_chatter_button_attachment').addClass('o_active_attach');
+
+ this._isAttachmentBoxOpen = true;
},
/**
* @private
@@ -286,6 +320,7 @@ var Chatter = Widget.extend({
if (self._reloadAfterPost(messageData)) {
self.trigger_up('reload');
} else if (messageData.attachment_ids.length) {
+ self._reloadAttachmentBox();
self.trigger_up('reload', {fieldNames: ['message_attachment_count']});
}
}).fail(function () {
@@ -320,6 +355,16 @@ var Chatter = Widget.extend({
messageData.partner_ids.length
);
},
+ /**
+ * @private
+ */
+ _reloadAttachmentBox: function () {
+ this._areAttachmentsLoaded = false;
+ if (this._isAttachmentBoxOpen) {
+ this._fetchAttachments().then(this._openAttachmentBox.bind(this));
+ }
+ this.trigger_up('reload', { fieldNames: ['message_attachment_count'] });
+ },
/**
* @private
* @param {Deferred} def
@@ -344,7 +389,11 @@ var Chatter = Widget.extend({
self.fields.activity.$el.appendTo(self.$el);
}
if (self.fields.followers) {
- self.fields.followers.$el.insertBefore(self.$('.o_chatter_button_attachment'));
+ if (self._disableAttachmentBox) {
+ self.fields.followers.$el.appendTo(self.$('.o_topbar_right_area'));
+ } else {
+ self.fields.followers.$el.insertBefore(self.$('.o_chatter_button_attachment'));
+ }
}
if (self.fields.thread) {
self.fields.thread.$el.appendTo(self.$el);
@@ -383,8 +432,7 @@ var Chatter = Widget.extend({
*/
_updateAttachmentCounter: function () {
var count = this.record.data.message_attachment_count || 0;
- this.$('.o_chatter_attachment_button_count').html(' ('+ count +')');
- this.$('.o_chatter_button_attachment').toggleClass('o_hidden', !count);
+ this.$('.o_chatter_attachment_button_count').html(count);
},
/**
* @private
@@ -427,12 +475,45 @@ var Chatter = Widget.extend({
/**
* @private
+ * @param {OdooEvent} ev
+ * @param {integer} ev.data.attachmentId
+ * @param {String} ev.data.attachmentName
*/
- _onOpenAttachments: function () {
- if (this.attachmentBoxOpened) {
+ _onDeleteAttachment: function (ev) {
+ ev.stopPropagation();
+ var self = this;
+ var options = {
+ confirm_callback: function () {
+ self._rpc({
+ model: 'ir.attachment',
+ method: 'unlink',
+ args: [parseInt(ev.data.attachmentId, 10)],
+ })
+ .then(function () {
+ self._reloadAttachmentBox();
+ self.fields.thread.removeAttachments([ev.data.attachmentId]);
+ self.trigger_up('reload');
+ });
+ }
+ };
+ var promptText = _.str.sprintf(_t("Do you really want to delete %s?"), _.escape(ev.data.attachmentName));
+ Dialog.confirm(this, promptText, options);
+ },
+ /**
+ * @private
+ */
+ _onClickAttachmentButton: function () {
+ if(this._disableAttachmentBox) {
+ return;
+ }
+ if (this._isAttachmentBoxOpen) {
this._closeAttachments();
} else {
- this._openAttachments();
+ var def;
+ if (!this._areAttachmentsLoaded) {
+ def = this._fetchAttachments();
+ }
+ $.when(def).then(this._openAttachmentBox.bind(this));
}
},
/**
@@ -483,6 +564,12 @@ var Chatter = Widget.extend({
_onOpenComposerNote: function () {
this._openComposer({ isLog: true });
},
+ /**
+ * @private
+ */
+ _onReloadAttachmentBox: function () {
+ this._reloadAttachmentBox();
+ },
/**
* @private
* @param {OdooEvent} event
diff --git a/addons/mail/static/src/js/models/messages/abstract_message.js b/addons/mail/static/src/js/models/messages/abstract_message.js
index f5cf55f0ef1..c1ae22b9708 100644
--- a/addons/mail/static/src/js/models/messages/abstract_message.js
+++ b/addons/mail/static/src/js/models/messages/abstract_message.js
@@ -335,6 +335,14 @@ var AbstractMessage = Class.extend({
needsModeration: function () {
return false;
},
+ /**
+ * @params {integer[]} attachmentIDs
+ */
+ removeAttachments: function (attachmentIDs) {
+ this._attachmentIDs = _.reject(this._attachmentIDs, function (attachment) {
+ return _.contains(attachmentIDs, attachment.id);
+ });
+ },
/**
* State whether this message should redirect to the author
* when clicking on the author of this message.
diff --git a/addons/mail/static/src/js/models/threads/document_thread.js b/addons/mail/static/src/js/models/threads/document_thread.js
index d902d69765c..6c15b39d25e 100644
--- a/addons/mail/static/src/js/models/threads/document_thread.js
+++ b/addons/mail/static/src/js/models/threads/document_thread.js
@@ -156,6 +156,14 @@ var DocumentThread = Thread.extend({
isLinkedToDocument: function () {
return true;
},
+ /**
+ * @param {integer[]} attachmentIDs
+ */
+ removeAttachmentsFromMessages: function (attachmentIDs) {
+ _.each(this.getMessages(), function (message) {
+ message.removeAttachments(attachmentIDs);
+ });
+ },
/**
* Set list of message IDs of this document thread
*
diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js
index 5c85a2adb37..a3807802ad7 100644
--- a/addons/mail/static/src/js/thread_field.js
+++ b/addons/mail/static/src/js/thread_field.js
@@ -70,6 +70,12 @@ var ThreadField = AbstractField.extend({
// Public
//--------------------------------------------------------------------------
+ /**
+ * @param {integer[]} attachmentIDs
+ */
+ removeAttachments: function (attachmentIDs) {
+ this._documentThread.removeAttachmentsFromMessages(attachmentIDs);
+ },
/**
* @override
* @return {boolean}
diff --git a/addons/mail/static/src/js/thread_widget.js b/addons/mail/static/src/js/thread_widget.js
index d812d403007..2e2b7e222c6 100644
--- a/addons/mail/static/src/js/thread_widget.js
+++ b/addons/mail/static/src/js/thread_widget.js
@@ -34,6 +34,7 @@ var ThreadWidget = Widget.extend({
'click .o_thread_show_more': '_onClickShowMore',
'click .o_attachment_download': '_onAttachmentDownload',
'click .o_attachment_view': '_onAttachmentView',
+ 'click .o_attachment_delete_cross': '_onDeleteAttachment',
'click .o_thread_message_needaction': '_onClickMessageNeedaction',
'click .o_thread_message_star': '_onClickMessageStar',
'click .o_thread_message_reply': '_onClickMessageReply',
@@ -410,6 +411,18 @@ var ThreadWidget = Widget.extend({
});
});
},
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onDeleteAttachment: function (ev) {
+ ev.stopPropagation();
+ var $target = $(ev.currentTarget);
+ this.trigger_up('delete_attachment', {
+ attachmentId: $target.data('id'),
+ attachmentName: $target.data('name')
+ });
+ },
/**
* @private
* @param {Object} options
diff --git a/addons/mail/static/src/scss/attachment_box.scss b/addons/mail/static/src/scss/attachment_box.scss
index ab412bf52fe..a9aa6aec260 100644
--- a/addons/mail/static/src/scss/attachment_box.scss
+++ b/addons/mail/static/src/scss/attachment_box.scss
@@ -1,25 +1,36 @@
.oe_chatter {
+
.btn.o_active_attach {
- color: #875A7B;
height: 300px;
}
- .o_chatter_attachment {
- padding: 3px;
+ .o_mail_chatter_attachments {
+ min-height: 50px;
- .o_border_dashed {
- position: relative;
- margin-top: 15px;
- margin-bottom: 30px;
- border-bottom: 1px dashed gray('300');
- text-align: center;
+ .o_chatter_attachment {
+ padding: 3px;
- .o_attach_title {
+ input.o_input_file {
+ position: absolute;
+ top: 1px;
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ .o_border_dashed {
+ position: relative;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ border-bottom: 1px dashed gray('300');
+ text-align: center;
+
+ .o_attach_title {
position: relative;
top: 10px;
margin: 0 auto;
padding: 0 10px;
font-weight: bold;
background: white;
+ }
}
}
}
diff --git a/addons/mail/static/src/scss/chatter.scss b/addons/mail/static/src/scss/chatter.scss
index 261552c2b23..599802382a1 100644
--- a/addons/mail/static/src/scss/chatter.scss
+++ b/addons/mail/static/src/scss/chatter.scss
@@ -4,6 +4,11 @@
width: 100%;
padding-top: 0!important;
+ .o_attachment_delete_cross {
+ @include o-position-absolute($right: 6px, $top:7px);
+ cursor: pointer;
+ }
+
.o_spinner {
text-align: center;
font-size: large;
@@ -21,9 +26,16 @@
position: relative;
float: right;
flex: 1 1 auto;
+
.o_chatter_button_attachment {
- height: 2.3em;
+ height: $o-statusbar-height - 2px;
float: right;
+ padding: 0px;
+ margin-right: 25px;
+
+ .fa-paperclip, .o_chatter_attachment_button_count {
+ font-weight: bold;
+ }
}
.o_followers {
height: $o-statusbar-height + 1;
diff --git a/addons/mail/static/src/scss/composer.scss b/addons/mail/static/src/scss/composer.scss
index aa5131b0de5..096c527aa16 100644
--- a/addons/mail/static/src/scss/composer.scss
+++ b/addons/mail/static/src/scss/composer.scss
@@ -434,6 +434,10 @@ $o-mail-attachment-margin: 5px;
font-size: $font-size-sm;
}
+ .o_attachment_delete_cross {
+ color: white;
+ }
+
.o_attachment_download {
@include o-position-absolute($right: 7px, $bottom:7px);
}
diff --git a/addons/mail/static/src/xml/chatter.xml b/addons/mail/static/src/xml/chatter.xml
index 7461f4b9055..7f8629f1bae 100644
--- a/addons/mail/static/src/xml/chatter.xml
+++ b/addons/mail/static/src/xml/chatter.xml
@@ -20,6 +20,16 @@