11 changed files with 2099 additions and 0 deletions
			
			
		| @ -0,0 +1,22 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################## | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| #    Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). | |||
| #    Author: Nilmar Shereef(<http://www.cybrosys.com>) | |||
| #    you can modify it under the terms of the GNU LESSER | |||
| #    GENERAL PUBLIC LICENSE (LGPL v3), Version 3. | |||
| # | |||
| #    It is forbidden to publish, distribute, sublicense, or sell copies | |||
| #    of the Software or modified copies of the Software. | |||
| # | |||
| #    This program is distributed in the hope that it will be useful, | |||
| #    but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | |||
| #    GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. | |||
| # | |||
| #    You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE | |||
| #    GENERAL PUBLIC LICENSE (LGPL v3) along with this program. | |||
| #    If not, see <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################## | |||
| @ -0,0 +1,41 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################## | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| #    Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). | |||
| #    Author: Nilmar Shereef(<http://www.cybrosys.com>) | |||
| #    you can modify it under the terms of the GNU LESSER | |||
| #    GENERAL PUBLIC LICENSE (LGPL v3), Version 3. | |||
| # | |||
| #    It is forbidden to publish, distribute, sublicense, or sell copies | |||
| #    of the Software or modified copies of the Software. | |||
| # | |||
| #    This program is distributed in the hope that it will be useful, | |||
| #    but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | |||
| #    GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. | |||
| # | |||
| #    You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE | |||
| #    GENERAL PUBLIC LICENSE (LGPL v3) along with this program. | |||
| #    If not, see <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################## | |||
| { | |||
|     'name': 'Sent Mails', | |||
|     'version': '1.0', | |||
|     'author': 'Cybrosys Techno Solutions', | |||
|     'company': 'Cybrosys Techno Solutions', | |||
|     'website': 'http://www.cybrosys.com', | |||
|     "category": "Discuss", | |||
|     'depends': ['base', 'mail'], | |||
|     'license': 'AGPL-3', | |||
|     'data': [ | |||
|         'views/get_sent_mails.xml', | |||
|     ], | |||
|     'qweb': [ | |||
|         'static/src/xml/client_action_sent_mails.xml', | |||
| 
 | |||
|     ], | |||
|     'installable': True, | |||
|     'auto_install': False | |||
| } | |||
| After Width: | Height: | Size: 50 KiB | 
| After Width: | Height: | Size: 54 KiB | 
| @ -0,0 +1,44 @@ | |||
| <section class="oe_container"> | |||
|      <div class="oe_row oe_spaced"> | |||
|         <h2 class="oe_slogan">Show sent mails</h2> | |||
|         <h3 class="oe_slogan">Shows the mails or discussions sent by current user</h3> | |||
|          <h4 class="oe_slogan">Author : Cybrosys Techno Solutions, www.cybrosys.com</h4> | |||
|     </div> | |||
| 
 | |||
|     <div class="oe_row oe_spaced"> | |||
|         <div class="oe_span12"> | |||
|             <p class='oe_mt32'> | |||
|                 ☛This module enables the feature to display the mails and discussions done by current user | |||
|             </p> | |||
|             <div class="oe_centeralign oe_websiteonly"> | |||
|             </div> | |||
|         </div> | |||
|     </div> | |||
| </section> | |||
| 
 | |||
| <section class="oe_container oe_dark"> | |||
|     <div class="oe_row"> | |||
|         <h2 class="oe_slogan">Sent Mail Menu Under Discuss</h2> | |||
|         <div class="oe_span6"> | |||
|             <div class="oe_row_img oe_centered"> | |||
|                 <img class="oe_picture oe_screenshot" src="sent_mails_demo.png"> | |||
|             </div> | |||
|         </div> | |||
|     </div> | |||
| </section> | |||
| 
 | |||
| <section class="oe_container oe_dark"> | |||
|     <h2 class="oe_slogan" style="margin-top:20px;" >Need Any Help?</h2> | |||
|     <div class="oe_slogan" style="margin-top:10px !important;"> | |||
|         <a  class="btn btn-primary btn-lg mt8" | |||
|             style="color: #FFFFFF !important;" href="http://www.cybrosys.com"><i | |||
|             class="fa fa-envelope"></i> Email </a> <a | |||
|             class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;" | |||
|             href="http://www.cybrosys.com/contact/"><i | |||
|             class="fa fa-phone"></i> Contact Us </a> <a | |||
|             class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;" | |||
|             href="http://www.cybrosys.com/odoo-customization-and-installation/"><i | |||
|             class="fa fa-check-square"></i> Request Customization </a> | |||
|     </div> | |||
|             <img src="cybro_logo.png" style="width: 190px; margin-bottom: 20px;" class="center-block"> | |||
| </section> | |||
| After Width: | Height: | Size: 52 KiB | 
| @ -0,0 +1,180 @@ | |||
| #!/usr/bin/env python | |||
| # -*- coding: utf-8 -*- | |||
| # Part of Odoo. See LICENSE file for full copyright and licensing details. | |||
| """ | |||
|     openerp_mailgate.py | |||
| """ | |||
| 
 | |||
| import cgitb | |||
| import time | |||
| import optparse | |||
| import sys | |||
| import xmlrpclib | |||
| import smtplib | |||
| from email.mime.multipart import MIMEMultipart | |||
| from email.mime.base import MIMEBase | |||
| from email.mime.text import MIMEText | |||
| from email.utils import COMMASPACE, formatdate | |||
| from email import Encoders | |||
| 
 | |||
| class DefaultConfig(object): | |||
|     """ | |||
|     Default configuration | |||
|     """ | |||
|     OPENERP_DEFAULT_USER_ID = 1 | |||
|     OPENERP_DEFAULT_PASSWORD = 'admin' | |||
|     OPENERP_HOSTNAME = 'localhost' | |||
|     OPENERP_PORT = 8069 | |||
|     OPENERP_DEFAULT_DATABASE = 'openerp' | |||
|     MAIL_ERROR = 'error@example.com' | |||
|     MAIL_SERVER = 'smtp.example.com' | |||
|     MAIL_SERVER_PORT = 25 | |||
|     MAIL_ADMINS = ('info@example.com',) | |||
| 
 | |||
| config = DefaultConfig() | |||
| 
 | |||
| 
 | |||
| def send_mail(_from_, to_, subject, text, files=None, server=config.MAIL_SERVER, port=config.MAIL_SERVER_PORT): | |||
|     assert isinstance(to_, (list, tuple)) | |||
| 
 | |||
|     if files is None: | |||
|         files = [] | |||
| 
 | |||
|     msg = MIMEMultipart() | |||
|     msg['From'] = _from_ | |||
|     msg['To'] = COMMASPACE.join(to_) | |||
|     msg['Date'] = formatdate(localtime=True) | |||
|     msg['Subject'] = subject | |||
| 
 | |||
|     msg.attach( MIMEText(text) ) | |||
| 
 | |||
|     for file_name, file_content in files: | |||
|         part = MIMEBase('application', "octet-stream") | |||
|         part.set_payload( file_content ) | |||
|         Encoders.encode_base64(part) | |||
|         part.add_header('Content-Disposition', 'attachment; filename="%s"' | |||
|                        % file_name) | |||
|         msg.attach(part) | |||
| 
 | |||
|     smtp = smtplib.SMTP(server, port=port) | |||
|     smtp.sendmail(_from_, to_, msg.as_string() ) | |||
|     smtp.close() | |||
| 
 | |||
| class RPCProxy(object): | |||
|     def __init__(self, uid, passwd, | |||
|                  host=config.OPENERP_HOSTNAME, | |||
|                  port=config.OPENERP_PORT, | |||
|                  path='object', | |||
|                  dbname=config.OPENERP_DEFAULT_DATABASE): | |||
|         self.rpc = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/%s' % (host, port, path), allow_none=True) | |||
|         self.user_id = uid | |||
|         self.passwd = passwd | |||
|         self.dbname = dbname | |||
| 
 | |||
|     def __call__(self, *request, **kwargs): | |||
|         return self.rpc.execute(self.dbname, self.user_id, self.passwd, *request, **kwargs) | |||
| 
 | |||
| class EmailParser(object): | |||
|     def __init__(self, uid, password, dbname, host, port, model=False, email_default=False): | |||
|         self.rpc = RPCProxy(uid, password, host=host, port=port, dbname=dbname) | |||
|         if model: | |||
|             try: | |||
|                 self.model_id = int(model) | |||
|                 self.model = str(model) | |||
|             except: | |||
|                 self.model_id = self.rpc('ir.model', 'search', [('model', '=', model)])[0] | |||
|                 self.model = str(model) | |||
|             self.email_default = email_default | |||
| 
 | |||
| 
 | |||
|     def parse(self, message, custom_values=None, save_original=None): | |||
|         # pass message as bytes because we don't know its encoding until we parse its headers | |||
|         # and hence can't convert it to utf-8 for transport | |||
|         return self.rpc('mail.thread', | |||
|                         'message_process', | |||
|                         self.model, | |||
|                         xmlrpclib.Binary(message), | |||
|                         custom_values or {}, | |||
|                         save_original or False) | |||
| 
 | |||
| def configure_parser(): | |||
|     parser = optparse.OptionParser(usage='usage: %prog [options]', version='%prog v1.1') | |||
|     group = optparse.OptionGroup(parser, "Note", | |||
|         "This program parse a mail from standard input and communicate " | |||
|         "with the Odoo server for case management in the CRM module.") | |||
|     parser.add_option_group(group) | |||
|     parser.add_option("-u", "--user", dest="userid", | |||
|                       help="Odoo user id to connect with", | |||
|                       default=config.OPENERP_DEFAULT_USER_ID, type='int') | |||
|     parser.add_option("-p", "--password", dest="password", | |||
|                       help="Odoo user password", | |||
|                       default=config.OPENERP_DEFAULT_PASSWORD) | |||
|     parser.add_option("-o", "--model", dest="model", | |||
|                       help="Name or ID of destination model", | |||
|                       default="crm.lead") | |||
|     parser.add_option("-m", "--default", dest="default", | |||
|                       help="Admin email for error notifications.", | |||
|                       default=None) | |||
|     parser.add_option("-d", "--dbname", dest="dbname", | |||
|                       help="Odoo database name (default: %default)", | |||
|                       default=config.OPENERP_DEFAULT_DATABASE) | |||
|     parser.add_option("--host", dest="host", | |||
|                       help="Odoo Server hostname", | |||
|                       default=config.OPENERP_HOSTNAME) | |||
|     parser.add_option("--port", dest="port", | |||
|                       help="Odoo Server XML-RPC port number", | |||
|                       default=config.OPENERP_PORT) | |||
|     parser.add_option("--custom-values", dest="custom_values", | |||
|                       help="Dictionary of extra values to pass when creating records", | |||
|                       default=None) | |||
|     parser.add_option("-s", dest="save_original", | |||
|                       action="store_true", | |||
|                       help="Keep a full copy of the email source attached to each message", | |||
|                       default=False) | |||
| 
 | |||
|     return parser | |||
| 
 | |||
| def main(): | |||
|     """ | |||
|     Receive the email via the stdin and send it to the OpenERP Server | |||
|     """ | |||
| 
 | |||
|     parser = configure_parser() | |||
|     (options, args) = parser.parse_args() | |||
|     email_parser = EmailParser(options.userid, | |||
|                                options.password, | |||
|                                options.dbname, | |||
|                                options.host, | |||
|                                options.port, | |||
|                                model=options.model, | |||
|                                email_default= options.default) | |||
|     msg_txt = sys.stdin.read() | |||
|     custom_values = {} | |||
|     try: | |||
|         custom_values = dict(eval(options.custom_values or "{}" )) | |||
|     except: | |||
|         import traceback | |||
|         traceback.print_exc() | |||
| 
 | |||
|     try: | |||
|         email_parser.parse(msg_txt, custom_values, options.save_original or False) | |||
|     except Exception: | |||
|         msg = '\n'.join([ | |||
|             'parameters', | |||
|             '==========', | |||
|             '%r' % (options,), | |||
|             'traceback', | |||
|             '=========', | |||
|             '%s' % (cgitb.text(sys.exc_info())), | |||
|         ]) | |||
| 
 | |||
|         subject = '[Odoo]:ERROR: Mailgateway - %s' % time.strftime('%Y-%m-%d %H:%M:%S') | |||
|         send_mail( | |||
|             config.MAIL_ERROR, | |||
|             config.MAIL_ADMINS, | |||
|             subject, msg, files=[('message.txt', msg_txt)] | |||
|         ) | |||
|         sys.stderr.write("Failed to deliver email to Odoo Server, sending error notification to %s\n" % config.MAIL_ADMINS) | |||
| 
 | |||
| if __name__ == '__main__': | |||
|     main() | |||
								
									
										File diff suppressed because it is too large
									
								
							
						
					| @ -0,0 +1,666 @@ | |||
| odoo.define('mail.chat_client_action', function (require) { | |||
| "use strict"; | |||
| 
 | |||
| var chat_manager = require('mail.chat_manager'); | |||
| var composer = require('mail.composer'); | |||
| var ChatThread = require('mail.ChatThread'); | |||
| 
 | |||
| var config = require('web.config'); | |||
| var ControlPanelMixin = require('web.ControlPanelMixin'); | |||
| var core = require('web.core'); | |||
| var data = require('web.data'); | |||
| var Dialog = require('web.Dialog'); | |||
| var framework = require('web.framework'); | |||
| var Model = require('web.Model'); | |||
| 
 | |||
| var pyeval = require('web.pyeval'); | |||
| var SearchView = require('web.SearchView'); | |||
| var Widget = require('web.Widget'); | |||
| 
 | |||
| var QWeb = core.qweb; | |||
| var _t = core._t; | |||
| 
 | |||
| /** | |||
|  * Widget : Invite People to Channel Dialog | |||
|  * | |||
|  * Popup containing a 'many2many_tags' custom input to select multiple partners. | |||
|  * Search user according to the input, and trigger event when selection is validated. | |||
|  **/ | |||
| var PartnerInviteDialog = Dialog.extend({ | |||
|     dialog_title: _t('Invite people'), | |||
|     template: "mail.PartnerInviteDialog", | |||
|     init: function(parent, title, channel_id){ | |||
|         this.channel_id = channel_id; | |||
| 
 | |||
|         this._super(parent, { | |||
|             title: title, | |||
|             size: "medium", | |||
|             buttons: [{ | |||
|                 text: _t("Invite"), | |||
|                 close: true, | |||
|                 classes: "btn-primary", | |||
|                 click: _.bind(this.on_click_add, this), | |||
|             }], | |||
|         }); | |||
|         this.PartnersModel = new Model('res.partner'); | |||
|     }, | |||
|     start: function(){ | |||
|         var self = this; | |||
|         this.$input = this.$('.o_mail_chat_partner_invite_input'); | |||
|         this.$input.select2({ | |||
|             width: '100%', | |||
|             allowClear: true, | |||
|             multiple: true, | |||
|             formatResult: function(item) { | |||
|                 var status = QWeb.render('mail.chat.UserStatus', {status: item.im_status}); | |||
|                 return $('<span>').text(item.text).prepend(status); | |||
|             }, | |||
|             query: function (query) { | |||
|                 self.PartnersModel.call('im_search', [query.term, 20]).then(function(result){ | |||
|                     var data = []; | |||
|                     _.each(result, function(partner){ | |||
|                         partner.text = partner.name; | |||
|                         data.push(partner); | |||
|                     }); | |||
|                     query.callback({results: data}); | |||
|                 }); | |||
|             } | |||
|         }); | |||
|         return this._super.apply(this, arguments); | |||
|     }, | |||
|     on_click_add: function(){ | |||
|         var self = this; | |||
|         var data = this.$input.select2('data'); | |||
|         if(data.length >= 1){ | |||
|             var ChannelModel = new Model('mail.channel'); | |||
|             return ChannelModel.call('channel_invite', [], {ids : [this.channel_id], partner_ids: _.pluck(data, 'id')}) | |||
|                 .then(function(){ | |||
|                     var names = _.escape(_.pluck(data, 'text').join(', ')); | |||
|                     var notification = _.str.sprintf(_t('You added <b>%s</b> to the conversation.'), names); | |||
|                     self.do_notify(_t('New people'), notification); | |||
|                 }); | |||
|         } | |||
|     }, | |||
| }); | |||
| 
 | |||
| var ChatAction = Widget.extend(ControlPanelMixin, { | |||
|     template: 'mail.client_action', | |||
| 
 | |||
|     events: { | |||
|         "click .o_mail_chat_channel_item": function (event) { | |||
|             event.preventDefault(); | |||
|             var channel_id = this.$(event.currentTarget).data('channel-id'); | |||
|             this.set_channel(chat_manager.get_channel(channel_id)); | |||
|         }, | |||
|         "click .o_mail_sidebar_title .o_add": function (event) { | |||
|             event.preventDefault(); | |||
|             var type = $(event.target).data("type"); | |||
|             this.$('.o_mail_add_channel[data-type=' + type + ']') | |||
|                 .show() | |||
|                 .find("input").focus(); | |||
|         }, | |||
|         "blur .o_mail_add_channel input": function () { | |||
|             this.$('.o_mail_add_channel') | |||
|                 .hide(); | |||
|         }, | |||
|         "click .o_mail_partner_unpin": function (event) { | |||
|             event.stopPropagation(); | |||
|             var channel_id = $(event.target).data("channel-id"); | |||
|             this.unsubscribe_from_channel(chat_manager.get_channel(channel_id)); | |||
|         }, | |||
|         "click .o_snackbar_undo": function (event) { | |||
|             event.preventDefault(); | |||
|             var channel = this.channel; | |||
|             this.$snackbar.remove(); | |||
|             this.clear_needactions_def.then(function (msgs_ids) { | |||
|                 chat_manager.undo_mark_as_read(msgs_ids, channel); | |||
|             }); | |||
|         }, | |||
|         "click .o_mail_annoying_notification_bar .fa-close": function () { | |||
|             this.$(".o_mail_annoying_notification_bar").slideUp(); | |||
|         }, | |||
|         "click .o_mail_request_permission": function (event) { | |||
|             event.preventDefault(); | |||
|             this.$(".o_mail_annoying_notification_bar").slideUp(); | |||
|             var def = window.Notification.requestPermission(); | |||
|             if (def) { | |||
|                 def.then(function () { | |||
|                     chat_manager.send_native_notification('Permission granted', 'Odoo has now the permission to send you native notifications on this device.'); | |||
|                 }); | |||
|             } | |||
|         }, | |||
|         "keydown": function (event) { | |||
|             if (event.which === $.ui.keyCode.ESCAPE && this.selected_message) { | |||
|                 this.unselect_message(); | |||
|             } | |||
|         }, | |||
|     }, | |||
| 
 | |||
|     on_attach_callback: function () { | |||
|         chat_manager.bus.trigger('client_action_open', true); | |||
|         if (this.channel) { | |||
|             this.thread.scroll_to({offset: this.channels_scrolltop[this.channel.id]}); | |||
|         } | |||
|     }, | |||
|     on_detach_callback: function () { | |||
|         chat_manager.bus.trigger('client_action_open', false); | |||
|         this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop(); | |||
|     }, | |||
| 
 | |||
|     init: function(parent, action, options) { | |||
|         this._super.apply(this, arguments); | |||
|         this.action_manager = parent; | |||
|         this.domain = []; | |||
|         this.action = action; | |||
|         this.options = options || {}; | |||
|         this.channels_scrolltop = {}; | |||
|         this.throttled_render_sidebar = _.throttle(this.render_sidebar.bind(this), 100, { leading: false }); | |||
|         this.notification_bar = (window.Notification && window.Notification.permission === "default"); | |||
|         this.selected_message = null; | |||
|     }, | |||
| 
 | |||
|     willStart: function () { | |||
|         return chat_manager.is_ready; | |||
|     }, | |||
| 
 | |||
|     start: function() { | |||
|         var self = this; | |||
| 
 | |||
|         // create searchview
 | |||
|         var options = { | |||
|             $buttons: $("<div>"), | |||
|             action: this.action, | |||
|             disable_groupby: true, | |||
|         }; | |||
|         var dataset = new data.DataSetSearch(this, 'mail.message'); | |||
|         var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; | |||
|         var default_channel_id = this.options.active_id || | |||
|                                  this.action.context.active_id || | |||
|                                  this.action.params.default_active_id || | |||
|                                  'channel_inbox'; | |||
|         var default_channel = chat_manager.get_channel(default_channel_id) || | |||
|                               chat_manager.get_channel('channel_inbox'); | |||
| 
 | |||
|         this.searchview = new SearchView(this, dataset, view_id, {}, options); | |||
|         this.searchview.on('search_data', this, this.on_search); | |||
| 
 | |||
|         this.basic_composer = new composer.BasicComposer(this, {mention_partners_restricted: true}); | |||
|         this.extended_composer = new composer.ExtendedComposer(this, {mention_partners_restricted: true}); | |||
|         this.thread = new ChatThread(this, { | |||
|             display_help: true, | |||
|             shorten_messages: false, | |||
|         }); | |||
| 
 | |||
|         this.$buttons = $(QWeb.render("mail.chat.ControlButtons", {})); | |||
|         this.$buttons.find('button').css({display:"inline-block"}); | |||
|         this.$buttons.on('click', '.o_mail_chat_button_invite', this.on_click_button_invite); | |||
|         this.$buttons.on('click', '.o_mail_chat_button_unsubscribe', this.on_click_button_unsubscribe); | |||
|         this.$buttons.on('click', '.o_mail_chat_button_settings', this.on_click_button_settings); | |||
|         this.$buttons.on('click', '.o_mail_toggle_channels', function () { | |||
|             self.$('.o_mail_chat_sidebar').slideToggle(200); | |||
|         }); | |||
|         this.$buttons.on('click', '.o_mail_chat_button_mark_read', function () { | |||
|             chat_manager.mark_all_as_read(self.channel, self.domain); | |||
|         }); | |||
|         this.$buttons.on('click', '.o_mail_chat_button_unstar_all', chat_manager.unstar_all); | |||
|         this.$buttons.on('click', '.o_mail_chat_button_new_message', this.on_click_new_message); | |||
|         this.$buttons.on('click', '.o_mail_chat_button_new_message_sent', this.on_click_new_message); | |||
| 
 | |||
|         this.thread.on('redirect', this, function (res_model, res_id) { | |||
|             chat_manager.redirect(res_model, res_id, this.set_channel.bind(this)); | |||
|         }); | |||
|         this.thread.on('redirect_to_channel', this, function (channel_id) { | |||
|             chat_manager.join_channel(channel_id).then(this.set_channel.bind(this)); | |||
|         }); | |||
|         this.thread.on('load_more_messages', this, this.load_more_messages); | |||
|         this.thread.on('mark_as_read', this, function (message_id) { | |||
|             chat_manager.mark_as_read([message_id]); | |||
|         }); | |||
|         this.thread.on('toggle_star_status', this, function (message_id) { | |||
|             chat_manager.toggle_star_status(message_id); | |||
|         }); | |||
|         this.thread.on('select_message', this, this.select_message); | |||
|         this.thread.on('unselect_message', this, this.unselect_message); | |||
| 
 | |||
|         this.basic_composer.on('post_message', this, this.on_post_message); | |||
|         this.basic_composer.on('input_focused', this, this.on_composer_input_focused); | |||
|         this.extended_composer.on('post_message', this, this.on_post_message); | |||
|         this.extended_composer.on('input_focused', this, this.on_composer_input_focused); | |||
| 
 | |||
|         var def1 = this.thread.appendTo(this.$('.o_mail_chat_content')); | |||
|         var def2 = this.basic_composer.appendTo(this.$('.o_mail_chat_content')); | |||
|         var def3 = this.extended_composer.appendTo(this.$('.o_mail_chat_content')); | |||
|         var def4 = this.searchview.appendTo($("<div>")).then(function () { | |||
|             self.$searchview_buttons = self.searchview.$buttons.contents(); | |||
|         }); | |||
| 
 | |||
|         this.render_sidebar(); | |||
| 
 | |||
|         return $.when(def1, def2, def3, def4) | |||
|             .then(this.set_channel.bind(this, default_channel)) | |||
|             .then(function () { | |||
|                 chat_manager.bus.on('open_channel', self, self.set_channel); | |||
|                 chat_manager.bus.on('new_message', self, self.on_new_message); | |||
|                 chat_manager.bus.on('update_message', self, self.on_update_message); | |||
|                 chat_manager.bus.on('new_channel', self, self.on_new_channel); | |||
|                 chat_manager.bus.on('anyone_listening', self, function (channel, query) { | |||
|                     query.is_displayed = query.is_displayed || (channel.id === self.channel.id && self.thread.is_at_bottom()); | |||
|                 }); | |||
|                 chat_manager.bus.on('unsubscribe_from_channel', self, self.render_sidebar); | |||
|                 chat_manager.bus.on('update_needaction', self, self.throttled_render_sidebar); | |||
|                 chat_manager.bus.on('update_channel_unread_counter', self, self.throttled_render_sidebar); | |||
|                 chat_manager.bus.on('update_dm_presence', self, self.throttled_render_sidebar); | |||
|                 self.thread.$el.on("scroll", null, _.debounce(function () { | |||
|                     if (self.thread.is_at_bottom()) { | |||
|                         chat_manager.mark_channel_as_seen(self.channel); | |||
|                     } | |||
|                 }, 100)); | |||
|             }); | |||
|     }, | |||
| 
 | |||
|     select_message: function(message_id) { | |||
|         this.$el.addClass('o_mail_selection_mode'); | |||
|         var message = chat_manager.get_message(message_id); | |||
|         this.selected_message = message; | |||
|         var subject = "Re: " + message.record_name; | |||
|         this.extended_composer.set_subject(subject); | |||
|         if (this.channel.type !== 'static') { | |||
|             this.basic_composer.toggle(false); | |||
|         } | |||
|         this.extended_composer.toggle(true); | |||
|         this.thread.scroll_to({id: message_id, duration: 200, only_if_necessary: true}); | |||
|         this.extended_composer.focus('body'); | |||
|     }, | |||
| 
 | |||
|     unselect_message: function() { | |||
|         this.basic_composer.toggle(this.channel.type !== 'static' && !this.channel.mass_mailing); | |||
|         this.extended_composer.toggle(this.channel.type !== 'static' && this.channel.mass_mailing); | |||
|         if (!config.device.touch) { | |||
|             var composer = this.channel.mass_mailing ? this.extended_composer : this.basic_composer; | |||
|             composer.focus(); | |||
|         } | |||
|         this.$el.removeClass('o_mail_selection_mode'); | |||
|         this.thread.unselect(); | |||
|         this.selected_message = null; | |||
|     }, | |||
| 
 | |||
|     render_sidebar: function () { | |||
|         var self = this; | |||
|         var $sidebar = $(QWeb.render("mail.chat.Sidebar", { | |||
|             active_channel_id: this.channel ? this.channel.id: undefined, | |||
|             channels: chat_manager.get_channels(), | |||
|             needaction_counter: chat_manager.get_needaction_counter(), | |||
|         })); | |||
|         this.$(".o_mail_chat_sidebar").html($sidebar.contents()); | |||
| 
 | |||
|         this.$('.o_mail_add_channel[data-type=public]').find("input").autocomplete({ | |||
|             source: function(request, response) { | |||
|                 self.last_search_val = _.escape(request.term); | |||
|                 self.do_search_channel(self.last_search_val).done(function(result){ | |||
|                     result.push({ | |||
|                         'label':  _.str.sprintf('<strong>'+_t("Create %s")+'</strong>', '<em>"#'+self.last_search_val+'"</em>'), | |||
|                         'value': '_create', | |||
|                     }); | |||
|                     response(result); | |||
|                 }); | |||
|             }, | |||
|             select: function(event, ui) { | |||
|                 if (self.last_search_val) { | |||
|                     if (ui.item.value === '_create') { | |||
|                         chat_manager.create_channel(self.last_search_val, "public"); | |||
|                     } else { | |||
|                         chat_manager.join_channel(ui.item.id); | |||
|                     } | |||
|                 } | |||
|             }, | |||
|             focus: function(event) { | |||
|                 event.preventDefault(); | |||
|             }, | |||
|             html: true, | |||
|         }); | |||
| 
 | |||
|         this.$('.o_mail_add_channel[data-type=dm]').find("input").autocomplete({ | |||
|             source: function(request, response) { | |||
|                 self.last_search_val = _.escape(request.term); | |||
|                 chat_manager.search_partner(self.last_search_val).done(response); | |||
|             }, | |||
|             select: function(event, ui) { | |||
|                 var partner_id = ui.item.id; | |||
|                 chat_manager.create_channel(partner_id, "dm"); | |||
|             }, | |||
|             focus: function(event) { | |||
|                 event.preventDefault(); | |||
|             }, | |||
|             html: true, | |||
|         }); | |||
| 
 | |||
|         this.$('.o_mail_add_channel[data-type=private]').find("input").on('keyup', this, function (event) { | |||
|             var name = _.escape($(event.target).val()); | |||
|             if(event.which === $.ui.keyCode.ENTER && name) { | |||
|                 chat_manager.create_channel(name, "private"); | |||
|             } | |||
|         }); | |||
|     }, | |||
| 
 | |||
|     render_snackbar: function (template, context, timeout) { | |||
|         if (this.$snackbar) { | |||
|             this.$snackbar.remove(); | |||
|         } | |||
|         timeout = timeout || 20000; | |||
|         this.$snackbar = $(QWeb.render(template, context)); | |||
|         this.$('.o_mail_chat_content').append(this.$snackbar); | |||
|         // Hide snackbar after [timeout] milliseconds (by default, 20s)
 | |||
|         var $snackbar = this.$snackbar; | |||
|         setTimeout(function() { $snackbar.fadeOut(); }, timeout); | |||
|     }, | |||
| 
 | |||
|     do_search_channel: function(search_val){ | |||
|         var Channel = new Model("mail.channel"); | |||
|         return Channel.call('channel_search_to_join', [search_val]).then(function(result){ | |||
|             var values = []; | |||
|             _.each(result, function(channel){ | |||
|                 var escaped_name = _.escape(channel.name); | |||
|                 values.push(_.extend(channel, { | |||
|                     'value': escaped_name, | |||
|                     'label': escaped_name, | |||
|                 })); | |||
|             }); | |||
|             return values; | |||
|         }); | |||
|     }, | |||
| 
 | |||
|     set_channel: function (channel) { | |||
|         var self = this; | |||
|         // Store scroll position of previous channel
 | |||
|         if (this.channel) { | |||
|             this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop(); | |||
|         } | |||
|         var new_channel_scrolltop = this.channels_scrolltop[channel.id]; | |||
| 
 | |||
|         this.channel = channel; | |||
|         this.messages_separator_position = undefined; // reset value on channel change
 | |||
|         this.unread_counter = this.channel.unread_counter; | |||
|         this.last_seen_message_id = this.channel.last_seen_message_id; | |||
|         this.clear_needactions_def = $.Deferred(); | |||
|         if (this.$snackbar) { | |||
|             this.$snackbar.remove(); | |||
|         } | |||
| 
 | |||
|         this.action.context.active_id = channel.id; | |||
|         this.action.context.active_ids = [channel.id]; | |||
| 
 | |||
|         return this.fetch_and_render_thread().then(function () { | |||
|             // Mark channel's messages as read and clear needactions
 | |||
|             if (channel.type !== 'static') { | |||
|                 // Display snackbar if needactions have been cleared
 | |||
|                 if (channel.needaction_counter > 0) { | |||
|                     self.render_snackbar('mail.chat.UndoSnackbar', { | |||
|                         nb_needactions: channel.needaction_counter, | |||
|                     }); | |||
|                 } | |||
|                 chat_manager.mark_channel_as_seen(channel); | |||
|                 self.clear_needactions_def = chat_manager.mark_all_as_read(channel); | |||
|             } | |||
| 
 | |||
|             // Update control panel
 | |||
|             self.set("title", '#' + channel.name); | |||
|             // Hide 'invite', 'unsubscribe' and 'settings' buttons in static channels and DM
 | |||
|             self.$buttons | |||
|                 .find('.o_mail_chat_button_invite, .o_mail_chat_button_unsubscribe, .o_mail_chat_button_settings') | |||
|                 .toggle(channel.type !== "dm" && channel.type !== 'static'); | |||
|             self.$buttons | |||
|                 .find('.o_mail_chat_button_mark_read, .o_mail_chat_button_new_message') | |||
|                 .toggle(channel.id === "channel_inbox"); | |||
|             self.$buttons | |||
|                 .find('.o_mail_chat_button_new_message_sent') | |||
|                 .toggle(channel.id === "channel_sent"); | |||
|             self.$buttons | |||
|                 .find('.o_mail_chat_button_unstar_all') | |||
|                 .toggle(channel.id === "channel_starred"); | |||
| 
 | |||
|             self.$('.o_mail_chat_channel_item') | |||
|                 .removeClass('o_active') | |||
|                 .filter('[data-channel-id=' + channel.id + ']') | |||
|                 .removeClass('o_unread_message') | |||
|                 .addClass('o_active'); | |||
| 
 | |||
|             var $new_messages_separator = self.$('.o_thread_new_messages_separator'); | |||
|             if ($new_messages_separator.length) { | |||
|                 self.thread.$el.scrollTo($new_messages_separator); | |||
|             } else { | |||
|                 self.thread.scroll_to({offset: new_channel_scrolltop}); | |||
|             } | |||
| 
 | |||
|             // Update control panel before focusing the composer, otherwise focus is on the searchview
 | |||
|             self.update_cp(); | |||
|             if (config.device.size_class === config.device.SIZES.XS) { | |||
|                 self.$('.o_mail_chat_sidebar').hide(); | |||
|             } | |||
| 
 | |||
|             // Display and focus the adequate composer, and unselect possibly selected message
 | |||
|             // to prevent sending messages as reply to that message
 | |||
|             self.unselect_message(); | |||
| 
 | |||
|             self.action_manager.do_push_state({ | |||
|                 action: self.action.id, | |||
|                 active_id: self.channel.id, | |||
|             }); | |||
|         }); | |||
|     }, | |||
|     unsubscribe_from_channel: function (channel) { | |||
|         var self = this; | |||
|         chat_manager | |||
|             .unsubscribe(channel) | |||
|             .then(this.render_sidebar.bind(this)) | |||
|             .then(this.set_channel.bind(this, chat_manager.get_channel("channel_inbox"))) | |||
|             .then(function () { | |||
|                 if (_.contains(['public', 'private'], channel.type)) { | |||
|                     var msg = _.str.sprintf(_t('You unsubscribed from <b>%s</b>.'), channel.name); | |||
|                     self.do_notify(_t("Unsubscribed"), msg); | |||
|                 } | |||
|                 delete self.channels_scrolltop[channel.id]; | |||
|             }); | |||
|     }, | |||
| 
 | |||
|     get_thread_rendering_options: function (messages) { | |||
|         // Compute position of the 'New messages' separator, only once when joining
 | |||
|         // a channel to keep it in the thread when new messages arrive
 | |||
|         if (_.isUndefined(this.messages_separator_position)) { | |||
|             if (!this.unread_counter) { | |||
|                 this.messages_separator_position = false; // no unread message -> don't display separator
 | |||
|             } else { | |||
|                 var msg = chat_manager.get_last_seen_message(this.channel); | |||
|                 this.messages_separator_position = msg ? msg.id : 'top'; | |||
|             } | |||
|         } | |||
|         return { | |||
|             channel_id: this.channel.id, | |||
|             display_load_more: !chat_manager.all_history_loaded(this.channel, this.domain), | |||
|             display_needactions: this.channel.display_needactions, | |||
|             messages_separator_position: this.messages_separator_position, | |||
|             squash_close_messages: this.channel.type !== 'static' && !this.channel.mass_mailing, | |||
|             display_empty_channel: !messages.length && !this.domain.length, | |||
|             display_no_match: !messages.length && this.domain.length, | |||
|             display_subject: this.channel.mass_mailing || this.channel.id === "channel_inbox", | |||
|             display_reply_icon: true, | |||
|         }; | |||
|     }, | |||
| 
 | |||
|     fetch_and_render_thread: function () { | |||
|         var self = this; | |||
|         return chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function(result) { | |||
|             self.thread.render(result, self.get_thread_rendering_options(result)); | |||
|             self.update_button_status(result.length === 0); | |||
|         }); | |||
|     }, | |||
| 
 | |||
|     update_button_status: function (disabled) { | |||
|         if (this.channel.id === "channel_inbox") { | |||
|             this.$buttons | |||
|                 .find('.o_mail_chat_button_mark_read') | |||
|                 .toggleClass('disabled', disabled); | |||
|         } | |||
|         if (this.channel.id === "channel_starred") { | |||
|             this.$buttons | |||
|                 .find('.o_mail_chat_button_unstar_all') | |||
|                 .toggleClass('disabled', disabled); | |||
|         } | |||
|     }, | |||
| 
 | |||
|     load_more_messages: function () { | |||
|         var self = this; | |||
|         var oldest_msg_id = this.$('.o_thread_message').first().data('messageId'); | |||
|         var oldest_msg_selector = '.o_thread_message[data-message-id="' + oldest_msg_id + '"]'; | |||
|         var offset = -framework.getPosition(document.querySelector(oldest_msg_selector)).top; | |||
|         return chat_manager | |||
|             .get_messages({channel_id: this.channel.id, domain: this.domain, load_more: true}) | |||
|             .then(function(result) { | |||
|                 if (self.messages_separator_position === 'top') { | |||
|                     self.messages_separator_position = undefined; // reset value to re-compute separator position
 | |||
|                 } | |||
|                 self.thread.render(result, self.get_thread_rendering_options(result)); | |||
|                 offset += framework.getPosition(document.querySelector(oldest_msg_selector)).top; | |||
|                 self.thread.scroll_to({offset: offset}); | |||
|             }); | |||
|     }, | |||
| 
 | |||
|     update_cp: function () { | |||
|         this.update_control_panel({ | |||
|             breadcrumbs: this.action_manager.get_breadcrumbs(), | |||
|             cp_content: { | |||
|                 $buttons: this.$buttons, | |||
|                 $searchview: this.searchview.$el, | |||
|                 $searchview_buttons: this.$searchview_buttons, | |||
|             }, | |||
|             searchview: this.searchview, | |||
|         }); | |||
|     }, | |||
| 
 | |||
|     do_show: function () { | |||
|         this._super.apply(this, arguments); | |||
|         this.update_cp(); | |||
|         this.action_manager.do_push_state({ | |||
|             action: this.action.id, | |||
|             active_id: this.channel.id, | |||
|         }); | |||
|     }, | |||
| 
 | |||
|     on_search: function (domains) { | |||
|         var result = pyeval.sync_eval_domains_and_contexts({ | |||
|             domains: domains | |||
|         }); | |||
| 
 | |||
|         this.domain = result.domain; | |||
|         this.fetch_and_render_thread(); | |||
|     }, | |||
| 
 | |||
|     on_post_message: function (message) { | |||
|         var self = this; | |||
|         var options = this.selected_message ? {} : {channel_id: this.channel.id}; | |||
|         if (this.selected_message) { | |||
|             message.subtype = 'mail.mt_comment'; | |||
|             message.subtype_id = false; | |||
|             message.message_type = 'comment'; | |||
|             message.content_subtype = 'html'; | |||
| 
 | |||
|             options.model = this.selected_message.model; | |||
|             options.res_id = this.selected_message.res_id; | |||
|         } | |||
|         chat_manager | |||
|             .post_message(message, options) | |||
|             .then(function() { | |||
|                 if (self.selected_message) { | |||
|                     self.render_snackbar('mail.chat.MessageSentSnackbar', {record_name: self.selected_message.record_name}, 5000); | |||
|                     self.unselect_message(); | |||
|                 } else { | |||
|                     self.thread.scroll_to(); | |||
|                 } | |||
|             }) | |||
|             .fail(function () { | |||
|                 // todo: display notification
 | |||
|             }); | |||
|     }, | |||
|     on_new_message: function (message) { | |||
|         var self = this; | |||
|         if (_.contains(message.channel_ids, this.channel.id)) { | |||
|             if (this.channel.type !== 'static' && this.thread.is_at_bottom()) { | |||
|                 chat_manager.mark_channel_as_seen(this.channel); | |||
|             } | |||
| 
 | |||
|             var should_scroll = this.thread.is_at_bottom(); | |||
|             this.fetch_and_render_thread().then(function () { | |||
|                 if (should_scroll) { | |||
|                     self.thread.scroll_to({id: message.id}); | |||
|                 } | |||
|             }); | |||
|         } | |||
|         // Re-render sidebar to indicate that there is a new message in the corresponding channels
 | |||
|         this.render_sidebar(); | |||
|         // Dump scroll position of channels in which the new message arrived
 | |||
|         this.channels_scrolltop = _.omit(this.channels_scrolltop, message.channel_ids); | |||
|     }, | |||
|     on_update_message: function (message) { | |||
|         var self = this; | |||
|         var current_channel_id = this.channel.id; | |||
|         if ((current_channel_id === "channel_starred" && !message.is_starred) || | |||
|             (current_channel_id === "channel_inbox" && !message.is_needaction)) { | |||
|             chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function (messages) { | |||
|                 var options = self.get_thread_rendering_options(messages); | |||
|                 self.thread.remove_message_and_render(message.id, messages, options).then(function () { | |||
|                     self.update_button_status(messages.length === 0); | |||
|                 }); | |||
|             }); | |||
|         } else if (_.contains(message.channel_ids, current_channel_id)) { | |||
|             this.fetch_and_render_thread(); | |||
|         } | |||
|     }, | |||
|     on_new_channel: function (channel) { | |||
|         this.render_sidebar(); | |||
|         if (channel.autoswitch) { | |||
|             this.set_channel(channel); | |||
|         } | |||
|     }, | |||
|     on_composer_input_focused: function () { | |||
|         var suggestions = chat_manager.get_mention_partner_suggestions(this.channel); | |||
|         var composer = this.channel.mass_mailing ? this.extended_composer : this.basic_composer; | |||
|         composer.mention_set_prefetched_partners(suggestions); | |||
|     }, | |||
| 
 | |||
|     on_click_button_invite: function () { | |||
|         var title = _.str.sprintf(_t('Invite people to #%s'), this.channel.name); | |||
|         new PartnerInviteDialog(this, title, this.channel.id).open(); | |||
|     }, | |||
| 
 | |||
|     on_click_button_unsubscribe: function () { | |||
|         this.unsubscribe_from_channel(this.channel); | |||
|     }, | |||
|     on_click_button_settings: function() { | |||
|         this.do_action({ | |||
|             type: 'ir.actions.act_window', | |||
|             res_model: "mail.channel", | |||
|             res_id: this.channel.id, | |||
|             views: [[false, 'form']], | |||
|             target: 'current' | |||
|         }); | |||
|     }, | |||
|     on_click_new_message: function () { | |||
|         this.do_action({ | |||
|             type: 'ir.actions.act_window', | |||
|             res_model: 'mail.compose.message', | |||
|             view_mode: 'form', | |||
|             view_type: 'form', | |||
|             views: [[false, 'form']], | |||
|             target: 'new', | |||
|             context: "{'default_no_auto_thread': False, 'active_model': 'mail.message'}", | |||
|         }); | |||
|     }, | |||
|     destroy: function() { | |||
|         this.$buttons.off().destroy(); | |||
|         this._super.apply(this, arguments); | |||
|     }, | |||
| }); | |||
| 
 | |||
| 
 | |||
| core.action_registry.add('mail.chat.instant_messaging', ChatAction); | |||
| 
 | |||
| }); | |||
| @ -0,0 +1,19 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <templates id="template" xml:space="preserve"> | |||
| 
 | |||
| <t t-extend="mail.chat.Sidebar"> | |||
|     <t t-jquery="div.o_mail_chat_sidebar" t-operation="prepend"> | |||
|         <div t-attf-class="o_mail_chat_channel_item #{(active_channel_id == 'channel_sent') ? 'o_active': ''}" | |||
|              data-channel-id="channel_sent"> | |||
|             <span class="o_channel_name"> <i class="fa fa-envelope-o"/> Sent </span> | |||
|         </div> | |||
|     </t> | |||
| </t> | |||
| 
 | |||
| <t t-extend="mail.chat.ControlButtons"> | |||
|     <t t-jquery="button.o_mail_chat_button_new_message" t-operation="before"> | |||
|         <button type="button" class="btn btn-primary btn-sm o_mail_chat_button_new_message_sent" title="Compose new messages">Send mails</button> | |||
|     </t> | |||
| </t> | |||
| 
 | |||
| </templates> | |||
| @ -0,0 +1,27 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <openerp> | |||
|     <data> | |||
|         <template id="mail.assets_backend" name="mail assets" inherit_id="web.assets_backend"> | |||
|             <xpath expr="." position="inside"> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/many2many_tags_email.js"></script> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/announcement.js"></script> | |||
| 
 | |||
|                 <script type="text/javascript" src="/sent_mails/static/src/js/client_action_sent_mail.js"></script> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/composer.js"></script> | |||
|                 <script type="text/javascript" src="/sent_mails/static/src/js/chat_manager_sent_mail.js"></script> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/chatter.js"></script> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/thread.js"></script> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/systray.js"></script> | |||
|                 <script type="text/javascript" src="/mail/static/src/js/window_manager.js"></script> | |||
| 
 | |||
|                 <link rel="stylesheet" href="/mail/static/src/less/announcement.less"/> | |||
|                 <link rel="stylesheet" href="/mail/static/src/less/client_action.less" type="text/less"/> | |||
|                 <link rel="stylesheet" href="/mail/static/src/less/chat_window.less" type="text/less"/> | |||
|                 <link rel="stylesheet" href="/mail/static/src/less/composer.less" type="text/less"/> | |||
|                 <link rel="stylesheet" href="/mail/static/src/less/chatter.less" type="text/less"/> | |||
|                 <link rel="stylesheet" href="/mail/static/src/less/thread.less" type="text/less"/> | |||
|             </xpath> | |||
|         </template> | |||
|     </data> | |||
| </openerp> | |||
					Loading…
					
					
				
		Reference in new issue