"),
- 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($("
"))).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($('
').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("%s", 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>/g;
- var subst = "$3";
- 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 -> [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 , )
- * TDE note : should be done server-side, in Python -> use mail.compose.message ?
- */
-function get_text2html(text) {
- return text
- .replace(/((?:https?|ftp):\/\/[\S]+)/g,'$1 ')
- .replace(/[\n\r]/g,'
');
-}
-
-/**
- * ------------------------------------------------------------
- * 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]*>)"), ' '+shortcodes[key]+' ');
- });
- 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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ 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 @@
-
-
-
-
-
-
-
-
- Log an internal note which will not be sent to followers.
- Note that users reading the document can read internal notes.
-
-
-
-
-
-
-
-
-
-
-
-
-
- To: Followers of
-
-
-
-
- this document
-
-
-
-
-
-
-
-
- ()
- (no email address)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- No Messages.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Add followers
-
-
- Add Channels
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ 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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- /web/binary/upload_attachment
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Mention(s):
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Note by
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- on
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+