diff options
| author | Géry Debongnie <ged@odoo.com> | 2015-09-30 14:14:30 +0200 |
|---|---|---|
| committer | Géry Debongnie <ged@odoo.com> | 2015-10-01 14:00:18 +0200 |
| commit | 2a5f07e0b919d4209c2cb72c6dfdc8b86cd5c7ff (patch) | |
| tree | 54cbc12fb66b72ad21f94857ac24052a3abf3f6a /addons | |
| parent | 62be162877f30a6ca0f61b38f0b070d56f7ab419 (diff) | |
[REF] mail: last part of the chat refactoring (client side)
Removal of old files and update of assets and imports
Joint work with aab
Diffstat (limited to 'addons')
18 files changed, 20 insertions, 4479 deletions
diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py index 58021bb2c9f..aec922b2b4d 100644 --- a/addons/mail/__openerp__.py +++ b/addons/mail/__openerp__.py @@ -58,11 +58,12 @@ Main Features 'installable': True, 'application': True, 'qweb': [ - 'static/src/xml/mail_thread.xml', - 'static/src/xml/mail_chatter.xml', - 'static/src/xml/mail_chat_common.xml', - 'static/src/xml/mail_chat_backend.xml', - 'static/src/xml/mail_followers.xml', + 'static/src/xml/client_action.xml', + 'static/src/xml/composer.xml', + 'static/src/xml/chatter.xml', + 'static/src/xml/systray.xml', + 'static/src/xml/thread.xml', + 'static/src/xml/chat_window.xml', 'static/src/xml/announcement.xml', ], } diff --git a/addons/mail/static/src/js/mail_chat_backend.js b/addons/mail/static/src/js/mail_chat_backend.js deleted file mode 100644 index d0966dd3088..00000000000 --- a/addons/mail/static/src/js/mail_chat_backend.js +++ /dev/null @@ -1,1051 +0,0 @@ -odoo.define('mail.chat_backend', function (require) { -"use strict"; - -var core = require('web.core'); -var data = require('web.data'); -var framework = require('web.framework'); -var Model = require('web.Model'); -var pyeval = require('web.pyeval'); -var SystrayMenu = require('web.SystrayMenu'); -var Widget = require('web.Widget'); -var Dialog = require('web.Dialog'); -var ControlPanelMixin = require('web.ControlPanelMixin'); -var SearchView = require('web.SearchView'); -var Sidebar = require('web.Sidebar'); -var WebClient = require('web.WebClient'); - -var session = require('web.session'); -var web_client = require('web.web_client'); - -var mail_chat_common = require('mail.chat_common'); -var mail_thread = require('mail.thread'); - -var _t = core._t; -var QWeb = core.qweb; -var internal_bus = core.bus; - - -/** - * Widget handeling the channels, in the backend - * - * Responsible to listen the bus and apply action with the received message. Add layer to coordinate the - * folded conversation and trigger event for the InstantMessagingView client action (using internal - * comminication bus). It is a component of the WebClient. - **/ -var ConversationManagerBackend = mail_chat_common.ConversationManager.extend({ - _setup: function(init_data){ - var self = this; - this._super.apply(this, arguments); - _.each(init_data.notifications, function(n){ - self.on_notification(n); - }); - }, - // window title - window_title_change: function() { - this._super.apply(this, arguments); - var title; - if (this.get("waiting_messages") !== 0) { - title = _.str.sprintf(_t("%d Messages"), this.get("waiting_messages")); - } - web_client.set_title_part("im_messages", title); - }, - // sessions and messages - session_apply: function(active_session){ - // for chat windows - if(active_session.is_minimized || (!active_session.is_minimized && active_session.state === 'closed')){ - this._super.apply(this, arguments); - } - // for client action - if(!active_session.is_minimized){ // if not minimized, - internal_bus.trigger('mail_session_receive', active_session); - } - }, - message_receive: function(message) { - var actual_channel_ids = _.map(_.keys(this.sessions), function(item){ - return parseInt(item); - }); - var message_channel_ids = message.channel_ids; - if(_.intersection(actual_channel_ids, message_channel_ids).length){ - this._super.apply(this, arguments); - } - // broadcast the message to the NotificationButton and the InstantMessagingView - internal_bus.trigger('mail_message_receive', message); - // increment the needaction top counter - if(message.needaction_partner_ids && _.contains(message.needaction_partner_ids, session.partner_id)){ - internal_bus.trigger('mail_needaction_new', 1); - } - }, -}); - - -/** - * Widget Minimized Conversation - * - * Add layer of WebClient integration, and user fold state handling (comminication with server) - **/ -mail_chat_common.Conversation.include({ - session_fold: function(state){ - var self = this; - var args = arguments; - var super_call = this._super; - // broadcast the state changing - return new Model("mail.channel").call("channel_fold", [], {"uuid" : this.get("session").uuid, "state" : state}).then(function(){ - super_call.apply(self, args); - }); - }, -}); - -/** - * Widget : Patch for WebClient - * - * Create the conversation manager, and attach it to the web_client. - **/ -WebClient.include({ - show_application: function(){ - var self = this; - var args = arguments; - var super_call = this._super; - this.mail_conversation_manager = new ConversationManagerBackend(this); - this.mail_conversation_manager.start().then(function(){ - super_call.apply(self, args); - self.mail_conversation_manager.bus.start_polling(); - }); - }, -}); - - -/** - * Widget Top Menu Notification Counter - * - * Counter of notification in the Systray Menu. Need to know if InstantMessagingView is displayed to - * increment (or not) the counter. On click, should redirect to the client action. - **/ -var NotificationTopButton = Widget.extend({ - template:'mail.chat.NotificationTopButton', - events: { - "click": "on_click", - }, - init: function(){ - this._super.apply(this, arguments); - this.set('counter', 0); - }, - willStart: function(){ - var self = this; - return $.when(session.rpc('/mail/needaction'), this._super()).then(function(needaction_count){ - self.set('counter', needaction_count); - }); - }, - start: function() { - this.on("change:counter", this, this.on_change_counter); - // events - internal_bus.on('mail_needaction_new', this, this.counter_increment); - internal_bus.on('mail_needaction_done', this, this.counter_decrement); - return this._super(); - }, - counter_increment: function(inc){ - this.set('counter', this.get('counter')+inc); - }, - counter_decrement: function(dec){ - this.set('counter', this.get('counter')-dec); - }, - on_change_counter: function() { - this.$('.fa-comment').html(this.get('counter') || ''); - }, - on_click: function(e){ - e.preventDefault(); - this.do_action({ - type: 'ir.actions.client', - tag: 'mail.chat.instant_messaging', - params: { - 'default_active_id': 'channel_inbox', - }, - }, { - clear_breadcrumbs: true, - }); - }, -}); - -SystrayMenu.Items.push(NotificationTopButton); - - -/** - * Abstract Class to 'Add More/Search' Widget - * - * Inputbox using jQueryUI autocomplete to fetch selection, like a Many2One field (on form view) - * Used to create or pin a mail.channel or a res.partner on the InstantMessagingView - **/ -var AbstractAddMoreSearch = Widget.extend({ - template: 'mail.chat.AbstractAddMoreSearch', - events: { - "click .o_mail_chat_add_more_text": "on_click_text", - "blur .o_mail_chat_search_input": "_toggle_elements", - }, - init: function(parent, options){ - this._super.apply(this, arguments); - options = _.defaults(options || {}, { - 'can_create': false, - 'label': _t('+ Add More'), - }); - this.limit = 10; - this.can_create = options.can_create; - this.label = options.label; - }, - start: function(){ - this.last_search_val = false; - this.$input = this.$('.o_mail_chat_search_input'); - this.$add_more_text = this.$('.o_mail_chat_add_more_text'); - this.$add_more_search_bar = this.$('.o_mail_chat_add_more_search_bar'); - this._bind_events(); - return this._super(); - }, - _bind_events: function(){ - // autocomplete - var self = this; - this.$input.autocomplete({ - source: function(request, response) { - self.last_search_val = request.term; - self.do_search(request.term).done(function(result){ - if(self.can_create){ - result.push({ - 'label': _.str.sprintf('<strong>'+_t("Create %s")+'</strong>', '<em>"'+self.last_search_val+'"</em>'), - 'value': '_create', - }); - } - response(result); - }); - }, - select: function(event, ui) { - self.on_click_item(ui.item); - }, - focus: function(event) { - event.preventDefault(); - }, - html: true, - }); - }, - // ui - _toggle_elements: function(){ - this.$add_more_text.toggle(); - this.$add_more_search_bar.toggle(); - this.$input.val(''); - this.$input.focus(); - }, - on_click_text: function(event){ - event.preventDefault(); - this._toggle_elements(); - }, - // to be redefined - do_search: function(){ - return $.when(); - }, - on_click_item: function(item){ - if(item.value === '_create'){ - if(this.last_search_val){ - this.trigger('item_create', this.last_search_val); - } - }else{ - this.trigger('item_clicked', item); - } - }, -}); - -var PartnerAddMoreSearch = AbstractAddMoreSearch.extend({ - /** - * Do the search call - * @override - */ - do_search: function(search_val){ - var Partner = new Model("res.partner"); - return Partner.call('im_search', [search_val, this.limit]).then(function(result){ - var values = []; - _.each(result, function(user){ - values.push(_.extend(user, { - 'value': user.name, - 'label': user.name, - })); - }); - return values; - }); - }, -}); - -var ChannelAddMoreSearch = AbstractAddMoreSearch.extend({ - /** - * Do the search call - * @override - */ - do_search: function(search_val){ - var Channel = new Model("mail.channel"); - return Channel.call('channel_search_to_join', [search_val]).then(function(result){ - var values = []; - _.each(result, function(channel){ - values.push(_.extend(channel, { - 'value': channel.name, - 'label': channel.name, - })); - }); - return values; - }); - }, -}); - -var PrivateGroupAddMoreSearch = AbstractAddMoreSearch.extend({ - _bind_events: function(){ - // don't call the super to avoid autocomplete - this.$input.on('keyup', this, this.on_keydown); - }, - on_keydown: function(event){ - if(event.which === $.ui.keyCode.ENTER && this.$input.val()){ - this.trigger('item_create', this.$input.val()); - } - }, -}); - - -/** - * Widget : Invite People to Channel Dialog - * - * Popup containing a 'many2many_tags' custom input to select multiple partners. - * Search user according to the input, and trigger event when selection is validated. - **/ -var PartnerInviteDialog = Dialog.extend({ - dialog_title: _t('Invite people'), - template: "mail.chat.PartnerInviteDialog", - init: function(parent, options){ - options = _.defaults(options || {}, { - buttons: [{ - text: _t("Invite"), - close: true, - classes: "btn-primary", - click: _.bind(this.on_click_add, this), - }], - channel: undefined, - }); - this._super.apply(this, arguments); - this.set("partners", []); - this.channel = options.channel; - this.PartnersModel = new Model('res.partner'); - this.ChannelModel = new Model('mail.channel'); - this.limit = 20; - }, - start: function(){ - var self = this; - this.$partner_invite_input = this.$('.o_mail_chat_partner_invite_input'); - this.$partner_invite_input.select2({ - width: '100%', - allowClear: true, - multiple: true, - formatResult: function(item){ - if(item.im_status === 'online'){ - return '<span class="fa fa-circle"> ' + item.text + '</span>'; - } - return '<span class="fa fa-circle-o"> ' + item.text + '</span>'; - }, - query: function (query) { - self.PartnersModel.call('im_search', [query.term, self.limit]).then(function(result){ - var data = []; - _.each(result, function(partner){ - partner.text = partner.name; - data.push(partner); - }); - query.callback({results: data}); - }); - } - }); - return this._super.apply(this, arguments); - }, - on_click_add: function(){ - var self = this; - var data = this.$partner_invite_input.select2('data'); - if(data.length >= 1){ - return this.ChannelModel.call('channel_invite', [], {"ids" : [this.channel.id], 'partner_ids': _.pluck(data, 'id')}).then(function(){ - var names = _.pluck(data, 'text').join(', '); - self.do_notify(_t('New people'), _.str.sprintf(_t('You added <b>%s</b> to the conversation.'), names)); - }); - } - }, -}); - -/** - * Client Action : Instant Messaging View, inspired by Slack.com - * - * Action replacing the Inbox, and the list of group (mailing list, multiple conversation, rooms, ...) - * Includes real time messages (received and sent), creating group, channel, chat conversation, ... - **/ -var ChatMailThread = Widget.extend(mail_thread.MailThreadMixin, ControlPanelMixin, { - template: 'mail.chat.ChatMailThread', - events: { - "click .o_mail_thread_show_more > button": "message_load_history", - // events from MailThreadMixin - "click .o_mail_redirect": "on_click_redirect", - "click .o_mail_expand": "on_message_body_toggle", - "click .o_mail_thread_message_star": "on_message_star", - // events specific for ChatMailThread - "click .o_mail_chat_channel_item": "on_click_channel", - "click .o_mail_chat_partner_item": "on_click_partner", - "click .o_mail_chat_partner_unpin": "on_click_partner_unpin", - "click .o_mail_thread_message_needaction": "on_message_needaction", - }, - init: function (parent, action) { - this._super.apply(this, arguments); - // attributes - this.action_manager = parent; - this.help_message = action.help || ''; - this.context = action.context; - this.action = action; - this.chatter_needaction_auto = false; - this.options = this.action.params; - // mail thread mixin - mail_thread.MailThreadMixin.init.call(this); - // components : conversation manager and search widget (channel_type + '_search_widget') - this.conv_manager = web_client.mail_conversation_manager; - this.channel_search_widget = new ChannelAddMoreSearch(this, {'label': _t('+ Subscribe'), 'can_create': true}); - this.group_search_widget = new PrivateGroupAddMoreSearch(this, {'label': _t('+ New private group'), 'can_create': true}); - this.partner_search_widget = new PartnerAddMoreSearch(this); - // options (update the default of MailThreadMixin) - this.options = _.extend(this.options, { - 'display_document_link': true, - 'display_needaction_button': true, - 'emoji_list': this.conv_manager.emoji_list, - 'default_username': _t('Anonymous'), - }); - this.emoji_set_substitution(this.conv_manager.emoji_list); - // channel business - this.channels = {}; - this.mapping = {}; // mapping partner_id/channel_id for 'direct message' channel - this.set('current_channel_id', false); - this.set('needaction_inbox_counter', 0); - this.search_domain = []; - // channel slots - this.set('channel_channel', []); - this.set('channel_direct_message', []); - this.set('channel_private_group', []); - this.set('partners', []); - // models - this.ChannelModel = new Model('mail.channel', this.context); - // control panel items - this.control_elements = {}; - }, - willStart: function(){ - var self = this; - return session.rpc('/mail/client_action').then(function(result){ - self.chatter_needaction_auto = result.chatter_needaction_auto; - self.set('needaction_inbox_counter', result.needaction_inbox_counter); - self.set('partners', result.channel_slots.partners); - self.mapping = result.channel_slots.mapping; - self._channel_slot(_.omit(result.channel_slots, 'partners', 'mapping')); - }); - }, - start: function(){ - var self = this; - this._super.apply(this, arguments); - - this.$messages = this.$('.o_mail_chat_messages'); - - mail_thread.MailThreadMixin.start.call(this); - // channel business events - this.on("change:current_channel_id", this, this.channel_change); - this.on("change:channel_channel", this, function(){ - self.channel_render('channel_channel'); - }); - this.on("change:channel_private_group", this, function(){ - self.channel_render('channel_private_group'); - }); - this.on("change:partners", this, this.partner_render); - - // search widget for channel - this.channel_search_widget.insertAfter(this.$('.o_mail_chat_channel_slot_channel_channel')); - this.channel_search_widget.on('item_create', this, function(name){ - self.channel_create(name, 'public'); - }); - this.channel_search_widget.on('item_clicked', this, function(item){ - self.channel_join_and_get_info(item.id).then(function(channel){ - self.channel_apply(channel); - }); - }); - // search widget for direct message - this.partner_search_widget.insertAfter(this.$('.o_mail_chat_channel_slot_partners')); - this.partner_search_widget.on('item_clicked', this, function(item){ - self.channel_get([item.id]); - self.partner_add(item); - }); - // search widget for private group - this.group_search_widget.insertAfter(this.$('.o_mail_chat_channel_slot_channel_private_group')); - this.group_search_widget.on('item_create', this, function(name){ - self.channel_create(name, 'private'); - }); - - // needaction inbox counter - this.on('change:needaction_inbox_counter', this, this.needaction_inbox_change); - - return $.when(this._super.apply(this, arguments), this.cp_render_searchview()).then(function(){ - // render control panel's elements - self.cp_render_elements(); - // apply default channel - var channel_id = self.context.active_id || self.action.params.default_active_id || 'channel_inbox'; - if(!_.isString(channel_id)){ - if(_.contains(_.keys(self.channels), channel_id)){ - self.set('current_channel_id', channel_id); - }else{ - self.channel_info(channel_id).then(function(channel){ - self.channel_apply(channel); - }); - } - }else{ - self.set('current_channel_id', channel_id); - } - // internal communication (bind here to avoid receving message from bus when client action still not totally ready) - internal_bus.on('mail_message_receive', self, self.message_receive); - internal_bus.on('mail_session_receive', self, self.channel_receive); - // needaction : the inbox has the same counter as the needaction top counter - internal_bus.on('mail_needaction_new', this, function(inc){ - self.set('needaction_inbox_counter' , self.get('needaction_inbox_counter') + inc); - }); - internal_bus.on('mail_needaction_done', this, function(dec){ - self.set('needaction_inbox_counter' , self.get('needaction_inbox_counter') - dec); - }); - }); - }, - // event actions - on_click_channel: function(event){ - event.preventDefault(); - var channel_id = this.$(event.currentTarget).data('channel-id'); - this.set('current_channel_id', channel_id); - }, - on_click_partner: function(event){ - if(!this.$(event.target).hasClass('o_mail_chat_channel_unpin')){ - event.preventDefault(); - var partner_id = this.$(event.currentTarget).data('partner-id'); - if(this.mapping[partner_id]){ // don't fetch if channel already in local - this.set('current_channel_id', this.mapping[partner_id]); - }else{ - this.channel_get([partner_id]); - } - } - }, - on_click_partner_unpin: function(event){ - event.preventDefault(); - var self = this; - var $source = this.$(event.currentTarget); - var partner_id = $source.data('partner-id'); - var channel_id = this.mapping[partner_id]; - var channel = this.channels[channel_id]; - this.channel_pin(channel.uuid, false).then(function(){ - self.set('partners', _.filter(self.get('partners'), function(p){ return p.id !== partner_id; })); - self.channel_remove(channel_id); - delete self.mapping[partner_id]; - // if unpin current channel, switch to inbox - if(self.get('current_channel_id') === channel_id){ - self.set('current_channel_id', 'channel_inbox'); - } - }); - }, - on_click_button_minimize: function(event){ - event.preventDefault(); - var current_channel = this.channels[this.get('current_channel_id')]; - if(current_channel){ - var channel_uuid = current_channel.uuid; - return this.ChannelModel.call("channel_minimize", [channel_uuid, true]); - } - return $.Deferred().reject(); - }, - on_click_button_invite: function(){ - new PartnerInviteDialog(this, { - title: _.str.sprintf(_t('Invite people to %s'), this.get_current_channel_name()), - channel: this.channels[this.get('current_channel_id')], - }).open(); - }, - on_click_button_unsubscribe: function(){ - var self = this; - this.ChannelModel.call('action_unfollow', [[this.get('current_channel_id')]]).then(function(){ - var channel = self.channels[self.get('current_channel_id')]; - var slot = self.get_channel_slot(channel); - self.set(slot, _.filter(self.get(slot), function(c){ return c.id !== channel.id; })); - self.do_notify(_t('Unsubscribe'), _.str.sprintf(_t('You unsubscribe from <b>%s</b>.'), self.get_current_channel_name())); - self.set('current_channel_id', 'channel_inbox'); // jump to inbox - }); - }, - on_click_button_settings: function(){ - this.do_action({ - type: 'ir.actions.act_window', - res_model: "mail.channel", - res_id: this.get('current_channel_id'), - views: [[false, 'form']], - target: 'current' - }, { - 'on_reverse_breadcrumb': this.on_reverse_breadcrumb, - }); - }, - on_message_needaction: function(event){ - var self = this; - mail_thread.MailThreadMixin.on_message_needaction.call(this, event).then(function(message_id){ - // decrement the needaction top counter - internal_bus.trigger('mail_needaction_done', 1); - // decrement the channel labels - var treated_messages = _.filter(self.get('messages'), function(m){ return m.id == message_id; }); - if(treated_messages.length){ - self.needaction_decrement(treated_messages[0].channel_ids || []); - } - }); - }, - // control panel - cp_update: function(){ - // toggle cp elements - var current_channel_id = this.get('current_channel_id'); - if(_.isString(current_channel_id)){ - this.control_elements.$buttons.hide(); - this.control_elements.$sidebar.hide(); - }else{ - this.control_elements.$buttons.show(); - if(this.channels[current_channel_id].channel_type === 'chat'){ - this.control_elements.$buttons.find('.o_mail_chat_button_invite').hide(); - this.control_elements.$sidebar.hide(); - } else { - this.control_elements.$buttons.find('.o_mail_chat_button_invite').show(); - this.control_elements.$sidebar.show(); - } - } - - // update control panel - var status = { - breadcrumbs: this.action_manager.get_breadcrumbs(), - cp_content: { - $buttons: this.control_elements.$buttons, - $searchview: this.control_elements.$searchview, - $searchview_buttons: this.control_elements.$searchview_buttons, - $sidebar: this.control_elements.$sidebar, - }, - searchview: this.searchview, - }; - this.update_control_panel(status); - }, - cp_render_elements: function() { - this.control_elements.$buttons = $(QWeb.render("mail.chat.ControlButtons", {})); - this.control_elements.$buttons.on('click', '.o_mail_chat_button_invite', _.bind(this.on_click_button_invite, this)); - this.control_elements.$buttons.on('click', '.o_mail_chat_button_minimize', _.bind(this.on_click_button_minimize, this)); - - var $sidebar_container = $('<div>'); - new Sidebar(this, { - sections: [ - {name: 'more', label: _t('More')}, - ], - items: { - more: [ - {label: _t('Unsubscribe'), classname: 'o_mail_chat_button_unsubscribe', callback: this.on_click_button_unsubscribe}, - {label: _t('Settings'), classname: 'o_mail_chat_button_settings', callback: this.on_click_button_settings}, - ], - }, - }).appendTo($sidebar_container); - this.control_elements.$sidebar = $sidebar_container.contents(); - }, - cp_render_searchview: function(){ - var self = this; - var options = { - $buttons: $("<div>"), - action: this.action, - disable_groupby: true, - }; - var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; - this.searchview = new SearchView(this, this.MessageDatasetSearch, view_id, {}, options); - - this.searchview.on('search_data', this, this.on_search); - return $.when(this.searchview.appendTo($("<div>"))).done(function() { - self.control_elements.$searchview = self.searchview.$el; - self.control_elements.$searchview_buttons = self.searchview.$buttons.contents(); - }); - }, - /** - * Method call when action if done after redirecting (using 'on_click_redirect') - * @override - */ - on_reverse_breadcrumb: function(){ - var current_channel_id = this.get('current_channel_id'); - this.cp_update(); // do not reload the client action, just display it, but a refresh of the control panel is needed. - // push state - this.action_manager.do_push_state({ - action: this.action.id, - active_id: current_channel_id, - }); - }, - // messages search methods - on_search: function(domains, contexts, groupbys){ - var self = this; - return pyeval.eval_domains_and_contexts({ - domains: [[]].concat(domains || []), - contexts: [this.context].concat(contexts || []), - group_by_seq: groupbys || [] - }).done(function(results){ - if (results.error) { - throw new Error(_.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s", JSON.stringify(results.error))); - } - // modify the search domain and do search - self.search_domain = results.domain; - return self.message_load_new(); - }); - }, - // channels - /** - * Set the channel slot - * @param {Object[]} fetch_result : should contains only the slot name (as key) and the list of channel header as value. - */ - _channel_slot: function(fetch_result){ - var self = this; - var channel_slots = _.keys(fetch_result); - _.each(channel_slots, function(slot){ - // update the channel slot - self.set(slot, fetch_result[slot]); - // flatten the result : update the complete channel list - _.each(fetch_result[slot], function(channel){ - self.channels[channel.id] = channel; - }); - }); - }, - /** - * Apply a channel means adding it, and swith to it - * @param {Object} channel : channel header - */ - channel_apply: function(channel){ - this.channel_add(channel); - this.set('current_channel_id', channel.id); - }, - /** - * Add the given channel, or update it if already exists and loaded - * @param {Object} channel : channel header to add - */ - channel_add: function(channel){ - var channel_slot = this.get_channel_slot(channel); - var existing = this.get(channel_slot); - if(_.contains(_.pluck(existing, 'id'), channel.id)){ - // update the old channel - var filtered_channels = _.filter(existing, function(item){ return item.id != channel.id; }); - this.set(channel_slot, filtered_channels.concat([channel])); - }else{ - // simply add the reveiced channel - this.set(channel_slot, existing.concat([channel])); - } - // also update the flatten list - this.channels[channel.id] = channel; - - // update the mapping for 'direct message' channel, and the partner list - if(channel_slot === 'channel_direct_message'){ - var partner = channel.direct_partner[0]; - this.mapping[partner.id] = channel.id; - this.partner_add(partner); - } - }, - channel_remove: function(channel_id){ - var channel = this.channels[channel_id]; - var slot = this.get_channel_slot(channel); - this.set(slot, _.filter(this.get(slot), function(c){ return c.id !== channel_id; })); - delete this.channels[channel_id]; - }, - /** - * Get the channel the current user has with the given partner, and get the channel header - * @param {Number[]} partner_ids : list of res.partner identifier - */ - channel_get: function(partner_ids){ - var self = this; - return this.ChannelModel.call('channel_get', [partner_ids]).then(function(channel){ - self.channel_apply(channel); - }); - }, - /** - * Create a channel with the given name and type, and apply it - * @param {String} channel_name : the name of the channel - * @param {String} privacy : the privacy of the channel (groups, public, ...) - */ - channel_create: function(channel_name, privacy){ - var self = this; - return this.ChannelModel.call('channel_create', [channel_name, privacy]).then(function(channel){ - self.channel_apply(channel); - }); - }, - channel_info: function(channel_id){ - return this.ChannelModel.call('channel_info', [[channel_id]]).then(function(channels){ - return channels[0]; - }); - }, - channel_pin: function(uuid, pinned){ - return this.ChannelModel.call('channel_pin', [uuid, pinned]); - }, - channel_join_and_get_info: function(channel_id){ - return this.ChannelModel.call('channel_join_and_get_info', [[channel_id]]); - }, - channel_change: function(){ - var self = this; - var current_channel_id = this.get('current_channel_id'); - // mail chat compose message (destroy and replace it) - if (this.mail_chat_compose_message) { - this.mail_chat_compose_message.destroy(); - } - this.mail_chat_compose_message = new mail_thread.MailComposeMessage(this, new data.DataSetSearch(this, 'mail.channel', this.context), { - 'emoji_list': this.options.emoji_list, - 'context': _.extend(this.context, { - 'default_res_id': current_channel_id, - }), - 'display_mode': 'chat', - }); - if(_.isString(current_channel_id)){ - this.$('.o_mail_chat_composer_wrapper').hide(); - }else{ - this.$('.o_mail_chat_composer_wrapper').show(); - } - this.mail_chat_compose_message.appendTo(this.$('.o_mail_chat_composer_wrapper')); - this.mail_chat_compose_message.focus(); - - // push state (the action is referred by action_manager, and no reloaded when jumping - // channel, so update context is required) - this.action.context.active_id = current_channel_id; - this.action.context.active_ids = [current_channel_id]; - web_client.action_manager.do_push_state({ - action: this.action.id, - active_id: current_channel_id, - }); - - // update title (to display in the breadcrumbs) according to current channel - this.set('title', this.get_current_channel_name()); - - // update control panel - this.cp_update(); - - // fetch the messages - return this.message_load_new().then(function(){ - // if normal channel, update last message id, and unbold - var def = $.Deferred().resolve(); - if(_.isNumber(current_channel_id)){ - def = self.ChannelModel.call('channel_seen', [[current_channel_id]]); - }else{ - // auto treat needaction from inbox - if(current_channel_id === 'channel_inbox' && self.chatter_needaction_auto){ - def = self.MessageDatasetSearch._model.query(['id', 'channel_ids']).filter([['needaction', '=', true]]).all().then(function(messages){ - // get needaction message ids - var message_ids = _.pluck(messages, 'id'); - if(message_ids){ - // call to unlink - self.MessageDatasetSearch._model.call('set_message_done',[message_ids]).then(function(){ - // decrement channel items in batch - var flatten_channel_ids = _.flatten(_.pluck(messages, 'channel_ids')); - var channel_count = _.countBy(flatten_channel_ids, function(cid) { - return cid; - }); - _.each(_.keys(channel_count), function(cid){ - self.needaction_decrement([cid], channel_count[cid]); - }); - // decrement the needaction top counter - internal_bus.trigger('mail_needaction_done', message_ids.length); - }); - } - }); - } - } - def.then(function(){ - self._toggle_unread_message(current_channel_id, false, true); - self._scroll(); - }); - }); - }, - channel_render: function(channel_slot){ - this.$('.o_mail_chat_channel_slot_' + channel_slot).replaceWith(QWeb.render("mail.chat.ChatMailThread.channels", {'widget': this, 'channel_slot': channel_slot})); - }, - // partners - partner_add: function(partner){ - var partners = _.filter(this.get('partners'), function(p){ return p.id != partner.id; }); - this.set('partners', partners.concat([partner])); - }, - partner_render: function(){ - this.$('.o_mail_chat_channel_slot_partners').replaceWith(QWeb.render("mail.chat.ChatMailThread.partners", {'widget': this})); - }, - // needaction - needaction_increment: function(channel_ids, inc){ - var self = this; - _.each(channel_ids, function(channel_id){ - if(self.channels[channel_id]){ - var count = (self.channels[channel_id].message_needaction_counter || 0) + (inc || 1); - self.channels[channel_id].message_needaction_counter = count; - self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').html(count); - self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').show(); - } - }); - }, - needaction_decrement: function(channel_ids, dec){ - var self = this; - _.each(channel_ids, function(channel_id){ - if(self.channels[channel_id]){ - var count = (self.channels[channel_id].message_needaction_counter || 0) - (dec || 1); - self.channels[channel_id].message_needaction_counter = count; - self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').html(count); - if(count <= 0){ - self.$('.o_mail_chat_needaction[data-channel-id='+channel_id+']').show(); - } - } - }); - }, - needaction_inbox_change: function(){ - var count = this.get('needaction_inbox_counter'); - var $elem = this.$('.o_mail_chat_needaction[data-channel-id="channel_inbox"]'); - $elem.html(count); - if(count > 0){ - $elem.show(); - }else{ - $elem.hide(); - } - }, - // from bus - channel_receive: function(channel){ - this.channel_add(channel); - }, - message_receive: function(message){ - var self = this; - var in_current_channel = _.contains(message.channel_ids, this.get('current_channel_id')); - // if current channel should reveice message, give it to it - if(in_current_channel){ - this.message_insert([message]); - } - // for other message channel, get the channel if not loaded yet, and bolded them - var other_message_channel_ids = _.without(message.channel_ids, this.get('current_channel_id')); - var active_channel_ids = _.map(_.keys(this.channels), function(cid){ - return parseInt(cid); - }); // integer as key of a dict is cast as string in javascript - var channel_to_fetch = _.difference(other_message_channel_ids, active_channel_ids); - // fetch unloaded channels and add it - var def = $.Deferred(); - if(channel_to_fetch.length >= 1){ - def = this.ChannelModel.call("channel_info", [], {"ids" : channel_to_fetch}).then(function(channels){ - _.each(channels, function(channel){ - self.channel_add(channel); - }); - }); - }else{ - def.resolve([]); - } - // bold the channel to indicate unread messages - def.then(function(){ - // bold channel having unread messages - _.each(other_message_channel_ids, function(channel_id){ - self._toggle_unread_message(channel_id, true); - }); - // auto scroll to bottom if the message arrived in the current channel - if(in_current_channel){ - self._scroll(); - } - }); - // if needaction, then increment the label - if(message.needaction_partner_ids && _.contains(message.needaction_partner_ids, session.partner_id)){ - this.needaction_increment(message.channel_ids || []); - } - }, - // utils function - get_channel_slot: function(channel){ - if(channel.channel_type === 'channel'){ - if(channel.public === 'private'){ - return 'channel_private_group'; - } - return 'channel_channel'; - } - if(channel.channel_type === 'chat'){ - return 'channel_direct_message'; - } - }, - get_current_channel_name: function(){ - var current_channel_id = this.get('current_channel_id'); - var current_channel = this.channels[current_channel_id]; - var current_channel_name = current_channel && current_channel.name || _t('Unknown'); - // virtual channel id (for inbox, or starred channel) - if(_.isString(current_channel_id)){ - if(current_channel_id == 'channel_inbox'){ - current_channel_name = _t('Inbox'); - } - if(current_channel_id == 'channel_starred'){ - current_channel_name = _t('Starred'); - } - } - return current_channel_name; - }, - _toggle_unread_message: function(channel_id, add_or_remove, set_as_active){ - var partner_id = false; - var inverse_mapping = _.invert(this.mapping); - // toggle bold channel/partner item - this.$('.o_mail_chat_sidebar .o_mail_chat_channel_item[data-channel-id="'+channel_id+'"]').toggleClass('o_mail_chat_channel_unread', add_or_remove); - if(_.contains(_.keys(inverse_mapping), channel_id.toString())){ - partner_id = parseInt(inverse_mapping[channel_id]); - this.$('.o_mail_chat_sidebar .o_mail_chat_partner_item[data-partner-id="'+partner_id+'"]').toggleClass('o_mail_chat_channel_unread', add_or_remove); - } - // set the channel/partner item as the active one - if(set_as_active){ - this.$('.o_mail_chat_sidebar .o_mail_chat_channel_item, .o_mail_chat_sidebar .o_mail_chat_partner_item').find('a').removeClass('active'); - this.$('.o_mail_chat_sidebar .o_mail_chat_channel_item[data-channel-id="'+channel_id+'"]').find('a').addClass('active'); - if(partner_id){ - this.$('.o_mail_chat_sidebar .o_mail_chat_partner_item[data-partner-id="'+partner_id+'"]').find('a').addClass('active'); - } - } - }, - _scroll: function(){ - var current_channel_id = this.get('current_channel_id'); - var current_channel = this.channels[current_channel_id]; - var last_seen_message; - if(_.isNumber(current_channel_id)){ - if(current_channel.seen_message_id){ - last_seen_message = this.$(".o_mail_thread_message[data-message-id="+current_channel.seen_message_id+"]"); - if(last_seen_message.length){ - this.$messages.scrollTop(last_seen_message.offset().top); - }else{ - this.$messages.scrollTop(0); - } - }else{ - this.$messages.scrollTop(this.$messages[0].scrollHeight); - } - current_channel.seen_message_id = false; - }else{ - this.$messages.scrollTop(this.$messages[0].scrollHeight); - } - }, - /** - * Render the messages - * @override - */ - message_render: function(){ - this.$messages.html(QWeb.render('mail.chat.ChatMailThread.content', {'widget': this})); - }, - /** - * Return the message domain for the current channel - * @override - */ - get_message_domain: function(){ - // default channel domain - var current_channel_id = this.get('current_channel_id'); - var domain = [['channel_ids', 'in', current_channel_id]]; - // virtual channel id (for inbox, or starred channel) - if(_.isString(current_channel_id)){ - if(current_channel_id == 'channel_inbox'){ - domain = [['needaction', '=', true]]; - } - if(current_channel_id == 'channel_starred'){ - domain = [['starred', '=', true]]; - } - } - // add search domain - domain = domain.concat(this.search_domain); - return domain; - }, - /** - * Extend message_load_history to keep scroll position at last seen message - */ - message_load_history: function() { - var self = this; - var oldest_msg_selector = '.o_mail_thread_message[data-message-id="' + this.get('messages')[0].id + '"]'; - var offset = -framework.getPosition(document.querySelector(oldest_msg_selector)).top; - mail_thread.MailThreadMixin.message_load_history.call(this).then(function() { - offset += framework.getPosition(document.querySelector(oldest_msg_selector)).top; - self.$messages.scrollTop(offset); - }); - }, -}); - -core.action_registry.add('mail.chat.instant_messaging', ChatMailThread); - - -return { - ChatMailThread: ChatMailThread, -}; - -}); diff --git a/addons/mail/static/src/js/mail_chat_common.js b/addons/mail/static/src/js/mail_chat_common.js deleted file mode 100644 index dd3b1801b31..00000000000 --- a/addons/mail/static/src/js/mail_chat_common.js +++ /dev/null @@ -1,412 +0,0 @@ -odoo.define('mail.chat_common', function (require) { -"use strict"; - -var bus = require('bus.bus'); -var core = require('web.core'); -var session = require('web.session'); -var time = require('web.time'); -var Widget = require('web.Widget'); -var mail_utils = require('mail.utils'); - -var _t = core._t; -var QWeb = core.qweb; - -var NBR_LIMIT_HISTORY = 20; - - -/** - * Widget handeling the sessions/conversations - * - * Responsible to listen the bus and do the correct action according to the received notifications (fetch - * conversation, add message, ...) - **/ -var ConversationManager = Widget.extend({ - init: function(parent, options) { - this._super(parent); - this.options = _.clone(options) || {}; - _.defaults(this.options, { - placeholder: _t("Say something..."), - default_username: _t("Anonymous"), - load_history: true, - focus: false, - }); - this.emoji_list = []; - // business - this.sessions = {}; - this.bus = bus.bus; - this.bus.on("notification", this, this.on_notification); - // ui - this.set("right_offset", 0); - this.set("bottom_offset", 0); - this.on("change:right_offset", this, this.calc_positions); - this.on("change:bottom_offset", this, this.calc_positions); - - this.set("waiting_messages", 0); - this.on("change:waiting_messages", this, this.window_title_change); - $(window).on("focus", _.bind(this.window_focus, this)); - this.window_title_change(); - // session widgets deferred (for their creation) - this._session_defs = {}; - }, - start: function(){ - var self = this; - return $.when(session.rpc("/mail/chat_init"), this._super.apply(this, arguments)).then(function(result){ - self._setup(result); - }); - }, - _setup: function(init_data){ - this.emoji_list = init_data.emoji; - this.options.emoji = mail_utils.shortcode_substitution(init_data.emoji); - }, - on_notification: function(notification, options) { - var self = this; - var channel = notification[0]; - var message = notification[1]; - var regex_uuid = new RegExp(/(\w{8}(-\w{4}){3}-\w{12}?)/g); - var is_channel_uuid = regex_uuid.test(channel); - - // (dbname, 'res.partner', partner_id) receive only the new mail.channel header - if((Array.isArray(channel) && (channel[1] === 'res.partner')) || is_channel_uuid){ - // activate the received session - if(message.uuid){ - this.session_apply(message, options); - } - } - // (dbname, 'mail.channel', channel_id) receive only the mail.message of the channel - if((Array.isArray(channel) && (channel[1] === 'mail.channel')) || is_channel_uuid){ - // message to display in the chatview - if (message.message_type) { - self.message_receive(message); - } - } - }, - // window focus unfocus beep and title - window_focus: function() { - this.set("waiting_messages", 0); - }, - window_beep: function() { - mail_utils.beep(session); - }, - window_title_change: function() { - if (this.get("waiting_messages") !== 0) { - this.window_beep(); - } - }, - // sessions methods - session_apply: function(session, options){ - // options used by this function : focus (load_history can be usefull) - var self = this; - options = _.defaults(options || {}, { - 'focus': true, - }, this.options); - // create/get the conversation widget - var conv = this.sessions[session.id]; - if (! conv) { - if(session.state !== 'closed'){ - conv = new Conversation(this, session, options); - // store this deferred to apply next action (notifications) when it is resolved. - this._session_defs[session.id] = conv.appendTo($("body")); - this._session_defs[session.id].then(function() { - self.calc_positions(); - }); - conv.on("destroyed", this, _.bind(this.session_delete, this)); - this.sessions[session.id] = conv; - } - }else{ - // update the state only when the 'contruction' deferred is resolved. - this._session_defs[session.id].then(function(){ - conv.set('session', session); - }); - } - return conv; - }, - session_delete: function(channel_id){ - delete this.sessions[channel_id]; - this.calc_positions(); - }, - /** - * Adding a message in all its channels - * /!\ Suppose the channel are already loaded in the ConversationManager /!\ - * @param {Object} message : the received message (formatted server side) - */ - message_receive: function(message){ - var self = this; - _.each(message.channel_ids, function(channel_id){ - self.sessions[channel_id].message_receive(message); - }); - }, - // others - calc_positions: function() { - var self = this; - var current = this.get("right_offset"); - _.each(this.sessions, function(s) { - s.set("bottom_position", self.get("bottom_offset")); - s.set("right_position", current); - current += s.$().outerWidth(true); - }); - }, - destroy: function() { - $(window).off("focus", this.window_focus); - $(window).off("blur", this.window_blur); - return this._super(); - } -}); - -/** - * Widget Conversation - * - * Display the conversation in a 'portable' window, manage sending and receiving message, - * and the fold state of the conversation. - **/ -var Conversation = Widget.extend({ - template: "mail.chat.im.Conversation", - events: { - "keydown input": "keydown", - "click .o_mail_chat_im_window_close": "on_click_close", - "click .o_mail_chat_im_window_header": "on_click_header" - }, - init: function(parent, session, options) { - this._super(parent); - this.options = _.defaults(options || {}, { - 'emoji': {}, - 'default_username': _t('Anonymous'), - 'placeholder': _t('Write something ...'), - 'loading_history': true, - 'focus': true, - }); - this.set("messages", []); - this.set("session", session); - this.set("pending", 0); - this.set("right_position", 0); - this.set("bottom_position", 0); - }, - willStart: function(){ - if(this.options.loading_history){ - return $.when(this.message_history(), this._super.apply(this, arguments)); - } - return this._super.apply(this, arguments); - }, - start: function() { - var self = this; - // ui - self.on("change:right_position", self, self.calc_pos); - self.on("change:bottom_position", self, self.calc_pos); - self.on("change:pending", self, self.message_pending_change); - - self.full_height = self.$().height(); - self.$('.o_mail_chat_im_window_content').on('scroll',function(){ - if($(this).scrollTop() === 0){ - self.message_history(); - } - }); - // business - self.on("change:session", self, self.session_change); - self.on("change:messages", self, self.message_render); - - return self._super.apply(this, arguments).then(function(){ - // place the conversation window correctly on the screen - self.calc_pos(); - // focus if needed - if(self.options.focus){ - self.focus(); - } - // re-apply the session to display the correct state - self.set('session', _.clone(self.get('session'))); - self._go_bottom(); - }); - }, - // ui - show: function(){ - this.$().animate({ - height: this.full_height - }); - this.set("pending", 0); - }, - hide: function(){ - this.$().animate({ - height: this.$(".o_mail_chat_im_window_header").outerHeight() - }); - }, - calc_pos: function() { - this.$().css("right", this.get("right_position")); - this.$().css("bottom", this.get("bottom_position")); - }, - // session - /** - * Set the fold state LOCALLY for the session. This will - * trigger a ui update - * @param {string} state : the fold state ('fold', 'closed' or 'open') - */ - session_fold: function(state){ - var session = this.get('session'); - session.state = state; - this.set('session', session); - }, - /** - * Triggered when the session has changed : it update the header name - * and fold the window (ui) according to its state. - */ - session_change: function(){ - // update conversation name in header - var name = this.get("session").name || _t('Unknown Channel'); - this.$(".o_mail_chat_im_window_header_name").text(name); - this.$(".o_mail_chat_im_window_header_name").attr('title', name); - // update the fold state - if(this.get("session").state){ - if(this.get("session").state === 'closed'){ - this.destroy(); - }else{ - if(this.get("session").state === 'open'){ - this.show(); - }else{ - this.hide(); - } - } - } - }, - // messages - /** - * Load the history of the current session, and insert it into already loaded messages - * @returns {Deferred} resolved with the new loaded messages (Object[]) - */ - message_history: function(){ - var self = this; - if(this.options.loading_history){ - var data = { - uuid: self.get("session").uuid, - limit: NBR_LIMIT_HISTORY, - last_id: _.first(this.get("messages")) ? _.first(this.get("messages")).id : false, - }; - return session.rpc("/mail/chat_history", data).then(function(messages){ - if(messages){ - self.message_insert(messages); - if(messages.length != NBR_LIMIT_HISTORY){ - self.options.loading_history = false; - } - }else{ - self.options.loading_history = false; - } - return messages; - }); - } - return $.Deferred().resolve([]); - }, - message_receive: function(message) { - // don't add duplicated message (this can happen when refreshing and receiving already recieved - // message that are still in the bus queue/buffer). - if(!_.contains(_.pluck(this.get('messages'), 'id'), message.id)){ - // is pending message ? - if (this.get('session').state === 'open') { - this.set("pending", 0); - } else { - if(!this.message_am_i_author(message)){ - this.set("pending", this.get("pending") + 1); - } - } - // insert it - this.message_insert([message]); - this._go_bottom(); - } - }, - message_send: function(message) { - var self = this; - var send_it = function() { - return session.rpc("/mail/chat_post", {uuid: self.get("session").uuid, message_content: message}); - }; - var tries = 0; - send_it().fail(function(error, e) { - e.preventDefault(); - tries += 1; - if (tries < 3) - return send_it(); - }); - }, - /** - * Preporcess the message (add informations in the Object, ...) - * @param {Object[]} messages : server side formatted message (from message_format method) - */ - _message_preprocess: function(raw_messages){ - var self = this; - _.each(raw_messages, function(m){ - m.date = moment(time.str_to_datetime(m.date)).format('YYYY-MM-DD HH:mm:ss'); // set the date in the correct browser user timezone - if(m.body){ - m.body = mail_utils.shortcode_apply(m.body, self.options.emoji); - } - _.each(m.attachment_ids, function(a){ - a.url = '/web/content/' + a.id + '?download=true'; - a.fa_class = mail_utils.attachment_mimetype_icon(a.mimetype); - }); - }); - return raw_messages; - }, - /** - * Insert new raw message into the current ones - * @param {Object[]} messages : server side formatted message (from message_format method) - */ - message_insert: function(raw_messages){ - var messages = this._message_preprocess(raw_messages); - this.set("messages", _.sortBy(this.get("messages").concat(messages), function(m){ return m.id; })); - }, - message_render: function(){ - var message_html = $(QWeb.render("mail.chat.im.Conversation.messages", {"widget": this})); - this.$('.o_mail_chat_im_window_content').html(message_html); - }, - message_am_i_author: function(message){ - return message.author_id && message.author_id[0] === session.partner_id; - }, - message_pending_change: function(){ - if (this.get("pending") === 0) { - this.$(".o_mail_chat_im_window_header_counter").text(""); - } else { - this.$(".o_mail_chat_im_window_header_counter").text("(" + this.get("pending") + ")"); - } - }, - // utils and event - _go_bottom: function() { - if(this.$(".o_mail_chat_im_window_content").length){ - this.$(".o_mail_chat_im_window_content").scrollTop(this.$(".o_mail_chat_im_window_content").get(0).scrollHeight); - } - }, - focus: function() { - this.$(".o_mail_chat_im_window_input").focus(); - }, - keydown: function(e) { - if(e && e.which == 27) { - if(this.$el.prev().find('.o_mail_chat_im_window_input').length > 0){ - this.$el.prev().find('.o_mail_chat_im_window_input').focus(); - }else{ - this.$el.next().find('.o_mail_chat_im_window_input').focus(); - } - e.stopPropagation(); - this.session_fold('closed'); - } - if(e && e.which !== 13) { - return; - } - var mes = this.$("input").val(); - if (! mes.trim()) { - return; - } - this.$("input").val(""); - this.message_send(mes); - }, - on_click_header: function(){ - this.session_fold(); - }, - on_click_close: function(event) { - event.stopPropagation(); // avoid triggering event for 'on_click_header' - this.session_fold('closed'); - }, - destroy: function() { - this.trigger("destroyed", this.get('session').id); - return this._super(); - } -}); - - -return { - Conversation: Conversation, - ConversationManager: ConversationManager, -}; - -}); diff --git a/addons/mail/static/src/js/mail_chatter.js b/addons/mail/static/src/js/mail_chatter.js deleted file mode 100644 index 8fbe8c1ea05..00000000000 --- a/addons/mail/static/src/js/mail_chatter.js +++ /dev/null @@ -1,459 +0,0 @@ -odoo.define('mail.chatter', function (require) { -"use strict"; - -var core = require('web.core'); -var web_client = require('web.web_client'); -var Widget = require('web.Widget'); -var form_common = require('web.form_common'); -var SystrayMenu = require('web.SystrayMenu'); -var mail_utils = require('mail.utils'); -var mail_thread = require('mail.thread'); - -var QWeb = core.qweb; -var _t = core._t; -var internal_bus = core.bus; - - -/** - * Mail Compose Message for Chatter - * - * Extends the basic MailComposeMessage Widget to add 'suggested partner' layer (open popup - * when suggested partner is selected without email, or other informations), and the button - * to open the 'mail.compose.message' wizard. - **/ -var ChatterMailComposeMessage = mail_thread.MailComposeMessage.extend({ - template: 'mail.chatter.ComposeMessage', - init: function(parent, dataset, options){ - this._super.apply(this, arguments); - this.suggested_partners = []; - this.context = options.context || {}; - this.options = _.defaults(options, { - 'record_name': false, - 'is_log': false, - 'internal_subtypes': [], - }); - this.events = _.extend(this.events, { - 'click .o_mail_compose_message_button_full_message': 'on_compose_message', - }); - }, - willStart: function(){ - if(this.options.is_log){ - return this._super.apply(this, arguments); - } - return $.when(this._super.apply(this, arguments), this.message_get_suggested_recipients()); - }, - start: function(){ - this.$input = this.$('.o_mail_compose_message_input'); - autosize(this.$input); - this.$input.focus(); - return this._super.apply(this, arguments); - }, - message_get_suggested_recipients: function(){ - var self = this; - var email_addresses = _.pluck(this.suggested_partners, 'email_address'); - return this.thread_dataset.call('message_get_suggested_recipients', [[this.context.default_res_id], this.context]).done(function (suggested_recipients) { - var thread_recipients = suggested_recipients[self.context.default_res_id]; - _.each(thread_recipients, function (recipient) { - var parsed_email = mail_utils.parse_email(recipient[1]); - if (_.indexOf(email_addresses, parsed_email[1]) == -1) { - self.suggested_partners.push({ - 'checked': true, - 'partner_id': recipient[0], - 'full_name': recipient[1], - 'name': parsed_email[0], - 'email_address': parsed_email[1], - 'reason': recipient[2], - }); - } - }); - }); - }, - /** - * Get the list of selected suggested partner - * @returns Array() : list of 'recipient' selected partner (may not be created in db) - **/ - get_checked_suggested_partners: function(){ - var self = this; - var checked_partners = []; - this.$('.o_mail_chatter_compose_message_suggested_partners input:checked').each(function(){ - var $input = $(this); - var full_name = $input.data('fullname'); - checked_partners = checked_partners.concat(_.filter(self.suggested_partners, function(item){ - return full_name === item.full_name; - })); - }); - return checked_partners; - }, - /** - * Check the additionnal partners (not necessary registered partners), and open a popup form view - * for the ones who informations is missing. - * @param Array : list of 'recipient' partners to complete informations or validate - * @returns Deferred resolved with the list of checked suggested partners (real partner) - **/ - check_suggested_partners: function (checked_suggested_partners) { - var self = this; - var check_done = $.Deferred(); - - var recipients = _.filter(checked_suggested_partners, function (recipient) { return recipient.checked; }); - var recipients_to_find = _.filter(recipients, function (recipient) { return (! recipient.partner_id); }); - var names_to_find = _.pluck(recipients_to_find, 'full_name'); - var recipients_to_check = _.filter(recipients, function (recipient) { return (recipient.partner_id && ! recipient.email_address); }); - var recipient_ids = _.pluck(_.filter(recipients, function (recipient) { return recipient.partner_id && recipient.email_address; }), 'partner_id'); - - var names_to_remove = []; - var recipient_ids_to_remove = []; - - // have unknown names -> call message_get_partner_info_from_emails to try to find partner_id - var find_done = $.Deferred(); - if (names_to_find.length > 0) { - find_done = self.thread_dataset.call('message_partner_info_from_emails', [[this.context.default_res_id], names_to_find]); - } else { - find_done.resolve([]); - } - - // for unknown names + incomplete partners -> open popup - cancel = remove from recipients - $.when(find_done).pipe(function (result) { - var emails_deferred = []; - var recipient_popups = result.concat(recipients_to_check); - - _.each(recipient_popups, function (partner_info) { - var deferred = $.Deferred(); - emails_deferred.push(deferred); - - var partner_name = partner_info.full_name; - var partner_id = partner_info.partner_id; - var parsed_email = mail_utils.parse_email(partner_name); - - var pop = new form_common.FormViewDialog(this, { - res_model: 'res.partner', - res_id: partner_id, - context: { - 'force_email': true, - 'ref': "compound_context", - 'default_name': parsed_email[0], - 'default_email': parsed_email[1], - }, - title: _t("Please complete partner's informations"), - }).open(); - pop.on('closed', self, function () { - deferred.resolve(); - }); - pop.view_form.on('on_button_cancel', self, function () { - names_to_remove.push(partner_name); - if (partner_id) { - recipient_ids_to_remove.push(partner_id); - } - }); - }); - $.when.apply($, emails_deferred).then(function () { - var new_names_to_find = _.difference(names_to_find, names_to_remove); - find_done = $.Deferred(); - if (new_names_to_find.length > 0) { - find_done = self.thread_dataset.call('message_partner_info_from_emails', [[self.context.default_res_id], new_names_to_find, true]); - } else { - find_done.resolve([]); - } - $.when(find_done).pipe(function (result) { - var recipient_popups = result.concat(recipients_to_check); - _.each(recipient_popups, function (partner_info) { - if (partner_info.partner_id && _.indexOf(partner_info.partner_id, recipient_ids_to_remove) == -1) { - recipient_ids.push(partner_info.partner_id); - } - }); - }).pipe(function () { - check_done.resolve(recipient_ids); - }); - }); - }); - return check_done; - }, - message_post: function(body, attachment_ids, values){ - var self = this; - var _super = self._super; - // default values - values = _.defaults(values || {}, { - 'message_type': 'comment', - 'content_subtype': 'html', - 'partner_ids': [], - 'subtype_id': false, - 'subtype': false, - }); - // update subtype - if(this.options.is_log) { - var subtype_id = parseInt(this.$('.o_mail_chatter_compose_message_subtype_select').val()); - if(_.indexOf(_.pluck(this.options.internal_subtypes, 'id'), subtype_id) == -1) { - values.subtype = 'mail.mt_note'; - }else{ - values.subtype_id = subtype_id; - } - }else{ - values.subtype = 'mail.mt_comment'; - } - // update partner_ids - var def = $.when(); - if(this.options.is_log){ - // directly call the super() - def = _.bind(_super, self)(body, attachment_ids, values); - }else{ - // call the super() when all partner popup are treated - var checked_suggested_partners = this.get_checked_suggested_partners(); - this.check_suggested_partners(checked_suggested_partners).done(function(partner_ids){ - // update context - values.partner_ids = partner_ids; - values.context = _.defaults(self.context, { - 'mail_post_autofollow': true, - 'mail_post_autofollow_partner_ids': partner_ids, - }); - def = _.bind(_super, self)(body, attachment_ids, values); - }); - } - return def; - }, - is_empty: function(){ - return this.$input.val() === ''; - }, - on_compose_message: function(){ - var self = this; - if (!this.do_check_attachment_upload()){ - return false; - } - - var recipient_done = $.Deferred(); - if (this.options.is_log){ - recipient_done.resolve([]); - }else{ - var checked_suggested_partners = this.get_checked_suggested_partners(); - recipient_done = this.check_suggested_partners(checked_suggested_partners); - } - recipient_done.then(function(partner_ids){ - var context = { - 'default_parent_id': self.id, - 'default_body': mail_utils.get_text2html(self.$('.o_mail_compose_message_input').val()), - 'default_attachment_ids': _.pluck(self.get('attachment_ids'), 'id'), - 'default_partner_ids': partner_ids, - 'default_is_log': self.options.is_log, - 'mail_post_autofollow': true, - 'mail_post_autofollow_partner_ids': partner_ids, - }; - - if (self.context.default_model && self.context.default_res_id) { - context.default_model = self.context.default_model; - context.default_res_id = self.context.default_res_id; - } - - self.do_action({ - type: 'ir.actions.act_window', - res_model: 'mail.compose.message', - view_mode: 'form', - view_type: 'form', - views: [[false, 'form']], - target: 'new', - context: context, - }, { - on_close: function() { - self.trigger('need_refresh'); - }, - }); - }); - } -}); - - -/** - * Document Chatter ('mail_thread' widget) - * - * Displayed on every document. Since it is displayed on a form view, it extends 'AbstractField' widget, and - * required the behaviour of 'MailThreadMixin' to manage mail.message (using common methods). - * This widget is responsible of the toggle the message composer and the custom message rendering. - **/ -var ChatterMailThread = form_common.AbstractField.extend(mail_thread.MailThreadMixin, { - template: 'mail.chatter.ChatterMailThread', - events: { - "click .o_mail_redirect": "on_click_redirect", - "click .o_mail_thread_message_star": "on_message_star", - "click .o_mail_thread_message_needaction": "on_message_needaction", - "click .o_mail_thread_show_more": "message_load_history", - "click .o_mail_expand": "on_message_body_toggle", - // toggle message composer (!! declaration order is important !!) - "click .o_mail_chatter_button_new_message": "on_open_composer", - "click .o_mail_chatter_button_log_note": "on_open_composer", - "blur .o_mail_compose_message_input": "on_close_composer", - "mousedown .o_mail_chatter_compose_message": function() { - this.stay_open = true; - }, - "focus .o_mail_compose_message_input": function() { - this.stay_open = false; - }, - "keydown .o_mail_compose_message_input": function() { - this.stay_open = false; - }, - }, - init: function (){ - this._super.apply(this, arguments); - mail_thread.MailThreadMixin.init.call(this); - this.message_composer = undefined; - this.record_name = ''; - this.context = this.options.context || {}; - // options from 'message_ids' node in field_view_get - this.options = _.defaults(this.options, { - 'display_log_button': true, - 'display_document_link': false, - 'internal_subtypes': [], - 'emoji_list': [], - }); - this.emoji_set_substitution(this.options.emoji_list); - }, - start: function(){ - mail_thread.MailThreadMixin.start.call(this); - return this._super.apply(this, arguments); - }, - /** - * When a message is correctly posted, fetch its data to render it - * @param {Number} message_id : the identifier of the new posted message - * @returns {Deferred} - */ - on_message_post: function(message_id){ - var self = this; - return this.message_format([message_id]).then(function(messages){ - self.stay_open = false; - self.message_insert(messages); - self.on_close_composer(); - }); - }, - /** - * The value of the field has change (modification or switch to next document). Form view is - * not re-rendered, simply updated. A re-fetch is needed. - * @override - */ - render_value: function(){ - var self = this; - // re-rendering for mail composer message - this.all_messages_loaded = this.get_value().length <= mail_thread.LIMIT_MESSAGE; - this.renderElement(); - // reset dataset - this.ThreadDataset = this.view.dataset; - // update context (require for mail.compose.message wizard) - this.context = _.extend({ - 'mail_read_set_read': true, // set messages as read in Chatter TODO JEM : not used anymore - 'default_res_id': this.view.datarecord.id || false, - 'default_model': this.view.model || false, - }, this.options.context || {}); - // fetch messages of current document - var ids = this.get_value() || []; - return this.message_format(ids.slice(0, mail_thread.LIMIT_MESSAGE)).then(function(raw_messages){ - self._message_replace(self._message_preprocess(raw_messages)); - self.record_name = _.last(raw_messages) ? _.last(raw_messages).record_name : ''; - // set the thread as read - self.ThreadDataset.call('message_set_read', [[self.view.datarecord.id]]).then(function(message_ids){ - // decrement the needaction top counter - internal_bus.trigger('mail_needaction_done', message_ids.length); - }); - }); - }, - // composer toggle - on_open_composer: function(event){ - var self = this; - this.$('.o_mail_chatter_compose_message').show(); - // destroy existing composer - if(this.message_composer){ - this.message_composer.destroy(); - } - // create the new composer - this.message_composer = new ChatterMailComposeMessage(this, this.ThreadDataset, { - 'internal_subtypes': this.options.internal_subtypes, - 'is_log': this.$(event.currentTarget).hasClass('o_mail_chatter_button_log_note'), - 'emoji_list': this.options.emoji_list, - 'context': this.context, - 'record_name': this.record_name, - }); - this.message_composer.appendTo(this.$('.o_mail_chatter_compose_message')); - this.message_composer.on('message_sent', this, this.on_message_post); - this.message_composer.on('need_refresh', this, function(){ - self.message_refresh(); - self.on_close_composer(); - }); - }, - on_close_composer: function(){ - if(this.message_composer && this.message_composer.is_empty() && !this.stay_open){ - this.$('.o_mail_chatter_compose_message').hide(); - } - }, - /** - * Refresh the current message, and set them as current message. Use 'message_fetch', since its - * domain don't use 'ids', so if there is new messages, they'll be fetched. - * @returns {Deferred} - */ - message_refresh: function(){ - return this.message_load_new(); - }, - /** - * Render the messages - * @override - */ - message_render: function(){ - this.$('.o_mail_chatter_messages').html(QWeb.render('mail.chatter.ChatterMailThread.messages', {'widget': this})); - - // display image thumbnail - this.$(".o_mail_chatter_messages .o_image[data-mimetype^='image']").each(function () { - var $img = $(this); - if (/gif|jpe|jpg|png/.test($img.data('mimetype')) && $img.data('src')) { - $img.css('background-image', "url('" + $img.data('src') + "')"); - } - }); - }, - /** - * Order the messages - * @param {Object[]} messages : messages to order - * @override - */ - _message_order: function(messages){ - messages = mail_thread.MailThreadMixin._message_order.call(this, messages); - return messages.reverse(); - }, - /** - * Return the message domain for the current document mail thread - * @override - */ - get_message_domain: function(){ - var res_id = this.ThreadDataset.ids[this.ThreadDataset.index]; - return [['model', '=', this.ThreadDataset.model], ['res_id', '=', res_id]]; - } -}); - -core.form_widget_registry.add('mail_thread', ChatterMailThread); - - - -/** - * Global ComposeMessage Top Button - * - * Add a link on the top user bar to write a full mail. It opens the form view - * of the mail.compose.message (in a modal). - */ -var ComposeMessageTopButton = Widget.extend({ - template:'mail.ComposeMessageTopButton', - events: { - "click": "on_compose_message", - }, - on_compose_message: function (ev) { - ev.preventDefault(); - web_client.action_manager.do_action({ - type: 'ir.actions.act_window', - res_model: 'mail.compose.message', - view_mode: 'form', - view_type: 'form', - views: [[false, 'form']], - target: 'new', - }); - }, -}); - -// Put the ComposeMessageTopButton widget in the systray menu -SystrayMenu.Items.push(ComposeMessageTopButton); - - -return { - ChatterMailThread: ChatterMailThread, -}; - -}); diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js deleted file mode 100644 index 8e09d1116c4..00000000000 --- a/addons/mail/static/src/js/mail_followers.js +++ /dev/null @@ -1,424 +0,0 @@ -odoo.define('mail.mail_followers', function (require) { -"use strict"; - -var ajax = require('web.ajax'); -var core = require('web.core'); -var data = require('web.data'); -var Dialog = require('web.Dialog'); -var form_common = require('web.form_common'); -var session = require('web.session'); -var web_client = require('web.web_client'); - -var _t = core._t; -var qweb = core.qweb; - -/** - * ------------------------------------------------------------ - * Mail Followers Widget - * ------------------------------------------------------------ - * - * This widget handles the display of a list of records as a vertical - * list, with an image on the left. The widget itself is a floatting - * right-sided box. - * This widget is mainly used to display the followers of records - * in OpenChatter. - */ - - -var Followers = form_common.AbstractField.extend({ - template: 'mail.Followers', - - init: function() { - this._super.apply(this, arguments); - - this.image = this.node.attrs.image || 'image_small'; - this.comment = this.node.attrs.help || false; - this.ds_model = new data.DataSetSearch(this, this.view.model); - this.ds_users = new data.DataSetSearch(this, 'res.users'); - - this.value = []; - this.followers = []; - this.data_subtype = {}; - - this.view_is_editable = this.__parentedParent.is_action_enabled('edit'); - }, - - start: function() { - // use actual_mode property on view to know if the view is in create mode anymore - this.view.on("change:actual_mode", this, this.on_check_visibility_mode); - this.on_check_visibility_mode(); - this.reinit(); - this.bind_events(); - return this._super(); - }, - - on_check_visibility_mode: function () { - this.set({"force_invisible": this.view.get("actual_mode") === "create"}); - }, - - set_value: function(_value) { - this.value = _value; - this._super(_value); - }, - - reinit: function() { - this.data_subtype = {}; - this.message_is_follower = undefined; - this.display_buttons(); - }, - - bind_events: function() { - var self = this; - - // event: click on '(Un)Follow' button, that toggles the follow for uid - this.$('.o_followers_follow_button').on('click', function () { - if($(this).hasClass('o_followers_notfollow')) { - self.do_follow(); - } - else { - self.do_unfollow([session.uid]); - } - }); - - // event: click on a subtype, that (un)subscribe for this subtype - this.$el.on('click', '.o_subtypes_list input', function(event) { - self.do_update_subscription(event); - var $list = self.$('.o_subtypes_list'); - if(!$list.hasClass('open')) { - $list.addClass('open'); - } - if(self.$('.o_subtypes_list ul')[0].children.length < 1) { - $list.removeClass('open'); - } - event.stopPropagation(); - }); - - // event: click on 'invite' button, that opens the invite wizard - this.$el.on('click', '.o_add_follower', function(e){ - e.preventDefault(); - self.on_invite_follower(false); - }); - this.$el.on('click', '.o_add_follower_channel', function(e){ - e.preventDefault(); - self.on_invite_follower(true); - }); - - // event: click on 'edit_subtype(pencil)' button to edit subscription - this.$el.on('click', '.o_edit_subtype', self.on_edit_subtype); - this.$el.on('click', '.o_remove_follower', self.on_remove_follower); - this.$el.on('click', 'a[data-partner]', self.on_follower_clicked); - }, - - on_edit_subtype: function(event) { - var self = this; - var $currentTarget = $(event.currentTarget); - var user_pid = $currentTarget.data('res-id'); - var follower_id = $currentTarget.data('id'); - self.dialog = new Dialog(this, { - size: 'small', - title: _t('Edit Subscription of ') + $currentTarget.siblings('a').text(), - buttons: [ - { - text: _t("Apply"), - click: function() { - self.do_update_subscription(event, user_pid); - }, - close: true - }, - { - text: _t("Cancel"), - close: true, - }, - ], - }).open(); - return self.fetch_subtypes(follower_id); - }, - - on_invite_follower: function (channel_only) { - var self = this; - var action = { - type: 'ir.actions.act_window', - res_model: 'mail.wizard.invite', - view_mode: 'form', - view_type: 'form', - views: [[false, 'form']], - name: _t('Invite Follower'), - target: 'new', - context: { - 'default_res_model': this.view.dataset.model, - 'default_res_id': this.view.datarecord.id, - 'mail_invite_follower_channel_only': channel_only, - }, - }; - this.do_action(action, { - on_close: function() { - self.read_value(); - }, - }); - }, - - on_remove_follower: function (event) { - var res_model = $(event.target).parent().find('a').data('res-model'); - var res_id = $(event.target).parent().find('a').data('res-id'); - if (res_model === 'res.partner') { return this.do_unfollow(undefined, [res_id], undefined); } - else { return this.do_unfollow(undefined, undefined, [res_id]); } - }, - - on_follower_clicked: function (event) { - event.preventDefault(); - var partner_id = $(event.target).data('partner'); - var state = { - 'model': 'res.partner', - 'id': partner_id, - 'title': this.record_name - }; - web_client.action_manager.do_push_state(state); - var action = { - type:'ir.actions.act_window', - view_type: 'form', - view_mode: 'form', - res_model: 'res.partner', - views: [[false, 'form']], - res_id: partner_id, - }; - this.do_action(action); - }, - - read_value: function () { - var self = this; - return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']) - .then(function (results) { - self.value = results[0].message_follower_ids; - self.render_value(); - }); - }, - - render_value: function () { - this.reinit(); - return this.fetch_followers(this.value); - }, - - fetch_followers: function (value_) { - this.value = value_ || []; - return ajax.jsonRpc('/mail/read_followers', 'call', {'follower_ids': this.value}) - .then(this.proxy('display_followers'), this.proxy('fetch_generic')) - .then(this.proxy('display_buttons')) - .then(this.proxy('fetch_subtypes')); - }, - - /** Read on res.partner failed: fall back on a generic case - - fetch current user partner_id (call because no other smart solution currently) FIXME - - then display a generic message about followers */ - fetch_generic: function () { - var self = this; - - return this.ds_users.call('read', [[session.uid], ['partner_id']]) - .then(function (results) { - var pid = results[0].partner_id[0]; - self.message_is_follower = (_.indexOf(self.value, pid) !== -1); - }) - .then(self.proxy('display_generic')); - }, - - _format_followers: function(count){ - var str = ''; - if(count <= 0){ - str = _t('No follower'); - }else if(count === 1){ - str = _t('One follower'); - }else{ - str = ''+count+' '+_t('followers'); - } - return str; - }, - - /* Display generic info about follower, for people not having access to res_partner */ - display_generic: function () { - this.$('.o_followers_list').empty(); - this.$('.o_followers_count').html(this._format_followers(this.value.length)); - }, - - /** Display the followers */ - display_followers: function (records) { - var self = this; - this.followers = records || this.followers; - - // clean and display title - var $followers_list = this.$('.o_followers_list').empty(); - this.$('.o_followers_count').html(this._format_followers(this.followers.length)); - var user_follower = _.filter(this.followers, function (rec) { return rec.is_uid;}); - this.message_is_follower = user_follower.length >= 1; - this.follower_id = this.message_is_follower ? user_follower[0].id : undefined; - - // render the dropdown content - $(qweb.render('mail.Followers.add_more', {'widget': self})).appendTo($followers_list); - var $follower_li; - _(this.followers).each(function (record) { - $follower_li = $(qweb.render('mail.Followers.partner', { - 'record': _.extend(record, {'avatar_url': '/web/image/' + record.res_model + '/' + record.res_id + '/image_small'}), - 'widget': self}) - ); - $follower_li.appendTo($followers_list); - - // On mouse-enter it will show the edit_subtype pencil. - if (record.is_editable) { - $follower_li.on('mouseenter mouseleave', function(e) { - $(e.currentTarget).find('.o_edit_subtype').toggleClass('hide', e.type === 'mouseleave'); - }); - } - }); - }, - - display_buttons: function () { - if (this.message_is_follower) { - this.$('button.o_followers_follow_button').removeClass('o_followers_notfollow').addClass('o_followers_following'); - } else { - this.$('button.o_followers_follow_button').removeClass('o_followers_following').addClass('o_followers_notfollow'); - } - }, - - /** Fetch subtypes, only if current user is follower */ - fetch_subtypes: function (follower_id) { - var self = this; - var dialog = false; - - if (follower_id) { - dialog = true; - } - else { - this.$('.o_subtypes_list ul').empty(); - if (! this.message_is_follower) { - this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', true); - return; - } - else { - this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', false); - } - } - if (this.follower_id) { - return ajax.jsonRpc('/mail/read_subscription_data', 'call', {'res_model': this.view.model, 'res_id': this.view.datarecord.id, 'follower_id': follower_id}) - .then(function (data) { self.display_subtypes(data, dialog); }); - } else { - return $.Deferred().resolve(); - } - }, - - /** Display subtypes: {'name': default, followed} */ - display_subtypes:function (data, dialog) { - var self = this; - var old_parent_model; - var $list; - if (dialog) { - $list = this.dialog.$el; - } else { - $list = this.$('.o_subtypes_list ul'); - } - $list.empty(); - - this.data_subtype = data; - this.records_length = $.map(data, function(value, index) { return index; }).length; - - if (this.records_length > 1) { - self.display_followers(); - } - - _.each(data, function (record) { - if (old_parent_model !== record.parent_model && old_parent_model !== undefined) { - $list.append($('<li>').addClass('divider')); - } - old_parent_model = record.parent_model; - record.followed = record.followed || undefined; - $(qweb.render('mail.Followers.subtype', {'record': record, - 'dialog': dialog})) - .appendTo($list); - }); - }, - - do_follow: function () { - var context = new data.CompoundContext(this.build_context(), {}); - this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', false); - this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], - [session.uid], - undefined, - context]) - .then(this.proxy('read_value')); - - _.each(this.$('.o_subtypes_list input'), function (record) { - $(record).attr('checked', 'checked'); - }); - }, - - do_unfollow: function (user_ids, partner_ids, channel_ids) { - if (confirm(_t("Warning! \n If you remove a follower, he won't be notified of any email or discussion on this document. Do you really want to remove this follower ?"))) { - _(this.$('.o_subtype_checkbox')).each(function (record) { - $(record).attr('checked',false); - }); - - this.$('.o_subtypes_list > .dropdown-toggle').attr('disabled', true); - var context = new data.CompoundContext(this.build_context(), {}); - - if (partner_ids || channel_ids) { - return this.ds_model.call( - 'message_unsubscribe', [ - [this.view.datarecord.id], - partner_ids, - channel_ids, - context] - ).then(this.proxy('read_value')); - } - else { - return this.ds_model.call( - 'message_unsubscribe_users', [ - [this.view.datarecord.id], - user_ids, - context] - ).then(this.proxy('read_value')); - } - } - return false; - }, - - do_update_subscription: function (event, user_pid) { - var self = this; - this.data_subtype = {}; - - var kwargs = {}; - var action_subscribe = 'message_subscribe_users'; - var subtypes = this.$('.o_followers_actions input[type="checkbox"]'); - - // Subtypes edited from the modal - if (user_pid) { - action_subscribe = 'message_subscribe'; - subtypes = this.dialog.$('input[type="checkbox"]'); - kwargs['partner_ids'] = [user_pid]; - }else{ - kwargs['user_ids'] = [session.uid]; - } - - // Get the subtype ids - var checklist = []; - _(subtypes).each(function (record) { - if ($(record).is(':checked')) { - checklist.push(parseInt($(record).data('id'))); - } - }); - kwargs['subtype_ids'] = checklist; - - // If no more subtype followed, unsubscribe the partner. - if (!checklist.length) { - if (!this.do_unfollow(undefined, [user_pid], undefined)) { - $(event.target).attr("checked", "checked"); - } else { - self.$('.o_subtypes_list ul').empty(); - } - } else { - kwargs['context'] = new data.CompoundContext(this.build_context(), {}); - return this.ds_model._model.call(action_subscribe, [[this.view.datarecord.id]], kwargs).then(this.proxy('read_value')); - } - }, -}); - -/* Add the widget to registry */ -core.form_widget_registry.add('mail_followers', Followers); - -}); diff --git a/addons/mail/static/src/js/mail_thread.js b/addons/mail/static/src/js/mail_thread.js deleted file mode 100644 index 6511e6a6583..00000000000 --- a/addons/mail/static/src/js/mail_thread.js +++ /dev/null @@ -1,823 +0,0 @@ -odoo.define('mail.thread', function (require) { -"use strict"; - -var core = require('web.core'); -var data = require('web.data'); -var Model = require('web.Model'); -var time = require('web.time'); -var Widget = require('web.Widget'); -var session = require('web.session'); -var web_client = require('web.web_client'); -var mail_utils = require('mail.utils'); - -var _t = core._t; -var QWeb = core.qweb; - - -var LIMIT_MESSAGE = 20; - -/** - * Widget : Input textbox to post messages - * - * Input with 2 buttons to manage attachment and emoji - * - Attachment : upload selected attachment (one at a time) and display them below the textbox. Prevent - posting message while uploading - * - Emoji : popover exposing emoji list, and append emoji shortcode to the textbox when click on emoji image - * It triggers 'message_sent' event with the posted message, when it is sent to the server. - */ -var MailComposeMessage = Widget.extend({ - template: 'mail.ComposeMessage', - events: { - "keydown .o_mail_compose_message_input": "on_keydown", - "keyup .o_mail_compose_message_input": "on_keyup", - "keypress .o_mail_compose_message_input": "on_keypress", - "change input.o_form_input_file": "on_attachment_change", - "click .o_mail_attachment_delete": "on_attachment_delete", - "click .o_mail_compose_message_button_attachment": 'on_click_attachment', - "click .o_mail_compose_message_button_send": 'message_send', - "click .o_mail_mention_proposition": "on_click_mention_item", - "mouseover .o_mail_mention_proposition": "on_hover_mention_proposition", - }, - /** - * Contructor - * @param {Object} options : the options of the MailComposeMessage - * @param {Object[]} [options.emoji_list] : the list of emoji - * @param {Object} options.context : the context. It should contains the default_res_id' (id of the current document). - * @param {chat|mail} options.display_mode : the 'chat' mode will display an input, when the 'mail' mode will display a texarea. - * @param {Char} [options.mention_delimiter] : the delimiter char to distinguish the beginning of a mention - * @param {Integer} [options.mention_min_length] : min length of the mention word required to start a search on partner - * @param {Integer} [options.mention_typing_speed] : delay before starting a search with the mention word - * @param {Integer} [options.mention_fetch_limit] : limit of partner fetch - */ - init: function(parent, dataset, options){ - this._super.apply(this, arguments); - this.thread_dataset = dataset; - this.options = _.defaults(options || {}, { - 'emoji_list': {}, - 'context': {}, - 'display_mode': 'mail', - 'mention_delimiter': '@', - 'mention_min_length': 2, - 'mention_typing_speed': 400, - 'mention_fetch_limit': 8, - }); - this.emoji_list = this.options.emoji_list; - this.context = this.options.context; - this.input_buffer = ''; // save the value at keydown - // attachment handeling - this.AttachmentDataSet = new data.DataSetSearch(this, 'ir.attachment', this.context); - this.fileupload_id = _.uniqueId('o_mail_chat_fileupload'); - this.set('attachment_ids', []); - // mention - this.PartnerModel = new Model('res.partner'); - this.set('mention_word', false); // word typed after the delimiter - this.set('mention_partners', []); // proposition of not-mention partner matching the mention_word - this.set('mention_selected_partners', []); // contains the mention partners sorted as they appear in the input text - }, - start: function(){ - var self = this; - this.$attachment_button = this.$(".o_mail_compose_message_button_attachment"); - this.$attachments_list = this.$el.filter('.o_mail_attachments_list'); - this.$mention_partner_tags = this.$el.filter('.o_mail_mention_partner_tags'); - this.$mention_dropdown = this.$('.o_mail_mention_dropdown'); - this.$input = this.$('.o_mail_compose_message_input'); - this.$input.focus(); - - // attachments - $(window).on(this.fileupload_id, this.on_attachment_loaded); - this.on("change:attachment_ids", this, this.attachment_render); - - // emoji - self.$('.o_mail_compose_message_button_emoji').popover({ - placement: 'top', - content: function(){ - if(!self.$emoji){ // lazy rendering - self.$emoji = $(QWeb.render('mail.ComposeMessage.emoji', {'widget': self})); - self.$emoji.filter('.o_mail_compose_message_emoji_img').on('click', self, self.on_click_emoji_img); - } - return self.$emoji; - }, - html: true, - container: '.o_mail_compose_message_emoji', - trigger: 'focus', - }); - - // mention - this.on('change:mention_word', this, this.mention_word_change); - this.on('change:mention_partners', this, this.mention_render_partner); - this.on('change:mention_selected_partners', this, this.mention_render_selected_partners); - return this._super(); - }, - // events - on_click_attachment: function(event){ - event.preventDefault(); - this.$('input.o_form_input_file').click(); - }, - on_click_emoji_img: function(event){ - this.$input.val(this.$input.val() + " " + $(event.currentTarget).data('emoji')+ " "); - this.$input.focus(); - }, - on_keydown: function(event){ - // Save the old input - this.input_buffer = this.$input.val(); - // Key Down displatching - switch(event.which) { - // BACKSPACE : check if need to remove a mention - case $.ui.keyCode.BACKSPACE: - this.mention_check_remove(event.which); - break; - // ENTER : submit the message only if the dropdown mention proposition is not displayed - case $.ui.keyCode.ENTER: - if(!this.get('mention_partners').length && this.options.display_mode === 'chat'){ - this.message_send(); - } - break; - // UP, DOWN : when navigate on the dropdown, prevent moving the cursor position in the input text - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - event.preventDefault(); - break; - } - }, - message_send: function(){ - var mes = mail_utils.get_text2html(this.$input.val()); - if (! mes.trim() && this.do_check_attachment_upload()) { - return; - } - this.$input.val(""); - this.message_post(mes, _.pluck(this.get('attachment_ids'), 'id')); - }, - on_keypress: function(event){ - switch(event.which) { - // ENTER : prevent moving cursor position when selecting a mention proposition in dropdown - case $.ui.keyCode.ENTER: - if(this.get('mention_partners').length){ - event.preventDefault(); - } - break; - } - }, - on_keyup: function(event){ - switch(event.which) { - // ESCAPED KEYS : do nothing - case $.ui.keyCode.END: - case $.ui.keyCode.PAGE_UP: - case $.ui.keyCode.PAGE_DOWN: - case $.ui.keyCode.ESCAPE: - // do nothing - break; - // ENTER, UP, DOWN : check if navigation in mention propositions - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - case $.ui.keyCode.ENTER: - this.mention_proposition_navigation(event.which); - break; - // DELETE : check if need to remove a mention - case $.ui.keyCode.DELETE: - this.mention_check_remove(event.which); - break; - // Otherwise, check is a mention is typed - default: - var mention_word = this.mention_detect_delimiter(); - if(mention_word){ - this.set('mention_word', mention_word); - }else{ - this.set('mention_partners', []); // close the dropdown - } - } - }, - /** - * Sent the message to the server to create a mail.message, using 'message_post' method - * @param {String} body : the core of the message (html, or plaintext) - * @param {Number[]} attachment_ids : attachment_ids linked to the message - * @param {Object} kwargs : other parameters (in a dictionnary) - * @returns {Deferred} resolved when the message is posted, and returning the new message id - */ - message_post: function(body, attachment_ids, kwargs){ - var self = this; - var values = _.defaults(kwargs || {}, { - 'message_type': 'comment', - 'content_subtype': 'html', - 'partner_ids': [], - 'subtype': 'mail.mt_comment', - }); - values = _.extend(values, { - 'body': this.mention_preprocess_message(body), - 'attachment_ids': attachment_ids || [], - 'partner_ids': _.pluck(this.get('mention_selected_partners'), 'id').concat(values.partner_ids), - }); - return this.thread_dataset._model.call('message_post', [this.context.default_res_id], values).then(function(message_id){ - self.reset(); // empty attachment, mention partners, ... - self.trigger('message_sent', message_id); - return message_id; - }); - }, - // attachment business - on_attachment_change: function(event){ - var $target = $(event.target); - if ($target.val() !== '') { - var filename = $target.val().replace(/.*[\\\/]/,''); - // if the files exits for this answer, delete the file before upload - var attachments = []; - for (var i in this.get('attachment_ids')) { - if ((this.get('attachment_ids')[i].filename || this.get('attachment_ids')[i].name) == filename) { - if (this.get('attachment_ids')[i].upload) { - return false; - } - this.AttachmentDataSet.unlink([this.get('attachment_ids')[i].id]); - } else { - attachments.push(this.get('attachment_ids')[i]); - } - } - // submit filename - this.$('form.o_form_binary_form').submit(); - this.$attachment_button.prop('disabled', true); - - attachments.push({ - 'id': 0, - 'name': filename, - 'filename': filename, - 'url': '', - 'upload': true, - 'mimetype': '', - }); - this.set('attachment_ids', attachments); - } - }, - on_attachment_loaded: function(event, result){ - var attachment_ids = []; - if (result.error || !result.id ) { - this.do_warn(result.error); - attachment_ids = _.filter(this.get('attachment_ids'), function (val) { return !val.upload; }); - }else{ - _.each(this.get('attachment_ids'), function(a){ - if (a.filename == result.filename && a.upload) { - attachment_ids.push({ - 'id': result.id, - 'name': result.name || result.filename, - 'filename': result.filename, - 'mimetype': result.mimetype, - 'url': session.url('/web/content', {'id': result.id, download: true}), - }); - }else{ - attachment_ids.push(a); - } - }); - } - this.set('attachment_ids', attachment_ids); - - // TODO JEM : understand the 2 lines below .... - var $input = this.$('input.o_form_input_file'); - $input.after($input.clone(true)).remove(); - - this.$attachment_button.prop('disabled', false); - }, - on_attachment_delete: function(event){ - event.stopPropagation(); - var self = this; - var attachment_id = $(event.target).data("id"); - if (attachment_id) { - var attachments = []; - _.each(this.get('attachment_ids'), function(attachment){ - if (attachment_id != attachment.id) { - attachments.push(attachment); - } else { - self.AttachmentDataSet.unlink([attachment_id]); - } - }); - this.set('attachment_ids', attachments); - } - }, - do_check_attachment_upload: function () { - if (_.find(this.get('attachment_ids'), function (file) {return file.upload;})) { - this.do_warn(_t("Uploading error"), _t("Please, wait while the file is uploading.")); - return false; - } - return true; - }, - attachment_render: function(){ - this.$attachments_list.html(QWeb.render('mail.ComposeMessage.attachments', {'widget': this})); - - // display image thumbnail - this.$attachments_list.find(".o_image[data-mimetype^='image']").each(function () { - var $img = $(this); - if (/gif|jpe|jpg|png/.test($img.data('mimetype')) && $img.data('src')) { - $img.css('background-image', "url('" + $img.data('src') + "')"); - } - }); - }, - // Mention - on_click_mention_item: function(event){ - event.preventDefault(); - - var text_input = this.$input.val(); - var partner_id = this.$(event.currentTarget).data('partner-id'); - var selected_partner = _.filter(this.get('mention_partners'), function(p){ - return p.id === partner_id; - })[0]; - - // add the mention partner to the list - var mention_selected_partners = this.get('mention_selected_partners'); - if(mention_selected_partners.length){ // there are already mention partner - // get mention matches (ordered by index in the text) - var matches = this.mention_get_match(text_input); - var index = this.mention_get_index(matches, this.get_cursor_position()); - mention_selected_partners.splice(index, 0, selected_partner); - mention_selected_partners = _.clone(mention_selected_partners); - }else{ // this is the first mentionned partner - mention_selected_partners = mention_selected_partners.concat([selected_partner]); - } - this.set('mention_selected_partners', mention_selected_partners); - - // update input text, and reset dropdown - var mention_word = this.get('mention_word'); - var cursor_position = this.get_cursor_position(); - var text_left = text_input.substring(0, cursor_position-(mention_word.length+1)); - var text_right = text_input.substring(cursor_position, text_input.length); - var text_input_new = text_left + this.options.mention_delimiter + selected_partner.name + ' ' + text_right; - this.$input.val(text_input_new); - this.set_cursor_position(text_left.length+selected_partner.name.length+2); - this.set('mention_partners', []); - }, - on_hover_mention_proposition: function(event){ - var $elem = this.$(event.currentTarget); - this.$('.o_mail_mention_proposition').removeClass('active'); - $elem.addClass('active'); - }, - mention_proposition_navigation: function(keycode){ - var $active = this.$('.o_mail_mention_proposition.active'); - if(keycode === $.ui.keyCode.ENTER){ // selecting proposition - if($active){ - $active.click(); - } - }else{ // navigation in propositions - var $to; - if(keycode === $.ui.keyCode.DOWN){ - $to = $active.next('.o_mail_mention_proposition:not(.active)'); - }else{ - $to = $active.prev('.o_mail_mention_proposition:not(.active)'); - } - if($to.length){ - this.$('.o_mail_mention_proposition').removeClass('active'); - $to.addClass('active'); - } - } - }, - /** - * Return the text attached to the mention delimiter - * @returns {String|false} : the text right after the delimiter or false - */ - mention_detect_delimiter: function(){ - var self = this; - var delimiter = self.options.mention_delimiter; - var validate_keyword = function(search_str){ - var pattern = "(^"+delimiter+"|(^\\s"+delimiter+"))"; - var regex_start = new RegExp(pattern, "g"); - search_str = search_str.replace(/^\s\s*|^[\n\r]/g, ''); - if (regex_start.test(search_str) && search_str.length > self.options.mention_min_length){ - search_str = search_str.replace(pattern, ''); - return search_str.indexOf(' ') < 0 && !/[\r\n]/.test(search_str) ? search_str.replace(delimiter, '') : false; - } - return false; - }; - var text_val = this.$input.val(); - var left_string = text_val.substring(0, this.get_cursor_position()); - var search_str = text_val.substring(left_string.lastIndexOf(delimiter) - 1, this.get_cursor_position()); - return validate_keyword(search_str); - }, - mention_word_change: function(){ - var self = this; - var word = this.get('mention_word'); - if(word){ - // start a timeout to fetch partner with the current 'mention word'. The timer avoid to start - // a RPC for each pushed key when the user is typing the partner name. The 'mention_typing_speed' - // option should approach the time for a human to type a letter. - clearTimeout(this.mention_fetch_timer); - this.mention_fetch_timer = setTimeout(function() { - self.mention_fetch_partner(); - }, this.options.mention_typing_speed); - }else{ - // when deleting mention word, avoid RPC call with last long enough word - clearTimeout(this.mention_fetch_timer); - } - }, - mention_fetch_partner: function(){ - var self = this; - var search_str = this.get('mention_word'); - this.PartnerModel.query(['id', 'name', 'email']) - .filter([['id', 'not in', _.pluck(this.get('mention_selected_partners'), 'id')],'|', ['name', 'ilike', search_str], ['email', 'ilike', search_str]]) - .limit(this.options.mention_fetch_limit) - .all().then(function(partners){ - self.set('mention_partners', partners); - }); - }, - mention_check_remove: function(keycode){ - var mention_selected_partners = this.get('mention_selected_partners'); - var cursor = this.get_cursor_position(); - var input_text = this.$input.val(); - var matches; - switch(keycode) { - // Remove a mention when a character belonging to a mention word is removed from the input text - case $.ui.keyCode.BACKSPACE: - matches = this.mention_get_match(this.$input.val()); - for(var i=0 ; i< matches.length ; i++){ - var m = matches[i]; - if(m.index <= cursor && cursor <= m.index+m[0].length){ - mention_selected_partners.splice(i, 1); - } - } - break; - // Remove a mention if the deleted string overlapse a mention - case $.ui.keyCode.DELETE: - var left_text = input_text.substring(0, cursor); - var right_text = input_text.substring(cursor, input_text.length); - var temp = this.input_buffer.substring(0, this.input_buffer.length - right_text.length); - var deleted_text = temp.substring(left_text.length, temp.length); - - var deleted_binf = left_text.length; - var deleted_bsup = left_text.length + deleted_text.length; - - matches = this.mention_get_match(this.input_buffer); - for(var i=0 ; i< matches.length ; i++){ - var m = matches[i]; - var m1 = m.index; - var m2 = m.index+m[0].length; - if(deleted_binf <= m2 && m1 <= deleted_bsup){ - mention_selected_partners.splice(i, 1); - } - } - break; - } - this.set('mention_selected_partners', _.clone(mention_selected_partners)); - }, - mention_preprocess_message: function(body){ - var partners = this.get('mention_selected_partners'); - if(partners.length){ - var matches = this.mention_get_match(body); - var substrings = []; - var start_index = 0; - for(var i=0; i < matches.length ; i++){ - var match = matches[i]; - var end_index = match.index+match[0].length; - var subtext = body.substring(start_index, end_index); - subtext = subtext.replace(match[0], _.str.sprintf("<span data-oe-model='res.partner' data-oe-id='%s'>%s</span>", partners[i].id, match[0])); - substrings.push(subtext); - start_index = end_index; - } - substrings.push(body.substring(start_index, body.length)); - return substrings.join(''); - } - return body; - }, - mention_render_partner: function(){ - this.$mention_dropdown.html(QWeb.render('mail.ComposeMessage.mention_menu', {'widget': this})); - if(this.get('mention_partners').length){ - this.$mention_dropdown.addClass('open'); - }else{ - this.$mention_dropdown.removeClass('open'); - } - }, - mention_render_selected_partners: function(){ - this.$mention_partner_tags.html(QWeb.render('mail.ComposeMessage.mention_tags', {'widget': this})); - }, - /** - * Return the matches (as RexExp.exec do) for the partner mention in the input text - * @param {String} input_text : the text to search matches - * @returns {Object[]} matches in the same format as RexExp.exec() - */ - mention_get_match: function(input_text){ - var self = this; - // create the regex of all mention partner name - var partner_names = _.pluck(this.get('mention_selected_partners'), 'name'); - var escaped_partner_names = _.map(partner_names, function(str){ - return "("+_.str.escapeRegExp(self.options.mention_delimiter+str)+")"; - }); - var regex_str = escaped_partner_names.join('|'); - // extract matches - var result = []; - if(regex_str.length){ - var myRegexp = new RegExp(regex_str, 'g'); - var match = myRegexp.exec(input_text); - while (match !== null) { - result.push(match); - match = myRegexp.exec(input_text); - } - } - return result; - }, - mention_get_index: function(matches, cursor_position){ - for(var i = 0 ; i < matches.length ; i++){ - if(cursor_position < matches[i].index){ - return i; - }else{ - if(i === matches.length-1){ - return i+1; - } - } - } - return 0; - }, - // others - reset: function(){ - this.set('attachment_ids', []); - this.set('mention_partners', []); - this.set('mention_selected_partners', []); - }, - focus: function(){ - this.$input.focus(); - }, - get_cursor_position: function() { - var el = this.$input.get(0); - if(!el){ - return 0; - } - if('selectionStart' in el) { - return el.selectionStart; - } else if('selection' in document) { - var cr = document.selection.createRange(); - return cr.moveStart('character', -el.focus().value.length).text.length - cr.text.length; - } - return 0; - }, - set_cursor_position: function(pos) { - this.$input.each(function(index, elem) { - if (elem.setSelectionRange){ - elem.setSelectionRange(pos, pos); - } - else if (elem.createTextRange){ - elem.createTextRange().collapse(true).moveEnd('character', pos).moveStart('character', pos).select(); - } - }); - }, -}); - - -/** - * Mail Thread Mixin : Messages Managment - * - * Load, Fetch, Display mail.message - * This is a mixin since it will be inherit by a form_common.AbstractField and a Wigdet (Client Action) - * - * @mixin - **/ -var MailThreadMixin = { - /** - * Constructor of the 'mail_thread' mixin - * @param {Boolean} [options.display_log_button] : display of the 'log a note' button - * @param {Boolean} [options.display_document_link] : display document link on message - * @param {Boolean} [options.display_needaction_button] : display the needaction 'check' button on messages - * @param {Object[]} [options.internal_subtypes]: list of internal subtype (for employee only) - * @param {Object[]} [options.emoji_list] : list of emoji - */ - init: function(){ - this.MessageDatasetSearch = new data.DataSetSearch(this, 'mail.message'); - this.set('messages', []); - this.emoji_substitution = {}; - // options - this.options = _.defaults(this.options || {}, { - 'display_log_button': false, - 'display_document_link': false, - 'display_needaction_button': false, - 'internal_subtypes': [], - 'emoji_list': [], - 'default_username': _t('Anonymous'), - }); - }, - start: function(){ - this.on("change:messages", this, this.message_render); - }, - // Common Actions (They should be bind on the implementing widget, the 'events' dict) - /** - * Generic redirect action : redirect to the form view if the - * click node contains 'data-oe-model' and 'data-oe-id'. - */ - on_click_redirect: function(event){ - event.preventDefault(); - var res_id = $(event.target).data('oe-id'); - var res_model = $(event.target).data('oe-model'); - web_client.action_manager.do_push_state({ - 'model': res_model, - 'id': res_id, - 'title': this.record_name, - }); - this.do_action({ - type:'ir.actions.act_window', - view_type: 'form', - view_mode: 'form', - res_model: res_model, - views: [[false, 'form']], - res_id: res_id, - }, { - 'on_reverse_breadcrumb': this.on_reverse_breadcrumb, - }); - }, - /** - * Will be called when clicking on the Breadcrumb (mainly of client action) - * Need to be override - */ - on_reverse_breadcrumb: function(){ - - }, - /** - * Toggle short / long body when present (hide signature / crop long - messages) - */ - on_message_body_toggle: function (event) { - event.preventDefault(); - var $source = this.$(event.currentTarget); - $source.parents('.o_mail_thread_message_core').find('.o_mail_body_short').toggle(); - $source.parents('.o_mail_thread_message_core').find('.o_mail_body_long').toggle(); - }, - /** - * Toggle a message as 'starred' (or not), by toggling the 'o_mail_message_starred' css class. - * Trigger when clicking on selector '.o_mail_thread_message_star'. - */ - on_message_star: function(event){ - var self = this; - var $source = this.$(event.currentTarget); - var mid = $source.data('message-id'); - var is_starred = !$source.hasClass('o_mail_message_starred'); - return this.MessageDatasetSearch.call('set_message_starred', [[mid], is_starred]).then(function(){ - $source.toggleClass('fa-star-o fa-star o_mail_message_starred'); - self._message_star([mid], $source.hasClass('o_mail_message_starred')); - return mid; - }); - }, - /** - * Star a formatted message in local - * @param Int[] message_ids : the identifiers of the message to set as (none-)starred by current user - * @param Boolean star : star or not the given messages - */ - _message_star: function(message_ids, star){ - _.each(this.get('messages'), function(m){ - if(_.contains(message_ids, m.id)){ - if(star){ - m.is_starred = true; - m.starred_partner_ids.push(session.partner_id); - }else{ - m.is_starred = false; - m.starred_partner_ids = _.without(m.starred_partner_ids, session.partner_id); - } - } - }); - }, - on_message_needaction: function(event){ - var self = this; - var $source = this.$(event.currentTarget); - var mid = $source.data('message-id'); - return this.MessageDatasetSearch.call('set_message_done', [[mid]]).then(function(){ - $source.remove(); - self._message_needaction([mid]); - return mid; - }); - }, - /** - * Make formatted messages a needaction - * @param Int[] message_ids : message ids to mark as needaction treated - */ - _message_needaction: function(message_ids, needaction){ - _.each(this.get('messages'), function(m){ - if(_.contains(message_ids, m.id)){ - m.is_needaction = false; - m.needaction_partner_ids = _.without(m.starred_partner_ids, session.partner_id); - } - }); - }, - // Message functions - /** - * Fetch given message - * @param {Number[]} message_ids : list of mail.message identifiers to fetch - * @returns {Deferred} resolved when the messages are loaded - */ - message_format: function(message_ids){ - return this.MessageDatasetSearch._model.call('message_format', [message_ids]); - }, - /** - * Fetch mail.message in the format defined server side - * @param {Array} domain : Odoo Domain of the message to fetch - * @param {Number} limit : the limit of messages to fetch - * @returns {Deferred} resolved when the messages are loaded - */ - message_fetch: function(domain, limit){ - domain = domain || this.get_message_domain(); - limit = limit || LIMIT_MESSAGE; - return this.MessageDatasetSearch._model.call('message_fetch', [domain], {limit: limit}); - }, - _message_replace: function(messages){ - this.set('messages', this._message_order(messages)); - }, - /** - * Order the message in a particular order, to be defined by implementation - * @param {Object[]} messages : messages to order - */ - _message_order: function(messages){ - return _.sortBy(messages, 'date'); - }, - /** - * Insert messages in the current list - * @param {Object[]} messages : list of mail.message (formatted server side) - */ - message_insert: function(raw_messages){ - var current_messages = this.get('messages'); - current_messages = current_messages.concat(this._message_preprocess(raw_messages)); - this._message_replace(current_messages); - }, - /** - * Load history of message, with the 'history' domain, and insert them in the current list - * @returns {Deferred} resolved when the messages are fetched and returning the raw - * messages (formatted server side) - */ - message_load_history: function(){ - var self = this; - return this.message_fetch(this.get_message_domain_history()).then(function(raw_messages){ - if (raw_messages.length < LIMIT_MESSAGE) { - self.all_messages_loaded = true; - } - self.message_insert(raw_messages); - return raw_messages; - }); - }, - /** - * Load messages according to the current domain, and set that new list as current messages, - * erasing the old ones. - * @returns {Deferred} resolved when the messages are fetched and returning the raw - * messages (formatted server side) - */ - message_load_new: function(){ - var self = this; - return this.message_fetch(this.get_message_domain()).then(function(raw_messages){ - self.all_messages_loaded = raw_messages.length < LIMIT_MESSAGE; - self._message_replace(self._message_preprocess(raw_messages)); - return raw_messages; - }); - }, - /** - * Preprocess the list of messages before rendering, add 'computed' field (is_needaction, - * is_starred, ...), and apply image shortcode to the message body. - * @param {Object[]} messages : list of raw mail.message (formatted server side) - * @returns list of messages. It can be sorted, grouped, ... - */ - _message_preprocess: function(messages){ - var self = this; - _.each(messages, function(m){ - m.is_needaction = _.contains(m.needaction_partner_ids, session.partner_id); - m.is_starred = _.contains(m.starred_partner_ids, session.partner_id); - m.date = moment(time.str_to_datetime(m.date)).format('YYYY-MM-DD HH:mm:ss'); // set the date in the correct browser user timezone - if(m.body){ - m.body = mail_utils.shortcode_apply(m.body, self.emoji_substitution); - m.body = self._message_preprocess_mention(m.body); - } - _.each(m.attachment_ids, function(a){ - a.url = '/web/content/' + a.id + '?download=true'; - }); - }); - return _.sortBy(messages, 'date'); - }, - /** - * Transform the mention 'span' tag into a link tag to the partner form - * @param {String} body : the message body (html) to transform - * @returns {String} the transformed body - */ - _message_preprocess_mention: function(body){ - var re = /<span data-oe-model="([^"]*)"\s+data-oe-id="([^"]*)">(.*?)\<\/span>/g; - var subst = "<a href='#model=$1&id=$2' class='o_mail_redirect' data-oe-model='$1' data-oe-id='$2'>$3</a>"; - return body.replace(re, subst); - }, - /** - * Take the current messages, render them, and insert the rendering in the DOM. - * This is triggered when the mesasge list change. - * Must be redefined, since it depends on the complete DOM widget - */ - message_render: function(){ - - }, - // Message Domains - /** - * Return the current domain to fetch the message of the thread. this should - * be redifined by the thread implementation. - */ - get_message_domain: function(){ - return []; - }, - get_message_domain_history: function(){ - return this.get_message_domain().concat([['id', '<', _.min(_.pluck(this.get('messages'), 'id'))]]); - }, - // Others - /** - * Set the list of emoji to be substituted in message body - * @param {Object[]} emoji_list : list of emoji Object - */ - emoji_set_substitution: function(emoji_list){ - this.emoji_substitution = mail_utils.shortcode_substitution(emoji_list); - }, -}; - - -return { - MailComposeMessage: MailComposeMessage, - MailThreadMixin: MailThreadMixin, - LIMIT_MESSAGE: LIMIT_MESSAGE, -}; - - -}); diff --git a/addons/mail/static/src/js/mail_utils.js b/addons/mail/static/src/js/mail_utils.js deleted file mode 100644 index 36f8fb9081e..00000000000 --- a/addons/mail/static/src/js/mail_utils.js +++ /dev/null @@ -1,134 +0,0 @@ -odoo.define('mail.utils', function () { -"use strict"; - -/** - * ------------------------------------------------------------ - * ChatterUtils - * ------------------------------------------------------------ - * - * This class holds a few tools method for Chatter. - * Some regular expressions not used anymore, kept because I want to - * - (^|\s)@((\w|@|\.)*): @login@log.log - * - (^|\s)\[(\w+).(\w+),(\d)\|*((\w|[@ .,])*)\]: [ir.attachment,3|My Label], - * for internal links - */ - - -/** parse text to find email: Tagada <address@mail.fr> -> [Tagada, address@mail.fr] or False */ -function parse_email(text) { - var result = text.match(/(.*)<(.*@.*)>/); - if (result) { - return [_.str.trim(result[1]), _.str.trim(result[2])]; - } - result = text.match(/(.*@.*)/); - if (result) { - return [_.str.trim(result[1]), _.str.trim(result[1])]; - } - return [text, false]; -} - -/** - * Replaces textarea text into html text (add <p>, <a>) - * TDE note : should be done server-side, in Python -> use mail.compose.message ? - */ -function get_text2html(text) { - return text - .replace(/((?:https?|ftp):\/\/[\S]+)/g,'<a href="$1">$1</a> ') - .replace(/[\n\r]/g,'<br/>'); -} - -/** - * ------------------------------------------------------------ - * MailChat Utils - * ------------------------------------------------------------ - */ - - /** - * Apply the given shortcode to the 'str' message. - * @param str : the text to substitute (html). - * @param shortcodes : dict where key is the shortcode, and the value is the substitution - */ -function shortcode_apply(str, shortcodes){ - var re_escape = function(str){ - return String(str).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1'); - }; - _.each(_.keys(shortcodes), function(key){ - str = str.replace( new RegExp("(?:^|\\s|<[a-z]*>)(" + re_escape(key) + ")(?:\\s|$|</[a-z]*>)"), ' <span class="o_mail_emoji">'+shortcodes[key]+'</span> '); - }); - return str; -} - -/** - * Transform the list of emoji (shortcode Object) into a key-array representing - * the substitution to apply - * @param {Ojbect[]} emoji_list : list of emoji object - * @returns {Object} mapping between the code (as key) and the substitution (as value) - */ -function shortcode_substitution(emoji_list){ - var emoji_substitution = {}; - _.each(emoji_list, function(emoji){ - emoji_substitution[emoji.source] = emoji.substitution; - }); - return emoji_substitution; -} - -/** - * Associate a font awesome class to a attachment file type - * @param {string} mimetype : mimetype to analyse - * @returns {string} the fa class associated - */ -function attachment_mimetype_icon(mimetype){ - var mapping = { - 'text/plain': 'fa fa-file-text-o', - 'text/css': 'fa fa-file-code-o', - 'text/javascript': 'fa fa-file-code-o', - 'text/html': 'fa fa-file-code-o', - 'image/jpeg': 'fa fa-file-photo-o', - 'image/png': 'fa fa-file-photo-o', - 'image/gif': 'fa fa-file-photo-o', - 'application/octet-stream': 'fa fa-file-code-o', - 'application/pdf': 'fa fa-file-pdf-o', - 'application/zip': 'fa fa-file-archive-o', - 'application/x-compressed': 'fa fa-file-archive-o', - 'application/msword': 'fa fa-file-word-o', - 'application/vnd.ms-excel': 'fa fa-file-excel-o', - 'application/mspowerpoint': 'fa fa-file-powerpoint-o', - 'application/powerpoint': 'fa fa-file-powerpoint-o', - 'application/vnd.ms-powerpoint': 'fa fa-file-powerpoint-o', - 'audio/mpeg3': 'fa fa-file-audio-o', - 'audio/x-mpeg-3': 'fa fa-file-audio-o', - 'audio/mpeg': 'fa fa-file-audio-o', - 'audio/midi': 'fa fa-file-audio-o', - 'video/msvideo': 'fa fa-file-movie-o', - 'video/mpeg': 'fa fa-file-movie-o', - 'video/quicktime': 'fa fa-file-movie-o', - }; - if(_.contains(_.keys(mapping), mimetype)){ - return mapping[mimetype]; - } - return 'fa fa-file-o'; -} - -/** - * Play the noise 'ting' to notify user - */ -function beep(session){ - if (typeof(Audio) === "undefined") { - return; - } - var audio = new Audio(); - var ext = audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3"; - audio.src = session.url("/mail/static/src/audio/ting") + ext; - audio.play(); -} - -return { - parse_email: parse_email, - get_text2html: get_text2html, - shortcode_apply: shortcode_apply, - shortcode_substitution: shortcode_substitution, - attachment_mimetype_icon: attachment_mimetype_icon, - beep: beep, -}; - -}); diff --git a/addons/mail/static/src/less/mail_chat_backend.less b/addons/mail/static/src/less/mail_chat_backend.less deleted file mode 100644 index f93d1ed2448..00000000000 --- a/addons/mail/static/src/less/mail_chat_backend.less +++ /dev/null @@ -1,128 +0,0 @@ - -// ------------------------------------------------------------------ -// Layout -// ------------------------------------------------------------------ -.o_mail_chat { - .o-position-absolute(0, 0, 0, 0); - height: 100%; - - @media (max-width: @screen-xs-max) { - position: static; - height: auto; - } - - .o-flex-display(); - .o-flex-flow(row, nowrap); - - .o_mail_chat_sidebar { - .o-flex(0, 0, 15%); - overflow: auto; - padding: 1%; - } - - .o_mail_chat_content { - .o-flex(0, 0, 85%); - height: 100%; - .o-flex-display(); - .o-flex-flow(column, nowrap); - @media (max-width: @screen-xs-max) { - height: auto; - } - - .o_mail_chat_messages { - .o-flex(1, 0, 0); - overflow: auto; - .o-webclient-padding(); - - .o_mail_chat_help_message { - padding: 25px 0; - text-align: justify; - } - } - - .o_mail_chat_composer_wrapper { - .o-flex(0, 0, auto); - .o-webclient-padding(@top: 10px, @bottom: 5px); - } - } -} - -// ------------------------------------------------------------------ -// Style -// ------------------------------------------------------------------ -.o_mail_chat { - .o_mail_chat_sidebar { - background-color: @gray; - - > .nav-pills { - > li { - > a { - color: @odoo-brand-optional; // community compatibility: override base.css - &:hover, &:focus { - background-color: @gray-dark; - } - &.active { - background-color: @gray-dark; - } - } - - &.o_mail_chat_channel_item, &.o_mail_chat_partner_item { - > a { - color: @gray-lighter; - > .badge { - color: white; - background-color: @odoo-brand-optional; - } - - > i.fa { - padding-top: 3px; - padding-right: 3px; - - &.o_online { - color: @odoo-brand-optional; - } - } - } - } - } - } - - .o_mail_chat_channel_item.o_mail_chat_channel_unread, .o_mail_chat_partner_item.o_mail_chat_channel_unread{ - font-weight: bold; - } - - .o_mail_chat_sidebar_title { - text-transform: uppercase; - color: @gray-lighter; - margin-top: 25px; - .user-select(none); - cursor: default; - } - - .o_mail_chat_add_more_search{ - padding: @padding-base-vertical @padding-base-horizontal; - .o_mail_chat_add_more_text{ - color: @gray-lighter; - display: inline-block; - font-style: italic; - cursor: pointer; - font-size: @font-size-base; - } - .o_mail_chat_add_more_text:hover{ - text-decoration: underline; - color: white; - cursor: pointer; - } - } - - } - - .o_mail_chat_content { - background-color: white; - - .o_mail_chat_composer_wrapper { - border-top: 1px solid @gray-lighter; - background-color: #21b799; - } - } -} diff --git a/addons/mail/static/src/less/mail_chat_common.less b/addons/mail/static/src/less/mail_chat_common.less deleted file mode 100644 index 97119ed506c..00000000000 --- a/addons/mail/static/src/less/mail_chat_common.less +++ /dev/null @@ -1,115 +0,0 @@ - -/* emoji size */ -.o_mail_emoji img{ - height: 20px; -} - -// ------------------------------------------------------------------ -// Chat Window -// ------------------------------------------------------------------ - -@media screen and (max-width:360px) { - .o_mail_chat_im_window { - width: 100%; - margin-right: 0; - } - .o_mail_chat_im_window .o_mail_chat_im_window_content { - width: 100% !important; - } - .o_mail_chat_im_window .o_mail_chat_im_window_input { - width: 100% !important; - } -} - - -.o_mail_chat_im_window { - position: fixed; - overflow: hidden; - margin-bottom: 5px; - margin-right: 6px; - background: rgba(250, 250, 250, 0.95); - border: 1px solid #DEDEDE; - width: 240px; - z-index:500; -} - -/* HEADER */ -.o_mail_chat_im_window .o_mail_chat_im_window_header { - padding: 3px 6px 2px; - cursor: pointer; - background: #DEDEDE; - background: -moz-linear-gradient(#FCFCFC, #DEDEDE); - background: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#DEDEDE)); - border-radius: 3px 3px 0 0; - -moz-border-radius: 3px 3px 0 0; - -webkit-border-radius: 3px 3px 0 0; -} - -.o_mail_chat_im_window .o_mail_chat_im_window_header_name { - max-width: 75%; - word-wrap: break-word; - display: inline-block; - height: 15px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.o_mail_chat_im_window .o_mail_chat_im_window_header_buttons{ - float: right; -} - - -/* CONTENT (messages, ...) */ -.o_mail_chat_im_window .o_mail_chat_im_window_content { - height: 280px; - overflow-y: scroll; -} - -.o_mail_chat_im_window .o_mail_chat_im_messages .o_mail_chat_im_window_date_sperator{ - padding: 5px; - padding-right: 10px; - text-align: right; - font-weight: bold; -} - -.o_mail_chat_im_window .o_mail_chat_im_message{ - margin: auto; - margin-bottom: 5px; -} - -.o_mail_chat_im_window .o_mail_chat_im_message .o_mail_chat_im_message_avatar{ - width: 20%; - display: inline-block; - vertical-align: top; -} - -.o_mail_chat_im_window .o_mail_chat_im_message .o_mail_chat_im_message_avatar img{ - width: 80%; - float: right; -} - -.o_mail_chat_im_window .o_mail_chat_im_message .o_mail_chat_im_message_core{ - width: 75%; - display: inline-block; - padding: 5px; - - background: white; - border-radius: 2px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); - max-width:100%; -} - -.o_mail_chat_im_window .o_mail_chat_im_message .o_mail_chat_im_message_core p{ - margin: 0; -} - -/* FOOTER */ -.o_mail_chat_im_window .o_mail_chat_im_window_footer { - padding:3px; - background: #DEDEDE; -} - -.o_mail_chat_im_window .o_mail_chat_im_window_footer .o_mail_chat_im_window_input { - width: 100%; -} diff --git a/addons/mail/static/src/less/mail_chatter.less b/addons/mail/static/src/less/mail_chatter.less deleted file mode 100644 index 501f74c15a2..00000000000 --- a/addons/mail/static/src/less/mail_chatter.less +++ /dev/null @@ -1,20 +0,0 @@ -.o_mail_chatter { - width: 100%; - - .o_mail_chatter_compose_message { - display: none; - margin-top: 10px; - .o_mail_chatter_compose_message_suggested_partners label { - vertical-align: middle; - display: block; - line-height: 14px; - } - .o_mail_compose_message_buttons > button { - .btn-sm; - } - } - - .o_mail_chatter_messages { - margin-top: 10px; - } -} diff --git a/addons/mail/static/src/less/mail_followers.less b/addons/mail/static/src/less/mail_followers.less deleted file mode 100644 index 557f9b4f286..00000000000 --- a/addons/mail/static/src/less/mail_followers.less +++ /dev/null @@ -1,94 +0,0 @@ -.o_mail_followers { - height: 0px; // to be on the same line as mail composer buttons - float: right; - @media (min-width: @screen-sm-min) { - &.o_form_field { - margin: 0; // override o_form_field's margin-bottom of 5px - } - } - @media (max-width: @screen-xs-max) { - height: auto; // to be displayed above the mail composer buttons - float: left; - } - - .o_followers_actions { - position: relative; - float: right; - - .o_followers_follow_button { - width: 110px; - - .o_follow, .o_followers_unfollow, .o_followers_following { - display: none; - } - // when following, show 'following' - &.o_followers_following .o_followers_following { - display: inline; - } - // when following and hovering, show 'unfollow' - &.o_followers_following:hover .o_followers_following { - display: none; - } - &.o_followers_following:hover .o_followers_unfollow { - display: inline; - } - // when not following show 'follow' - &.o_followers_notfollow .o_follow { - display: inline; - } - } - - .o_subtype { - padding-left: 10px; - } - - } - - .o_followers_title_box { - position: relative; - float: right; - margin-left: 5px; - > button { - width: 120px; - } - - .o_followers_count { - padding-right: 5px; - } - - .o_followers_list { - font-size: small; - - .o_partner { - @partner_avatar_size: 24px; - position: relative; // required for old design compatibility - > a { - max-width: 160px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - height: @partner_avatar_size; - padding: 3px @partner_avatar_size + 8px; - } - > img { - .o-position-absolute(); - width: @partner_avatar_size; - } - .o_remove_follower { - .o-position-absolute(6px, 5px); - cursor: pointer; - } - .o_edit_subtype { - .o-position-absolute(6px, 18px); - cursor: pointer; - } - } - - } - } -} - -// Edit subtypes in dialog -.modal .modal-body > li.o_subtype { - list-style: none; -} diff --git a/addons/mail/static/src/less/mail_thread.less b/addons/mail/static/src/less/mail_thread.less deleted file mode 100644 index 3f7b78201fe..00000000000 --- a/addons/mail/static/src/less/mail_thread.less +++ /dev/null @@ -1,194 +0,0 @@ -// ------------------------------------------------------------------ -// Mail Thread 'Mixin' -// ------------------------------------------------------------------ -.o_mail_thread { - .o_mail_thread_date_separator, .o_mail_thread_message { - margin: auto; // override the margin-{left,right}: -15px defined on .row - } - .o_mail_thread_date_separator { - .o_mail_thread_date_wrapper { - border-bottom: solid @gray-lighter 2px; - margin-bottom: 15px; - .o_mail_thread_date_separator_title { - display: table; - position: relative; - top: 10px; - margin: 0 auto; - padding: 0 10px 0 10px; - font-weight: bold; - background: white; - } - } - } - @mail_thread_avatar_size: 50px; - .o_mail_thread_message { - .o-flex-display(); - margin: 10px 0; - .o_mail_thread_message_avatar { - .o-flex(0, 0, @mail_thread_avatar_size); - margin-right: 10px; - @media (max-width: @screen-xs-max) { - .o-flex(0, 0, @mail_thread_avatar_size/2); - } - .o_mail_thread_avatar { - display: block; - margin: 0 auto; // center the avatar - max-width: @mail_thread_avatar_size; - @media (max-width: @screen-xs-max) { - max-width: @mail_thread_avatar_size/2; - } - } - } - .o_mail_thread_message_core { - .o-flex(1, 1, auto); - > p { - margin: 0 0 9px; // Required by the old design to override a general rule on p's - } - .o_mail_body_long { - display: none; - } - } - .o_mail_thread_message_star, .o_mail_thread_message_needaction { - cursor: pointer; - opacity: 0.5; - &:hover { - opacity: 1; - } - } - .o_mail_thread_message_star.o_mail_message_starred { - color: gold; - } - .o_mail_redirect { - cursor: pointer; - } - } - .o_mail_thread_show_more { - text-align: center; - } -} - -// ------------------------------------------------------------------ -// Mail Compose Message -// ------------------------------------------------------------------ -.o_mail_chat_composer_wrapper { - .o_mail_compose_message { // use flex for the layout in the Chat app - .o-flex-display(); - .o_mail_compose_message_input { - .o-flex(1, 1, auto); - } - .o_mail_compose_message_buttons { - .o-flex(0, 0, auto); - } - } -} -.o_mail_compose_message { - width: 100%; - input.o_mail_compose_message_input { - border: none; - padding: 5px; - margin-right: 5px; - } - textarea.o_mail_compose_message_input { - width: 100%; - margin: 5px 0; - } -} -.o_mail_mention_partner_tags { - margin-top: 5px; - text-align: justify; -} -.o_mail_attachments_list .o_mail_chat_im_attachment_files { - margin-top: 5px; - text-align: justify; -} -// Emoji size in the popover -.o_mail_compose_message_emoji { - .o_mail_compose_message_emoji_img > img { - cursor: pointer; - height: 26px; - } -} - -// ------------------------------------------------------------------ -// Attachment Icons (common for mail thread message and compose message) -// ------------------------------------------------------------------ -@o-mail-attachment-image-size: 60px; - -@-webkit-keyframes oe_mail_attach_loading_anim { - 0% { background: @odoo-brand-optional } - 50% { background: darken(@odoo-brand-optional, 10%) } - 100% { background: @odoo-brand-optional } -} -@-moz-keyframes oe_mail_attach_loading_anim { - 0% { background: @odoo-brand-optional } - 50% { background: darken(@odoo-brand-optional, 10%) } - 100% { background: @odoo-brand-optional } -} -@-o-keyframes oe_mail_attach_loading_anim { - 0% { background: @odoo-brand-optional } - 50% { background: darken(@odoo-brand-optional, 10%) } - 100% { background: @odoo-brand-optional } -} -@keyframes oe_mail_attach_loading_anim { - 0% { background: @odoo-brand-optional } - 50% { background: darken(@odoo-brand-optional, 10%) } - 100% { background: @odoo-brand-optional } -} - -.o_mail_attachment { - position: relative; - width: 2*@o-mail-attachment-image-size; - display: inline-block; - height: 50px; - text-align: center; - margin-bottom: 15px; - - .o_image { - .o-position-absolute(@top: 0, @left: @o-mail-attachment-image-size/2); - width: @o-mail-attachment-image-size; - height: @o-mail-attachment-image-size; - } - - .o_mail_attachment_name { - display: inline-block; - max-width: 100%; - margin-top: @o-mail-attachment-image-size; - overflow: hidden; - text-overflow: ellipsis; - color: @odoo-main-text-color; - } - - &.o_mail_uploading { - .o_mail_attachment_progress_bar { - display: block; - .o-position-absolute(@top: 18px, @left: 16px, @right: 16px); - height: 17px; - line-height: 13px; - color: white; - border-radius: @border-radius-base; - border: solid 1px grey; - box-shadow: 0px 1px 10px @odoo-shadow-color; - .o-animation(@name: oe_mail_attach_loading_anim, @duration: 1s, @timing-function: linear, @iteration-count: infinite); - } - .o_mail_attachment_delete { - display: none; - } - } - - .o_mail_attachment_progress_bar { - display: none; - } - - .o_mail_attachment_delete { - .o-position-absolute(@top: 0, @right: 0); - color: @odoo-main-text-color; - cursor: pointer; - opacity: 0; - .o-transition(opacity, 0.2s, linear); - } - - &:hover .o_mail_attachment_delete { - opacity: 1; - .o-transition(opacity, 0.2s, linear); - } -} diff --git a/addons/mail/static/src/xml/mail_chat_backend.xml b/addons/mail/static/src/xml/mail_chat_backend.xml deleted file mode 100644 index 253cd1e95aa..00000000000 --- a/addons/mail/static/src/xml/mail_chat_backend.xml +++ /dev/null @@ -1,139 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates xml:space="preserve"> - - <!-- Notification Counter of the Systray Menu --> - <t t-name="mail.chat.NotificationTopButton"> - <li t-att-title='_t("Notifications")'> - <a href="#"> - <i class="fa fa-comment"><t t-esc="widget.get('counter') || ''"/></i> - </a> - </li> - </t> - - <!-- Add More / Search Box --> - <t t-name="mail.chat.AbstractAddMoreSearch"> - <div class="o_mail_chat_add_more_search"> - <a href="#" class="o_mail_chat_add_more_text"><t t-esc="widget.label"/></a> - <div class="o_mail_chat_add_more_search_bar" style="display: none;"> - <input type="text" class="o_mail_chat_search_input"/> - </div> - </div> - </t> - - <!-- PartnerInviteDialog : content of the dialog --> - <t t-name="mail.chat.PartnerInviteDialog"> - <div> - <input type="text" class="o_mail_chat_partner_invite_input" id="mail_search_partners"/> - </div> - </t> - - - <!-- - ChatMailThread - Layout of the client action - param: widget : the widget (ChatMailThread) - --> - <t t-name="mail.chat.ChatMailThread"> - <div class="o_mail_chat o_mail_thread"> - <!-- Sidebar --> - <div class="o_mail_chat_sidebar"> - <!-- Static channel list --> - <ul class="nav nav-pills nav-stacked"> - <li class="o_mail_chat_channel_item" data-channel-id="channel_inbox"> - <a href="#">@ Inbox - <span class="badge pull-right o_mail_chat_needaction" data-channel-id="channel_inbox" t-att-style="((widget.get('needaction_inbox_counter') > 0) ? 'display:block;' : 'display:none;')"> - <t t-esc="widget.get('needaction_inbox_counter')"/> - </span> - </a> - </li> - <li class="o_mail_chat_channel_item" data-channel-id="channel_starred"> - <a href="#"><i class="fa fa-star-o"> Starred</i></a> - </li> - </ul> - <!-- Channel list --> - <h4 class="o_mail_chat_sidebar_title"><t t-esc="_t('Channels')"/></h4> - <t t-call="mail.chat.ChatMailThread.channels"> - <t t-set="channel_slot" t-value="'channel_channel'"/> - </t> - <!-- Direct Message list (res.users)--> - <h4 class="o_mail_chat_sidebar_title"><t t-esc="_t('Direct Message')"/></h4> - <t t-call="mail.chat.ChatMailThread.partners"/> - <!-- Private Group list --> - <h4 class="o_mail_chat_sidebar_title"><t t-esc="_t('Private Group')"/></h4> - <t t-call="mail.chat.ChatMailThread.channels"> - <t t-set="channel_slot" t-value="'channel_private_group'"/> - </t> - </div> - <!-- Content --> - <div class="o_mail_chat_content"> - <div class="o_mail_chat_messages"/> - <div class="o_mail_chat_composer_wrapper"/> - </div> - </div> - </t> - - <!-- - List of channel in the sidebar - param: channel_slot : the name of the channel slot - param: widget : the widget (ChatMailThread) - --> - <t t-name="mail.chat.ChatMailThread.channels"> - <ul t-att-class="'nav nav-pills nav-stacked o_mail_chat_channel_slot_' + channel_slot" data-toggle="pill"> - <t t-foreach="widget.get(channel_slot)" t-as="channel"> - <li t-att-class="'o_mail_chat_channel_item ' + (channel.message_needaction_counter > 0 ? 'o_mail_chat_channel_unread' : ' ')" t-att-data-channel-id="channel.id" role="presentation"> - <a t-att-href="_.str.sprintf('#active_id=%s&action=%s', channel.id, widget.action.id)"> - <t t-esc="channel.name"/> - <span class="badge pull-right o_mail_chat_needaction" t-att-data-channel-id="channel.id" t-att-style="((channel.message_needaction_counter > 0) ? 'display:block;' : 'display:none;')"> - <t t-esc="channel.message_needaction_counter"/> - </span> - </a> - </li> - </t> - </ul> - </t> - - <!-- - List of Partners of Direct Message channel - param: widget : the widget (ChatMailThread) - --> - <t t-name="mail.chat.ChatMailThread.partners"> - <ul class="nav nav-pills nav-stacked o_mail_chat_channel_slot_partners"> - <t t-foreach="widget.get('partners')" t-as="partner"> - <li class="o_mail_chat_partner_item" t-att-data-partner-id="partner.id" role="presentation"> - <a href="#"> - <i t-att-class="'fa ' + (partner.im_status == 'online' ? 'fa-circle' : 'fa-circle-o')"/> - <t t-esc="partner.name"/> - <span class="fa fa-times pull-right o_mail_chat_partner_unpin" t-att-title="_t('Close')" t-att-data-partner-id="partner.id"/> - </a> - </li> - </t> - </ul> - </t> - - <!-- - Channel content (messages) or 'Help Message' of the client action - param: widget : the widget (ChatMailThread) - --> - <t t-name="mail.chat.ChatMailThread.content"> - <t t-if="widget.get('messages').length"> - <t t-call="mail.Thread.load_more"/> - <t t-call="mail.Thread.messages"/> - </t> - <t t-if="!widget.get('messages').length"> - <div class="o_mail_chat_help_message"> - <t t-raw="widget.help_message"/> - </div> - </t> - </t> - - <!-- - Buttons of the Control Panel - --> - <t t-name="mail.chat.ControlButtons"> - <div> - <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_invite" t-att-title='_t("Invite people")'>Invite</button> - <button type="button" class="btn btn-default btn-sm o_mail_chat_button_minimize" t-att-title='_t("Minimize the conversation")'>Minimize</button> - </div> - </t> - -</templates> diff --git a/addons/mail/static/src/xml/mail_chat_common.xml b/addons/mail/static/src/xml/mail_chat_common.xml deleted file mode 100644 index 7bbbc3f94d7..00000000000 --- a/addons/mail/static/src/xml/mail_chat_common.xml +++ /dev/null @@ -1,93 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates xml:space="preserve"> - - <t t-name="mail.chat.im.Conversation"> - <div class="o_mail_chat_im_window openerp"> - <div class="o_mail_chat_im_window_header"> - <span class="o_mail_chat_im_window_header_name"> - <t t-raw="widget.get('session').name"/> - </span> - <span class="nbr_messages o_mail_chat_im_window_header_counter"/> - <span class="o_mail_chat_im_window_header_buttons"> - <div class="o_mail_chat_im_window_close">×</div> - </span> - </div> - <div class="o_mail_chat_im_window_content"> - <t t-call="mail.chat.im.Conversation.messages"/> - </div> - <div class="oe_im_chatview_footer o_mail_chat_im_window_footer"> - <input class="o_mail_chat_im_window_input" t-att-placeholder="widget.options.placeholder"/> - </div> - </div> - </t> - - <t t-name="mail.chat.im.Conversation.messages"> - <div class="o_mail_chat_im_messages"> - <t t-set="current_day" t-value="0"/> - <t t-foreach="widget.get('messages')" t-as="message"> - <div t-if="current_day != message.date.split(' ')[0]" class="row o_mail_chat_im_window_date_sperator"> - <t t-raw="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('LL')"/> - <t t-set="current_day" t-value="message.date.split(' ')[0]"/> - </div> - <div class="o_mail_chat_im_message"> - <div class="o_mail_chat_im_message_avatar"> - <img t-if="message.message_type == 'email' && (!message.author_id || !message.author_id[0]) && message.email_from" src="/mail/static/src/img/email_icon.png" alt="avatar" class="img-responsive img-rounded"/> - <img t-if="message.message_type == 'email' && (message.author_id && message.author_id[0]) && !message.email_from" t-att-src="'/web/image/res.partner/'+message.author_id[0]+'/image_small'" class="img-responsive img-rounded"/> - <img t-if="message.message_type !== 'email' && (message.author_id && message.author_id[0])" t-att-src="'/web/image/mail.message/'+message.id+'/author_avatar'" class="img-responsive img-rounded"/> - <img t-if="message.message_type !== 'email' && (!message.author_id || !message.author_id[0])" src="/mail/static/src/img/smiley/avatar.jpg" class="img-responsive img-rounded"/> - </div> - <div class="o_mail_chat_im_message_core"> - <!-- author --> - <span class="text-muted"> - <t t-if="message.author_id && message.author_id[0]"> - <strong><t t-esc="message.author_id[1]"/></strong> - - </t> - <t t-if="!message.author_id || !message.author_id[0]"> - <strong><t t-esc="default_username || widget.options.default_username"/></strong> - - </t> - <t t-if="!message.author_id"> - <strong><t t-esc="message.email_from"/></strong> - - </t> - <span class="small"><t t-esc="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('hh:mm')"/></span> - </span> - <!-- body --> - <p class="o_mail_chat_im_message_body"> - <t t-if="message.body"> - <t t-raw="message.body"/> <!-- TODO JEM : tracking message and attachments ??!! --> - </t> - <t t-if="!message.body"> - <t t-call="mail.chat.im.Conversation.tracking_message"/> - </t> - </p> - <!-- Attachments --> - <div t-attf-class="o_mail_chat_im_message_attachment"> - <t t-foreach="message.attachment_ids" t-as="attachment"> - <a t-att-href='attachment.url' target="_blank"> - <i t-att-class="attachment.fa_class" t-att-title="'Download ' + (attachment.name || attachment.filename)"/> - </a> - </t> - </div> - </div> - </div> - </t> - </div> - </t> - - <t t-name="mail.chat.im.Conversation.tracking_message"> - <div> - <ul class="o_mail_message_tracking"> - <t t-foreach='message.tracking_value_ids' t-as='value'> - <li> - <t t-esc="value.changed_field"/> : - <t t-if="value.old_value"> - <span class="o_mail_message_tracking_value"> <t t-esc="value.old_value"/> </span> - <span> <i class="fa fa-long-arrow-right"></i> </span> - </t> - <span class="o_mail_message_tracking_value"> <t t-esc="value.new_value"/> </span> - </li> - </t> - </ul> - </div> - </t> - -</templates>
\ No newline at end of file diff --git a/addons/mail/static/src/xml/mail_chatter.xml b/addons/mail/static/src/xml/mail_chatter.xml deleted file mode 100644 index 860633d6096..00000000000 --- a/addons/mail/static/src/xml/mail_chatter.xml +++ /dev/null @@ -1,98 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates> - - <t t-name="mail.chatter.ComposeMessage"> - <div class="mail_chatter_message_composer"> - <!-- Log a Note (subtypes)--> - <t t-if="widget.options.is_log"> - <span class="text-muted"> - Log an internal note which will not be sent to followers.<br/> - Note that users reading the document can read internal notes. - </span> - <!-- Internal subtypes --> - <div t-att-class="'form-group col-md-8 ' + (widget.options.internal_subtypes.length <= 1 ? ' hide' : '')"> - <label class="col-sm-2 control-label">Activity</label> - <div class="col-sm-4"> - <select class="form-control o_mail_chatter_compose_message_subtype_select"> - <t t-foreach="widget.options.internal_subtypes" t-as="subtype"> - <option t-att-value="subtype['id']"><t t-raw="subtype['name']"/></option> - </t> - </select> - </div> - </div> - </t> - <!-- New Message (list of followers)--> - <t t-if="!widget.options.is_log"> - <!-- Text for followers --> - <span class="text-muted"> - To: Followers of - <t t-if="widget.options.record_name"> - <t t-raw="'"' + widget.options.record_name + '"'"/> - </t> - <t t-if="!widget.options.record_name"> - this document - </t> - </span> - <!-- List of followers --> - <div class="o_mail_chatter_compose_message_suggested_partners"> - <t t-foreach='widget.suggested_partners' t-as='recipient'> - <div t-attf-title="Add as recipient and follower (reason: #{recipient.reason})"> - <input type="checkbox" - t-att-checked="recipient.checked ? 'checked' : undefined" - t-att-data-fullname="recipient.full_name"/> - <t t-esc="recipient.name"/> - <t t-if="recipient.email_address">(<t t-esc="recipient.email_address"/>)</t> - <t t-if="!recipient.email_address">(no email address)</t> - </div> - </t> - </div> - </t> - <!-- Composer Message --> - <t t-call="mail.ComposeMessage"/> - </div> - </t> - - - <t t-name="mail.chatter.ChatterMailThread"> - <div class="o_mail_chatter o_mail_thread"> - <!-- Compose buttons ('new message' and 'log a note') --> - <div class="o_mail_chatter_compose_buttons"> - <button type="button" class="btn btn-sm btn-primary o_mail_chatter_button_new_message" title="Send a message"> - New message - </button> - <button t-if="widget.options.display_log_button" class="btn btn-sm btn-link o_mail_chatter_button_log_note" title="Log a note. Followers will not be notified."> - Log an internal note - </button> - </div> - - <!-- wrap div for ChatterMailComposeMessage --> - <div class="o_mail_chatter_compose_message"/> - <!-- Message list --> - <div class="o_mail_chatter_messages"/> - </div> - </t> - - <t t-name="mail.chatter.ChatterMailThread.messages"> - <t t-if="widget.get('messages').length"> - <t t-call="mail.Thread.messages"/> - <t t-call="mail.Thread.load_more"/> - </t> - <t t-if="!widget.get('messages').length"> - No Messages. - </t> - </t> - - <!-- - ComposeMessage.ButtonTopBar - render of the button on the user bar for open wizard compose message - --> - <t t-name="mail.ComposeMessageTopButton"> - <li title='Compose new Message' class="oe_topbar_item oe_topbar_compose_full_email"> - <a href="#"> - <i class="fa fa-envelope-o fa-fw"></i> - </a> - </li> - </t> - - -</templates> diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml deleted file mode 100644 index c25d3954bb1..00000000000 --- a/addons/mail/static/src/xml/mail_followers.xml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates> - -<!-- - Mail Follower (root template) - Template used to display the followers, the actions and the subtypes in a record. ---> -<t t-name="mail.Followers"> - <div class="o_mail_followers"> - <div t-attf-class="o_followers_title_box"> - <button t-attf-class="btn btn-sm btn-default dropdown-toggle" - data-toggle="dropdown" aria-expanded="false"> - <span class="o_followers_count"/> - <span class="caret"/> - </button> - <ul class="dropdown-menu o_followers_list" role="menu"></ul> - </div> - <div class="o_followers_actions"> - <div t-attf-class="btn-group btn-group-sm o_subtypes_list"> - <button class="btn btn-primary o_followers_follow_button o_followers_notfollow"> - <span class="o_follow">Follow</span> - <span class="fa fa-times o_followers_unfollow" aria-hidden="true"> Unfollow</span> - <span class="fa fa-check o_followers_following" aria-hidden="true"> Following</span> - </button><button t-attf-class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> - <span class="caret"/> - </button> - <ul class="dropdown-menu" role="menu"/> - </div> - </div> - </div> -</t> - -<!-- - Mail Followers Partner template - Template used to display a partner following the record ---> -<t t-name="mail.Followers.partner"> - <li class='o_partner'> - <img t-att-src="record.avatar_url"/> - <a t-attf-href="#model=res.partner&id=#{record.res_id}" - t-att-title="record.name" - t-att-data-res-model="record.res_model" - t-att-data-res-id="record.res_id" - t-att-data-id="record.id"><t t-esc="record.name"/></a> - <i t-if="record.is_editable and (widget.records_length > 1)" - class="fa fa-pencil o_edit_subtype hide" title="Edit subscription" - t-att-data-id="record.id" - t-att-data-res-model="record.res_model" - t-att-data-res-id="record.res_id"/> - <i t-if="widget.view_is_editable" class="fa fa-remove o_remove_follower" - title="Remove this follower"/> - </li> -</t> - -<t t-name="mail.Followers.add_more"> - <li t-if="widget.view_is_editable" class="o_add_follower"> - <a href="#"> Add followers </a> - </li> - <li t-if="widget.view_is_editable" class="o_add_follower_channel"> - <a href="#"> Add Channels </a> - </li> - <li t-if="widget.view_is_editable and (widget.followers.length > 0)" class="divider"/> -</t> - -<!-- - Mail Followers Subtype template - Template used to display message subtypes of a follower subscription ---> -<t t-name="mail.Followers.subtype"> - <li class="o_subtype"> - <input type="checkbox" t-att-checked="record.followed" - t-att-id="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')" - t-att-data-id="record.id" t-att-name="record.name" class="o_subtype_checkbox"/> - <span t-att-for="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')"> - <t t-raw="record.name"/> - </span> - </li> -</t> - -</templates>
\ No newline at end of file diff --git a/addons/mail/static/src/xml/mail_thread.xml b/addons/mail/static/src/xml/mail_thread.xml deleted file mode 100644 index 1323cb9c21e..00000000000 --- a/addons/mail/static/src/xml/mail_thread.xml +++ /dev/null @@ -1,196 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<templates> - - <t t-name="mail.ComposeMessage"> - <div class="o_mail_compose_message"> - <div class="dropup o_mail_mention_dropdown"/> - <!-- composer is an input text if display mode is 'chat' and a textarea if it is 'mail' --> - <t t-if="widget.options.display_mode === 'chat'"> - <input type="text" class="o_mail_compose_message_input" placeholder="Write something" name="message" id="send_message"/> - </t> - <t t-if="widget.options.display_mode === 'mail'"> - <textarea class="o_mail_compose_message_input" placeholder="Write something" name="message" id="send_message"/> - </t> - <div class="btn-group o_mail_compose_message_buttons"> - <button t-if="widget.options.display_mode === 'mail'" class="btn btn-primary o_mail_compose_message_button_send" type="button"> - Send - </button> - <button class="btn btn-icon fa fa-smile-o o_mail_compose_message_button_emoji" type="button" data-toggle="popover"/> - <button class="btn btn-icon fa fa-paperclip o_mail_compose_message_button_attachment" type="button" data-toggle="popover"/> - <button t-if="widget.options.display_mode === 'mail'" class="btn btn-icon fa fa-pencil-square-o o_mail_compose_message_button_full_message" type="button" data-toggle="popover"/> - </div> - </div> - <div class="o_mail_mention_partner_tags"/> - <div class="o_mail_attachments_list"/> - - <span class="o_mail_attachment_file hide"> - <t t-call="HiddenInputFile"> - <t t-set="fileupload_id" t-value="widget.fileupload_id"/> - <t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t> - <input type="hidden" name="model" value="mail.compose.message"/> - <input type="hidden" name="id" value="0"/> - <input type="hidden" name="session_id" t-att-value="widget.session.session_id"/> - </t> - </span> - <span class="o_mail_compose_message_emoji"></span> - - </t> - - <t t-name="mail.ComposeMessage.emoji"> - <t t-foreach="widget.options.emoji_list" t-as="emoji"> - <span t-att-data-emoji="emoji.source" class="o_mail_compose_message_emoji_img" t-att-title="emoji.description"> - <t t-raw="emoji.substitution"/> - </span> - </t> - </t> - - <t t-name="mail.ComposeMessage.attachments"> - <div class="o_mail_chat_im_attachment_files"> - <t t-foreach="widget.get('attachment_ids')" t-as='attachment'> - <div t-attf-class="o_mail_attachment #{attachment.upload ? 'o_mail_uploading' : ''}" t-att-title="attachment.name"> - <a class="o_image" t-att-href='attachment.url' target="_blank" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"> - <div class='o_mail_attachment_name'> - <t t-raw='attachment.name'/> - </div> - </a> - <div class="o_mail_attachment_delete"> - <i class='fa fa-times-circle' title="Delete this attachment" t-att-data-id="attachment.id"/> - </div> - <div class="o_mail_attachment_progress_bar"> - Uploading - </div> - </div> - </t> - </div> - </t> - - <t t-name="mail.ComposeMessage.mention_menu"> - <ul class="dropdown-menu" aria-labelledby="dropdownMailMentionMenu"> - <t t-foreach="widget.get('mention_partners')" t-as="partner"> - <li t-att-data-partner-id="partner.id" t-att-class="'o_mail_mention_proposition ' + (partner_index === 0 ? 'active' : '')"> - <a href="#"> - <t t-esc="partner.name"/> (<t t-esc="partner.email || _t('No email')"/>)</a> - </li> - </t> - </ul> - </t> - - <t t-name="mail.ComposeMessage.mention_tags"> - <t t-if="widget.get('mention_selected_partners').length">Mention(s):</t> - <t t-foreach="widget.get('mention_selected_partners')" t-as="partner"> - <span class="badge"><t t-esc="partner.name"/></span> - </t> - </t> - - - <!-- - Message Thread - --> - <t t-name="mail.Thread.messages"> - <t t-set="current_day" t-value="0"/> - <t t-foreach="widget.get('messages')" t-as="message"> - <div t-if="current_day != message.date.split(' ')[0]" class="o_mail_thread_date_separator"> - <div class="o_mail_thread_date_wrapper"> - <div class="o_mail_thread_date_separator_title"> - <t t-raw="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('LL')"/> - </div> - </div> - <t t-set="current_day" t-value="message.date.split(' ')[0]"/> - </div> - <div class="o_mail_thread_message" t-att-data-message-id="message.id"> - <div class="o_mail_thread_message_avatar"> - <img t-if="message.message_type == 'email' && (!message.author_id || !message.author_id[0]) && message.email_from" src="/mail/static/src/img/email_icon.png" alt="avatar" class="o_mail_thread_avatar img-rounded"/> - <img t-if="message.message_type == 'email' && (message.author_id && message.author_id[0]) && !message.email_from" t-att-src="'/web/image/res.partner/'+message.author_id[0]+'/image_small'" class="o_mail_thread_avatar img-rounded"/> - <img t-if="message.message_type !== 'email' && (message.author_id && message.author_id[0])" t-att-src="'/web/image/res.partner/'+message.author_id[0]+'/image_small'" class="o_mail_thread_avatar img-rounded"/> - <img t-if="message.message_type !== 'email' && (!message.author_id || !message.author_id[0])" src="/mail/static/src/img/smiley/avatar.jpg" class="o_mail_thread_avatar img-rounded"/> - </div> - <div t-att-class="'o_mail_thread_message_core ' + 'o_mail_thread_message_type_' + message.message_type + ' ' + (message.is_note ? 'o_mail_thread_message_subtype_note' : '')"> - <!-- actions --> - <span class="pull-right"> - <i t-if="message.is_needaction && widget.options.display_needaction_button" class="fa fa-check o_mail_thread_message_needaction" t-att-data-message-id="message.id" t-att-title="_t('Mark as Done')"/> - <i t-att-class="'fa fa-lg o_mail_thread_message_star ' + (message.is_starred ? 'o_mail_message_starred fa-star' : 'fa-star-o')" t-att-data-message-id="message.id" t-att-title="_t('Mark as Todo')"/> - </span> - - <p class="text-muted"> - <t t-if="message.is_note"> - Note by - </t> - <!-- author : partner or email_from --> - <t t-if="message.author_id && message.author_id[1]"> - <strong class="o_mail_redirect" data-oe-model="res.partner" t-att-data-oe-id="message.author_id[0]"><t t-esc="message.author_id[1]"/></strong> - </t> - <t t-if="message.author_id && !message.author_id[1] && message.email_from"> - <strong><t t-esc="message.email_from"/></strong> - </t> - <t t-if="message.author_id && !message.author_id[1] && !message.email_from"> - <strong><t t-esc="widget.options.default_username"/></strong> - </t> - - <small t-att-title="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('LLL')"> - - <t t-esc="moment(message.date, 'YYYY-MM-DD hh:mm:ss').format('hh:mm')"/> - </small> - <t t-if="message.model && (message.model != 'mail.channel') && widget.options.display_document_link"> - on <a class="o_mail_redirect" t-att-data-oe-model="message.model" t-att-data-oe-id="message.res_id" href="#"><t t-esc="message.record_name"/></a> - </t> - </p> - <!-- tracking message--> - <t t-if="message.tracking_value_ids && message.tracking_value_ids.length > 0"> - <t t-call="mail.Thread.tracking_message"> - <t t-set="message" t-value="message"/> - </t> - </t> - <!-- normal message --> - <t t-if="!(message.tracking_value_ids && message.tracking_value_ids.length > 0)"> - <t t-if="message.body_short"> - <div class="o_mail_body_short"> - <t t-raw="message.body_short"/> - <span class="o_mail_expand"><a href="#">... read more</a></span> - </div> - <div class="o_mail_body_long"> - <t t-raw="message.body"/> - <span class="o_mail_expand"><a href="#">... read less</a></span> - </div> - </t> - <t t-if="! message.body_short"> - <t t-raw="message.body"/> - </t> - </t> - <!-- Attachments --> - <t t-foreach="message.attachment_ids" t-as="attachment"> - <div t-attf-class="o_mail_attachment" t-att-title="attachment.name"> - <a class="o_image" t-att-href='attachment.url' target="_blank" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"> - <div class='o_mail_attachment_name'> - <t t-raw='attachment.name'/> - </div> - </a> - </div> - </t> - </div> - </div> - </t> - </t> - - <t t-name="mail.Thread.load_more"> - <div t-if="!widget.all_messages_loaded" class="o_mail_thread_show_more"> - <button class="btn btn-sm btn-link">-------- Show more messages --------</button> - </div> - </t> - - <t t-name="mail.Thread.tracking_message"> - <div> - <ul class="o_mail_thread_message_tracking"> - <t t-foreach='message.tracking_value_ids' t-as='value'> - <li> - <t t-esc="value.changed_field"/> : - <t t-if="value.old_value"> - <span class="o_mail_message_tracking_value"> <t t-esc="value.old_value"/> </span> - <span> <i class="fa fa-long-arrow-right"></i> </span> - </t> - <span class="o_mail_message_tracking_value"> <t t-esc="value.new_value"/> </span> - </li> - </t> - </ul> - </div> - </t> - -</templates> diff --git a/addons/mail/views/mail_templates.xml b/addons/mail/views/mail_templates.xml index dfe1e8befc4..8afb474e9f2 100644 --- a/addons/mail/views/mail_templates.xml +++ b/addons/mail/views/mail_templates.xml @@ -3,24 +3,24 @@ <data> <template id="assets_backend" name="mail assets" inherit_id="web.assets_backend"> <xpath expr="." position="inside"> - <script type="text/javascript" src="/mail/static/src/js/mail_utils.js"></script> - <script type="text/javascript" src="/mail/static/src/js/mail_followers.js"></script> <script type="text/javascript" src="/mail/static/src/js/many2many_tags_email.js"></script> <script type="text/javascript" src="/mail/static/src/js/announcement.js"></script> - <link rel="stylesheet" href="/mail/static/src/less/announcement.less"/> - <link rel="stylesheet" href="/mail/static/src/less/mail_thread.less" type="text/less"/> - <link rel="stylesheet" href="/mail/static/src/less/mail_chatter.less" type="text/less"/> - <link rel="stylesheet" href="/mail/static/src/less/mail_chat_backend.less" type="text/less"/> - <link rel="stylesheet" href="/mail/static/src/less/mail_chat_common.less"/> - <link rel="stylesheet" href="/mail/static/src/less/mail_followers.less"/> - - <script type="text/javascript" src="/mail/static/src/js/mail_thread.js"></script> - <script type="text/javascript" src="/mail/static/src/js/mail_chatter.js"></script> - - <script type="text/javascript" src="/mail/static/src/js/mail_chat_common.js"></script> - <script type="text/javascript" src="/mail/static/src/js/mail_chat_backend.js"></script> + <script type="text/javascript" src="/mail/static/src/js/client_action.js"></script> + <script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script> + <script type="text/javascript" src="/mail/static/src/js/composer.js"></script> + <script type="text/javascript" src="/mail/static/src/js/chat_manager.js"></script> + <script type="text/javascript" src="/mail/static/src/js/chatter.js"></script> + <script type="text/javascript" src="/mail/static/src/js/thread.js"></script> + <script type="text/javascript" src="/mail/static/src/js/systray.js"></script> + <script type="text/javascript" src="/mail/static/src/js/window_manager.js"></script> + <link rel="stylesheet" href="/mail/static/src/less/announcement.less"/> + <link rel="stylesheet" href="/mail/static/src/less/client_action.less" type="text/less"/> + <link rel="stylesheet" href="/mail/static/src/less/chat_window.less" type="text/less"/> + <link rel="stylesheet" href="/mail/static/src/less/composer.less" type="text/less"/> + <link rel="stylesheet" href="/mail/static/src/less/chatter.less" type="text/less"/> + <link rel="stylesheet" href="/mail/static/src/less/thread.less" type="text/less"/> </xpath> </template> </data> |
