[REF][IMP] *: mail js refactoring
This commit improves the JS code of the mail module, which comes maily from the new coding guidelines and the addition of "JS services". JS services are important objects that do not fit well in the component tree, such as chat_manager or ajax. The benefits of JS services are improved readability of the code, reduced coupling, and more testable modules. In particular, discuss was hard to Summary of the changes: - ClientAction has been renamed into Discuss - Clear instantiation of chatManager - New coding guidelines in most mail modules - JS Services can interact with each other - Chat Manager and Window Manager are services - Window Manager renamed to Chat Window Manager - bus.bus is now encapsulated in Bus Service (a service) - The test infrastructure has been tweaked with JS Services - Chat Mixin has been removed A future improvement would be to translate some 'trigger' into 'trigger_up'.
This commit is contained in:
committed by
Alexandre Kühn
parent
0cd656b5af
commit
02ec09cb1c
@@ -0,0 +1,96 @@
|
|||||||
|
odoo.define('bus.BusService', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var bus = require('bus.bus').bus;
|
||||||
|
|
||||||
|
var AbstractService = require('web.AbstractService');
|
||||||
|
|
||||||
|
var BusService = AbstractService.extend({
|
||||||
|
name: 'bus_service',
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
init: function () {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
this.bus = bus;
|
||||||
|
this._audio = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Public
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bus
|
||||||
|
*
|
||||||
|
* @return {web.Bus}
|
||||||
|
*/
|
||||||
|
getBus: function () {
|
||||||
|
return this.bus;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification, and notify once per browser's tab
|
||||||
|
*
|
||||||
|
* @param {web.Widget} widget the widget that called sendNotification
|
||||||
|
* @param {string} title
|
||||||
|
* @param {string} content
|
||||||
|
*/
|
||||||
|
sendNotification: function (widget, title, content) {
|
||||||
|
if (window.Notification && Notification.permission === "granted") {
|
||||||
|
if (this.bus.is_master) {
|
||||||
|
this._sendNativeNotification(title, content);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
widget.do_notify(title, content);
|
||||||
|
if (this.bus.is_master) {
|
||||||
|
this._beep(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily play the 'beep' audio on sent notification
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {web.Widget} widget
|
||||||
|
*/
|
||||||
|
_beep: function (widget) {
|
||||||
|
if (typeof(Audio) !== "undefined") {
|
||||||
|
if (!this._audio) {
|
||||||
|
this._audio = new Audio();
|
||||||
|
var ext = this._audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3";
|
||||||
|
var session = widget.getSession();
|
||||||
|
this._audio.src = session.url("/mail/static/src/audio/ting" + ext);
|
||||||
|
}
|
||||||
|
this._audio.play();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Show a browser notification
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} title
|
||||||
|
* @param {string} content
|
||||||
|
*/
|
||||||
|
_sendNativeNotification: function (title, content) {
|
||||||
|
var notification = new Notification(title, {body: content, icon: "/mail/static/src/img/odoo_o.png"});
|
||||||
|
notification.onclick = function () {
|
||||||
|
window.focus();
|
||||||
|
if (this.cancel) {
|
||||||
|
this.cancel();
|
||||||
|
} else if (this.close) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return BusService;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<template id="assets_backend" name="im assets" inherit_id="web.assets_backend">
|
<template id="assets_backend" name="im assets" inherit_id="web.assets_backend">
|
||||||
<xpath expr="." position="inside">
|
<xpath expr="." position="inside">
|
||||||
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
|
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
|
||||||
|
<script type="text/javascript" src="/bus/static/src/js/services/bus_service.js"></script>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
odoo.define('base_calendar.base_calendar', function (require) {
|
odoo.define('base_calendar.base_calendar', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var bus = require('bus.bus').bus;
|
|
||||||
var BasicModel = require('web.BasicModel');
|
var BasicModel = require('web.BasicModel');
|
||||||
var field_registry = require('web.field_registry');
|
var field_registry = require('web.field_registry');
|
||||||
var Notification = require('web.Notification');
|
var Notification = require('web.Notification');
|
||||||
@@ -99,7 +98,7 @@ WebClient.include({
|
|||||||
// in which the current user is involved is created, edited or deleted
|
// in which the current user is involved is created, edited or deleted
|
||||||
this.calendar_notif_timeouts = {};
|
this.calendar_notif_timeouts = {};
|
||||||
this.calendar_notif = {};
|
this.calendar_notif = {};
|
||||||
bus.on('notification', this, function (notifications) {
|
this.call('bus_service', 'getBus').on('notification', this, function (notifications) {
|
||||||
_.each(notifications, (function (notification) {
|
_.each(notifications, (function (notification) {
|
||||||
if (notification[0][1] === 'calendar.alarm') {
|
if (notification[0][1] === 'calendar.alarm') {
|
||||||
this.display_calendar_notif(notification[1]);
|
this.display_calendar_notif(notification[1]);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
odoo.define('im_livechat.im_livechat', function (require) {
|
odoo.define('im_livechat.im_livechat', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var local_storage = require('web.local_storage');
|
|
||||||
var bus = require('bus.bus').bus;
|
|
||||||
var config = require('web.config');
|
var config = require('web.config');
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
|
var ServiceProviderMixin = require('web.ServiceProviderMixin');
|
||||||
var session = require('web.session');
|
var session = require('web.session');
|
||||||
var time = require('web.time');
|
var time = require('web.time');
|
||||||
var utils = require('web.utils');
|
var utils = require('web.utils');
|
||||||
@@ -20,10 +19,10 @@ var LIVECHAT_COOKIE_HISTORY = 'im_livechat_history';
|
|||||||
var HISTORY_LIMIT = 15;
|
var HISTORY_LIMIT = 15;
|
||||||
|
|
||||||
// History tracking
|
// History tracking
|
||||||
var page = window.location.href.replace(/^.*\/\/[^\/]+/, '');
|
var page = window.location.href.replace(/^.*\/\/[^/]+/, '');
|
||||||
var page_history = utils.get_cookie(LIVECHAT_COOKIE_HISTORY);
|
var page_history = utils.get_cookie(LIVECHAT_COOKIE_HISTORY);
|
||||||
var url_history = [];
|
var url_history = [];
|
||||||
if(page_history){
|
if (page_history) {
|
||||||
url_history = JSON.parse(page_history) || [];
|
url_history = JSON.parse(page_history) || [];
|
||||||
}
|
}
|
||||||
if (!_.contains(url_history, page)) {
|
if (!_.contains(url_history, page)) {
|
||||||
@@ -34,8 +33,11 @@ if (!_.contains(url_history, page)) {
|
|||||||
utils.set_cookie(LIVECHAT_COOKIE_HISTORY, JSON.stringify(url_history), 60*60*24); // 1 day cookie
|
utils.set_cookie(LIVECHAT_COOKIE_HISTORY, JSON.stringify(url_history), 60*60*24); // 1 day cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
var LivechatButton = Widget.extend({
|
* In order to handle services like ajax in the external lib,
|
||||||
|
* The parent of Livechat, LivechatButton, is set as a service provider
|
||||||
|
*/
|
||||||
|
var LivechatButton = Widget.extend(ServiceProviderMixin, {
|
||||||
className:"openerp o_livechat_button hidden-print",
|
className:"openerp o_livechat_button hidden-print",
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
@@ -54,6 +56,7 @@ var LivechatButton = Widget.extend({
|
|||||||
this.chat_window = null;
|
this.chat_window = null;
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
this.server_url = server_url;
|
this.server_url = server_url;
|
||||||
|
this.busBus = this.call('bus_service', 'getBus');
|
||||||
},
|
},
|
||||||
|
|
||||||
willStart: function () {
|
willStart: function () {
|
||||||
@@ -87,7 +90,7 @@ var LivechatButton = Widget.extend({
|
|||||||
this.auto_popup_timeout = setTimeout(this.open_chat.bind(this), this.rule.auto_popup_timer*1000);
|
this.auto_popup_timeout = setTimeout(this.open_chat.bind(this), this.rule.auto_popup_timer*1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bus.on('notification', this, function (notifications) {
|
this.busBus.on('notification', this, function (notifications) {
|
||||||
var self = this;
|
var self = this;
|
||||||
_.each(notifications, function (notification) {
|
_.each(notifications, function (notification) {
|
||||||
self._on_notification(notification);
|
self._on_notification(notification);
|
||||||
@@ -95,9 +98,9 @@ var LivechatButton = Widget.extend({
|
|||||||
});
|
});
|
||||||
return this._super();
|
return this._super();
|
||||||
},
|
},
|
||||||
_on_notification: function(notification){
|
_on_notification: function (notification){
|
||||||
if (this.channel && (notification[0] === this.channel.uuid)) {
|
if (this.channel && (notification[0] === this.channel.uuid)) {
|
||||||
if(notification[1]._type === "history_command") { // history request
|
if (notification[1]._type === "history_command") { // history request
|
||||||
var cookie = utils.get_cookie(LIVECHAT_COOKIE_HISTORY);
|
var cookie = utils.get_cookie(LIVECHAT_COOKIE_HISTORY);
|
||||||
var history = cookie ? JSON.parse(cookie) : [];
|
var history = cookie ? JSON.parse(cookie) : [];
|
||||||
session.rpc("/im_livechat/history", {
|
session.rpc("/im_livechat/history", {
|
||||||
@@ -105,7 +108,7 @@ var LivechatButton = Widget.extend({
|
|||||||
channel_uuid: this.channel.uuid,
|
channel_uuid: this.channel.uuid,
|
||||||
page_history: history,
|
page_history: history,
|
||||||
});
|
});
|
||||||
}else{ // normal message
|
} else { // normal message
|
||||||
this.add_message(notification[1]);
|
this.add_message(notification[1]);
|
||||||
this.render_messages();
|
this.render_messages();
|
||||||
if (this.chat_window.folded || !this.chat_window.thread.is_at_bottom()) {
|
if (this.chat_window.folded || !this.chat_window.thread.is_at_bottom()) {
|
||||||
@@ -114,7 +117,7 @@ var LivechatButton = Widget.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
load_qweb_template: function(){
|
load_qweb_template: function (){
|
||||||
var xml_files = ['/mail/static/src/xml/chat_window.xml',
|
var xml_files = ['/mail/static/src/xml/chat_window.xml',
|
||||||
'/mail/static/src/xml/thread.xml',
|
'/mail/static/src/xml/thread.xml',
|
||||||
'/im_livechat/static/src/xml/im_livechat.xml'];
|
'/im_livechat/static/src/xml/im_livechat.xml'];
|
||||||
@@ -153,8 +156,8 @@ var LivechatButton = Widget.extend({
|
|||||||
self.send_welcome_message();
|
self.send_welcome_message();
|
||||||
self.render_messages();
|
self.render_messages();
|
||||||
|
|
||||||
bus.add_channel(channel.uuid);
|
this.busBus.add_channel(channel.uuid);
|
||||||
bus.start_polling();
|
this.busBus.start_polling();
|
||||||
|
|
||||||
utils.set_cookie('im_livechat_session', JSON.stringify(channel), 60*60);
|
utils.set_cookie('im_livechat_session', JSON.stringify(channel), 60*60);
|
||||||
utils.set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60*60);
|
utils.set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60*60);
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
odoo.define('im_livechat.chat_client_action', function (require) {
|
odoo.define('im_livechat.chat_discuss', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require('mail.chat_client_action');
|
var Discuss = require('mail.chat_discuss');
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
var core = require('web.core');
|
|
||||||
|
|
||||||
core.action_registry.get('mail.chat.instant_messaging').include({
|
Discuss.include({
|
||||||
_renderSidebar: function (options) {
|
_renderSidebar: function (options) {
|
||||||
// Override to sort livechat channels by last message's date
|
// Override to sort livechat channels by last message's date
|
||||||
var channel_partition = _.partition(options.channels, function (channel) {
|
var channel_partition = _.partition(options.channels, function (channel) {
|
||||||
@@ -19,13 +17,4 @@ core.action_registry.get('mail.chat.instant_messaging').include({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
chat_manager.bus.on('new_message', null, function (msg) {
|
|
||||||
_.each(msg.channel_ids, function (channel_id) {
|
|
||||||
var channel = chat_manager.get_channel(channel_id);
|
|
||||||
if (channel) {
|
|
||||||
channel.last_message_date = msg.date; // update the last message's date of the channel
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</t>
|
</t>
|
||||||
|
|
||||||
<!-- Mobile templates -->
|
<!-- Mobile templates -->
|
||||||
<t t-extend="mail.client_action_mobile">
|
<t t-extend="mail.discuss_mobile">
|
||||||
<t t-jquery=".o_mail_mobile_tabs" t-operation="append">
|
<t t-jquery=".o_mail_mobile_tabs" t-operation="append">
|
||||||
<div class="o_mail_mobile_tab" data-type="livechat">
|
<div class="o_mail_mobile_tab" data-type="livechat">
|
||||||
<span class="fa fa-comments"/>
|
<span class="fa fa-comments"/>
|
||||||
|
|||||||
@@ -128,6 +128,7 @@
|
|||||||
<script type="text/javascript" src="/web/static/src/js/core/ajax.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/ajax.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/time.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/time.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/mixins.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/mixins.js"></script>
|
||||||
|
<script type="text/javascript" src="/web/static/src/js/core/service_mixins.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/rpc.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/rpc.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/widget.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/widget.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/registry.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/registry.js"></script>
|
||||||
@@ -141,6 +142,7 @@
|
|||||||
<script type="text/javascript" src="/web/static/src/js/services/local_storage_service.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/services/local_storage_service.js"></script>
|
||||||
<!-- Bus, Mail, Livechat -->
|
<!-- Bus, Mail, Livechat -->
|
||||||
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
|
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
|
||||||
|
<script type="text/javascript" src="/bus/static/src/js/services/bus_service.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/thread.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/thread.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/document_viewer.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/document_viewer.js"></script>
|
||||||
|
|||||||
@@ -44,9 +44,9 @@
|
|||||||
'application': True,
|
'application': True,
|
||||||
'qweb': [
|
'qweb': [
|
||||||
'static/src/xml/activity.xml',
|
'static/src/xml/activity.xml',
|
||||||
'static/src/xml/client_action.xml',
|
|
||||||
'static/src/xml/composer.xml',
|
'static/src/xml/composer.xml',
|
||||||
'static/src/xml/chatter.xml',
|
'static/src/xml/chatter.xml',
|
||||||
|
'static/src/xml/discuss.xml',
|
||||||
'static/src/xml/followers.xml',
|
'static/src/xml/followers.xml',
|
||||||
'static/src/xml/systray.xml',
|
'static/src/xml/systray.xml',
|
||||||
'static/src/xml/thread.xml',
|
'static/src/xml/thread.xml',
|
||||||
|
|||||||
@@ -258,8 +258,8 @@ class MailController(http.Controller):
|
|||||||
def needaction(self):
|
def needaction(self):
|
||||||
return request.env['res.partner'].get_needaction_count()
|
return request.env['res.partner'].get_needaction_count()
|
||||||
|
|
||||||
@http.route('/mail/client_action', type='json', auth='user')
|
@http.route('/mail/init_messaging', type='json', auth='user')
|
||||||
def mail_client_action(self):
|
def mail_init_messaging(self):
|
||||||
values = {
|
values = {
|
||||||
'needaction_inbox_counter': request.env['res.partner'].get_needaction_count(),
|
'needaction_inbox_counter': request.env['res.partner'].get_needaction_count(),
|
||||||
'starred_counter': request.env['res.partner'].get_starred_count(),
|
'starred_counter': request.env['res.partner'].get_starred_count(),
|
||||||
|
|||||||
@@ -186,17 +186,6 @@ class Message(models.Model):
|
|||||||
|
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def mark_as_unread(self, channel_ids=None):
|
|
||||||
""" Add needactions to messages for the current partner. """
|
|
||||||
partner_id = self.env.user.partner_id.id
|
|
||||||
for message in self:
|
|
||||||
message.write({'needaction_partner_ids': [(4, partner_id)]})
|
|
||||||
|
|
||||||
ids = [m.id for m in self]
|
|
||||||
notification = {'type': 'mark_as_unread', 'message_ids': ids, 'channel_ids': channel_ids}
|
|
||||||
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification)
|
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def set_message_done(self):
|
def set_message_done(self):
|
||||||
""" Remove the needaction from messages for the current partner. """
|
""" Remove the needaction from messages for the current partner. """
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,130 +0,0 @@
|
|||||||
odoo.define('mail.chat_mixin', function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
|
|
||||||
var ChatMixin = {
|
|
||||||
// return a deferred that resolves when chat manager is ready
|
|
||||||
_chatReady: function() {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('chat_manager_ready', {
|
|
||||||
callback: function(def) { result = def;}
|
|
||||||
});
|
|
||||||
return $.when(result);
|
|
||||||
},
|
|
||||||
_getBus: function() {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('get_bus', {
|
|
||||||
callback: function(bus) { result = bus;}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
_getCannedResponses: function() {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('get_canned_responses', {
|
|
||||||
callback: function(responses) { result = responses;}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
_getEmojis: function() {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('get_emojis', {
|
|
||||||
callback: function(emojis) { result = emojis;}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
_getMentionPartnerSuggestions: function(channel_id) {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('get_mention_partner_suggestions', {
|
|
||||||
channel_id: channel_id,
|
|
||||||
callback: function(res) { result = res; },
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
_getMessages: function(options) {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('get_messages', {
|
|
||||||
options: options,
|
|
||||||
callback: function(messages) { result = messages;}
|
|
||||||
});
|
|
||||||
return result ? result : $.when([]);
|
|
||||||
},
|
|
||||||
_joinChannel: function(channel_id) {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('join_channel', {
|
|
||||||
channel_id: channel_id,
|
|
||||||
callback: function(def) { result = def; },
|
|
||||||
});
|
|
||||||
return $.when(result);
|
|
||||||
},
|
|
||||||
_postMessage: function(message, options) {
|
|
||||||
var result;
|
|
||||||
this.trigger_up('post_message', {
|
|
||||||
message: message,
|
|
||||||
options: options,
|
|
||||||
callback: function(def) { result = def;}
|
|
||||||
});
|
|
||||||
return $.when(result);
|
|
||||||
},
|
|
||||||
_removeChatterMessages: function(model) {
|
|
||||||
this.trigger_up('remove_chatter_messages', {
|
|
||||||
model: model,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_toggleStarStatus: function(message_id) {
|
|
||||||
this.trigger_up('toggle_star_status', {
|
|
||||||
message_id: message_id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return ChatMixin;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
odoo.define('mail.chat_service', function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
var web_client = require('web.web_client');
|
|
||||||
|
|
||||||
web_client.on('get_emojis', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.get_emojis());
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('get_canned_responses', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.get_canned_responses());
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('chat_manager_ready', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.is_ready);
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('get_messages', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.get_messages(event.data.options));
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('post_message', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.post_message(event.data.message, event.data.options));
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('get_bus', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.bus);
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('remove_chatter_messages', web_client, function(event) {
|
|
||||||
chat_manager.remove_chatter_messages(event.data.model);
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('toggle_star_status', web_client, function(event) {
|
|
||||||
chat_manager.toggle_star_status(event.data.message_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('join_channel', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.join_channel(event.data.channel_id));
|
|
||||||
});
|
|
||||||
|
|
||||||
web_client.on('get_mention_partner_suggestions', web_client, function(event) {
|
|
||||||
event.data.callback(chat_manager.get_mention_partner_suggestions(event.data.channel_id));
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -140,7 +140,9 @@ return Widget.extend({
|
|||||||
};
|
};
|
||||||
this.$input.val('');
|
this.$input.val('');
|
||||||
if (content) {
|
if (content) {
|
||||||
this.trigger('post_message', message, this.channel_id);
|
this.trigger('post_message', message, {
|
||||||
|
channelID: this.channel_id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ odoo.define('mail.Chatter', function (require) {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var Activity = require('mail.Activity');
|
var Activity = require('mail.Activity');
|
||||||
var chat_mixin = require('mail.chat_mixin');
|
|
||||||
var ChatterComposer = require('mail.ChatterComposer');
|
var ChatterComposer = require('mail.ChatterComposer');
|
||||||
var Followers = require('mail.Followers');
|
var Followers = require('mail.Followers');
|
||||||
var ThreadField = require('mail.ThreadField');
|
var ThreadField = require('mail.ThreadField');
|
||||||
@@ -22,7 +21,7 @@ var QWeb = core.qweb;
|
|||||||
// with each other.
|
// with each other.
|
||||||
// It synchronizes the rendering of those widgets (as they may be asynchronous), to limitate
|
// It synchronizes the rendering of those widgets (as they may be asynchronous), to limitate
|
||||||
// the flickering when switching between records
|
// the flickering when switching between records
|
||||||
var Chatter = Widget.extend(chat_mixin, {
|
var Chatter = Widget.extend({
|
||||||
template: 'mail.Chatter',
|
template: 'mail.Chatter',
|
||||||
custom_events: {
|
custom_events: {
|
||||||
reload_mail_fields: '_onReloadMailFields',
|
reload_mail_fields: '_onReloadMailFields',
|
||||||
@@ -52,7 +51,7 @@ var Chatter = Widget.extend(chat_mixin, {
|
|||||||
// mention: get the prefetched partners and use them as mention suggestions
|
// mention: get the prefetched partners and use them as mention suggestions
|
||||||
// if there is a follower widget, the followers will be added to the
|
// if there is a follower widget, the followers will be added to the
|
||||||
// suggestions as well once fetched
|
// suggestions as well once fetched
|
||||||
this.mentionPartnerSuggestions = this._getMentionPartnerSuggestions();
|
this.mentionPartnerSuggestions = this.call('chat_manager', 'getMentionPartnerSuggestions');
|
||||||
this.mentionSuggestions = this.mentionPartnerSuggestions;
|
this.mentionSuggestions = this.mentionPartnerSuggestions;
|
||||||
|
|
||||||
this.fields = {};
|
this.fields = {};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
odoo.define('mail.ChatterComposer', function (require) {
|
odoo.define('mail.ChatterComposer', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// var chat_manager = require('mail.chat_manager');
|
|
||||||
var composer = require('mail.composer');
|
var composer = require('mail.composer');
|
||||||
var utils = require('mail.utils');
|
var utils = require('mail.utils');
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
odoo.define('mail.composer', function (require) {
|
odoo.define('mail.composer', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var chat_mixin = require('mail.chat_mixin');
|
|
||||||
var DocumentViewer = require('mail.DocumentViewer');
|
var DocumentViewer = require('mail.DocumentViewer');
|
||||||
var utils = require('mail.utils');
|
var utils = require('mail.utils');
|
||||||
|
|
||||||
@@ -385,7 +384,7 @@ var MentionManager = Widget.extend({
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var BasicComposer = Widget.extend(chat_mixin, {
|
var BasicComposer = Widget.extend({
|
||||||
template: "mail.ChatComposer",
|
template: "mail.ChatComposer",
|
||||||
events: {
|
events: {
|
||||||
"keydown .o_composer_input": "_onKeydown",
|
"keydown .o_composer_input": "_onKeydown",
|
||||||
@@ -482,7 +481,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
|
|||||||
self.trigger('input_focused');
|
self.trigger('input_focused');
|
||||||
});
|
});
|
||||||
this.$input.html(this.options.default_body);
|
this.$input.html(this.options.default_body);
|
||||||
this.$input.css('min-height', this.options.input_min_height)
|
this.$input.css('min-height', this.options.input_min_height);
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
$(window).on(this.fileupload_id, this.on_attachment_loaded);
|
$(window).on(this.fileupload_id, this.on_attachment_loaded);
|
||||||
@@ -728,7 +727,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
|
|||||||
},
|
},
|
||||||
// remove mention when user try to edit or remove it.
|
// remove mention when user try to edit or remove it.
|
||||||
_removeMention: function (event) {
|
_removeMention: function (event) {
|
||||||
if (window.getSelection().anchorNode.parentNode.tagName == 'A') {
|
if (window.getSelection().anchorNode.parentNode.tagName === 'A') {
|
||||||
document.getElementById(window.getSelection().anchorNode.parentNode.id).remove();
|
document.getElementById(window.getSelection().anchorNode.parentNode.id).remove();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -790,8 +789,8 @@ var BasicComposer = Widget.extend(chat_mixin, {
|
|||||||
var self = this;
|
var self = this;
|
||||||
var def = $.Deferred();
|
var def = $.Deferred();
|
||||||
clearTimeout(this.canned_timeout);
|
clearTimeout(this.canned_timeout);
|
||||||
this.canned_timeout = setTimeout(function() {
|
this.canned_timeout = setTimeout(function () {
|
||||||
var canned_responses = self._getCannedResponses();
|
var canned_responses = self.call('chat_manager', 'getCannedResponses');
|
||||||
var matches = fuzzy.filter(utils.unaccent(search), _.pluck(canned_responses, 'source'));
|
var matches = fuzzy.filter(utils.unaccent(search), _.pluck(canned_responses, 'source'));
|
||||||
var indexes = _.pluck(matches.slice(0, self.options.mention_fetch_limit), 'index');
|
var indexes = _.pluck(matches.slice(0, self.options.mention_fetch_limit), 'index');
|
||||||
def.resolve(_.map(indexes, function (i) {
|
def.resolve(_.map(indexes, function (i) {
|
||||||
@@ -876,7 +875,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
|
|||||||
_onEmojiButtonClick: function () {
|
_onEmojiButtonClick: function () {
|
||||||
if (!this.$emojisContainer) { // lazy rendering
|
if (!this.$emojisContainer) { // lazy rendering
|
||||||
this.$emojisContainer = $(QWeb.render('mail.ChatComposer.emojis', {
|
this.$emojisContainer = $(QWeb.render('mail.ChatComposer.emojis', {
|
||||||
emojis: this._getEmojis(),
|
emojis: this.call('chat_manager', 'getEmojis'),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (this.$emojisContainer.parent().length) {
|
if (this.$emojisContainer.parent().length) {
|
||||||
@@ -938,7 +937,7 @@ var BasicComposer = Widget.extend(chat_mixin, {
|
|||||||
*/
|
*/
|
||||||
_onEmojiImageClick: function (ev) {
|
_onEmojiImageClick: function (ev) {
|
||||||
this.$input.html(this.$input.html() + " " + $(ev.currentTarget).data('emoji') + " ");
|
this.$input.html(this.$input.html() + " " + $(ev.currentTarget).data('emoji') + " ");
|
||||||
this._placeCaretAtEnd(this.$input[0])
|
this._placeCaretAtEnd(this.$input[0]);
|
||||||
this._hideEmojis();
|
this._hideEmojis();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+161
-144
@@ -1,10 +1,8 @@
|
|||||||
odoo.define('mail.chat_client_action', function (require) {
|
odoo.define('mail.chat_discuss', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
var ChatThread = require('mail.ChatThread');
|
var ChatThread = require('mail.ChatThread');
|
||||||
var composer = require('mail.composer');
|
var composer = require('mail.composer');
|
||||||
var utils = require('mail.utils');
|
|
||||||
|
|
||||||
var config = require('web.config');
|
var config = require('web.config');
|
||||||
var ControlPanelMixin = require('web.ControlPanelMixin');
|
var ControlPanelMixin = require('web.ControlPanelMixin');
|
||||||
@@ -33,9 +31,11 @@ var PartnerInviteDialog = Dialog.extend({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
|
* @param {integer|string} channelID id of the channel,
|
||||||
|
* a string for static channels (e.g. 'channel_inbox').
|
||||||
*/
|
*/
|
||||||
init: function (parent, title, channel_id) {
|
init: function (parent, title, channelID) {
|
||||||
this.channel_id = channel_id;
|
this.channelID = channelID;
|
||||||
|
|
||||||
this._super(parent, {
|
this._super(parent, {
|
||||||
title: title,
|
title: title,
|
||||||
@@ -51,7 +51,8 @@ var PartnerInviteDialog = Dialog.extend({
|
|||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
start: function (){
|
start: function () {
|
||||||
|
var self = this;
|
||||||
this.$input = this.$('.o_mail_chat_partner_invite_input');
|
this.$input = this.$('.o_mail_chat_partner_invite_input');
|
||||||
this.$input.select2({
|
this.$input.select2({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -62,7 +63,7 @@ var PartnerInviteDialog = Dialog.extend({
|
|||||||
return $('<span>').text(item.text).prepend(status);
|
return $('<span>').text(item.text).prepend(status);
|
||||||
},
|
},
|
||||||
query: function (query) {
|
query: function (query) {
|
||||||
chat_manager.search_partner(query.term, 20).then(function (partners) {
|
self.call('chat_manager', 'searchPartner', query.term, 20).then(function (partners) {
|
||||||
query.callback({
|
query.callback({
|
||||||
results: _.map(partners, function (partner) {
|
results: _.map(partners, function (partner) {
|
||||||
return _.extend(partner, { text: partner.label });
|
return _.extend(partner, { text: partner.label });
|
||||||
@@ -80,7 +81,7 @@ var PartnerInviteDialog = Dialog.extend({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @returns {Deferred}
|
* @returns {$.Promise}
|
||||||
*/
|
*/
|
||||||
_addChannel: function () {
|
_addChannel: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -89,22 +90,23 @@ var PartnerInviteDialog = Dialog.extend({
|
|||||||
return this._rpc({
|
return this._rpc({
|
||||||
model: 'mail.channel',
|
model: 'mail.channel',
|
||||||
method: 'channel_invite',
|
method: 'channel_invite',
|
||||||
args: [this.channel_id],
|
args: [this.channelID],
|
||||||
kwargs: {partner_ids: _.pluck(data, 'id')},
|
kwargs: {partner_ids: _.pluck(data, 'id')},
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
var names = _.escape(_.pluck(data, 'text').join(', '));
|
var names = _.escape(_.pluck(data, 'text').join(', '));
|
||||||
var notification = _.str.sprintf(_t('You added <b>%s</b> to the conversation.'), names);
|
var notification = _.str.sprintf(_t('You added <b>%s</b> to the conversation.'), names);
|
||||||
self.do_notify(_t('New people'), notification);
|
self.do_notify(_t('New people'), notification);
|
||||||
// Clear the members_deferred to fetch again the partner
|
// Clear the membersDeferred to fetch again the partner
|
||||||
// when get_mention_partner_suggestions from the chat_manager is triggered
|
// when getMentionPartnerSuggestions from the chatManager is triggered
|
||||||
delete chat_manager.get_channel(self.channel_id).members_deferred;
|
var channel = this.call('chat_manager', 'getChannel', self.channelID);
|
||||||
|
delete channel.membersDeferred;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var ChatAction = Widget.extend(ControlPanelMixin, {
|
var Discuss = Widget.extend(ControlPanelMixin, {
|
||||||
template: 'mail.client_action',
|
template: 'mail.discuss',
|
||||||
custom_events: {
|
custom_events: {
|
||||||
search: '_onSearch',
|
search: '_onSearch',
|
||||||
},
|
},
|
||||||
@@ -129,7 +131,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
this.domain = [];
|
this.domain = [];
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.options = options || {};
|
this.options = options || {};
|
||||||
this.channels_scrolltop = {};
|
this.channelsScrolltop = {};
|
||||||
this.throttledUpdateChannels = _.throttle(this._updateChannels.bind(this), 100, { leading: false });
|
this.throttledUpdateChannels = _.throttle(this._updateChannels.bind(this), 100, { leading: false });
|
||||||
this.notification_bar = (window.Notification && window.Notification.permission === "default");
|
this.notification_bar = (window.Notification && window.Notification.permission === "default");
|
||||||
this.selected_message = null;
|
this.selected_message = null;
|
||||||
@@ -144,13 +146,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
*/
|
*/
|
||||||
willStart: function () {
|
willStart: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var view_id = this.action && this.action.search_view_id && this.action.search_view_id[0];
|
var viewID = this.action && this.action.search_view_id && this.action.search_view_id[0];
|
||||||
var def = this
|
var def = this
|
||||||
.loadFieldView(this.dataset, view_id, 'search')
|
.loadFieldView(this.dataset, viewID, 'search')
|
||||||
.then(function (fields_view) {
|
.then(function (fields_view) {
|
||||||
self.fields_view = fields_view;
|
self.fields_view = fields_view;
|
||||||
});
|
});
|
||||||
return $.when(this._super(), chat_manager.is_ready, def);
|
var chatReady = this.call('chat_manager', 'isReady');
|
||||||
|
return $.when(this._super(), chatReady, def);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
@@ -158,30 +161,31 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
start: function () {
|
start: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var default_channel = chat_manager.get_channel(this.defaultChannelID) ||
|
var defaultChannel = this.call('chat_manager', 'getChannel', this.defaultChannelID) ||
|
||||||
chat_manager.get_channel('channel_inbox');
|
this.call('chat_manager', 'getChannel', 'channel_inbox');
|
||||||
this.basic_composer = new composer.BasicComposer(this, {mention_partners_restricted: true});
|
|
||||||
this.extended_composer = new composer.ExtendedComposer(this, {mention_partners_restricted: true});
|
this.basicComposer = new composer.BasicComposer(this, {mention_partners_restricted: true});
|
||||||
this.basic_composer.on('post_message', this, this._onPostMessage);
|
this.extendedComposer = new composer.ExtendedComposer(this, {mention_partners_restricted: true});
|
||||||
this.basic_composer.on('input_focused', this, this._onComposerFocused);
|
this.basicComposer.on('post_message', this, this._onPostMessage);
|
||||||
this.extended_composer.on('post_message', this, this._onPostMessage);
|
this.basicComposer.on('input_focused', this, this._onComposerFocused);
|
||||||
this.extended_composer.on('input_focused', this, this._onComposerFocused);
|
this.extendedComposer.on('post_message', this, this._onPostMessage);
|
||||||
|
this.extendedComposer.on('input_focused', this, this._onComposerFocused);
|
||||||
|
|
||||||
var defs = [];
|
var defs = [];
|
||||||
defs.push(this._renderButtons());
|
defs.push(this._renderButtons());
|
||||||
defs.push(this._renderThread());
|
defs.push(this._renderThread());
|
||||||
defs.push(this.basic_composer.appendTo(this.$('.o_mail_chat_content')));
|
defs.push(this.basicComposer.appendTo(this.$('.o_mail_chat_content')));
|
||||||
defs.push(this.extended_composer.appendTo(this.$('.o_mail_chat_content')));
|
defs.push(this.extendedComposer.appendTo(this.$('.o_mail_chat_content')));
|
||||||
defs.push(this._renderSearchView());
|
defs.push(this._renderSearchView());
|
||||||
|
|
||||||
return $.when.apply($, defs)
|
return $.when.apply($, defs)
|
||||||
.then(this._setChannel.bind(this, default_channel))
|
.then(this._setChannel.bind(this, defaultChannel))
|
||||||
.then(this._updateChannels.bind(this))
|
.then(this._updateChannels.bind(this))
|
||||||
.then(function () {
|
.then(function () {
|
||||||
self._startListening();
|
self._startListening();
|
||||||
self.thread.$el.on("scroll", null, _.debounce(function () {
|
self.thread.$el.on("scroll", null, _.debounce(function () {
|
||||||
if (self.thread.is_at_bottom()) {
|
if (self.thread.is_at_bottom()) {
|
||||||
chat_manager.mark_channel_as_seen(self.channel);
|
self.call('chat_manager', 'markChannelAsSeen', self.channel);
|
||||||
}
|
}
|
||||||
}, 100));
|
}, 100));
|
||||||
});
|
});
|
||||||
@@ -210,17 +214,19 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
on_attach_callback: function () {
|
on_attach_callback: function () {
|
||||||
chat_manager.bus.trigger('client_action_open', true);
|
var chatBus = this.call('chat_manager', 'getChatBus');
|
||||||
|
chatBus.trigger('discuss_open', true);
|
||||||
if (this.channel) {
|
if (this.channel) {
|
||||||
this.thread.scroll_to({offset: this.channels_scrolltop[this.channel.id]});
|
this.thread.scroll_to({offset: this.channelsScrolltop[this.channel.id]});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
on_detach_callback: function () {
|
on_detach_callback: function () {
|
||||||
chat_manager.bus.trigger('client_action_open', false);
|
var chatBus = this.call('chat_manager', 'getChatBus');
|
||||||
this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop();
|
chatBus.trigger('discuss_open', false);
|
||||||
|
this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop();
|
||||||
},
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -229,14 +235,17 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @returns {Deferred}
|
* @returns {$.Promise}
|
||||||
*/
|
*/
|
||||||
_fetchAndRenderThread: function () {
|
_fetchAndRenderThread: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
return chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function (result) {
|
return this.call('chat_manager', 'getMessages', {
|
||||||
self.thread.render(result, self._getThreadRenderingOptions(result));
|
channelID: this.channel.id,
|
||||||
self._updateButtonStatus(result.length === 0);
|
domain: this.domain
|
||||||
});
|
}).then(function (messages) {
|
||||||
|
self.thread.render(messages, self._getThreadRenderingOptions(messages));
|
||||||
|
self._updateButtonStatus(messages.length === 0);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -250,13 +259,13 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
if (!this.unread_counter) {
|
if (!this.unread_counter) {
|
||||||
this.messages_separator_position = false; // no unread message -> don't display separator
|
this.messages_separator_position = false; // no unread message -> don't display separator
|
||||||
} else {
|
} else {
|
||||||
var msg = chat_manager.get_last_seen_message(this.channel);
|
var msg = this.call('chat_manager', 'getLastSeenMessage', this.channel);
|
||||||
this.messages_separator_position = msg ? msg.id : 'top';
|
this.messages_separator_position = msg ? msg.id : 'top';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
channel_id: this.channel.id,
|
channel_id: this.channel.id,
|
||||||
display_load_more: !chat_manager.all_history_loaded(this.channel, this.domain),
|
display_load_more: !this.call('chat_manager', 'isAllHistoryLoaded', this.channel, this.domain),
|
||||||
display_needactions: this.channel.display_needactions,
|
display_needactions: this.channel.display_needactions,
|
||||||
messages_separator_position: this.messages_separator_position,
|
messages_separator_position: this.messages_separator_position,
|
||||||
squash_close_messages: this.channel.type !== 'static' && !this.channel.mass_mailing,
|
squash_close_messages: this.channel.type !== 'static' && !this.channel.mass_mailing,
|
||||||
@@ -268,22 +277,27 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
* Load more messages for the current thread
|
||||||
|
*
|
||||||
* @private
|
* @private
|
||||||
* @returns {Deferred}
|
* @returns {$.Promise}
|
||||||
*/
|
*/
|
||||||
_loadMoreMessages: function () {
|
_loadMoreMessages: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var oldest_msg_id = this.$('.o_thread_message').first().data('messageId');
|
var oldestMsgID = this.$('.o_thread_message').first().data('messageId');
|
||||||
var oldest_msg_selector = '.o_thread_message[data-message-id="' + oldest_msg_id + '"]';
|
var oldestMsgSelector = '.o_thread_message[data-message-id="' + oldestMsgID + '"]';
|
||||||
var offset = -dom.getPosition(document.querySelector(oldest_msg_selector)).top;
|
var offset = -dom.getPosition(document.querySelector(oldestMsgSelector)).top;
|
||||||
return chat_manager
|
return this.call('chat_manager', 'getMessages', {
|
||||||
.get_messages({channel_id: this.channel.id, domain: this.domain, load_more: true})
|
channelID: this.channel.id,
|
||||||
.then(function (result) {
|
domain: this.domain,
|
||||||
|
loadMore: true
|
||||||
|
})
|
||||||
|
.then(function (messages) {
|
||||||
if (self.messages_separator_position === 'top') {
|
if (self.messages_separator_position === 'top') {
|
||||||
self.messages_separator_position = undefined; // reset value to re-compute separator position
|
self.messages_separator_position = undefined; // reset value to re-compute separator position
|
||||||
}
|
}
|
||||||
self.thread.render(result, self._getThreadRenderingOptions(result));
|
self.thread.render(messages, self._getThreadRenderingOptions(messages));
|
||||||
offset += dom.getPosition(document.querySelector(oldest_msg_selector)).top;
|
offset += dom.getPosition(document.querySelector(oldestMsgSelector)).top;
|
||||||
self.thread.scroll_to({offset: offset});
|
self.thread.scroll_to({offset: offset});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -313,9 +327,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
select: function (event, ui) {
|
select: function (event, ui) {
|
||||||
if (self.last_search_val) {
|
if (self.last_search_val) {
|
||||||
if (ui.item.value === '_create') {
|
if (ui.item.value === '_create') {
|
||||||
chat_manager.create_channel(self.last_search_val, "public");
|
self.call('chat_manager', 'createChannel', self.last_search_val, "public");
|
||||||
} else {
|
} else {
|
||||||
chat_manager.join_channel(ui.item.id);
|
self.call('chat_manager', 'joinChannel', ui.item.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -327,23 +341,23 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
} else if (type === 'private') {
|
} else if (type === 'private') {
|
||||||
$input.on('keyup', this, function (event) {
|
$input.on('keyup', this, function (event) {
|
||||||
var name = _.escape($(event.target).val());
|
var name = _.escape($(event.target).val());
|
||||||
if(event.which === $.ui.keyCode.ENTER && name) {
|
if (event.which === $.ui.keyCode.ENTER && name) {
|
||||||
chat_manager.create_channel(name, "private");
|
self.call('chat_manager', 'createChannel', name, "private");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'dm') {
|
} else if (type === 'dm') {
|
||||||
$input.autocomplete({
|
$input.autocomplete({
|
||||||
source: function (request, response) {
|
source: function (request, response) {
|
||||||
self.last_search_val = _.escape(request.term);
|
self.last_search_val = _.escape(request.term);
|
||||||
chat_manager.search_partner(self.last_search_val, 10).done(response);
|
self.call('chat_manager', 'searchPartner', self.last_search_val, 10).done(response);
|
||||||
},
|
},
|
||||||
select: function (event, ui) {
|
select: function (event, ui) {
|
||||||
var partner_id = ui.item.id;
|
var partner_id = ui.item.id;
|
||||||
var dm = chat_manager.get_dm_from_partner_id(partner_id);
|
var dm = self.call('chat_manager', 'getDmFromPartnerID', partner_id);
|
||||||
if (dm) {
|
if (dm) {
|
||||||
self._setChannel(dm);
|
self._setChannel(dm);
|
||||||
} else {
|
} else {
|
||||||
chat_manager.create_channel(partner_id, "dm");
|
self.call('chat_manager', 'createChannel', partner_id, "dm");
|
||||||
}
|
}
|
||||||
// clear the input
|
// clear the input
|
||||||
$(this).val('');
|
$(this).val('');
|
||||||
@@ -418,20 +432,21 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
*/
|
*/
|
||||||
_renderThread: function () {
|
_renderThread: function () {
|
||||||
|
var self = this;
|
||||||
this.thread = new ChatThread(this, {display_help: true});
|
this.thread = new ChatThread(this, {display_help: true});
|
||||||
|
|
||||||
this.thread.on('redirect', this, function (res_model, res_id) {
|
this.thread.on('redirect', this, function (resModel, resID) {
|
||||||
chat_manager.redirect(res_model, res_id, this._setChannel.bind(this));
|
self.call('chat_manager', 'redirect', resModel, resID, self._setChannel.bind(self));
|
||||||
});
|
});
|
||||||
this.thread.on('redirect_to_channel', this, function (channel_id) {
|
this.thread.on('redirect_to_channel', this, function (channelID) {
|
||||||
chat_manager.join_channel(channel_id).then(this._setChannel.bind(this));
|
self.call('chat_manager', 'joinChannel', channelID).then(this._setChannel.bind(this));
|
||||||
});
|
});
|
||||||
this.thread.on('load_more_messages', this, this._loadMoreMessages);
|
this.thread.on('load_more_messages', this, this._loadMoreMessages);
|
||||||
this.thread.on('mark_as_read', this, function (message_id) {
|
this.thread.on('mark_as_read', this, function (messageID) {
|
||||||
chat_manager.mark_as_read([message_id]);
|
self.call('chat_manager', 'markAsRead', [messageID]);
|
||||||
});
|
});
|
||||||
this.thread.on('toggle_star_status', this, function (message_id) {
|
this.thread.on('toggle_star_status', this, function (messageID) {
|
||||||
chat_manager.toggle_star_status(message_id);
|
self.call('chat_manager', 'toggleStarStatus', messageID);
|
||||||
});
|
});
|
||||||
this.thread.on('select_message', this, this._selectMessage);
|
this.thread.on('select_message', this, this._selectMessage);
|
||||||
this.thread.on('unselect_message', this, this._unselectMessage);
|
this.thread.on('unselect_message', this, this._unselectMessage);
|
||||||
@@ -444,12 +459,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_restoreChannelState: function () {
|
_restoreChannelState: function () {
|
||||||
var $new_messages_separator = this.$('.o_thread_new_messages_separator');
|
var $newMessagesSeparator = this.$('.o_thread_new_messages_separator');
|
||||||
if ($new_messages_separator.length) {
|
if ($newMessagesSeparator.length) {
|
||||||
this.thread.$el.scrollTo($new_messages_separator);
|
this.thread.$el.scrollTo($newMessagesSeparator);
|
||||||
} else {
|
} else {
|
||||||
var new_channel_scrolltop = this.channels_scrolltop[this.channel.id];
|
var newChannelScrolltop = this.channelsScrolltop[this.channel.id];
|
||||||
this.thread.scroll_to({offset: new_channel_scrolltop});
|
this.thread.scroll_to({offset: newChannelScrolltop});
|
||||||
}
|
}
|
||||||
this._restoreComposerState(this.channel);
|
this._restoreComposerState(this.channel);
|
||||||
},
|
},
|
||||||
@@ -461,7 +476,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
if (channel.type === 'static') {
|
if (channel.type === 'static') {
|
||||||
return; // no composer in static channels
|
return; // no composer in static channels
|
||||||
}
|
}
|
||||||
var composer = channel.mass_mailing ? this.extended_composer : this.basic_composer;
|
var composer = channel.mass_mailing ? this.extendedComposer : this.basicComposer;
|
||||||
var composerState = this.composerStates[channel.uuid];
|
var composerState = this.composerStates[channel.uuid];
|
||||||
if (composerState) {
|
if (composerState) {
|
||||||
composer.setState(composerState);
|
composer.setState(composerState);
|
||||||
@@ -469,14 +484,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {string} search_val
|
* @param {string} searchVal
|
||||||
* @returns {Deferred<Array>}
|
* @returns {$.Promise<Array>}
|
||||||
*/
|
*/
|
||||||
_searchChannel: function (search_val){
|
_searchChannel: function (searchVal){
|
||||||
return this._rpc({
|
return this._rpc({
|
||||||
model: 'mail.channel',
|
model: 'mail.channel',
|
||||||
method: 'channel_search_to_join',
|
method: 'channel_search_to_join',
|
||||||
args: [search_val]
|
args: [searchVal]
|
||||||
})
|
})
|
||||||
.then(function (result){
|
.then(function (result){
|
||||||
var values = [];
|
var values = [];
|
||||||
@@ -492,27 +507,27 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {integer} message_id
|
* @param {integer} messageID
|
||||||
*/
|
*/
|
||||||
_selectMessage: function (message_id) {
|
_selectMessage: function (messageID) {
|
||||||
this.$el.addClass('o_mail_selection_mode');
|
this.$el.addClass('o_mail_selection_mode');
|
||||||
var message = chat_manager.get_message(message_id);
|
var message = this._getMessage(messageID);
|
||||||
this.selected_message = message;
|
this.selected_message = message;
|
||||||
var subject = "Re: " + message.record_name;
|
var subject = "Re: " + message.record_name;
|
||||||
this.extended_composer.set_subject(subject);
|
this.extendedComposer.set_subject(subject);
|
||||||
|
|
||||||
if (this.channel.type !== 'static') {
|
if (this.channel.type !== 'static') {
|
||||||
this.basic_composer.do_hide();
|
this.basicComposer.do_hide();
|
||||||
}
|
}
|
||||||
this.extended_composer.do_show();
|
this.extendedComposer.do_show();
|
||||||
|
|
||||||
this.thread.scroll_to({id: message_id, duration: 200, only_if_necessary: true});
|
this.thread.scroll_to({id: messageID, duration: 200, only_if_necessary: true});
|
||||||
this.extended_composer.focus('body');
|
this.extendedComposer.focus('body');
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {Object} channel
|
* @param {Object} channel
|
||||||
* @returns {Deferred}
|
* @returns {$.Promise}
|
||||||
*/
|
*/
|
||||||
_setChannel: function (channel) {
|
_setChannel: function (channel) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -534,9 +549,8 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
return this._fetchAndRenderThread().then(function () {
|
return this._fetchAndRenderThread().then(function () {
|
||||||
// Mark channel's messages as read and clear needactions
|
// Mark channel's messages as read and clear needactions
|
||||||
if (channel.type !== 'static') {
|
if (channel.type !== 'static') {
|
||||||
chat_manager.mark_channel_as_seen(channel);
|
self.call('chat_manager', 'markChannelAsSeen', channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore scroll position and composer of the new current channel
|
// Restore scroll position and composer of the new current channel
|
||||||
self._restoreChannelState();
|
self._restoreChannelState();
|
||||||
|
|
||||||
@@ -556,25 +570,26 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Binds handlers on chat_manager events
|
* Binds handlers on chatManager events
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_startListening: function () {
|
_startListening: function () {
|
||||||
chat_manager.bus.on('open_channel', this, this._setChannel);
|
var chatBus = this.call('chat_manager', 'getChatBus');
|
||||||
chat_manager.bus.on('new_message', this, this._onNewMessage);
|
chatBus.on('open_channel', this, this._setChannel);
|
||||||
chat_manager.bus.on('update_message', this, this._onMessageUpdated);
|
chatBus.on('new_message', this, this._onNewMessage);
|
||||||
chat_manager.bus.on('new_channel', this, this._onNewChannel);
|
chatBus.on('update_message', this, this._onMessageUpdated);
|
||||||
chat_manager.bus.on('anyone_listening', this, function (channel, query) {
|
chatBus.on('new_channel', this, this._onNewChannel);
|
||||||
|
chatBus.on('anyone_listening', this, function (channel, query) {
|
||||||
query.is_displayed = query.is_displayed ||
|
query.is_displayed = query.is_displayed ||
|
||||||
(channel.id === this.channel.id && this.thread.is_at_bottom());
|
(channel.id === this.channel.id && this.thread.is_at_bottom());
|
||||||
});
|
});
|
||||||
chat_manager.bus.on('unsubscribe_from_channel', this, this._onChannelLeft);
|
chatBus.on('unsubscribe_from_channel', this, this._onChannelLeft);
|
||||||
chat_manager.bus.on('update_needaction', this, this.throttledUpdateChannels);
|
chatBus.on('update_needaction', this, this.throttledUpdateChannels);
|
||||||
chat_manager.bus.on('update_starred', this, this.throttledUpdateChannels);
|
chatBus.on('update_starred', this, this.throttledUpdateChannels);
|
||||||
chat_manager.bus.on('update_channel_unread_counter', this, this.throttledUpdateChannels);
|
chatBus.on('update_channel_unread_counter', this, this.throttledUpdateChannels);
|
||||||
chat_manager.bus.on('update_dm_presence', this, this.throttledUpdateChannels);
|
chatBus.on('update_dm_presence', this, this.throttledUpdateChannels);
|
||||||
chat_manager.bus.on('activity_updated', this, this.throttledUpdateChannels);
|
chatBus.on('activity_updated', this, this.throttledUpdateChannels);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Stores the scroll position and composer state of the current channel
|
* Stores the scroll position and composer state of the current channel
|
||||||
@@ -583,7 +598,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
*/
|
*/
|
||||||
_storeChannelState: function () {
|
_storeChannelState: function () {
|
||||||
if (this.channel) {
|
if (this.channel) {
|
||||||
this.channels_scrolltop[this.channel.id] = this.thread.get_scrolltop();
|
this.channelsScrolltop[this.channel.id] = this.thread.get_scrolltop();
|
||||||
this._storeComposerState(this.channel);
|
this._storeComposerState(this.channel);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -595,7 +610,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
if (channel.type === 'static') {
|
if (channel.type === 'static') {
|
||||||
return; // no composer in static channels
|
return; // no composer in static channels
|
||||||
}
|
}
|
||||||
var composer = channel.mass_mailing ? this.extended_composer : this.basic_composer;
|
var composer = channel.mass_mailing ? this.extendedComposer : this.basicComposer;
|
||||||
this.composerStates[channel.uuid] = composer.getState();
|
this.composerStates[channel.uuid] = composer.getState();
|
||||||
composer.clear_composer();
|
composer.clear_composer();
|
||||||
},
|
},
|
||||||
@@ -603,11 +618,11 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_unselectMessage: function () {
|
_unselectMessage: function () {
|
||||||
this.basic_composer.do_toggle(this.channel.type !== 'static' && !this.channel.mass_mailing);
|
this.basicComposer.do_toggle(this.channel.type !== 'static' && !this.channel.mass_mailing);
|
||||||
this.extended_composer.do_toggle(this.channel.type !== 'static' && this.channel.mass_mailing);
|
this.extendedComposer.do_toggle(this.channel.type !== 'static' && this.channel.mass_mailing);
|
||||||
|
|
||||||
if (!config.device.touch) {
|
if (!config.device.touch) {
|
||||||
var composer = this.channel.mass_mailing ? this.extended_composer : this.basic_composer;
|
var composer = this.channel.mass_mailing ? this.extendedComposer : this.basicComposer;
|
||||||
composer.focus();
|
composer.focus();
|
||||||
}
|
}
|
||||||
this.$el.removeClass('o_mail_selection_mode');
|
this.$el.removeClass('o_mail_selection_mode');
|
||||||
@@ -623,11 +638,11 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
var self = this;
|
var self = this;
|
||||||
var $sidebar = this._renderSidebar({
|
var $sidebar = this._renderSidebar({
|
||||||
active_channel_id: this.channel ? this.channel.id: undefined,
|
active_channel_id: this.channel ? this.channel.id: undefined,
|
||||||
channels: chat_manager.get_channels(),
|
channels: this.call('chat_manager', 'getChannels'),
|
||||||
needaction_counter: chat_manager.get_needaction_counter(),
|
needaction_counter: this.call('chat_manager', 'getNeedactionCounter'),
|
||||||
starred_counter: chat_manager.get_starred_counter(),
|
starred_counter: this.call('chat_manager', 'getStarredCounter'),
|
||||||
});
|
});
|
||||||
this.$(".o_mail_chat_sidebar").html($sidebar.contents());
|
self.$(".o_mail_chat_sidebar").html($sidebar.contents());
|
||||||
_.each(['dm', 'public', 'private'], function (type) {
|
_.each(['dm', 'public', 'private'], function (type) {
|
||||||
var $input = self.$('.o_mail_add_channel[data-type=' + type + '] input');
|
var $input = self.$('.o_mail_add_channel[data-type=' + type + '] input');
|
||||||
self._prepareAddChannelInput($input, type);
|
self._prepareAddChannelInput($input, type);
|
||||||
@@ -729,27 +744,29 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
*/
|
*/
|
||||||
_onChannelClicked: function (event) {
|
_onChannelClicked: function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var channel_id = $(event.currentTarget).data('channel-id');
|
var channelID = $(event.currentTarget).data('channel-id');
|
||||||
this._setChannel(chat_manager.get_channel(channel_id));
|
var channel = this.call('chat_manager', 'getChannel', channelID);
|
||||||
|
this._setChannel(channel);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {integer || string} channel_id
|
* @param {integer|string} channelID
|
||||||
*/
|
*/
|
||||||
_onChannelLeft: function (channel_id) {
|
_onChannelLeft: function (channelID) {
|
||||||
if (this.channel.id === channel_id) {
|
if (this.channel.id === channelID) {
|
||||||
this._setChannel(chat_manager.get_channel("channel_inbox"));
|
var channel = this.call('chat_manager', 'getChannel', 'channel_inbox');
|
||||||
|
this._setChannel(channel);
|
||||||
}
|
}
|
||||||
this._updateChannels();
|
this._updateChannels();
|
||||||
delete this.channels_scrolltop[channel_id];
|
delete this.channelsScrolltop[channelID];
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onComposerFocused: function () {
|
_onComposerFocused: function () {
|
||||||
var composer = this.channel.mass_mailing ? this.extended_composer : this.basic_composer;
|
var composer = this.channel.mass_mailing ? this.extendedComposer : this.basicComposer;
|
||||||
var commands = chat_manager.get_commands(this.channel);
|
var commands = this.call('chat_manager', 'getCommands', this.channel);
|
||||||
var partners = chat_manager.get_mention_partner_suggestions(this.channel);
|
var partners = this.call('chat_manager', 'getMentionPartnerSuggestions', this.channel);
|
||||||
composer.mention_set_enabled_commands(commands);
|
composer.mention_set_enabled_commands(commands);
|
||||||
composer.mention_set_prefetched_partners(partners);
|
composer.mention_set_prefetched_partners(partners);
|
||||||
},
|
},
|
||||||
@@ -757,7 +774,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onMarkAllReadClicked: function () {
|
_onMarkAllReadClicked: function () {
|
||||||
chat_manager.mark_all_as_read(this.channel, this.domain);
|
this.call('chat_manager', 'markAllAsRead', this.channel, this.domain);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -766,16 +783,20 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
*/
|
*/
|
||||||
_onMessageUpdated: function (message, type) {
|
_onMessageUpdated: function (message, type) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var current_channel_id = this.channel.id;
|
var currentChannelID = this.channel.id;
|
||||||
if ((current_channel_id === "channel_starred" && !message.is_starred) ||
|
if ((currentChannelID === "channel_starred" && !message.is_starred) ||
|
||||||
(current_channel_id === "channel_inbox" && !message.is_needaction)) {
|
(currentChannelID === "channel_inbox" && !message.is_needaction)) {
|
||||||
chat_manager.get_messages({channel_id: this.channel.id, domain: this.domain}).then(function (messages) {
|
this.call('chat_manager', 'getMessages', {
|
||||||
var options = self._getThreadRenderingOptions(messages);
|
channelID: this.channel.id,
|
||||||
self.thread.remove_message_and_render(message.id, messages, options).then(function () {
|
domain: this.domain
|
||||||
self._updateButtonStatus(messages.length === 0, type);
|
}).then(function (messages) {
|
||||||
|
var options = self._getThreadRenderingOptions(messages);
|
||||||
|
self.thread.remove_message_and_render(message.id, messages, options).then(function () {
|
||||||
|
self._updateButtonStatus(messages.length === 0, type);
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} else if (_.contains(message.channel_ids, currentChannelID)) {
|
||||||
} else if (_.contains(message.channel_ids, current_channel_id)) {
|
|
||||||
this._fetchAndRenderThread();
|
this._fetchAndRenderThread();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -797,9 +818,8 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
var self = this;
|
var self = this;
|
||||||
if (_.contains(message.channel_ids, this.channel.id)) {
|
if (_.contains(message.channel_ids, this.channel.id)) {
|
||||||
if (this.channel.type !== 'static' && this.thread.is_at_bottom()) {
|
if (this.channel.type !== 'static' && this.thread.is_at_bottom()) {
|
||||||
chat_manager.mark_channel_as_seen(this.channel);
|
this.call('chat_manager', 'markChannelAsSeen', this.channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
var should_scroll = this.thread.is_at_bottom();
|
var should_scroll = this.thread.is_at_bottom();
|
||||||
this._fetchAndRenderThread().then(function () {
|
this._fetchAndRenderThread().then(function () {
|
||||||
if (should_scroll) {
|
if (should_scroll) {
|
||||||
@@ -810,7 +830,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
// Re-render sidebar to indicate that there is a new message in the corresponding channels
|
// Re-render sidebar to indicate that there is a new message in the corresponding channels
|
||||||
this._updateChannels();
|
this._updateChannels();
|
||||||
// Dump scroll position of channels in which the new message arrived
|
// Dump scroll position of channels in which the new message arrived
|
||||||
this.channels_scrolltop = _.omit(this.channels_scrolltop, message.channel_ids);
|
this.channelsScrolltop = _.omit(this.channelsScrolltop, message.channel_ids);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -818,18 +838,14 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
*/
|
*/
|
||||||
_onPostMessage: function (message) {
|
_onPostMessage: function (message) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var options = this.selected_message ? {} : {channel_id: this.channel.id};
|
var options = this.selected_message ? {} : {channelID: this.channel.id};
|
||||||
if (this.selected_message) {
|
if (this.selected_message) {
|
||||||
message.subtype = this.selected_message.is_note ? 'mail.mt_note': 'mail.mt_comment';
|
message.subtype = this.selected_message.is_note ? 'mail.mt_note': 'mail.mt_comment';
|
||||||
message.subtype_id = false;
|
message.subtype_id = false;
|
||||||
message.message_type = 'comment';
|
message.message_type = 'comment';
|
||||||
message.content_subtype = 'html';
|
message.content_subtype = 'html';
|
||||||
|
|
||||||
options.model = this.selected_message.model;
|
|
||||||
options.res_id = this.selected_message.res_id;
|
|
||||||
}
|
}
|
||||||
chat_manager
|
this.call('chat_manager', 'postMessage', message, options)
|
||||||
.post_message(message, options)
|
|
||||||
.then(function () {
|
.then(function () {
|
||||||
if (self.selected_message) {
|
if (self.selected_message) {
|
||||||
self._renderSnackbar('mail.chat.MessageSentSnackbar', {record_name: self.selected_message.record_name}, 5000);
|
self._renderSnackbar('mail.chat.MessageSentSnackbar', {record_name: self.selected_message.record_name}, 5000);
|
||||||
@@ -890,10 +906,10 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
if (def) {
|
if (def) {
|
||||||
def.then(function (value) {
|
def.then(function (value) {
|
||||||
if (value === 'denied') {
|
if (value === 'denied') {
|
||||||
utils.send_notification(self, _t('Permission denied'),
|
self._sendNotification(self, _t('Permission denied'),
|
||||||
_t('Odoo will not have the permission to send native notifications on this device.'));
|
_t('Odoo will not have the permission to send native notifications on this device.'));
|
||||||
} else {
|
} else {
|
||||||
utils.send_notification(self, _t('Permission granted'),
|
self._sendNotification(self, _t('Permission granted'),
|
||||||
_t('Odoo has now the permission to send you native notifications on this device.'));
|
_t('Odoo has now the permission to send you native notifications on this device.'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -936,25 +952,26 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
|||||||
*/
|
*/
|
||||||
_onUnpinChannel: function (event) {
|
_onUnpinChannel: function (event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
var channel_id = $(event.target).data("channel-id");
|
var channelID = $(event.target).data("channel-id");
|
||||||
chat_manager.unsubscribe(chat_manager.get_channel(channel_id));
|
var channel = this.call('chat_manager', 'getChannel', channelID);
|
||||||
|
this.call('chat_manager', 'unsubscribe', channel);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onUnstarAllClicked: function () {
|
_onUnstarAllClicked: function () {
|
||||||
chat_manager.unstar_all();
|
this.call('chat_manager', 'unstarAll');
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onUnsubscribeButtonClicked: function () {
|
_onUnsubscribeButtonClicked: function () {
|
||||||
chat_manager.unsubscribe(this.channel);
|
this._unsubscribe(this.channel);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
core.action_registry.add('mail.chat.instant_messaging', ChatAction);
|
core.action_registry.add('mail.chat.instant_messaging', Discuss);
|
||||||
|
|
||||||
return ChatAction;
|
return Discuss;
|
||||||
|
|
||||||
});
|
});
|
||||||
+18
-20
@@ -1,12 +1,10 @@
|
|||||||
odoo.define('mail.chat_client_action_mobile', function (require) {
|
odoo.define('mail.chat_discuss_mobile', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ChatAction = require('mail.chat_client_action');
|
var Discuss = require('mail.chat_discuss');
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
|
|
||||||
var config = require('web.config');
|
var config = require('web.config');
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var session = require('web.session');
|
|
||||||
|
|
||||||
var QWeb = core.qweb;
|
var QWeb = core.qweb;
|
||||||
|
|
||||||
@@ -14,10 +12,10 @@ if (!config.device.isMobile) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatAction.include({
|
Discuss.include({
|
||||||
template: 'mail.client_action_mobile',
|
template: 'mail.discuss_mobile',
|
||||||
need_control_panel: false, // in mobile, we use a custom control panel
|
need_control_panel: false, // in mobile, we use a custom control panel
|
||||||
events: _.extend(ChatAction.prototype.events, {
|
events: _.extend(Discuss.prototype.events, {
|
||||||
'click .o_mail_mobile_tab': '_onMobileTabClicked',
|
'click .o_mail_mobile_tab': '_onMobileTabClicked',
|
||||||
'click .o_channel_inbox_item': '_onMobileInboxButtonClicked',
|
'click .o_channel_inbox_item': '_onMobileInboxButtonClicked',
|
||||||
'click .o_mail_channel_preview': '_onMobileChannelClicked',
|
'click .o_mail_channel_preview': '_onMobileChannelClicked',
|
||||||
@@ -103,10 +101,11 @@ ChatAction.include({
|
|||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @private
|
* @private
|
||||||
|
* @param {Object} channel
|
||||||
*/
|
*/
|
||||||
_setChannel: function (channel) {
|
_setChannel: function (channel) {
|
||||||
if (channel.type !== 'static') {
|
if (channel.type !== 'static') {
|
||||||
chat_manager.detach_channel(channel);
|
this.call('chat_manager', 'detachChannel', channel.id);
|
||||||
} else {
|
} else {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
}
|
}
|
||||||
@@ -170,8 +169,9 @@ ChatAction.include({
|
|||||||
if (inInbox) {
|
if (inInbox) {
|
||||||
def = this._fetchAndRenderThread();
|
def = this._fetchAndRenderThread();
|
||||||
} else {
|
} else {
|
||||||
var channels = _.where(chat_manager.get_channels(), {type: type});
|
var allChannels = this.call('chat_manager', 'getChannels');
|
||||||
def = chat_manager.get_channels_preview(channels);
|
var channels = _.where(allChannels, {type: type});
|
||||||
|
def = this.call('chat_manager', 'getChannelsPreview', channels);
|
||||||
}
|
}
|
||||||
return $.when(def).then(function (channelsPreview) {
|
return $.when(def).then(function (channelsPreview) {
|
||||||
// update content
|
// update content
|
||||||
@@ -179,19 +179,15 @@ ChatAction.include({
|
|||||||
if (!previouslyInInbox) {
|
if (!previouslyInInbox) {
|
||||||
self.$('.o_mail_chat_tab_pane').remove();
|
self.$('.o_mail_chat_tab_pane').remove();
|
||||||
self.$mainContent.append(self.thread.$el);
|
self.$mainContent.append(self.thread.$el);
|
||||||
self.$mainContent.append(self.extended_composer.$el);
|
self.$mainContent.append(self.extendedComposer.$el);
|
||||||
}
|
}
|
||||||
self._restoreChannelState();
|
self._restoreChannelState();
|
||||||
} else {
|
} else {
|
||||||
self.thread.$el.detach();
|
self.thread.$el.detach();
|
||||||
self.extended_composer.$el.detach();
|
self.extendedComposer.$el.detach();
|
||||||
var $content = $(QWeb.render("mail.chat.MobileTabPane", {
|
var $content = $(QWeb.render("mail.chat.MobileTabPane", {
|
||||||
channels: channelsPreview,
|
channels: channelsPreview,
|
||||||
get_message_body_preview: chat_manager.get_message_body_preview,
|
|
||||||
moment: moment,
|
|
||||||
partner_id: session.partner_id,
|
|
||||||
type: type,
|
type: type,
|
||||||
widget: self,
|
|
||||||
}));
|
}));
|
||||||
self._prepareAddChannelInput($content.find('.o_mail_add_channel input'), type);
|
self._prepareAddChannelInput($content.find('.o_mail_add_channel input'), type);
|
||||||
self.$mainContent.html($content);
|
self.$mainContent.html($content);
|
||||||
@@ -252,7 +248,8 @@ ChatAction.include({
|
|||||||
* @param {MouseEvent}
|
* @param {MouseEvent}
|
||||||
*/
|
*/
|
||||||
_onMobileInboxButtonClicked: function (event) {
|
_onMobileInboxButtonClicked: function (event) {
|
||||||
this._setChannel(chat_manager.get_channel($(event.currentTarget).data('type')));
|
var channel = this.call('chat_manager', 'getChannel', $(event.currentTarget).data('type'));
|
||||||
|
this._setChannel(channel);
|
||||||
this._updateContent(this.channel.id);
|
this._updateContent(this.channel.id);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -264,7 +261,8 @@ ChatAction.include({
|
|||||||
_onMobileTabClicked: function (event) {
|
_onMobileTabClicked: function (event) {
|
||||||
var type = $(event.currentTarget).data('type');
|
var type = $(event.currentTarget).data('type');
|
||||||
if (type === 'channel_inbox') {
|
if (type === 'channel_inbox') {
|
||||||
this._setChannel(chat_manager.get_channel('channel_inbox'));
|
var channel = this.call('chat_manager', 'getChannel', 'channel_inbox');
|
||||||
|
this._setChannel(channel);
|
||||||
}
|
}
|
||||||
this._updateContent(type);
|
this._updateContent(type);
|
||||||
},
|
},
|
||||||
@@ -275,8 +273,8 @@ ChatAction.include({
|
|||||||
* @param {MouseEvent}
|
* @param {MouseEvent}
|
||||||
*/
|
*/
|
||||||
_onMobileChannelClicked: function (event) {
|
_onMobileChannelClicked: function (event) {
|
||||||
var channelId = $(event.currentTarget).data("channel_id");
|
var channelID = $(event.currentTarget).data("channel_id");
|
||||||
chat_manager.detach_channel(chat_manager.get_channel(channelId));
|
this.call('chat_manager', 'detachChannel', channelID);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
odoo.define('mail.ExtendedChatWindow', function (require) {
|
odoo.define('mail.ExtendedChatWindow', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var core = require('web.core');
|
|
||||||
|
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
var ChatWindow = require('mail.ChatWindow');
|
var ChatWindow = require('mail.ChatWindow');
|
||||||
var composer = require('mail.composer');
|
var composer = require('mail.composer');
|
||||||
|
|
||||||
@@ -19,27 +16,29 @@ return ChatWindow.extend({
|
|||||||
this.$el.addClass('o_thread_less');
|
this.$el.addClass('o_thread_less');
|
||||||
this.$('.o_chat_search_input input')
|
this.$('.o_chat_search_input input')
|
||||||
.autocomplete({
|
.autocomplete({
|
||||||
source: function(request, response) {
|
source: function (request, response) {
|
||||||
chat_manager.search_partner(request.term, 10).done(response);
|
self.call('chat_manager', 'searchPartner', request.term, 10).done(response);
|
||||||
},
|
},
|
||||||
select: function(event, ui) {
|
select: function (event, ui) {
|
||||||
self.trigger('open_dm_session', ui.item.id);
|
self.trigger('open_dm_session', ui.item.id);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.focus();
|
.focus();
|
||||||
} else if (!self.options.input_less) {
|
} else if (!self.options.input_less) {
|
||||||
var basic_composer = new composer.BasicComposer(self, {mention_partners_restricted: true, isMini: true});
|
var basicComposer = new composer.BasicComposer(self, {mention_partners_restricted: true, isMini: true});
|
||||||
basic_composer.on('post_message', self, function (message) {
|
basicComposer.on('post_message', self, function (message) {
|
||||||
this.trigger('post_message', message, this.channel_id);
|
self.call('chat_manager', 'postMessage', message, {
|
||||||
|
channelID: self.channel_id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
basic_composer.once('input_focused', self, function () {
|
basicComposer.once('input_focused', self, function () {
|
||||||
var channel = chat_manager.get_channel(this.channel_id);
|
var channel = self.call('chat_manager', 'getChannel', self.channel_id);
|
||||||
var commands = chat_manager.get_commands(channel);
|
var commands = self.call('chat_manager', 'getCommands', channel);
|
||||||
var partners = chat_manager.get_mention_partner_suggestions(channel);
|
var partners = self.call('chat_manager', 'getMentionPartnerSuggestions', channel);
|
||||||
basic_composer.mention_set_enabled_commands(commands);
|
basicComposer.mention_set_enabled_commands(commands);
|
||||||
basic_composer.mention_set_prefetched_partners(partners);
|
basicComposer.mention_set_prefetched_partners(partners);
|
||||||
});
|
});
|
||||||
def = basic_composer.replace(self.$('.o_chat_composer'));
|
def = basicComposer.replace(self.$('.o_chat_composer'));
|
||||||
}
|
}
|
||||||
return $.when(this._super(), def);
|
return $.when(this._super(), def);
|
||||||
},
|
},
|
||||||
@@ -49,7 +48,8 @@ return ChatWindow.extend({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
on_reverse_breadcrumb: function () {
|
on_reverse_breadcrumb: function () {
|
||||||
chat_manager.bus.trigger('client_action_open', false);
|
var chatBus = this.call('chat_manager', 'getChatBus');
|
||||||
|
chatBus.trigger('discuss_open', false);
|
||||||
},
|
},
|
||||||
on_click_expand: _.debounce(function (event) {
|
on_click_expand: _.debounce(function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,532 @@
|
|||||||
|
odoo.define('mail.ChatWindowManager', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var ExtendedChatWindow = require('mail.ExtendedChatWindow');
|
||||||
|
|
||||||
|
var AbstractService = require('web.AbstractService');
|
||||||
|
var config = require('web.config');
|
||||||
|
var core = require('web.core');
|
||||||
|
var utils = require('web.utils');
|
||||||
|
var web_client = require('web.web_client');
|
||||||
|
|
||||||
|
var _t = core._t;
|
||||||
|
var QWeb = core.qweb;
|
||||||
|
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
var CHAT_WINDOW_WIDTH = 325 + 5; // 5 pixels between windows
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service handles chat channels that are displayed in "chat window" mode
|
||||||
|
*/
|
||||||
|
var ChatWindowManager = AbstractService.extend({
|
||||||
|
name: 'chat_window_manager',
|
||||||
|
dependencies: ['chat_manager'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
init: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
|
||||||
|
this.chatSessions = [];
|
||||||
|
this.newChatSession;
|
||||||
|
this.displayState = {
|
||||||
|
chatWindowsHidden: false, // chat windows aren't displayed when the client action is open
|
||||||
|
hiddenSessions: [],
|
||||||
|
hiddenUnreadCounter: 0, // total number of unread msgs in hidden chat windows
|
||||||
|
nbSlots: 0,
|
||||||
|
spaceLeft: 0,
|
||||||
|
windowsDropdownIsOpen: false, // used to keep dropdown open when closing chat windows
|
||||||
|
};
|
||||||
|
var chatReady = this.call('chat_manager', 'isReady');
|
||||||
|
chatReady.then(function () {
|
||||||
|
var channels = self.call('chat_manager', 'getChannels');
|
||||||
|
_.each(channels, function (channel) {
|
||||||
|
if (channel.is_detached) {
|
||||||
|
self.openChat(channel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var chatBus = this.call('chat_manager', 'getChatBus');
|
||||||
|
chatBus.on('update_message', this, this._onUpdateMessage);
|
||||||
|
chatBus.on('new_message', this, this._onNewMessage);
|
||||||
|
chatBus.on('anyone_listening', this, this._onAnyoneListening);
|
||||||
|
chatBus.on('unsubscribe_from_channel', this, this._onUnsubscribeFromChannel);
|
||||||
|
chatBus.on('update_channel_unread_counter', this, this._onUpdateChannelUnreadCounter);
|
||||||
|
chatBus.on('update_dm_presence', this, this._onUpdateDmPresence);
|
||||||
|
chatBus.on('detach_channel', this, this._onDetachChannel);
|
||||||
|
chatBus.on('discuss_open', this, this._onDiscussOpen);
|
||||||
|
|
||||||
|
core.bus.on('resize', this, _.debounce(this._repositionWindows.bind(this), 100));
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Public
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} chatSession
|
||||||
|
* @param {integer} chatSession.id
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
closeChat: function (chatSession, options) {
|
||||||
|
var session = _.find(this.chatSessions, {id: chatSession.id});
|
||||||
|
if (session) {
|
||||||
|
this._closeChat(session, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @param {Object} session
|
||||||
|
* @param {integer} session.id
|
||||||
|
* @param {boolean} [session.is_chat] true for dm? => server_type="chat" & type="dm"...
|
||||||
|
* @param {boolean} [session.mass_mailing]
|
||||||
|
* @param {string} [session.status] e.g. 'offline'
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {boolean} [options.passively] if set to true, open the chat window
|
||||||
|
* without focusing the input and marking messages as read if it is not
|
||||||
|
* open yet, and do nothing otherwise
|
||||||
|
*/
|
||||||
|
openChat: function (session, options) {
|
||||||
|
var self = this;
|
||||||
|
if (!session) {
|
||||||
|
this._openChatWithoutSession();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
var chatSession = _.findWhere(this.chatSessions, {id: session.id});
|
||||||
|
if (!chatSession) {
|
||||||
|
var prefix = !session.is_chat ? "#" : "";
|
||||||
|
var windowOptions = {
|
||||||
|
autofocus: !options.passively,
|
||||||
|
input_less: session.mass_mailing,
|
||||||
|
status: session.status,
|
||||||
|
};
|
||||||
|
chatSession = {
|
||||||
|
id: session.id,
|
||||||
|
uuid: session.uuid,
|
||||||
|
name: session.name,
|
||||||
|
keep_unread: options.passively, // don't automatically mark unread messages as seen
|
||||||
|
window: new ExtendedChatWindow(web_client, session.id, prefix + session.name, session.is_folded, session.unread_counter, windowOptions),
|
||||||
|
};
|
||||||
|
chatSession.window.on("close_chat_session", null, function () {
|
||||||
|
self._closeChat(chatSession);
|
||||||
|
self.call('chat_manager', 'closeChatSession', chatSession.id);
|
||||||
|
});
|
||||||
|
chatSession.window.on("toggle_star_status", null, function (messageID) {
|
||||||
|
self.call('chat_manager', 'toggleStarStatus', messageID);
|
||||||
|
});
|
||||||
|
|
||||||
|
chatSession.window.on("fold_channel", null, function (channelID, folded) {
|
||||||
|
self.call('chat_manager', 'foldChannel', channelID, folded);
|
||||||
|
});
|
||||||
|
|
||||||
|
chatSession.window.on("post_message", null, function (message, channelID) {
|
||||||
|
self.call('chat_manager', 'postMessage', message, {
|
||||||
|
channelID: channelID
|
||||||
|
}).then(function () {
|
||||||
|
chatSession.window.thread.scroll_to();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
chatSession.window.on("redirect", null, function (resModel, resID) {
|
||||||
|
self.call('chat_manager', 'redirect', resModel, resID, self.openChat.bind(self));
|
||||||
|
});
|
||||||
|
chatSession.window.on("redirect_to_channel", null, function (channelID) {
|
||||||
|
var session = _.findWhere(self.chatSessions, {id: channelID});
|
||||||
|
if (!session) {
|
||||||
|
self.call('chat_manager', 'joinChannel', channelID).then(function (channel) {
|
||||||
|
self.call('chat_manager', 'detachChannel', channelID);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
session.window.toggle_fold(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var removeNewChat = false;
|
||||||
|
if (options.passively) {
|
||||||
|
this.chatSessions.push(chatSession); // simply insert the window to the left
|
||||||
|
} else if (this.newChatSession && this.newChatSession.partner_id && this.newChatSession.partner_id === session.direct_partner_id) {
|
||||||
|
// the window takes the place of the 'newChatSession' window
|
||||||
|
this.chatSessions[_.indexOf(this.chatSessions, this.newChatSession)] = chatSession;
|
||||||
|
removeNewChat = true;
|
||||||
|
} else {
|
||||||
|
this._addChatSession(chatSession); // add session such that window is visible
|
||||||
|
}
|
||||||
|
|
||||||
|
chatSession.window.appendTo($('body'))
|
||||||
|
.then(function () {
|
||||||
|
self._repositionWindows({remove_new_chat: removeNewChat});
|
||||||
|
return self.call('chat_manager', 'getMessages', {channelID: chatSession.id});
|
||||||
|
}).then(function (messages) {
|
||||||
|
chatSession.window.render(messages);
|
||||||
|
chatSession.window.thread.scroll_to();
|
||||||
|
setTimeout(function () {
|
||||||
|
chatSession.window.thread.$el.on("scroll", null, _.debounce(function () {
|
||||||
|
if (!chatSession.keep_unread && chatSession.window.thread.is_at_bottom()) {
|
||||||
|
self.call('chat_manager', 'markChannelAsSeen', session);
|
||||||
|
}
|
||||||
|
}, 100));
|
||||||
|
}, 0); // setTimeout to prevent to execute handler on first scroll_to, which is asynchronous
|
||||||
|
if (options.passively) {
|
||||||
|
// mark first unread messages as seen when focusing the window, then on scroll to bottom as usual
|
||||||
|
chatSession.window.$('.o_mail_thread, .o_chat_composer').one('click', function () {
|
||||||
|
self.call('chat_manager', 'markChannelAsSeen', session);
|
||||||
|
});
|
||||||
|
} else if (!self.displayState.chatWindowsHidden && !session.is_folded) {
|
||||||
|
self.call('chat_manager', 'markChannelAsSeen', session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (!options.passively) {
|
||||||
|
if (chatSession.window.is_hidden) {
|
||||||
|
this._makeSessionVisible(chatSession);
|
||||||
|
} else if (session.is_folded !== chatSession.window.folded) {
|
||||||
|
chatSession.window.toggle_fold(session.is_folded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Called when unfolding the chat window
|
||||||
|
*
|
||||||
|
* @param {Object} channel
|
||||||
|
* @param {integer} channel.id
|
||||||
|
* @param {boolean} [channel.is_folded]
|
||||||
|
*/
|
||||||
|
toggleFoldChat: function (channel) {
|
||||||
|
var session = _.find(this.chatSessions, {id: channel.id});
|
||||||
|
if (session) {
|
||||||
|
session.window.toggle_fold(channel.is_folded);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add chatSession such that it will be the left-most visible window
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} chatSession
|
||||||
|
*/
|
||||||
|
_addChatSession: function (chatSession) {
|
||||||
|
this._computeAvailableSlots(this.chatSessions.length+1);
|
||||||
|
this.chatSessions.splice(this.displayState.nbSlots-1, 0, chatSession);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} chatSession
|
||||||
|
* @param {boolean} [chatSession.keep_unread]
|
||||||
|
* @param {Object} chatSession.window
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {boolean} [options.keep_open_if_unread]
|
||||||
|
*/
|
||||||
|
_closeChat: function (chatSession, options) {
|
||||||
|
if (options && options.keep_open_if_unread && chatSession.keep_unread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.chatSessions = _.without(this.chatSessions, chatSession);
|
||||||
|
chatSession.window.destroy();
|
||||||
|
this._repositionWindows();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_closeNewChat: function () {
|
||||||
|
this.chatSessions = _.without(this.chatSessions, this.newChatSession);
|
||||||
|
this._repositionWindows({remove_new_chat: true});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {integer} nbWindows
|
||||||
|
*/
|
||||||
|
_computeAvailableSlots: function (nbWindows) {
|
||||||
|
if (config.device.isMobile) {
|
||||||
|
this.displayState.nbSlots = 1; // one chat window full screen in mobile
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var width = window.innerWidth;
|
||||||
|
var nbSlots = Math.floor(width/CHAT_WINDOW_WIDTH);
|
||||||
|
var spaceLeft = width - (Math.min(nbSlots, nbWindows)*CHAT_WINDOW_WIDTH);
|
||||||
|
if (nbSlots < nbWindows && spaceLeft < 50) {
|
||||||
|
nbSlots--; // leave at least 50px for the hidden windows dropdown button
|
||||||
|
spaceLeft += CHAT_WINDOW_WIDTH;
|
||||||
|
}
|
||||||
|
this.displayState.nbSlots = nbSlots;
|
||||||
|
this.displayState.spaceLeft = spaceLeft;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_destroyNewChat: function () {
|
||||||
|
this.newChatSession.window.destroy();
|
||||||
|
this.newChatSession = undefined;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} session
|
||||||
|
* @param {Widget} session.window
|
||||||
|
*/
|
||||||
|
_makeSessionVisible: function (session) {
|
||||||
|
utils.swap(this.chatSessions, session, this.chatSessions[this.displayState.nbSlots-1]);
|
||||||
|
this._repositionWindows();
|
||||||
|
session.window.toggle_fold(false);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_openChatWithoutSession: function () {
|
||||||
|
var self = this;
|
||||||
|
if (!this.newChatSession) {
|
||||||
|
this.newChatSession = {
|
||||||
|
id: '_open',
|
||||||
|
window: new ExtendedChatWindow(web_client, undefined, _t('New message'), false, 0, {thread_less: true}),
|
||||||
|
};
|
||||||
|
this.newChatSession.window.on("close_chat_session", null, this._closeNewChat.bind(this));
|
||||||
|
this.newChatSession.window.on('open_dm_session', null, function (partner_id) {
|
||||||
|
self.newChatSession.partner_id = partner_id;
|
||||||
|
var dm = self.call('chat_manager', 'getDmFromPartnerID', partner_id);
|
||||||
|
if (!dm) {
|
||||||
|
self.call('chat_manager', 'openAndDetachDm', partner_id);
|
||||||
|
} else {
|
||||||
|
var dmSession = _.findWhere(self.chatSessions, {id: dm.id});
|
||||||
|
if (!dmSession) {
|
||||||
|
self.call('chat_manager', 'detachChannel', dm.id);
|
||||||
|
} else {
|
||||||
|
self._closeChat(dmSession);
|
||||||
|
dm.is_folded = false;
|
||||||
|
self.openChat(dm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._addChatSession(this.newChatSession);
|
||||||
|
this.newChatSession.window.appendTo($('body')).then(this._repositionWindows.bind(this));
|
||||||
|
} else {
|
||||||
|
if (this.newChatSession.window.is_hidden) {
|
||||||
|
this._makeSessionVisible(this.newChatSession);
|
||||||
|
} else if (this.newChatSession.window.folded) {
|
||||||
|
this.newChatSession.window.toggle_fold(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} message
|
||||||
|
* @param {integer[]} message.channel_ids
|
||||||
|
* @param {boolean} scrollBottom
|
||||||
|
*/
|
||||||
|
_updateSessions: function (message, scrollBottom) {
|
||||||
|
var self = this;
|
||||||
|
_.each(this.chatSessions, function (session) {
|
||||||
|
if (_.contains(message.channel_ids, session.id)) {
|
||||||
|
var messageVisible = !self.displayState.chatWindowsHidden && !session.window.folded &&
|
||||||
|
!session.window.is_hidden && session.window.thread.is_at_bottom();
|
||||||
|
if (messageVisible && !session.keep_unread) {
|
||||||
|
var channel = self.call('chat_manager', 'getChannel', session.id);
|
||||||
|
self.call('chat_manager', 'markChannelAsSeen', channel);
|
||||||
|
}
|
||||||
|
self.call('chat_manager', 'getMessages', {channelID: session.id}).then(function (messages) {
|
||||||
|
session.window.render(messages);
|
||||||
|
if (scrollBottom && messageVisible) {
|
||||||
|
session.window.thread.scroll_to();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_renderHiddenSessionsDropdown: function () {
|
||||||
|
var $dropdown = $(QWeb.render("mail.ChatWindowsDropdown", {
|
||||||
|
sessions: this.displayState.hiddenSessions,
|
||||||
|
open: this.displayState.windowsDropdownIsOpen,
|
||||||
|
unread_counter: this.displayState.hiddenUnreadCounter,
|
||||||
|
widget: {isMobile: config.device.isMobile},
|
||||||
|
}));
|
||||||
|
return $dropdown;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_repositionHiddenSessionsDropdown: function () {
|
||||||
|
// Unfold dropdown to the left if there is enough place
|
||||||
|
var $dropdownUL = this.displayState.$hiddenWindowsDropdown.children('ul');
|
||||||
|
if (this.displayState.spaceLeft > $dropdownUL.width() + 10) {
|
||||||
|
$dropdownUL.addClass('dropdown-menu-right');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {boolean} [options.remove_new_chat]
|
||||||
|
*/
|
||||||
|
_repositionWindows: function (options) {
|
||||||
|
var self = this;
|
||||||
|
if (options && options.remove_new_chat) {
|
||||||
|
this._destroyNewChat();
|
||||||
|
}
|
||||||
|
if (this.displayState.chatWindowsHidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._computeAvailableSlots(this.chatSessions.length);
|
||||||
|
var hiddenSessions = [];
|
||||||
|
var hiddenUnreadCounter = 0;
|
||||||
|
var nbSlots = this.displayState.nbSlots;
|
||||||
|
_.each(this.chatSessions, function (session, index) {
|
||||||
|
if (index < nbSlots) {
|
||||||
|
session.window.$el.css({right: CHAT_WINDOW_WIDTH*index, bottom: 0});
|
||||||
|
session.window.do_show();
|
||||||
|
} else {
|
||||||
|
hiddenSessions.push(session);
|
||||||
|
hiddenUnreadCounter += session.window.unread_msgs;
|
||||||
|
session.window.do_hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.displayState.hiddenSessions = hiddenSessions;
|
||||||
|
this.displayState.hiddenUnreadCounter = hiddenUnreadCounter;
|
||||||
|
|
||||||
|
if (this.displayState.$hidden_windows_dropdown) {
|
||||||
|
this.displayState.$hidden_windows_dropdown.remove();
|
||||||
|
}
|
||||||
|
if (hiddenSessions.length) {
|
||||||
|
this.displayState.$hiddenWindowsDropdown = this._renderHiddenSessionsDropdown();
|
||||||
|
var $hiddenWindowsDropdown = this.displayState.$hiddenWindowsDropdown;
|
||||||
|
$hiddenWindowsDropdown.css({right: CHAT_WINDOW_WIDTH * nbSlots, bottom: 0})
|
||||||
|
.appendTo($('body'));
|
||||||
|
this._repositionHiddenSessionsDropdown();
|
||||||
|
this.displayState.windowsDropdownIsOpen = false;
|
||||||
|
|
||||||
|
$hiddenWindowsDropdown.on('click', '.o_chat_header', function (event) {
|
||||||
|
var sessionID = $(event.currentTarget).data('session-id');
|
||||||
|
var session = _.findWhere(hiddenSessions, {id: sessionID});
|
||||||
|
if (session) {
|
||||||
|
self._makeSessionVisible(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$hiddenWindowsDropdown.on('click', '.o_chat_window_close', function (event) {
|
||||||
|
var sessionID = $(event.currentTarget).closest('.o_chat_header').data('session-id');
|
||||||
|
var session = _.findWhere(hiddenSessions, {id: sessionID});
|
||||||
|
if (session) {
|
||||||
|
session.window.on_click_close(event);
|
||||||
|
self.displayState.windowsDropdownIsOpen = true; // keep the dropdown open
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Handlers
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} channel
|
||||||
|
* @param {integer|string} channel.id
|
||||||
|
* @param {Object} query
|
||||||
|
*/
|
||||||
|
_onAnyoneListening: function (channel, query) {
|
||||||
|
_.each(this.chatSessions, function (session) {
|
||||||
|
if (channel.id === session.id && session.window.thread.is_at_bottom() && !session.window.is_hidden) {
|
||||||
|
query.is_displayed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} channel
|
||||||
|
* @param {integer} channel.id
|
||||||
|
*/
|
||||||
|
_onDetachChannel: function (channel) {
|
||||||
|
var chatSession = _.findWhere(this.chatSessions, {id: channel.id});
|
||||||
|
if (!chatSession || chatSession.window.folded) {
|
||||||
|
this.call('chat_manager', 'detachChannel', channel.id);
|
||||||
|
} else if (chatSession.window.is_hidden) {
|
||||||
|
this._makeSessionVisible(chatSession);
|
||||||
|
} else {
|
||||||
|
chatSession.window.focus_input();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {boolean} open
|
||||||
|
*/
|
||||||
|
_onDiscussOpen: function (open) {
|
||||||
|
this.displayState.chatWindowsHidden = open;
|
||||||
|
if (open) {
|
||||||
|
$('body').addClass('o_no_chat_window');
|
||||||
|
} else {
|
||||||
|
$('body').removeClass('o_no_chat_window');
|
||||||
|
this._repositionWindows();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
_onNewMessage: function (message) {
|
||||||
|
this._updateSessions(message, true);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} channel
|
||||||
|
* @param {integer} channel.unread_counter
|
||||||
|
*/
|
||||||
|
_onUpdateChannelUnreadCounter: function (channel) {
|
||||||
|
var self = this;
|
||||||
|
this.displayState.hiddenUnreadCounter = 0;
|
||||||
|
_.each(self.chatSessions, function (session) {
|
||||||
|
if (channel.id === session.id) {
|
||||||
|
session.window.update_unread(channel.unread_counter);
|
||||||
|
if (channel.unread_counter === 0) {
|
||||||
|
session.keep_unread = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (session.window.is_hidden) {
|
||||||
|
self.displayState.hiddenUnreadCounter += session.window.unread_msgs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (self.displayState.$hidden_windows_dropdown) {
|
||||||
|
self.displayState.$hidden_windows_dropdown.html(self.renderHiddenSessionsDropdown().html());
|
||||||
|
self._repositionHiddenSessionsDropdown();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} channel
|
||||||
|
* @param {string|integer} channel.id
|
||||||
|
* @param {string} channel.status e.g. 'offline'
|
||||||
|
*/
|
||||||
|
_onUpdateDmPresence: function (channel) {
|
||||||
|
_.each(this.chatSessions, function (session) {
|
||||||
|
if (channel.id === session.id) {
|
||||||
|
session.window.update_status(channel.status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
_onUpdateMessage: function (message) {
|
||||||
|
this._updateSessions(message, false);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {string|integer} channelID
|
||||||
|
*/
|
||||||
|
_onUnsubscribeFromChannel: function (channelID) {
|
||||||
|
var self = this;
|
||||||
|
_.each(self.chatSessions, function (session) {
|
||||||
|
if (channelID === session.id) {
|
||||||
|
self._closeChat(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return ChatWindowManager;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -7,8 +7,6 @@ var session = require('web.session');
|
|||||||
var SystrayMenu = require('web.SystrayMenu');
|
var SystrayMenu = require('web.SystrayMenu');
|
||||||
var Widget = require('web.Widget');
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
|
|
||||||
var QWeb = core.qweb;
|
var QWeb = core.qweb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,16 +33,20 @@ var MessagingMenu = Widget.extend({
|
|||||||
this.$filter_buttons = this.$('.o_filter_button');
|
this.$filter_buttons = this.$('.o_filter_button');
|
||||||
this.$channels_preview = this.$('.o_mail_navbar_dropdown_channels');
|
this.$channels_preview = this.$('.o_mail_navbar_dropdown_channels');
|
||||||
this.filter = false;
|
this.filter = false;
|
||||||
chat_manager.bus.on("update_needaction", this, this.update_counter);
|
var chatBus = this.call('chat_manager', 'getChatBus');
|
||||||
chat_manager.bus.on("update_channel_unread_counter", this, this.update_counter);
|
chatBus.on("update_needaction", this, this.update_counter);
|
||||||
chat_manager.is_ready.then(this.update_counter.bind(this));
|
chatBus.on("update_channel_unread_counter", this, this.update_counter);
|
||||||
|
var chatReady = this.call('chat_manager', 'isReady');
|
||||||
|
chatReady.then(this.update_counter.bind(this));
|
||||||
return this._super();
|
return this._super();
|
||||||
},
|
},
|
||||||
is_open: function () {
|
is_open: function () {
|
||||||
return this.$el.hasClass('open');
|
return this.$el.hasClass('open');
|
||||||
},
|
},
|
||||||
update_counter: function () {
|
update_counter: function () {
|
||||||
var counter = chat_manager.get_needaction_counter() + chat_manager.get_unread_conversation_counter();
|
var needactionCounter = this.call('chat_manager', 'getNeedactionCounter');
|
||||||
|
var unreadConversationCounter = this.call('chat_manager', 'getUnreadConversationCounter');
|
||||||
|
var counter = needactionCounter + unreadConversationCounter;
|
||||||
this.$('.o_notification_counter').text(counter);
|
this.$('.o_notification_counter').text(counter);
|
||||||
this.$el.toggleClass('o_no_notification', !counter);
|
this.$el.toggleClass('o_no_notification', !counter);
|
||||||
if (this.is_open()) {
|
if (this.is_open()) {
|
||||||
@@ -56,9 +58,10 @@ var MessagingMenu = Widget.extend({
|
|||||||
|
|
||||||
// Display spinner while waiting for channels preview
|
// Display spinner while waiting for channels preview
|
||||||
this.$channels_preview.html(QWeb.render('Spinner'));
|
this.$channels_preview.html(QWeb.render('Spinner'));
|
||||||
|
var chatReady = this.call('chat_manager', 'isReady');
|
||||||
chat_manager.is_ready.then(function () {
|
chatReady.then(function () {
|
||||||
var channels = _.filter(chat_manager.get_channels(), function (channel) {
|
var allChannels = self.call('chat_manager', 'getChannels');
|
||||||
|
var channels = _.filter(allChannels, function (channel) {
|
||||||
if (self.filter === 'chat') {
|
if (self.filter === 'chat') {
|
||||||
return channel.is_chat;
|
return channel.is_chat;
|
||||||
} else if (self.filter === 'channels') {
|
} else if (self.filter === 'channels') {
|
||||||
@@ -67,7 +70,7 @@ var MessagingMenu = Widget.extend({
|
|||||||
return channel.type !== 'static';
|
return channel.type !== 'static';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
chat_manager.get_messages({channel_id: 'channel_inbox'}).then(function(result) {
|
self.call('chat_manager', 'getMessages', {channelID: 'channel_inbox'}).then(function (result) {
|
||||||
var res = [];
|
var res = [];
|
||||||
_.each(result, function (message) {
|
_.each(result, function (message) {
|
||||||
message.unread_counter = 1;
|
message.unread_counter = 1;
|
||||||
@@ -82,7 +85,7 @@ var MessagingMenu = Widget.extend({
|
|||||||
if (self.filter === 'channel_inbox' || !self.filter) {
|
if (self.filter === 'channel_inbox' || !self.filter) {
|
||||||
channels = _.union(channels, res);
|
channels = _.union(channels, res);
|
||||||
}
|
}
|
||||||
chat_manager.get_channels_preview(channels).then(self._render_channels_preview.bind(self));
|
self.call('chat_manager', 'getChannelsPreview', channels).then(self._render_channels_preview.bind(self));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -105,7 +108,7 @@ var MessagingMenu = Widget.extend({
|
|||||||
this.update_channels_preview();
|
this.update_channels_preview();
|
||||||
},
|
},
|
||||||
on_click_new_message: function () {
|
on_click_new_message: function () {
|
||||||
chat_manager.bus.trigger('open_chat');
|
this.call('chat_window_manager', 'openChat');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
@@ -133,13 +136,13 @@ var MessagingMenu = Widget.extend({
|
|||||||
this.do_action('mail.mail_channel_action_client_chat', {clear_breadcrumbs: true})
|
this.do_action('mail.mail_channel_action_client_chat', {clear_breadcrumbs: true})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
self.trigger_up('hide_home_menu'); // we cannot 'go back to previous page' otherwise
|
self.trigger_up('hide_home_menu'); // we cannot 'go back to previous page' otherwise
|
||||||
core.bus.trigger('change_menu_section', chat_manager.get_discuss_menu_id());
|
core.bus.trigger('change_menu_section', self.call('chat_manager', 'getDiscussMenuID').bind(self));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var channel = chat_manager.get_channel(channelID);
|
var channel = this.call('chat_manager', 'getChannel', channelID);
|
||||||
if (channel) {
|
if (channel) {
|
||||||
chat_manager.open_channel(channel);
|
self.call('chat_manager', 'openChannel', channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -156,8 +159,10 @@ var ActivityMenu = Widget.extend({
|
|||||||
},
|
},
|
||||||
start: function () {
|
start: function () {
|
||||||
this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels');
|
this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels');
|
||||||
chat_manager.bus.on("activity_updated", this, this._updateCounter);
|
var chatBus = this.call('chat_manager', 'getChatBus');
|
||||||
chat_manager.is_ready.then(this._updateCounter.bind(this));
|
chatBus.on("activity_updated", this, this._updateCounter);
|
||||||
|
var chatReady = this.call('chat_manager', 'isReady');
|
||||||
|
chatReady.then(this._updateCounter.bind(this));
|
||||||
this._updateActivityPreview();
|
this._updateActivityPreview();
|
||||||
return this._super();
|
return this._super();
|
||||||
},
|
},
|
||||||
@@ -168,7 +173,7 @@ var ActivityMenu = Widget.extend({
|
|||||||
* Make RPC and get current user's activity details
|
* Make RPC and get current user's activity details
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_getActivityData: function(){
|
_getActivityData: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
return self._rpc({
|
return self._rpc({
|
||||||
@@ -179,7 +184,7 @@ var ActivityMenu = Widget.extend({
|
|||||||
},
|
},
|
||||||
}).then(function (data) {
|
}).then(function (data) {
|
||||||
self.activities = data;
|
self.activities = data;
|
||||||
self.activityCounter = _.reduce(data, function(total_count, p_data){ return total_count + p_data.total_count; }, 0);
|
self.activityCounter = _.reduce(data, function (total_count, p_data) { return total_count + p_data.total_count; }, 0);
|
||||||
self.$('.o_notification_counter').text(self.activityCounter);
|
self.$('.o_notification_counter').text(self.activityCounter);
|
||||||
self.$el.toggleClass('o_no_notification', !self.activityCounter);
|
self.$el.toggleClass('o_no_notification', !self.activityCounter);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ var Thread = Widget.extend({
|
|||||||
|
|
||||||
this.attachments = _.uniq(_.flatten(_.map(messages, 'attachment_ids')));
|
this.attachments = _.uniq(_.flatten(_.map(messages, 'attachment_ids')));
|
||||||
|
|
||||||
_.each(msgs, function(msg) {
|
_.each(msgs, function (msg) {
|
||||||
var $msg = self.$('.o_thread_message[data-message-id="'+ msg.id +'"]');
|
var $msg = self.$('.o_thread_message[data-message-id="'+ msg.id +'"]');
|
||||||
$msg.find('.o_mail_timestamp').data('date', msg.date);
|
$msg.find('.o_mail_timestamp').data('date', msg.date);
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ var Thread = Widget.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!this.update_timestamps_interval) {
|
if (!this.update_timestamps_interval) {
|
||||||
this.update_timestamps_interval = setInterval(function() {
|
this.update_timestamps_interval = setInterval(function () {
|
||||||
self.update_timestamps();
|
self.update_timestamps();
|
||||||
}, 1000*60);
|
}, 1000*60);
|
||||||
}
|
}
|
||||||
@@ -172,17 +172,17 @@ var Thread = Widget.extend({
|
|||||||
// nodeType 1: element_node
|
// nodeType 1: element_node
|
||||||
// nodeType 3: text_node
|
// nodeType 3: text_node
|
||||||
var $children = $element.contents()
|
var $children = $element.contents()
|
||||||
.filter(function() {
|
.filter(function () {
|
||||||
return this.nodeType === 1 || this.nodeType === 3 && this.nodeValue.trim();
|
return this.nodeType === 1 || this.nodeType === 3 && this.nodeValue.trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
_.each($children, function(child) {
|
_.each($children, function (child) {
|
||||||
var $child = $(child);
|
var $child = $(child);
|
||||||
|
|
||||||
// Hide Text nodes if "stopSpelling"
|
// Hide Text nodes if "stopSpelling"
|
||||||
if (child.nodeType === 3 && $child.prevAll("[id*='stopSpelling']").length > 0) {
|
if (child.nodeType === 3 && $child.prevAll("[id*='stopSpelling']").length > 0) {
|
||||||
// Convert Text nodes to Element nodes
|
// Convert Text nodes to Element nodes
|
||||||
var $child = $('<span>', {
|
$child = $('<span>', {
|
||||||
text: child.textContent,
|
text: child.textContent,
|
||||||
"data-o-mail-quote": "1",
|
"data-o-mail-quote": "1",
|
||||||
});
|
});
|
||||||
@@ -203,7 +203,7 @@ var Thread = Widget.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_.each(groups, function(group) {
|
_.each(groups, function (group) {
|
||||||
// Insert link just before the first node
|
// Insert link just before the first node
|
||||||
var $read_more = $('<a>', {
|
var $read_more = $('<a>', {
|
||||||
class: "o_mail_read_more",
|
class: "o_mail_read_more",
|
||||||
@@ -213,7 +213,7 @@ var Thread = Widget.extend({
|
|||||||
|
|
||||||
// Toggle All next nodes
|
// Toggle All next nodes
|
||||||
var is_read_more = true;
|
var is_read_more = true;
|
||||||
$read_more.click(function(e) {
|
$read_more.click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
is_read_more = !is_read_more;
|
is_read_more = !is_read_more;
|
||||||
_.each(group, function ($child) {
|
_.each(group, function ($child) {
|
||||||
@@ -226,7 +226,7 @@ var Thread = Widget.extend({
|
|||||||
},
|
},
|
||||||
update_timestamps: function () {
|
update_timestamps: function () {
|
||||||
var isAtBottom = this.is_at_bottom();
|
var isAtBottom = this.is_at_bottom();
|
||||||
this.$('.o_mail_timestamp').each(function() {
|
this.$('.o_mail_timestamp').each(function () {
|
||||||
var date = $(this).data('date');
|
var date = $(this).data('date');
|
||||||
$(this).html(time_from_now(date));
|
$(this).html(time_from_now(date));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
odoo.define('mail.ThreadField', function (require) {
|
odoo.define('mail.ThreadField', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var chat_mixin = require('mail.chat_mixin');
|
|
||||||
var ChatThread = require('mail.ChatThread');
|
var ChatThread = require('mail.ChatThread');
|
||||||
|
|
||||||
var AbstractField = require('web.AbstractField');
|
var AbstractField = require('web.AbstractField');
|
||||||
@@ -15,14 +14,15 @@ var _t = core._t;
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// 'mail_thread' widget: displays the thread of messages
|
// 'mail_thread' widget: displays the thread of messages
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
var ThreadField = AbstractField.extend(chat_mixin, {
|
var ThreadField = AbstractField.extend({
|
||||||
// inherited
|
// inherited
|
||||||
init: function () {
|
init: function () {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
this.msgIDs = this.value.res_ids;
|
this.msgIDs = this.value.res_ids;
|
||||||
},
|
},
|
||||||
willStart: function () {
|
willStart: function () {
|
||||||
return this.alive(this._chatReady());
|
var chatReady = this.call('chat_manager', 'isReady');
|
||||||
|
return this.alive(chatReady);
|
||||||
},
|
},
|
||||||
start: function () {
|
start: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -39,7 +39,9 @@ var ThreadField = AbstractField.extend(chat_mixin, {
|
|||||||
this.thread.on('load_more_messages', this, this._onLoadMoreMessages);
|
this.thread.on('load_more_messages', this, this._onLoadMoreMessages);
|
||||||
this.thread.on('redirect', this, this._onRedirect);
|
this.thread.on('redirect', this, this._onRedirect);
|
||||||
this.thread.on('redirect_to_channel', this, this._onRedirectToChannel);
|
this.thread.on('redirect_to_channel', this, this._onRedirectToChannel);
|
||||||
this.thread.on('toggle_star_status', this, this._toggleStarStatus);
|
this.thread.on('toggle_star_status', this, function (messageID) {
|
||||||
|
self.call('chat_manager', 'toggleStarStatus', messageID);
|
||||||
|
});
|
||||||
|
|
||||||
var def1 = this.thread.appendTo(this.$el);
|
var def1 = this.thread.appendTo(this.$el);
|
||||||
var def2 = this._super.apply(this, arguments);
|
var def2 = this._super.apply(this, arguments);
|
||||||
@@ -47,10 +49,9 @@ var ThreadField = AbstractField.extend(chat_mixin, {
|
|||||||
return this.alive($.when(def1, def2)).then(function () {
|
return this.alive($.when(def1, def2)).then(function () {
|
||||||
// unwrap the thread to remove an unnecessary level on div
|
// unwrap the thread to remove an unnecessary level on div
|
||||||
self.setElement(self.thread.$el);
|
self.setElement(self.thread.$el);
|
||||||
|
var chatBus = self.call('chat_manager', 'getChatBus');
|
||||||
var bus = self._getBus();
|
chatBus.on('new_message', self, self._onNewMessage);
|
||||||
bus.on('new_message', self, self._onNewMessage);
|
chatBus.on('update_message', self, self._onUpdateMessage);
|
||||||
bus.on('update_message', self, self._onUpdateMessage);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_render: function () {
|
_render: function () {
|
||||||
@@ -60,7 +61,7 @@ var ThreadField = AbstractField.extend(chat_mixin, {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
this._removeChatterMessages(this.model);
|
this.call('chat_manager', 'removeChatterMessages', this.model);
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
_reset: function (record) {
|
_reset: function (record) {
|
||||||
@@ -82,7 +83,7 @@ var ThreadField = AbstractField.extend(chat_mixin, {
|
|||||||
postMessage: function (message) {
|
postMessage: function (message) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var options = {model: this.model, res_id: this.res_id};
|
var options = {model: this.model, res_id: this.res_id};
|
||||||
return this._postMessage(message, options)
|
return this.call('chat_manager', 'postMessage', message, options)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
if (message.partner_ids.length) {
|
if (message.partner_ids.length) {
|
||||||
self.trigger_up('reload_mail_fields', {followers: true});
|
self.trigger_up('reload_mail_fields', {followers: true});
|
||||||
@@ -123,7 +124,7 @@ var ThreadField = AbstractField.extend(chat_mixin, {
|
|||||||
var self = this;
|
var self = this;
|
||||||
options = options || {};
|
options = options || {};
|
||||||
options.ids = ids;
|
options.ids = ids;
|
||||||
var fetch_def = this.dp.add(this._getMessages(options));
|
var fetch_def = this.dp.add(this.call('chat_manager', 'getMessages', options));
|
||||||
return fetch_def.then(function (raw_messages) {
|
return fetch_def.then(function (raw_messages) {
|
||||||
var isCreateMode = false;
|
var isCreateMode = false;
|
||||||
if (!self.res_id) {
|
if (!self.res_id) {
|
||||||
@@ -147,7 +148,7 @@ var ThreadField = AbstractField.extend(chat_mixin, {
|
|||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
*/
|
*/
|
||||||
_onLoadMoreMessages: function () {
|
_onLoadMoreMessages: function () {
|
||||||
this._fetchAndRenderThread(this.msgIDs, {force_fetch: true});
|
this._fetchAndRenderThread(this.msgIDs, {forceFetch: true});
|
||||||
},
|
},
|
||||||
_onNewMessage: function (message) {
|
_onNewMessage: function (message) {
|
||||||
if (message.model === this.model && message.res_id === this.res_id) {
|
if (message.model === this.model && message.res_id === this.res_id) {
|
||||||
@@ -161,7 +162,7 @@ var ThreadField = AbstractField.extend(chat_mixin, {
|
|||||||
},
|
},
|
||||||
_onRedirectToChannel: function (channelID) {
|
_onRedirectToChannel: function (channelID) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this._joinChannel(channelID).then(function () {
|
this.call('chat_manager', 'joinChannel', channelID).then(function () {
|
||||||
// Execute Discuss client action with 'channel' as default channel
|
// Execute Discuss client action with 'channel' as default channel
|
||||||
self.do_action('mail.mail_channel_action_client_chat', {active_id: channelID});
|
self.do_action('mail.mail_channel_action_client_chat', {active_id: channelID});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,48 +1,6 @@
|
|||||||
odoo.define('mail.utils', function (require) {
|
odoo.define('mail.utils', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var bus = require('bus.bus').bus;
|
|
||||||
|
|
||||||
|
|
||||||
function send_notification(widget, title, content) {
|
|
||||||
if (window.Notification && Notification.permission === "granted") {
|
|
||||||
if (bus.is_master) {
|
|
||||||
_send_native_notification(title, content);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
widget.do_notify(title, content);
|
|
||||||
if (bus.is_master) {
|
|
||||||
_beep(widget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function _send_native_notification(title, content) {
|
|
||||||
var notification = new Notification(title, {body: content, icon: "/mail/static/src/img/odoo_o.png"});
|
|
||||||
notification.onclick = function () {
|
|
||||||
window.focus();
|
|
||||||
if (this.cancel) {
|
|
||||||
this.cancel();
|
|
||||||
} else if (this.close) {
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var _beep = (function () {
|
|
||||||
if (typeof(Audio) === "undefined") {
|
|
||||||
return function () {};
|
|
||||||
}
|
|
||||||
var audio;
|
|
||||||
return function (widget) {
|
|
||||||
if (!audio) {
|
|
||||||
audio = new Audio();
|
|
||||||
var ext = audio.canPlayType("audio/ogg; codecs=vorbis") ? ".ogg" : ".mp3";
|
|
||||||
var session = widget.getSession();
|
|
||||||
audio.src = session.url("/mail/static/src/audio/ting" + ext);
|
|
||||||
}
|
|
||||||
audio.play();
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
function parse_and_transform(html_string, transform_function) {
|
function parse_and_transform(html_string, transform_function) {
|
||||||
var open_token = "OPEN" + Date.now();
|
var open_token = "OPEN" + Date.now();
|
||||||
var string = html_string.replace(/</g, open_token);
|
var string = html_string.replace(/</g, open_token);
|
||||||
@@ -141,7 +99,6 @@ function unaccent (str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
send_notification: send_notification,
|
|
||||||
parse_and_transform: parse_and_transform,
|
parse_and_transform: parse_and_transform,
|
||||||
add_link: add_link,
|
add_link: add_link,
|
||||||
linkify: linkify,
|
linkify: linkify,
|
||||||
|
|||||||
@@ -1,400 +0,0 @@
|
|||||||
odoo.define('mail.window_manager', function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
var ExtendedChatWindow = require('mail.ExtendedChatWindow');
|
|
||||||
|
|
||||||
var config = require('web.config');
|
|
||||||
var core = require('web.core');
|
|
||||||
var utils = require('web.utils');
|
|
||||||
var web_client = require('web.web_client');
|
|
||||||
|
|
||||||
var _t = core._t;
|
|
||||||
var QWeb = core.qweb;
|
|
||||||
|
|
||||||
// chat window management
|
|
||||||
//----------------------------------------------------------------
|
|
||||||
var CHAT_WINDOW_WIDTH = 325 + 5; // 5 pixels between windows
|
|
||||||
var chat_sessions = [];
|
|
||||||
var new_chat_session;
|
|
||||||
var display_state = {
|
|
||||||
chat_windows_hidden: false, // chat windows aren't displayed when the client action is open
|
|
||||||
hidden_sessions: [],
|
|
||||||
hidden_unread_counter: 0, // total number of unread msgs in hidden chat windows
|
|
||||||
nb_slots: 0,
|
|
||||||
space_left: 0,
|
|
||||||
windows_dropdown_is_open: false, // used to keep dropdown open when closing chat windows
|
|
||||||
};
|
|
||||||
|
|
||||||
function add_chat_session (chat_session) {
|
|
||||||
// adds chat_session such that it will be the left-most visible window
|
|
||||||
compute_available_slots(chat_sessions.length+1);
|
|
||||||
chat_sessions.splice(display_state.nb_slots-1, 0, chat_session);
|
|
||||||
}
|
|
||||||
|
|
||||||
// options.passively: if set to true, open the chat window without focusing the
|
|
||||||
// input and marking messages as read if it is not open yet, and do nothing
|
|
||||||
// otherwise
|
|
||||||
function open_chat (session, options) {
|
|
||||||
if (!session) {
|
|
||||||
open_chat_without_session();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
options = options || {};
|
|
||||||
var chat_session = _.findWhere(chat_sessions, {id: session.id});
|
|
||||||
if (!chat_session) {
|
|
||||||
var prefix = !session.is_chat ? "#" : "";
|
|
||||||
var window_options = {
|
|
||||||
autofocus: !options.passively,
|
|
||||||
input_less: session.mass_mailing,
|
|
||||||
status: session.status,
|
|
||||||
};
|
|
||||||
chat_session = {
|
|
||||||
id: session.id,
|
|
||||||
uuid: session.uuid,
|
|
||||||
name: session.name,
|
|
||||||
keep_unread: options.passively, // don't automatically mark unread messages as seen
|
|
||||||
window: new ExtendedChatWindow(web_client, session.id, prefix + session.name, session.is_folded, session.unread_counter, window_options),
|
|
||||||
};
|
|
||||||
chat_session.window.on("close_chat_session", null, function () {
|
|
||||||
close_chat(chat_session);
|
|
||||||
chat_manager.close_chat_session(chat_session.id);
|
|
||||||
});
|
|
||||||
chat_session.window.on("toggle_star_status", null, function (message_id) {
|
|
||||||
chat_manager.toggle_star_status(message_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_session.window.on("fold_channel", null, function (channel_id, folded) {
|
|
||||||
chat_manager.fold_channel(channel_id, folded);
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_session.window.on("post_message", null, function (message, channel_id) {
|
|
||||||
chat_manager
|
|
||||||
.post_message(message, {channel_id: channel_id})
|
|
||||||
.then(function () {
|
|
||||||
chat_session.window.thread.scroll_to();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
chat_session.window.on("redirect", null, function (res_model, res_id) {
|
|
||||||
chat_manager.redirect(res_model, res_id, open_chat);
|
|
||||||
});
|
|
||||||
chat_session.window.on("redirect_to_channel", null, function (channel_id) {
|
|
||||||
var session = _.findWhere(chat_sessions, {id: channel_id});
|
|
||||||
if (!session) {
|
|
||||||
chat_manager.join_channel(channel_id).then(function (channel) {
|
|
||||||
chat_manager.detach_channel(channel);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
session.window.toggle_fold(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var remove_new_chat = false;
|
|
||||||
if (options.passively) {
|
|
||||||
chat_sessions.push(chat_session); // simply insert the window to the left
|
|
||||||
} else if (new_chat_session && new_chat_session.partner_id && new_chat_session.partner_id === session.direct_partner_id) {
|
|
||||||
// the window takes the place of the 'new_chat_session' window
|
|
||||||
chat_sessions[_.indexOf(chat_sessions, new_chat_session)] = chat_session;
|
|
||||||
remove_new_chat = true;
|
|
||||||
} else {
|
|
||||||
add_chat_session(chat_session); // add session such that window is visible
|
|
||||||
}
|
|
||||||
|
|
||||||
chat_session.window.appendTo($('body'))
|
|
||||||
.then(function () {
|
|
||||||
reposition_windows({remove_new_chat: remove_new_chat});
|
|
||||||
return chat_manager.get_messages({channel_id: chat_session.id});
|
|
||||||
}).then(function (messages) {
|
|
||||||
chat_session.window.render(messages);
|
|
||||||
chat_session.window.thread.scroll_to();
|
|
||||||
setTimeout(function () {
|
|
||||||
chat_session.window.thread.$el.on("scroll", null, _.debounce(function () {
|
|
||||||
if (!chat_session.keep_unread && chat_session.window.thread.is_at_bottom()) {
|
|
||||||
chat_manager.mark_channel_as_seen(session);
|
|
||||||
}
|
|
||||||
}, 100));
|
|
||||||
}, 0); // setTimeout to prevent to execute handler on first scroll_to, which is asynchronous
|
|
||||||
if (options.passively) {
|
|
||||||
// mark first unread messages as seen when focusing the window, then on scroll to bottom as usual
|
|
||||||
chat_session.window.$('.o_mail_thread, .o_chat_composer').one('click', function () {
|
|
||||||
chat_manager.mark_channel_as_seen(session);
|
|
||||||
});
|
|
||||||
} else if (!display_state.chat_windows_hidden && !session.is_folded) {
|
|
||||||
chat_manager.mark_channel_as_seen(session);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (!options.passively) {
|
|
||||||
if (chat_session.window.is_hidden) {
|
|
||||||
make_session_visible(chat_session);
|
|
||||||
} else if (session.is_folded !== chat_session.window.folded) {
|
|
||||||
chat_session.window.toggle_fold(session.is_folded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function open_chat_without_session () {
|
|
||||||
if (!new_chat_session) {
|
|
||||||
new_chat_session = {
|
|
||||||
id: '_open',
|
|
||||||
window: new ExtendedChatWindow(web_client, undefined, _t('New message'), false, 0, {thread_less: true}),
|
|
||||||
};
|
|
||||||
new_chat_session.window.on("close_chat_session", null, close_new_chat);
|
|
||||||
new_chat_session.window.on('open_dm_session', null, function (partner_id) {
|
|
||||||
new_chat_session.partner_id = partner_id;
|
|
||||||
var dm = chat_manager.get_dm_from_partner_id(partner_id);
|
|
||||||
if (!dm) {
|
|
||||||
chat_manager.open_and_detach_dm(partner_id);
|
|
||||||
} else {
|
|
||||||
var dm_session = _.findWhere(chat_sessions, {id: dm.id});
|
|
||||||
if (!dm_session) {
|
|
||||||
chat_manager.detach_channel(dm);
|
|
||||||
} else {
|
|
||||||
close_chat(dm_session);
|
|
||||||
dm.is_folded = false;
|
|
||||||
open_chat(dm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
add_chat_session(new_chat_session);
|
|
||||||
new_chat_session.window.appendTo($('body')).then(reposition_windows);
|
|
||||||
} else {
|
|
||||||
if (new_chat_session.window.is_hidden) {
|
|
||||||
make_session_visible(new_chat_session);
|
|
||||||
} else if (new_chat_session.window.folded) {
|
|
||||||
new_chat_session.window.toggle_fold(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function close_chat (chat_session, options) {
|
|
||||||
if (options && options.keep_open_if_unread && chat_session.keep_unread) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
chat_sessions = _.without(chat_sessions, chat_session);
|
|
||||||
chat_session.window.destroy();
|
|
||||||
reposition_windows();
|
|
||||||
}
|
|
||||||
|
|
||||||
function close_new_chat () {
|
|
||||||
chat_sessions = _.without(chat_sessions, new_chat_session);
|
|
||||||
reposition_windows({remove_new_chat: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroy_new_chat () {
|
|
||||||
new_chat_session.window.destroy();
|
|
||||||
new_chat_session = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_fold_chat (channel) {
|
|
||||||
var session = _.find(chat_sessions, {id: channel.id});
|
|
||||||
if (session) {
|
|
||||||
session.window.toggle_fold(channel.is_folded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function compute_available_slots (nb_windows) {
|
|
||||||
if (config.device.isMobile) {
|
|
||||||
display_state.nb_slots = 1; // one chat window full screen in mobile
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var width = window.innerWidth;
|
|
||||||
var nb_slots = Math.floor(width/CHAT_WINDOW_WIDTH);
|
|
||||||
var space_left = width - (Math.min(nb_slots, nb_windows)*CHAT_WINDOW_WIDTH);
|
|
||||||
if (nb_slots < nb_windows && space_left < 50) {
|
|
||||||
nb_slots--; // leave at least 50px for the hidden windows dropdown button
|
|
||||||
space_left += CHAT_WINDOW_WIDTH;
|
|
||||||
}
|
|
||||||
display_state.nb_slots = nb_slots;
|
|
||||||
display_state.space_left = space_left;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reposition_windows = function (options) {
|
|
||||||
if (options && options.remove_new_chat) {
|
|
||||||
destroy_new_chat();
|
|
||||||
}
|
|
||||||
if (display_state.chat_windows_hidden) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
compute_available_slots(chat_sessions.length);
|
|
||||||
var hidden_sessions = [];
|
|
||||||
var hidden_unread_counter = 0;
|
|
||||||
var nb_slots = display_state.nb_slots;
|
|
||||||
_.each(chat_sessions, function (session, index) {
|
|
||||||
if (index < nb_slots) {
|
|
||||||
session.window.$el.css({right: CHAT_WINDOW_WIDTH*index, bottom: 0});
|
|
||||||
session.window.do_show();
|
|
||||||
} else {
|
|
||||||
hidden_sessions.push(session);
|
|
||||||
hidden_unread_counter += session.window.unread_msgs;
|
|
||||||
session.window.do_hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
display_state.hidden_sessions = hidden_sessions;
|
|
||||||
display_state.hidden_unread_counter = hidden_unread_counter;
|
|
||||||
|
|
||||||
if (display_state.$hidden_windows_dropdown) {
|
|
||||||
display_state.$hidden_windows_dropdown.remove();
|
|
||||||
}
|
|
||||||
if (hidden_sessions.length) {
|
|
||||||
display_state.$hidden_windows_dropdown = render_hidden_sessions_dropdown();
|
|
||||||
var $hidden_windows_dropdown = display_state.$hidden_windows_dropdown;
|
|
||||||
$hidden_windows_dropdown.css({right: CHAT_WINDOW_WIDTH * nb_slots, bottom: 0})
|
|
||||||
.appendTo($('body'));
|
|
||||||
reposition_hidden_sessions_dropdown();
|
|
||||||
display_state.windows_dropdown_is_open = false;
|
|
||||||
|
|
||||||
$hidden_windows_dropdown.on('click', '.o_chat_header', function (event) {
|
|
||||||
var session_id = $(event.currentTarget).data('session-id');
|
|
||||||
var session = _.findWhere(hidden_sessions, {id: session_id});
|
|
||||||
if (session) {
|
|
||||||
make_session_visible(session);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$hidden_windows_dropdown.on('click', '.o_chat_window_close', function (event) {
|
|
||||||
var session_id = $(event.currentTarget).closest('.o_chat_header').data('session-id');
|
|
||||||
var session = _.findWhere(hidden_sessions, {id: session_id});
|
|
||||||
if (session) {
|
|
||||||
session.window.on_click_close(event);
|
|
||||||
display_state.windows_dropdown_is_open = true; // keep the dropdown open
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function make_session_visible (session) {
|
|
||||||
utils.swap(chat_sessions, session, chat_sessions[display_state.nb_slots-1]);
|
|
||||||
reposition_windows();
|
|
||||||
session.window.toggle_fold(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function render_hidden_sessions_dropdown () {
|
|
||||||
var $dropdown = $(QWeb.render("mail.ChatWindowsDropdown", {
|
|
||||||
sessions: display_state.hidden_sessions,
|
|
||||||
open: display_state.windows_dropdown_is_open,
|
|
||||||
unread_counter: display_state.hidden_unread_counter,
|
|
||||||
widget: {isMobile: config.device.isMobile},
|
|
||||||
}));
|
|
||||||
return $dropdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reposition_hidden_sessions_dropdown () {
|
|
||||||
// Unfold dropdown to the left if there is enough place
|
|
||||||
var $dropdown_ul = display_state.$hidden_windows_dropdown.children('ul');
|
|
||||||
if (display_state.space_left > $dropdown_ul.width() + 10) {
|
|
||||||
$dropdown_ul.addClass('dropdown-menu-right');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_sessions (message, scrollBottom) {
|
|
||||||
_.each(chat_sessions, function (session) {
|
|
||||||
if (_.contains(message.channel_ids, session.id)) {
|
|
||||||
var message_visible = !display_state.chat_windows_hidden && !session.window.folded &&
|
|
||||||
!session.window.is_hidden && session.window.thread.is_at_bottom();
|
|
||||||
if (message_visible && !session.keep_unread) {
|
|
||||||
chat_manager.mark_channel_as_seen(chat_manager.get_channel(session.id));
|
|
||||||
}
|
|
||||||
chat_manager.get_messages({channel_id: session.id}).then(function (messages) {
|
|
||||||
session.window.render(messages);
|
|
||||||
if (scrollBottom && message_visible) {
|
|
||||||
session.window.thread.scroll_to();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
core.bus.on('web_client_ready', null, function () {
|
|
||||||
chat_manager.bus.on('open_chat', null, open_chat);
|
|
||||||
chat_manager.bus.on('close_chat', null, function (channel, options) {
|
|
||||||
var session = _.find(chat_sessions, {id: channel.id});
|
|
||||||
if (session) {
|
|
||||||
close_chat(session, options);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
chat_manager.bus.on('channel_toggle_fold', null, toggle_fold_chat);
|
|
||||||
|
|
||||||
chat_manager.bus.on('new_message', null, function (message) {
|
|
||||||
update_sessions(message, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.bus.on('update_message', null, function (message) {
|
|
||||||
update_sessions(message, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.bus.on('anyone_listening', null, function (channel, query) {
|
|
||||||
_.each(chat_sessions, function (session) {
|
|
||||||
if (channel.id === session.id && session.window.thread.is_at_bottom() && !session.window.is_hidden) {
|
|
||||||
query.is_displayed = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.bus.on('unsubscribe_from_channel', null, function (channel_id) {
|
|
||||||
_.each(chat_sessions, function (session) {
|
|
||||||
if (channel_id === session.id) {
|
|
||||||
close_chat(session);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.bus.on('update_channel_unread_counter', null, function (channel) {
|
|
||||||
display_state.hidden_unread_counter = 0;
|
|
||||||
_.each(chat_sessions, function (session) {
|
|
||||||
if (channel.id === session.id) {
|
|
||||||
session.window.update_unread(channel.unread_counter);
|
|
||||||
if (channel.unread_counter === 0) {
|
|
||||||
session.keep_unread = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (session.window.is_hidden) {
|
|
||||||
display_state.hidden_unread_counter += session.window.unread_msgs;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (display_state.$hidden_windows_dropdown) {
|
|
||||||
display_state.$hidden_windows_dropdown.html(render_hidden_sessions_dropdown().html());
|
|
||||||
reposition_hidden_sessions_dropdown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.bus.on('update_dm_presence', null, function (channel) {
|
|
||||||
_.each(chat_sessions, function (session) {
|
|
||||||
if (channel.id === session.id) {
|
|
||||||
session.window.update_status(channel.status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.is_ready.then(function() {
|
|
||||||
_.each(chat_manager.get_channels(), function (channel) {
|
|
||||||
if (channel.is_detached) {
|
|
||||||
open_chat(channel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.bus.on('detach_channel', null, function (channel) {
|
|
||||||
var chat_session = _.findWhere(chat_sessions, {id: channel.id});
|
|
||||||
if (!chat_session || chat_session.window.folded) {
|
|
||||||
chat_manager.detach_channel(channel);
|
|
||||||
} else if (chat_session.window.is_hidden) {
|
|
||||||
make_session_visible(chat_session);
|
|
||||||
} else {
|
|
||||||
chat_session.window.focus_input();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.bus.on('client_action_open', null, function (open) {
|
|
||||||
display_state.chat_windows_hidden = open;
|
|
||||||
if (open) {
|
|
||||||
$('body').addClass('o_no_chat_window');
|
|
||||||
} else {
|
|
||||||
$('body').removeClass('o_no_chat_window');
|
|
||||||
reposition_windows();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
core.bus.on('resize', null, _.debounce(reposition_windows, 100));
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
+1
-1
@@ -356,7 +356,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Channel preview: shared between client action (mobile) and systray
|
// Channel preview: shared between discuss (mobile) and systray
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
.o_mail_channel_preview {
|
.o_mail_channel_preview {
|
||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<templates xml:space="preserve">
|
<templates xml:space="preserve">
|
||||||
|
|
||||||
<!-- Client Action -->
|
<!-- Discuss -->
|
||||||
<t t-name="mail.client_action">
|
<t t-name="mail.discuss">
|
||||||
<div class="o_mail_chat">
|
<div class="o_mail_chat">
|
||||||
<div class="o_mail_chat_sidebar"/>
|
<div class="o_mail_chat_sidebar"/>
|
||||||
<div class="o_mail_chat_content">
|
<div class="o_mail_chat_content">
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- Mobile templates -->
|
<!-- Mobile templates -->
|
||||||
<t t-name="mail.client_action_mobile">
|
<t t-name="mail.discuss_mobile">
|
||||||
<div class="o_mail_chat">
|
<div class="o_mail_chat">
|
||||||
<div class="o_mail_chat_mobile_control_panel"/>
|
<div class="o_mail_chat_mobile_control_panel"/>
|
||||||
<div class="o_mail_chat_mobile_inbox_buttons">
|
<div class="o_mail_chat_mobile_inbox_buttons">
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
<!-- The ChannelPreview template is used by the client action in mobile, and by the systray menu -->
|
<!-- The ChannelPreview template is used by the discuss in mobile, and by the systray menu -->
|
||||||
<t t-name="mail.chat.ChannelPreview">
|
<t t-name="mail.chat.ChannelPreview">
|
||||||
<div t-attf-class="o_mail_channel_preview #{channel.unread_counter ? 'o_channel_unread' : ''}"
|
<div t-attf-class="o_mail_channel_preview #{channel.unread_counter ? 'o_channel_unread' : ''}"
|
||||||
t-att-data-channel_id="channel.id" t-att-data-res_id="channel.res_id" t-att-data-res_model="channel.model">
|
t-att-data-channel_id="channel.id" t-att-data-res_id="channel.res_id" t-att-data-res_model="channel.model">
|
||||||
+9
-1
@@ -1,10 +1,15 @@
|
|||||||
odoo.define('mail.chat_window_test', function (require) {
|
odoo.define('mail.chat_window_test', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var ChatManager = require('mail.ChatManager');
|
||||||
|
var ChatWindow = require('mail.ExtendedChatWindow');
|
||||||
|
var mailTestUtils = require('mail.testUtils');
|
||||||
|
|
||||||
var framework = require('web.framework');
|
var framework = require('web.framework');
|
||||||
var testUtils = require('web.test_utils');
|
var testUtils = require('web.test_utils');
|
||||||
var Widget = require('web.Widget');
|
var Widget = require('web.Widget');
|
||||||
var ChatWindow = require('mail.ExtendedChatWindow');
|
|
||||||
|
var createBusService = mailTestUtils.createBusService;
|
||||||
|
|
||||||
QUnit.module('mail', {}, function () {
|
QUnit.module('mail', {}, function () {
|
||||||
|
|
||||||
@@ -88,6 +93,8 @@ QUnit.module('mail', {}, function () {
|
|||||||
QUnit.test('emoji popover should open correctly in chat windows', function (assert) {
|
QUnit.test('emoji popover should open correctly in chat windows', function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
|
var BusService = createBusService();
|
||||||
|
|
||||||
function createParent(params) {
|
function createParent(params) {
|
||||||
var widget = new Widget();
|
var widget = new Widget();
|
||||||
|
|
||||||
@@ -100,6 +107,7 @@ QUnit.module('mail', {}, function () {
|
|||||||
}
|
}
|
||||||
var parent = createParent({
|
var parent = createParent({
|
||||||
data: {},
|
data: {},
|
||||||
|
services: [ChatManager, BusService],
|
||||||
});
|
});
|
||||||
|
|
||||||
var $dom = $('#qunit-fixture');
|
var $dom = $('#qunit-fixture');
|
||||||
@@ -2,6 +2,8 @@ odoo.define('mail.chatter_tests', function (require) {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var Composers = require('mail.composer');
|
var Composers = require('mail.composer');
|
||||||
|
var ChatManager = require('mail.ChatManager');
|
||||||
|
var mailTestUtils = require('mail.testUtils');
|
||||||
|
|
||||||
var Bus = require('web.Bus');
|
var Bus = require('web.Bus');
|
||||||
var concurrency = require('web.concurrency');
|
var concurrency = require('web.concurrency');
|
||||||
@@ -11,6 +13,7 @@ var testUtils = require('web.test_utils');
|
|||||||
|
|
||||||
var BasicComposer = Composers.BasicComposer;
|
var BasicComposer = Composers.BasicComposer;
|
||||||
|
|
||||||
|
var createBusService = mailTestUtils.createBusService;
|
||||||
var createAsyncView = testUtils.createAsyncView;
|
var createAsyncView = testUtils.createAsyncView;
|
||||||
var createView = testUtils.createView;
|
var createView = testUtils.createView;
|
||||||
|
|
||||||
@@ -18,6 +21,7 @@ QUnit.module('mail', {}, function () {
|
|||||||
|
|
||||||
QUnit.module('Chatter', {
|
QUnit.module('Chatter', {
|
||||||
beforeEach: function () {
|
beforeEach: function () {
|
||||||
|
this.BusService = createBusService();
|
||||||
this.data = {
|
this.data = {
|
||||||
partner: {
|
partner: {
|
||||||
fields: {
|
fields: {
|
||||||
@@ -104,9 +108,8 @@ QUnit.test('basic rendering', function (assert) {
|
|||||||
'<field name="activity_ids" widget="mail_activity"/>' +
|
'<field name="activity_ids" widget="mail_activity"/>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</form>',
|
'</form>',
|
||||||
res_id: 2,
|
|
||||||
mockRPC: function (route, args) {
|
mockRPC: function (route, args) {
|
||||||
if ('/web/dataset/call_kw/mail.followers/read' === route) {
|
if (route === '/web/dataset/call_kw/mail.followers/read') {
|
||||||
unwanted_read_count++;
|
unwanted_read_count++;
|
||||||
}
|
}
|
||||||
if (route === '/mail/read_followers') {
|
if (route === '/mail/read_followers') {
|
||||||
@@ -118,28 +121,8 @@ QUnit.test('basic rendering', function (assert) {
|
|||||||
}
|
}
|
||||||
return this._super(route, args);
|
return this._super(route, args);
|
||||||
},
|
},
|
||||||
intercepts: {
|
services: [ChatManager, this.BusService],
|
||||||
get_messages: function (event) {
|
res_id: 2,
|
||||||
// msgRpc++;
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback($.when([{
|
|
||||||
attachment_ids: [],
|
|
||||||
body: "",
|
|
||||||
date: moment("2016-12-20 09:35:40"),
|
|
||||||
id: 34,
|
|
||||||
res_id: 3,
|
|
||||||
author_id: ["3", "Fu Ck Mil Grom"],
|
|
||||||
}]));
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
},
|
|
||||||
get_session: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback({uid: 1});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.ok(form.$('.o_mail_activity').length, "there should be an activity widget");
|
assert.ok(form.$('.o_mail_activity').length, "there should be an activity widget");
|
||||||
@@ -182,16 +165,7 @@ QUnit.test('chatter in create mode', function (assert) {
|
|||||||
}
|
}
|
||||||
return this._super(route, args);
|
return this._super(route, args);
|
||||||
},
|
},
|
||||||
intercepts: {
|
services: [ChatManager, this.BusService],
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback($.when([]));
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(form.$('.o_chatter').length, 1,
|
assert.strictEqual(form.$('.o_chatter').length, 1,
|
||||||
@@ -258,16 +232,7 @@ QUnit.test('chatter rendering inside the sheet', function (assert) {
|
|||||||
}
|
}
|
||||||
return this._super(route, args);
|
return this._super(route, args);
|
||||||
},
|
},
|
||||||
intercepts: {
|
services: [ChatManager, this.BusService],
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback($.when([]));
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(form.$('.o_chatter').length, 1,
|
assert.strictEqual(form.$('.o_chatter').length, 1,
|
||||||
@@ -405,6 +370,9 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
|
|||||||
var done = assert.async();
|
var done = assert.async();
|
||||||
assert.expect(28);
|
assert.expect(28);
|
||||||
|
|
||||||
|
var bus = new Bus();
|
||||||
|
var BusService = createBusService(bus);
|
||||||
|
|
||||||
// Remove the mention throttle to speed up the test
|
// Remove the mention throttle to speed up the test
|
||||||
var mentionThrottle = BasicComposer.prototype.MENTION_THROTTLE;
|
var mentionThrottle = BasicComposer.prototype.MENTION_THROTTLE;
|
||||||
BasicComposer.prototype.MENTION_THROTTLE = 1;
|
BasicComposer.prototype.MENTION_THROTTLE = 1;
|
||||||
@@ -414,7 +382,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
|
|||||||
attachment_ids: [],
|
attachment_ids: [],
|
||||||
author_id: ["1", "John Doe"],
|
author_id: ["1", "John Doe"],
|
||||||
body: "A message",
|
body: "A message",
|
||||||
date: moment("2016-12-20 09:35:40"),
|
date: "2016-12-20 09:35:40",
|
||||||
displayed_author: "John Doe",
|
displayed_author: "John Doe",
|
||||||
id: 1,
|
id: 1,
|
||||||
is_note: false,
|
is_note: false,
|
||||||
@@ -423,7 +391,6 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
|
|||||||
model: 'partner',
|
model: 'partner',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
}];
|
}];
|
||||||
var bus = new Bus();
|
|
||||||
var getSuggestionsDef = $.Deferred();
|
var getSuggestionsDef = $.Deferred();
|
||||||
var form = createView({
|
var form = createView({
|
||||||
View: FormView,
|
View: FormView,
|
||||||
@@ -439,6 +406,12 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
|
|||||||
'</form>',
|
'</form>',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
mockRPC: function (route, args) {
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method === 'message_format') {
|
||||||
|
var requested_msgs = _.filter(messages, function (msg) {
|
||||||
|
return _.contains(args.args[0], msg.id);
|
||||||
|
});
|
||||||
|
return $.when(requested_msgs);
|
||||||
|
}
|
||||||
if (args.method === 'message_get_suggested_recipients') {
|
if (args.method === 'message_get_suggested_recipients') {
|
||||||
return $.when({2: []});
|
return $.when({2: []});
|
||||||
}
|
}
|
||||||
@@ -446,52 +419,43 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
|
|||||||
getSuggestionsDef.resolve();
|
getSuggestionsDef.resolve();
|
||||||
return $.when([{email: "test@odoo.com", id: 1, name: "Test User"}]);
|
return $.when([{email: "test@odoo.com", id: 1, name: "Test User"}]);
|
||||||
}
|
}
|
||||||
return this._super(route, args);
|
if (args.method === 'message_post') {
|
||||||
},
|
|
||||||
session: {},
|
|
||||||
intercepts: {
|
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
var requested_msgs = _.filter(messages, function (msg) {
|
|
||||||
return _.contains(event.data.options.ids, msg.id);
|
|
||||||
});
|
|
||||||
event.data.callback($.when(requested_msgs));
|
|
||||||
},
|
|
||||||
post_message: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
var msg_id = messages[messages.length-1].id + 1;
|
var msg_id = messages[messages.length-1].id + 1;
|
||||||
messages.push({
|
messages.push({
|
||||||
attachment_ids: [],
|
attachment_ids: args.kwargs.attachment_ids,
|
||||||
author_id: ["42", "Me"],
|
author_id: ["42", "Me"],
|
||||||
body: event.data.message.content,
|
body: args.kwargs.body,
|
||||||
date: moment(), // now
|
date: "2016-12-20 10:35:40",
|
||||||
displayed_author: "Me",
|
displayed_author: "Me",
|
||||||
id: msg_id,
|
id: msg_id,
|
||||||
is_note: event.data.message.subtype === 'mail.mt_note',
|
is_note: args.kwargs.subtype === 'mail.mt_note',
|
||||||
is_discussion: event.data.message.subtype === 'mail.mt_comment',
|
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
|
||||||
is_starred: false,
|
is_starred: false,
|
||||||
model: 'partner',
|
model: 'partner',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
});
|
});
|
||||||
bus.trigger('new_message', {
|
return $.when(msg_id);
|
||||||
id: msg_id,
|
}
|
||||||
model: event.data.options.model,
|
if (args.method === 'toggle_message_starred') {
|
||||||
res_id: event.data.options.res_id,
|
assert.ok(_.contains(args.args[0], 2),
|
||||||
});
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(bus);
|
|
||||||
},
|
|
||||||
toggle_star_status: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
assert.strictEqual(event.data.message_id, 2,
|
|
||||||
"toggle_star_status should have been triggered for message 2 (twice)");
|
"toggle_star_status should have been triggered for message 2 (twice)");
|
||||||
var msg = _.findWhere(messages, {id: event.data.message_id});
|
var msg = _.findWhere(messages, {id: args.args[0][0]});
|
||||||
msg.is_starred = !msg.is_starred;
|
msg.is_starred = !msg.is_starred;
|
||||||
bus.trigger('update_message', msg);
|
// simulate notification received by chat_manager from longpoll
|
||||||
},
|
var data = {
|
||||||
|
info: false,
|
||||||
|
message_ids: [msg.id],
|
||||||
|
starred: msg.is_starred,
|
||||||
|
type: 'toggle_star',
|
||||||
|
};
|
||||||
|
var notification = [[false, 'res.partner'], data];
|
||||||
|
bus.trigger('notification', [notification]);
|
||||||
|
return $.when();
|
||||||
|
}
|
||||||
|
return this._super(route, args);
|
||||||
},
|
},
|
||||||
|
session: {},
|
||||||
|
services: [ChatManager, BusService],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.ok(form.$('.o_chatter_topbar .o_chatter_button_log_note').length,
|
assert.ok(form.$('.o_chatter_topbar .o_chatter_button_log_note').length,
|
||||||
@@ -578,7 +542,6 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert)
|
|||||||
assert.expect(5);
|
assert.expect(5);
|
||||||
|
|
||||||
var messages = [];
|
var messages = [];
|
||||||
var bus = new Bus();
|
|
||||||
var form = createView({
|
var form = createView({
|
||||||
View: FormView,
|
View: FormView,
|
||||||
model: 'partner',
|
model: 'partner',
|
||||||
@@ -594,45 +557,35 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert)
|
|||||||
res_id: 2,
|
res_id: 2,
|
||||||
session: {},
|
session: {},
|
||||||
mockRPC: function (route, args) {
|
mockRPC: function (route, args) {
|
||||||
if (route === "/web/dataset/call_kw/partner/message_get_suggested_recipients") {
|
if (args.method === 'message_format') {
|
||||||
|
var requested_msgs = _.filter(messages, function (msg) {
|
||||||
|
return _.contains(args.args[0], msg.id);
|
||||||
|
});
|
||||||
|
return $.when(requested_msgs);
|
||||||
|
}
|
||||||
|
if (args.method === 'message_get_suggested_recipients') {
|
||||||
return $.when({2: []});
|
return $.when({2: []});
|
||||||
}
|
}
|
||||||
return this._super(route, args);
|
if (args.method === 'message_post') {
|
||||||
},
|
|
||||||
intercepts: {
|
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
var requested_msgs = _.filter(messages, function (msg) {
|
|
||||||
return _.contains(event.data.options.ids, msg.id);
|
|
||||||
});
|
|
||||||
event.data.callback($.when(requested_msgs));
|
|
||||||
},
|
|
||||||
post_message: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
messages.push({
|
messages.push({
|
||||||
attachment_ids: [],
|
attachment_ids: args.kwargs.attachment_ids,
|
||||||
author_id: ["42", "Me"],
|
author_id: ["42", "Me"],
|
||||||
body: event.data.message.content,
|
body: args.kwargs.body,
|
||||||
date: moment(), // now
|
date: "2016-12-20 10:35:40",
|
||||||
displayed_author: "Me",
|
displayed_author: "Me",
|
||||||
id: 42,
|
id: 42,
|
||||||
is_note: event.data.message.subtype === 'mail.mt_note',
|
is_note: args.kwargs.subtype === 'mail.mt_note',
|
||||||
is_discussion: event.data.message.subtype === 'mail.mt_comment',
|
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
|
||||||
is_starred: false,
|
is_starred: false,
|
||||||
model: 'partner',
|
model: 'partner',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
});
|
});
|
||||||
bus.trigger('new_message', {
|
return $.when(42);
|
||||||
id: 42,
|
}
|
||||||
model: event.data.options.model,
|
|
||||||
res_id: event.data.options.res_id,
|
return this._super(route, args);
|
||||||
});
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(bus);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
services: [ChatManager, this.BusService],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(form.$('.o_thread_message').length, 0, "thread should not contain messages");
|
assert.strictEqual(form.$('.o_thread_message').length, 0, "thread should not contain messages");
|
||||||
@@ -685,7 +638,7 @@ QUnit.test('chatter: Attachment viewer', function (assert) {
|
|||||||
}],
|
}],
|
||||||
author_id: ["1", "John Doe"],
|
author_id: ["1", "John Doe"],
|
||||||
body: "Attachement viewer test",
|
body: "Attachement viewer test",
|
||||||
date: moment("2016-12-20 09:35:40"),
|
date: "2016-12-20 09:35:40",
|
||||||
displayed_author: "John Doe",
|
displayed_author: "John Doe",
|
||||||
id: 1,
|
id: 1,
|
||||||
is_note: false,
|
is_note: false,
|
||||||
@@ -694,6 +647,7 @@ QUnit.test('chatter: Attachment viewer', function (assert) {
|
|||||||
model: 'partner',
|
model: 'partner',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
var form = createView({
|
var form = createView({
|
||||||
View: FormView,
|
View: FormView,
|
||||||
model: 'partner',
|
model: 'partner',
|
||||||
@@ -707,27 +661,21 @@ QUnit.test('chatter: Attachment viewer', function (assert) {
|
|||||||
'</div>' +
|
'</div>' +
|
||||||
'</form>',
|
'</form>',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
mockRPC: function (route) {
|
mockRPC: function (route, args) {
|
||||||
if(_.str.contains(route, '/mail/attachment/preview/') ||
|
if (args.method === 'message_format') {
|
||||||
|
var requested_msgs = _.filter(messages, function (msg) {
|
||||||
|
return _.contains(args.args[0], msg.id);
|
||||||
|
});
|
||||||
|
return $.when(requested_msgs);
|
||||||
|
}
|
||||||
|
if (_.str.contains(route, '/mail/attachment/preview/') ||
|
||||||
_.str.contains(route, '/web/static/lib/pdfjs/web/viewer.html')){
|
_.str.contains(route, '/web/static/lib/pdfjs/web/viewer.html')){
|
||||||
var canvas = document.createElement('canvas');
|
var canvas = document.createElement('canvas');
|
||||||
return $.when(canvas.toDataURL());
|
return $.when(canvas.toDataURL());
|
||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
intercepts: {
|
services: [ChatManager, this.BusService],
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
var requested_msgs = _.filter(messages, function (msg) {
|
|
||||||
return _.contains(event.data.options.ids, msg.id);
|
|
||||||
});
|
|
||||||
event.data.callback($.when(requested_msgs));
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
assert.strictEqual(form.$('.o_thread_message .o_attachment').length, 4,
|
assert.strictEqual(form.$('.o_thread_message .o_attachment').length, 4,
|
||||||
"there should be three attachment on message");
|
"there should be three attachment on message");
|
||||||
@@ -783,6 +731,9 @@ QUnit.test('form activity widget: schedule next activity', function (assert) {
|
|||||||
'</form>',
|
'</form>',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
mockRPC: function (route, args) {
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method === 'message_format') {
|
||||||
|
return $.when([]);
|
||||||
|
}
|
||||||
if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
||||||
assert.ok(_.isEqual(args.args[0], [1]), "should call 'action_feedback' for id 1");
|
assert.ok(_.isEqual(args.args[0], [1]), "should call 'action_feedback' for id 1");
|
||||||
assert.strictEqual(args.kwargs.feedback, 'everything is ok',
|
assert.strictEqual(args.kwargs.feedback, 'everything is ok',
|
||||||
@@ -795,15 +746,8 @@ QUnit.test('form activity widget: schedule next activity', function (assert) {
|
|||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
services: [ChatManager, this.BusService],
|
||||||
intercepts: {
|
intercepts: {
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback($.when([]));
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
},
|
|
||||||
do_action: function (event) {
|
do_action: function (event) {
|
||||||
assert.deepEqual(event.data.action, {
|
assert.deepEqual(event.data.action, {
|
||||||
context: {
|
context: {
|
||||||
@@ -856,11 +800,8 @@ QUnit.test('form activity widget: schedule activity does not discard changes', f
|
|||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
services: [ChatManager, this.BusService],
|
||||||
intercepts: {
|
intercepts: {
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
},
|
|
||||||
do_action: function (event) {
|
do_action: function (event) {
|
||||||
event.data.options.on_close();
|
event.data.options.on_close();
|
||||||
},
|
},
|
||||||
@@ -922,6 +863,12 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) {
|
|||||||
'</form>',
|
'</form>',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
mockRPC: function (route, args) {
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method === 'message_format') {
|
||||||
|
var requested_msgs = _.filter(messages, function (msg) {
|
||||||
|
return _.contains(args.args[0], msg.id);
|
||||||
|
});
|
||||||
|
return $.when(requested_msgs);
|
||||||
|
}
|
||||||
if (route === '/web/dataset/call_kw/mail.activity/unlink') {
|
if (route === '/web/dataset/call_kw/mail.activity/unlink') {
|
||||||
assert.ok(_.isEqual(args.args[0], [1]), "should call 'unlink' for id 1");
|
assert.ok(_.isEqual(args.args[0], [1]), "should call 'unlink' for id 1");
|
||||||
} else if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
} else if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
|
||||||
@@ -934,7 +881,7 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) {
|
|||||||
attachment_ids: [],
|
attachment_ids: [],
|
||||||
author_id: ["1", "John Doe"],
|
author_id: ["1", "John Doe"],
|
||||||
body: "The activity has been done",
|
body: "The activity has been done",
|
||||||
date: moment("2016-12-20 09:35:40"),
|
date: "2016-12-20 09:35:40",
|
||||||
displayed_author: "John Doe",
|
displayed_author: "John Doe",
|
||||||
id: 1,
|
id: 1,
|
||||||
is_note: true,
|
is_note: true,
|
||||||
@@ -956,16 +903,7 @@ QUnit.test('form activity widget: mark as done and remove', function (assert) {
|
|||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
intercepts: {
|
services: [ChatManager, this.BusService],
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback($.when(messages));
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 2,
|
assert.strictEqual(form.$('.o_mail_activity .o_thread_message').length, 2,
|
||||||
@@ -1203,6 +1141,16 @@ QUnit.test('does not render and crash when destroyed before chat system is ready
|
|||||||
'</form>',
|
'</form>',
|
||||||
res_id: 2,
|
res_id: 2,
|
||||||
mockRPC: function (route, args) {
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method === 'message_format') {
|
||||||
|
return $.when([{
|
||||||
|
attachment_ids: [],
|
||||||
|
body: "",
|
||||||
|
date: "2016-12-20 09:35:40",
|
||||||
|
id: 34,
|
||||||
|
res_id: 3,
|
||||||
|
author_id: ["3", "Fu Ck Mil Grom"],
|
||||||
|
}]);
|
||||||
|
}
|
||||||
if (route === '/mail/read_followers') {
|
if (route === '/mail/read_followers') {
|
||||||
return $.when({
|
return $.when({
|
||||||
followers: [],
|
followers: [],
|
||||||
@@ -1211,26 +1159,8 @@ QUnit.test('does not render and crash when destroyed before chat system is ready
|
|||||||
}
|
}
|
||||||
return this._super(route, args);
|
return this._super(route, args);
|
||||||
},
|
},
|
||||||
|
services: [ChatManager, this.BusService],
|
||||||
intercepts: {
|
intercepts: {
|
||||||
chat_manager_ready: function (event) {
|
|
||||||
// we delay the return of the chat_manager ready event
|
|
||||||
event.data.callback(def);
|
|
||||||
},
|
|
||||||
get_messages: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback($.when([{
|
|
||||||
attachment_ids: [],
|
|
||||||
body: "",
|
|
||||||
date: moment("2016-12-20 09:35:40"),
|
|
||||||
id: 34,
|
|
||||||
res_id: 3,
|
|
||||||
author_id: ["3", "Fu Ck Mil Grom"],
|
|
||||||
}]));
|
|
||||||
},
|
|
||||||
get_bus: function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.data.callback(new Bus());
|
|
||||||
},
|
|
||||||
get_session: function (event) {
|
get_session: function (event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.data.callback({uid: 1});
|
event.data.callback({uid: 1});
|
||||||
@@ -1239,10 +1169,10 @@ QUnit.test('does not render and crash when destroyed before chat system is ready
|
|||||||
});
|
});
|
||||||
|
|
||||||
form.destroy();
|
form.destroy();
|
||||||
// here, the chat_manager system is ready, and the chatter can try to render
|
// here, the chatManager system is ready, and the chatter can try to render
|
||||||
// itself. We simply make sure here that no crashes occur (since the form
|
// itself. We simply make sure here that no crashes occur (since the form
|
||||||
// view is destroyed, all rpcs will be dropped, and many other mechanisms
|
// view is destroyed, all rpcs will be dropped, and many other mechanisms
|
||||||
// relying on events will not work, such as _getBus)
|
// relying on events will not work, such as the chat bus)
|
||||||
def.resolve();
|
def.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
odoo.define('mail.client_action_mobile_tests', function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var session = require('web.session');
|
|
||||||
var testUtils = require('web.test_utils');
|
|
||||||
var Widget = require('web.Widget');
|
|
||||||
|
|
||||||
var ChatAction = require('mail.chat_client_action');
|
|
||||||
var chat_manager = require('mail.chat_manager');
|
|
||||||
|
|
||||||
QUnit.module('mail', {}, function () {
|
|
||||||
|
|
||||||
QUnit.module('Discuss client action in mobile', {
|
|
||||||
beforeEach: function () {
|
|
||||||
this.data = {
|
|
||||||
'mail.message': {
|
|
||||||
fields: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.createChatAction = function (params) {
|
|
||||||
var Parent = Widget.extend({
|
|
||||||
do_push_state: function () {},
|
|
||||||
});
|
|
||||||
var parent = new Parent();
|
|
||||||
testUtils.addMockEnvironment(parent, {
|
|
||||||
data: this.data,
|
|
||||||
archs: {
|
|
||||||
'mail.message,false,search': '<search/>',
|
|
||||||
},
|
|
||||||
session: params.session || {},
|
|
||||||
intercepts: params.intercepts || {},
|
|
||||||
});
|
|
||||||
var chatAction = new ChatAction(parent, params);
|
|
||||||
chatAction.set_cp_bus(new Widget());
|
|
||||||
chatAction.appendTo($('#qunit-fixture'));
|
|
||||||
|
|
||||||
return chatAction;
|
|
||||||
};
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test('mobile basic rendering', function (assert) {
|
|
||||||
// This is a very basic first test for the client action. However, with
|
|
||||||
// the chat_manager, it is hard to override RPCs (for instance, the
|
|
||||||
// /mail/client_action route is always called when the test suite is
|
|
||||||
// launched), and we must wait for this RPC to be done before starting to
|
|
||||||
// test the interface. This should be refactored to facilitate the testing.
|
|
||||||
assert.expect(9);
|
|
||||||
var done = assert.async();
|
|
||||||
|
|
||||||
var rpc = session.rpc;
|
|
||||||
|
|
||||||
var chatAction = this.createChatAction({
|
|
||||||
id: 1,
|
|
||||||
context: {},
|
|
||||||
params: {},
|
|
||||||
intercepts: {
|
|
||||||
get_session: function (ev) {
|
|
||||||
ev.data.callback({});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
session: {
|
|
||||||
rpc: function (route, args) {
|
|
||||||
if (args.method === 'message_fetch') {
|
|
||||||
return $.when([]);
|
|
||||||
}
|
|
||||||
return rpc.apply(this, arguments);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
chat_manager.is_ready.then(function () {
|
|
||||||
// test basic rendering in mobile
|
|
||||||
assert.strictEqual(chatAction.$('.o_mail_chat_mobile_control_panel').length, 1,
|
|
||||||
"should have rendered a control panel");
|
|
||||||
assert.strictEqual(chatAction.$('.o_mail_chat_content .o_mail_no_content').length, 1,
|
|
||||||
"should display the no content message");
|
|
||||||
assert.strictEqual(chatAction.$('.o_mail_mobile_tabs').length, 1,
|
|
||||||
"should have rendered the tabs");
|
|
||||||
assert.ok(chatAction.$('.o_mail_mobile_tab[data-type=channel_inbox]').hasClass('active'),
|
|
||||||
"should be in inbox tab");
|
|
||||||
assert.strictEqual(chatAction.$('.o_mail_chat_mobile_inbox_buttons:visible').length, 1,
|
|
||||||
"inbox/starred buttons should be visible");
|
|
||||||
assert.ok(chatAction.$('.o_mail_chat_mobile_inbox_buttons .o_channel_inbox_item[data-type=channel_inbox]').hasClass('btn-primary'),
|
|
||||||
"should be in inbox");
|
|
||||||
|
|
||||||
// move to DMs tab
|
|
||||||
chatAction.$('.o_mail_mobile_tab[data-type=dm]').click();
|
|
||||||
assert.ok(chatAction.$('.o_mail_mobile_tab[data-type=dm]').hasClass('active'),
|
|
||||||
"should be in DMs tab");
|
|
||||||
assert.strictEqual(chatAction.$('.o_mail_chat_content .o_mail_no_content').length, 0,
|
|
||||||
"should display the no content message");
|
|
||||||
chatAction.$('.o_mail_chat_button_dm').click(); // click to add a channel
|
|
||||||
assert.strictEqual(chatAction.$('.o_mail_add_channel input:visible').length, 1,
|
|
||||||
"should display the input to add a channel");
|
|
||||||
|
|
||||||
chatAction.destroy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
odoo.define('mail.discuss_mobile_tests', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var ChatManager = require('mail.ChatManager');
|
||||||
|
var mailTestUtils = require('mail.testUtils');
|
||||||
|
|
||||||
|
var createBusService = mailTestUtils.createBusService;
|
||||||
|
var createDiscuss = mailTestUtils.createDiscuss;
|
||||||
|
|
||||||
|
QUnit.module('mail', {}, function () {
|
||||||
|
|
||||||
|
QUnit.module('Discuss client action in mobile', {
|
||||||
|
beforeEach: function () {
|
||||||
|
this.BusService = createBusService();
|
||||||
|
this.data = {
|
||||||
|
'mail.message': {
|
||||||
|
fields: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('mobile basic rendering', function (assert) {
|
||||||
|
// This is a very basic first test for the client action. However, with
|
||||||
|
// the chat_manager, it is hard to override RPCs (for instance, the
|
||||||
|
// /mail/client_action route is always called when the test suite is
|
||||||
|
// launched), and we must wait for this RPC to be done before starting to
|
||||||
|
// test the interface. This should be refactored to facilitate the testing.
|
||||||
|
assert.expect(9);
|
||||||
|
var done = assert.async();
|
||||||
|
|
||||||
|
var discuss = createDiscuss({
|
||||||
|
id: 1,
|
||||||
|
context: {},
|
||||||
|
params: {},
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method === 'message_fetch') {
|
||||||
|
return $.when([]);
|
||||||
|
}
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
services: [ChatManager, this.BusService],
|
||||||
|
});
|
||||||
|
|
||||||
|
var chatReady = discuss.call('chat_manager', 'isReady');
|
||||||
|
chatReady.then(function () {
|
||||||
|
// test basic rendering in mobile
|
||||||
|
assert.strictEqual(discuss.$('.o_mail_chat_mobile_control_panel').length, 1,
|
||||||
|
"should have rendered a control panel");
|
||||||
|
assert.strictEqual(discuss.$('.o_mail_chat_content .o_mail_no_content').length, 1,
|
||||||
|
"should display the no content message");
|
||||||
|
assert.strictEqual(discuss.$('.o_mail_mobile_tabs').length, 1,
|
||||||
|
"should have rendered the tabs");
|
||||||
|
assert.ok(discuss.$('.o_mail_mobile_tab[data-type=channel_inbox]').hasClass('active'),
|
||||||
|
"should be in inbox tab");
|
||||||
|
assert.strictEqual(discuss.$('.o_mail_chat_mobile_inbox_buttons:visible').length, 1,
|
||||||
|
"inbox/starred buttons should be visible");
|
||||||
|
assert.ok(discuss.$('.o_mail_chat_mobile_inbox_buttons .o_channel_inbox_item[data-type=channel_inbox]').hasClass('btn-primary'),
|
||||||
|
"should be in inbox");
|
||||||
|
|
||||||
|
// move to DMs tab
|
||||||
|
discuss.$('.o_mail_mobile_tab[data-type=dm]').click();
|
||||||
|
assert.ok(discuss.$('.o_mail_mobile_tab[data-type=dm]').hasClass('active'),
|
||||||
|
"should be in DMs tab");
|
||||||
|
assert.strictEqual(discuss.$('.o_mail_chat_content .o_mail_no_content').length, 0,
|
||||||
|
"should display the no content message");
|
||||||
|
discuss.$('.o_mail_chat_button_dm').click(); // click to add a channel
|
||||||
|
assert.strictEqual(discuss.$('.o_mail_add_channel input:visible').length, 1,
|
||||||
|
"should display the input to add a channel");
|
||||||
|
|
||||||
|
discuss.destroy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
odoo.define('mail.discuss_test', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var testUtils = require('web.test_utils');
|
||||||
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
|
var mailTestUtils = require('mail.testUtils');
|
||||||
|
|
||||||
|
var createDiscuss = mailTestUtils.createDiscuss;
|
||||||
|
|
||||||
|
QUnit.module('mail', {}, function () {
|
||||||
|
|
||||||
|
QUnit.module('Discuss client action', {
|
||||||
|
beforeEach: function () {
|
||||||
|
this.data = {
|
||||||
|
'mail.message': {
|
||||||
|
fields: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.skip('mobile basic rendering', function (assert) {
|
||||||
|
// Unfortunately, this test is skipped for now because there is no way to
|
||||||
|
// execute the whole test suite in mobile (it only works test by test), so
|
||||||
|
// as the client action include for mobile is rejected when we are not in
|
||||||
|
// mobile, it isn't possible to test it
|
||||||
|
// Moreover, RPCs done by the chat_manager (e.g. message_fetch) should be
|
||||||
|
// properly mocked.
|
||||||
|
assert.expect(11);
|
||||||
|
|
||||||
|
var parent = new Widget();
|
||||||
|
testUtils.addMockEnvironment(parent, {
|
||||||
|
data: this.data,
|
||||||
|
archs: {
|
||||||
|
'mail.message,false,search': '<search/>',
|
||||||
|
},
|
||||||
|
config: {device: {isMobile: true}},
|
||||||
|
});
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
id: 1,
|
||||||
|
context: {},
|
||||||
|
params: {},
|
||||||
|
};
|
||||||
|
var discuss = createDiscuss(params);
|
||||||
|
|
||||||
|
// test basic rendering in mobile
|
||||||
|
assert.equal(discuss.$(".o_mail_chat_mobile_control_panel").length, 1, "Mobile control panel created");
|
||||||
|
assert.equal(discuss.$(".o_mail_mobile_tab").length, 4, "Four mobile tabs created");
|
||||||
|
assert.equal(discuss.$('.o_mail_chat_content').length, 1, "One default chat content pane created");
|
||||||
|
assert.equal(discuss.$(".o_mail_chat_tab_pane").length, 3, "Three mobile tab panes created");
|
||||||
|
|
||||||
|
// Inbox
|
||||||
|
assert.equal(discuss.activeMobileTab, "channel_inbox", "'channel_inbox' is default active tab");
|
||||||
|
assert.ok(discuss.$(".o_channel_inbox_item:nth(0)").hasClass("btn-primary"), "Showing 'Inbox'");
|
||||||
|
|
||||||
|
// Starred
|
||||||
|
discuss.$(".o_channel_inbox_item[data-type='channel_starred']").click();
|
||||||
|
assert.ok(discuss.$(".o_channel_inbox_item:nth(1)").hasClass("btn-primary"), "Clicked on 'Starred'");
|
||||||
|
|
||||||
|
assert.ok(discuss.$(".o_mail_chat_content").is(":visible"), "Default main content pane visible");
|
||||||
|
|
||||||
|
discuss.$(".o_mail_mobile_tab[data-type='dm']").click();
|
||||||
|
assert.equal(discuss.activeMobileTab, "dm", "After click on 'Conversation', is now active tab");
|
||||||
|
|
||||||
|
assert.ok(!discuss.$(".o_mail_chat_content").is(":visible"), "none", "'Main' content pane is invisible");
|
||||||
|
assert.ok(discuss.$(".o_mail_chat_tab_pane:nth(0)").is(":visible"), "'Conversation' pane is visible");
|
||||||
|
|
||||||
|
discuss.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
odoo.define('mail.MockServer', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var MockServer = require('web.MockServer');
|
||||||
|
|
||||||
|
MockServer.include({
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_performRpc: function (route, args) {
|
||||||
|
if (route === '/mail/init_messaging') {
|
||||||
|
return $.when(this._mockInitMessaging(args));
|
||||||
|
}
|
||||||
|
return this._super(route, args);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Simulate the '/mail/init_messaging' route
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
_mockInitMessaging: function () {
|
||||||
|
return {
|
||||||
|
'needaction_inbox_counter': 0,
|
||||||
|
'starred_counter': 0,
|
||||||
|
'channel_slots': [],
|
||||||
|
'commands': [],
|
||||||
|
'mention_partner_suggestions': [],
|
||||||
|
'shortcodes': [],
|
||||||
|
'menu_id': false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
odoo.define('mail.testUtils', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Discuss = require('mail.chat_discuss');
|
||||||
|
|
||||||
|
var AbstractService = require('web.AbstractService');
|
||||||
|
var Bus = require('web.Bus');
|
||||||
|
var testUtils = require('web.test_utils');
|
||||||
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Utils
|
||||||
|
*
|
||||||
|
* In this module, we define some utility functions to create mock objects
|
||||||
|
* in the mail module, such as the BusService or Discuss.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mock bus_service, using 'bus' instead of bus.bus
|
||||||
|
*
|
||||||
|
* @param {web.bus} bus
|
||||||
|
* @return {AbstractService} the mock bus_service
|
||||||
|
*/
|
||||||
|
function createBusService(bus) {
|
||||||
|
|
||||||
|
var BusService = AbstractService.extend({
|
||||||
|
name: 'bus_service',
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
init: function (parent) {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
if (!bus) {
|
||||||
|
bus = new Bus();
|
||||||
|
}
|
||||||
|
this.bus = new _.extend(bus, {
|
||||||
|
/**
|
||||||
|
* Do nothing
|
||||||
|
*/
|
||||||
|
start_polling: function () {},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Public
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bus
|
||||||
|
*/
|
||||||
|
getBus: function () {
|
||||||
|
return this.bus;
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return BusService;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDiscuss(params) {
|
||||||
|
var Parent = Widget.extend({
|
||||||
|
do_push_state: function () {},
|
||||||
|
});
|
||||||
|
var parent = new Parent();
|
||||||
|
testUtils.addMockEnvironment(parent, {
|
||||||
|
data: params.data,
|
||||||
|
archs: {
|
||||||
|
'mail.message,false,search': '<search/>',
|
||||||
|
},
|
||||||
|
mockRPC: params.mockRPC,
|
||||||
|
services: params.services,
|
||||||
|
});
|
||||||
|
var discuss = new Discuss(parent, params);
|
||||||
|
discuss.set_cp_bus(new Widget());
|
||||||
|
discuss.appendTo($('#qunit-fixture'));
|
||||||
|
|
||||||
|
return discuss;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createBusService: createBusService,
|
||||||
|
createDiscuss: createDiscuss,
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
odoo.define('mail.systray_tests', function (require) {
|
odoo.define('mail.systray_tests', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var ChatManager = require('mail.ChatManager');
|
||||||
var systray = require('mail.systray');
|
var systray = require('mail.systray');
|
||||||
|
var mailTestUtils = require('mail.testUtils');
|
||||||
|
|
||||||
var testUtils = require('web.test_utils');
|
var testUtils = require('web.test_utils');
|
||||||
|
|
||||||
|
var createBusService = mailTestUtils.createBusService;
|
||||||
|
|
||||||
QUnit.module('mail', {}, function () {
|
QUnit.module('mail', {}, function () {
|
||||||
|
|
||||||
QUnit.module('ActivityMenu', {
|
QUnit.module('ActivityMenu', {
|
||||||
beforeEach: function () {
|
beforeEach: function () {
|
||||||
|
this.BusService = createBusService();
|
||||||
this.data = {
|
this.data = {
|
||||||
'mail.activity.menu': {
|
'mail.activity.menu': {
|
||||||
fields: {
|
fields: {
|
||||||
@@ -54,12 +58,13 @@ QUnit.test('activity menu widget: menu with no records', function (assert) {
|
|||||||
|
|
||||||
var activityMenu = new systray.ActivityMenu();
|
var activityMenu = new systray.ActivityMenu();
|
||||||
testUtils.addMockEnvironment(activityMenu, {
|
testUtils.addMockEnvironment(activityMenu, {
|
||||||
mockRPC: function (route, args) {
|
mockRPC: function (route, args) {
|
||||||
if (args.method === 'activity_user_count') {
|
if (args.method === 'activity_user_count') {
|
||||||
return $.when([]);
|
return $.when([]);
|
||||||
}
|
}
|
||||||
return this._super(route, args);
|
return this._super(route, args);
|
||||||
},
|
},
|
||||||
|
services: [ChatManager, this.BusService]
|
||||||
});
|
});
|
||||||
activityMenu.appendTo($('#qunit-fixture'));
|
activityMenu.appendTo($('#qunit-fixture'));
|
||||||
assert.ok(activityMenu.$('.o_no_activity').hasClass('o_no_activity'), "should not have instance of widget");
|
assert.ok(activityMenu.$('.o_no_activity').hasClass('o_no_activity'), "should not have instance of widget");
|
||||||
@@ -76,8 +81,9 @@ QUnit.test('activity menu widget: activity menu with 3 records', function (asser
|
|||||||
return $.when(self.data['mail.activity.menu']['records']);
|
return $.when(self.data['mail.activity.menu']['records']);
|
||||||
}
|
}
|
||||||
return this._super(route, args);
|
return this._super(route, args);
|
||||||
},
|
},
|
||||||
});
|
services: [ChatManager, this.BusService],
|
||||||
|
});
|
||||||
activityMenu.appendTo($('#qunit-fixture'));
|
activityMenu.appendTo($('#qunit-fixture'));
|
||||||
assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget');
|
assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget');
|
||||||
assert.ok(activityMenu.$('.o_mail_channel_preview').hasClass('o_mail_channel_preview'), "should instance of widget");
|
assert.ok(activityMenu.$('.o_mail_channel_preview').hasClass('o_mail_channel_preview'), "should instance of widget");
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
<xpath expr="." position="inside">
|
<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/many2many_tags_email.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="/mail/static/src/js/client_action.js"></script>
|
|
||||||
<script type="text/javascript" src="/mail/static/src/js/client_action_mobile.js"></script>
|
|
||||||
<script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script>
|
||||||
|
<script type="text/javascript" src="/mail/static/src/js/discuss.js"></script>
|
||||||
|
<script type="text/javascript" src="/mail/static/src/js/discuss_mobile.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/extended_chat_window.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/extended_chat_window.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/document_viewer.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/document_viewer.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/composer.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/composer.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/chat_manager.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/services/chat_manager.js"></script>
|
||||||
|
<script type="text/javascript" src="/mail/static/src/js/services/chat_window_manager.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/chatter.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/chatter.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/chatter_composer.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/chatter_composer.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/followers.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/followers.js"></script>
|
||||||
@@ -24,13 +25,11 @@
|
|||||||
<script type="text/javascript" src="/mail/static/src/js/systray.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/systray.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/tour.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/tour.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/utils.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/utils.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/window_manager.js"></script>
|
|
||||||
<script type="text/javascript" src="/mail/static/src/js/activity.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/activity.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/chat_mixin.js"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/mail/static/src/less/announcement.less"/>
|
<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/chat_window.less" type="text/less"/>
|
||||||
|
<link rel="stylesheet" href="/mail/static/src/less/discuss.less" type="text/less"/>
|
||||||
<link rel="stylesheet" href="/mail/static/src/less/extended_chat_window.less" type="text/less"/>
|
<link rel="stylesheet" href="/mail/static/src/less/extended_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/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/chatter.less" type="text/less"/>
|
||||||
@@ -43,15 +42,18 @@
|
|||||||
|
|
||||||
<template id="qunit_suite" name="mail_tests" inherit_id="web.qunit_suite">
|
<template id="qunit_suite" name="mail_tests" inherit_id="web.qunit_suite">
|
||||||
<xpath expr="//t[@t-set='head']" position="inside">
|
<xpath expr="//t[@t-set='head']" position="inside">
|
||||||
|
<script type="text/javascript" src="/mail/static/tests/chat_window_tests.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/tests/chatter_tests.js"></script>
|
<script type="text/javascript" src="/mail/static/tests/chatter_tests.js"></script>
|
||||||
|
<script type="text/javascript" src="/mail/static/tests/discuss_tests.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/tests/systray_tests.js"></script>
|
<script type="text/javascript" src="/mail/static/tests/systray_tests.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/tests/chat_window_test.js"></script>
|
<script type="text/javascript" src="/mail/static/tests/helpers/mock_server.js"></script>
|
||||||
|
<script type="text/javascript" src="/mail/static/tests/helpers/test_utils.js"></script>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="qunit_mobile_suite" name="mail_mobile_tests" inherit_id="web.qunit_mobile_suite">
|
<template id="qunit_mobile_suite" name="mail_mobile_tests" inherit_id="web.qunit_mobile_suite">
|
||||||
<xpath expr="//t[@t-set='head']" position="inside">
|
<xpath expr="//t[@t-set='head']" position="inside">
|
||||||
<script type="text/javascript" src="/mail/static/tests/client_action_mobile_tests.js"></script>
|
<script type="text/javascript" src="/mail/static/tests/discuss_mobile_tests.js"></script>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ var data_manager = require('web.data_manager');
|
|||||||
var Dialog = require('web.Dialog');
|
var Dialog = require('web.Dialog');
|
||||||
var dom = require('web.dom');
|
var dom = require('web.dom');
|
||||||
var Loading = require('web.Loading');
|
var Loading = require('web.Loading');
|
||||||
var mixins = require('web.mixins');
|
|
||||||
var NotificationManager = require('web.NotificationManager');
|
var NotificationManager = require('web.NotificationManager');
|
||||||
var RainbowMan = require('web.RainbowMan');
|
var RainbowMan = require('web.RainbowMan');
|
||||||
|
var ServiceProviderMixin = require('web.ServiceProviderMixin');
|
||||||
var session = require('web.session');
|
var session = require('web.session');
|
||||||
var Widget = require('web.Widget');
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
var qweb = core.qweb;
|
var qweb = core.qweb;
|
||||||
|
|
||||||
var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
|
var AbstractWebClient = Widget.extend(ServiceProviderMixin, {
|
||||||
custom_events: {
|
custom_events: {
|
||||||
clear_uncommitted_changes: function (e) {
|
clear_uncommitted_changes: function (e) {
|
||||||
this.clear_uncommitted_changes().then(e.data.callback);
|
this.clear_uncommitted_changes().then(e.data.callback);
|
||||||
@@ -90,8 +90,8 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
|
|||||||
},
|
},
|
||||||
init: function (parent) {
|
init: function (parent) {
|
||||||
this.client_options = {};
|
this.client_options = {};
|
||||||
mixins.ServiceProvider.init.call(this);
|
|
||||||
this._super(parent);
|
this._super(parent);
|
||||||
|
ServiceProviderMixin.init.call(this);
|
||||||
this.origin = undefined;
|
this.origin = undefined;
|
||||||
this._current_state = null;
|
this._current_state = null;
|
||||||
this.menu_dm = new concurrency.DropMisordered();
|
this.menu_dm = new concurrency.DropMisordered();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ var crash_manager = require('web.crash_manager');
|
|||||||
var framework = require('web.framework');
|
var framework = require('web.framework');
|
||||||
var session = require('web.session');
|
var session = require('web.session');
|
||||||
|
|
||||||
|
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
var _lt = core._lt;
|
var _lt = core._lt;
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,22 @@ odoo.define('web.AbstractService', function (require) {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var Class = require('web.Class');
|
var Class = require('web.Class');
|
||||||
|
var Mixins = require('web.mixins');
|
||||||
|
var ServicesMixin = require('web.ServicesMixin');
|
||||||
|
|
||||||
var AbstractService = Class.extend({
|
var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
|
||||||
Services: [],
|
Services: [],
|
||||||
|
dependencies: [],
|
||||||
name: null,
|
name: null,
|
||||||
|
init: function (parent) {
|
||||||
|
Mixins.EventDispatcherMixin.init.call(this, arguments);
|
||||||
|
this.setParent(parent);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var realExtend = AbstractService.extend;
|
var realExtend = AbstractService.extend;
|
||||||
|
|
||||||
AbstractService.extend = function() {
|
AbstractService.extend = function () {
|
||||||
var Service = realExtend.apply(this, arguments);
|
var Service = realExtend.apply(this, arguments);
|
||||||
AbstractService.prototype.Services.push(Service);
|
AbstractService.prototype.Services.push(Service);
|
||||||
return Service;
|
return Service;
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ var mixins = require('web.mixins');
|
|||||||
* @class Bus
|
* @class Bus
|
||||||
*/
|
*/
|
||||||
return Class.extend(mixins.EventDispatcherMixin, {
|
return Class.extend(mixins.EventDispatcherMixin, {
|
||||||
init: function() {
|
init: function () {
|
||||||
mixins.EventDispatcherMixin.init.call(this, parent);
|
mixins.EventDispatcherMixin.init.call(this, arguments);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ odoo.define('web.mixins', function (require) {
|
|||||||
|
|
||||||
var Class = require('web.Class');
|
var Class = require('web.Class');
|
||||||
var utils = require('web.utils');
|
var utils = require('web.utils');
|
||||||
var AbstractService = require('web.AbstractService');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mixin to structure objects' life-cycles folowing a parent-children
|
* Mixin to structure objects' life-cycles folowing a parent-children
|
||||||
@@ -409,134 +408,10 @@ var PropertiesMixin = _.extend({}, EventDispatcherMixin, {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var ServiceProvider = {
|
|
||||||
services: {},
|
|
||||||
init: function () {
|
|
||||||
var self = this;
|
|
||||||
_.each(AbstractService.prototype.Services, function (Service) {
|
|
||||||
var service = new Service();
|
|
||||||
self.services[service.name] = service;
|
|
||||||
});
|
|
||||||
this.custom_events = _.clone(this.custom_events);
|
|
||||||
this.custom_events.call_service = this._call_service.bind(this);
|
|
||||||
},
|
|
||||||
_call_service: function (event) {
|
|
||||||
var service = this.services[event.data.service];
|
|
||||||
var args = (event.data.args || []).concat(event.target);
|
|
||||||
var result = service[event.data.method].apply(service, args);
|
|
||||||
event.data.callback(result);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ParentedMixin: ParentedMixin,
|
ParentedMixin: ParentedMixin,
|
||||||
EventDispatcherMixin: EventDispatcherMixin,
|
EventDispatcherMixin: EventDispatcherMixin,
|
||||||
PropertiesMixin: PropertiesMixin,
|
PropertiesMixin: PropertiesMixin,
|
||||||
ServiceProvider: ServiceProvider,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
odoo.define('web.ServicesMixin', function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var rpc = require('web.rpc');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @mixin
|
|
||||||
* @name ServicesMixin
|
|
||||||
*/
|
|
||||||
var ServicesMixin = {
|
|
||||||
call: function (service, method) {
|
|
||||||
var args = Array.prototype.slice.call(arguments, 2);
|
|
||||||
var result;
|
|
||||||
this.trigger_up('call_service', {
|
|
||||||
service: service,
|
|
||||||
method: method,
|
|
||||||
args: args,
|
|
||||||
callback: function (r) {
|
|
||||||
result = r;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Builds and executes RPC query. Returns a deferred's promise resolved with
|
|
||||||
* the RPC result.
|
|
||||||
*
|
|
||||||
* @param {string} params either a route or a model
|
|
||||||
* @param {string} options if a model is given, this argument is a method
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
_rpc: function (params, options) {
|
|
||||||
var query = rpc.buildQuery(params);
|
|
||||||
var def = this.call('ajax', 'rpc', query.route, query.params, options);
|
|
||||||
return def ? def.promise() : $.Deferred().promise();
|
|
||||||
},
|
|
||||||
loadFieldView: function (dataset, view_id, view_type, options) {
|
|
||||||
return this.loadViews(dataset.model, dataset.get_context().eval(), [[view_id, view_type]], options).then(function (result) {
|
|
||||||
return result[view_type];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
loadViews: function (modelName, context, views, options) {
|
|
||||||
var def = $.Deferred();
|
|
||||||
this.trigger_up('load_views', {
|
|
||||||
modelName: modelName,
|
|
||||||
context: context,
|
|
||||||
views: views,
|
|
||||||
options: options,
|
|
||||||
on_success: def.resolve.bind(def),
|
|
||||||
});
|
|
||||||
return def;
|
|
||||||
},
|
|
||||||
loadFilters: function (dataset, action_id) {
|
|
||||||
var def = $.Deferred();
|
|
||||||
this.trigger_up('load_filters', {
|
|
||||||
dataset: dataset,
|
|
||||||
action_id: action_id,
|
|
||||||
on_success: def.resolve.bind(def),
|
|
||||||
});
|
|
||||||
return def;
|
|
||||||
},
|
|
||||||
// Session stuff
|
|
||||||
getSession: function () {
|
|
||||||
var session;
|
|
||||||
this.trigger_up('get_session', {
|
|
||||||
callback: function (result) {
|
|
||||||
session = result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return session;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Informs the action manager to do an action. This supposes that the action
|
|
||||||
* manager can be found amongst the ancestors of the current widget.
|
|
||||||
* If that's not the case this method will simply return an unresolved
|
|
||||||
* deferred.
|
|
||||||
*
|
|
||||||
* @param {any} action
|
|
||||||
* @param {any} options
|
|
||||||
* @returns {Deferred}
|
|
||||||
*/
|
|
||||||
do_action: function (action, options) {
|
|
||||||
var def = $.Deferred();
|
|
||||||
|
|
||||||
this.trigger_up('do_action', {
|
|
||||||
action: action,
|
|
||||||
options: options,
|
|
||||||
on_success: function (result) { def.resolve(result); },
|
|
||||||
on_fail: function (result) { def.reject(result); },
|
|
||||||
});
|
|
||||||
return def;
|
|
||||||
},
|
|
||||||
do_notify: function (title, message, sticky, className) {
|
|
||||||
this.trigger_up('notification', {title: title, message: message, sticky: sticky, className: className});
|
|
||||||
},
|
|
||||||
do_warn: function (title, message, sticky, className) {
|
|
||||||
this.trigger_up('warning', {title: title, message: message, sticky: sticky, className: className});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return ServicesMixin;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
odoo.define('web.ServiceProviderMixin', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var AbstractService = require('web.AbstractService');
|
||||||
|
var utils = require('web.utils');
|
||||||
|
|
||||||
|
var ServiceProviderMixin = {
|
||||||
|
services: {},
|
||||||
|
init: function () {
|
||||||
|
// to properly instantiate services with this as parent, this mixin
|
||||||
|
// assumes that it is used along the EventDispatcherMixin, and that
|
||||||
|
// EventDispatchedMixin's init is called first
|
||||||
|
var self = this;
|
||||||
|
// as EventDispatcherMixin's init is already called, this handler has to
|
||||||
|
// be bound manually
|
||||||
|
this.on('call_service', this, this._call_service.bind(this));
|
||||||
|
|
||||||
|
var sortedServices = this._sortServices(AbstractService.prototype.Services);
|
||||||
|
_.each(sortedServices, function (Service) {
|
||||||
|
var service = new Service(self);
|
||||||
|
self.services[service.name] = service;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_call_service: function (event) {
|
||||||
|
var service = this.services[event.data.service];
|
||||||
|
var args = event.data.args || [];
|
||||||
|
// ajax service uses an extra 'target' argument for rpc
|
||||||
|
if (event.data.service === 'ajax' && event.data.method === 'rpc') {
|
||||||
|
args = args.concat(event.target);
|
||||||
|
}
|
||||||
|
var result = service[event.data.method].apply(service, args);
|
||||||
|
event.data.callback(result);
|
||||||
|
},
|
||||||
|
_sortServices: function (services) {
|
||||||
|
var nodes = {};
|
||||||
|
// Create nodes (services)
|
||||||
|
_.each(services, function (Service) {
|
||||||
|
nodes[Service.prototype.name] = Service.prototype.dependencies;
|
||||||
|
});
|
||||||
|
var sorted;
|
||||||
|
try {
|
||||||
|
sorted = utils.topologicalSort(nodes);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('topologicalSort Error:', err.message);
|
||||||
|
sorted = nodes;
|
||||||
|
}
|
||||||
|
// Sort services based on sorted
|
||||||
|
// Note: we convert sorted to an object key=>index for efficiency
|
||||||
|
var sortedObj = _.invert(_.object(_.pairs(sorted)));
|
||||||
|
sorted = _.sortBy(services, function (Service) {
|
||||||
|
return sortedObj[Service.prototype.name];
|
||||||
|
});
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return ServiceProviderMixin;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
odoo.define('web.ServicesMixin', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var rpc = require('web.rpc');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin
|
||||||
|
* @name ServicesMixin
|
||||||
|
*/
|
||||||
|
var ServicesMixin = {
|
||||||
|
call: function (service, method) {
|
||||||
|
var args = Array.prototype.slice.call(arguments, 2);
|
||||||
|
var result;
|
||||||
|
this.trigger_up('call_service', {
|
||||||
|
service: service,
|
||||||
|
method: method,
|
||||||
|
args: args,
|
||||||
|
callback: function (r) {
|
||||||
|
result = r;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Builds and executes RPC query. Returns a deferred's promise resolved with
|
||||||
|
* the RPC result.
|
||||||
|
*
|
||||||
|
* @param {string} params either a route or a model
|
||||||
|
* @param {string} options if a model is given, this argument is a method
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
_rpc: function (params, options) {
|
||||||
|
var query = rpc.buildQuery(params);
|
||||||
|
var def = this.call('ajax', 'rpc', query.route, query.params, options);
|
||||||
|
return def ? def.promise() : $.Deferred().promise();
|
||||||
|
},
|
||||||
|
loadFieldView: function (dataset, view_id, view_type, options) {
|
||||||
|
return this.loadViews(dataset.model, dataset.get_context().eval(), [[view_id, view_type]], options).then(function (result) {
|
||||||
|
return result[view_type];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadViews: function (modelName, context, views, options) {
|
||||||
|
var def = $.Deferred();
|
||||||
|
this.trigger_up('load_views', {
|
||||||
|
modelName: modelName,
|
||||||
|
context: context,
|
||||||
|
views: views,
|
||||||
|
options: options,
|
||||||
|
on_success: def.resolve.bind(def),
|
||||||
|
});
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
loadFilters: function (dataset, action_id) {
|
||||||
|
var def = $.Deferred();
|
||||||
|
this.trigger_up('load_filters', {
|
||||||
|
dataset: dataset,
|
||||||
|
action_id: action_id,
|
||||||
|
on_success: def.resolve.bind(def),
|
||||||
|
});
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
// Session stuff
|
||||||
|
getSession: function () {
|
||||||
|
var session;
|
||||||
|
this.trigger_up('get_session', {
|
||||||
|
callback: function (result) {
|
||||||
|
session = result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Informs the action manager to do an action. This supposes that the action
|
||||||
|
* manager can be found amongst the ancestors of the current widget.
|
||||||
|
* If that's not the case this method will simply return an unresolved
|
||||||
|
* deferred.
|
||||||
|
*
|
||||||
|
* @param {any} action
|
||||||
|
* @param {any} options
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
do_action: function (action, options) {
|
||||||
|
var def = $.Deferred();
|
||||||
|
|
||||||
|
this.trigger_up('do_action', {
|
||||||
|
action: action,
|
||||||
|
options: options,
|
||||||
|
on_success: function (result) { def.resolve(result); },
|
||||||
|
on_fail: function (result) { def.reject(result); },
|
||||||
|
});
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
do_notify: function (title, message, sticky, className) {
|
||||||
|
this.trigger_up('notification', {title: title, message: message, sticky: sticky, className: className});
|
||||||
|
},
|
||||||
|
do_warn: function (title, message, sticky, className) {
|
||||||
|
this.trigger_up('warning', {title: title, message: message, sticky: sticky, className: className});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return ServicesMixin;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -300,6 +300,37 @@ var utils = {
|
|||||||
}
|
}
|
||||||
return fn(mod, Math.floor(x));
|
return fn(mod, Math.floor(x));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Topological sort of nodes
|
||||||
|
*
|
||||||
|
* @param {Object} nodes where key is name of node, and value is list of
|
||||||
|
* names of dependent nodes.
|
||||||
|
* @param {string[]} nodes[name] list of named dependencies of node having
|
||||||
|
* 'name' as its name.
|
||||||
|
* @return {string[]} topological sort of nodes by their names.
|
||||||
|
* @throws {Error} if there is at least one circular
|
||||||
|
* dependency between nodes
|
||||||
|
*/
|
||||||
|
topologicalSort: function (nodes) {
|
||||||
|
var sorted = [];
|
||||||
|
var unresolved = nodes;
|
||||||
|
while (!_.isEmpty(unresolved)) {
|
||||||
|
// Get all nodes that have no dependency
|
||||||
|
var resolvable = _.pick(unresolved, function (dependencies, name) {
|
||||||
|
return _.isEmpty(_.difference(dependencies, sorted));
|
||||||
|
});
|
||||||
|
if (_.isEmpty(resolvable)) {
|
||||||
|
throw new Error("Circular dependency detected");
|
||||||
|
}
|
||||||
|
// Append them to sorted
|
||||||
|
sorted = _.union(sorted, _.keys(resolvable));
|
||||||
|
// Remove these nodes from unresolved
|
||||||
|
_.each(resolvable, function (dependencies, name) {
|
||||||
|
unresolved = _.omit(unresolved, name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sorted;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision
|
* performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision
|
||||||
* See the corresponding float_round() in server/tools/float_utils.py for more info
|
* See the corresponding float_round() in server/tools/float_utils.py for more info
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ odoo.define('web.ReportService', function (require) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var AbstractService = require('web.AbstractService');
|
var AbstractService = require('web.AbstractService');
|
||||||
var session = require('web.session');
|
|
||||||
|
|
||||||
var wkhtmltopdfState;
|
|
||||||
|
|
||||||
var ReportService = AbstractService.extend({
|
var ReportService = AbstractService.extend({
|
||||||
name: 'report',
|
name: 'report',
|
||||||
|
dependencies: ['ajax'],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the state of the installation of wkhtmltopdf on the server.
|
* Checks the state of the installation of wkhtmltopdf on the server.
|
||||||
@@ -21,12 +19,12 @@ var ReportService = AbstractService.extend({
|
|||||||
* (possible values are 'ok', 'broken', 'install', 'upgrade', 'workers').
|
* (possible values are 'ok', 'broken', 'install', 'upgrade', 'workers').
|
||||||
*/
|
*/
|
||||||
checkWkhtmltopdf: function () {
|
checkWkhtmltopdf: function () {
|
||||||
if (!wkhtmltopdfState) {
|
if (!this.wkhtmltopdfState) {
|
||||||
// AAB: for now, services aren't available for each other, so we
|
this.wkhtmltopdfState = this._rpc({
|
||||||
// can't use this._rpc (the ajax service)
|
route:'/report/check_wkhtmltopdf'
|
||||||
wkhtmltopdfState = session.rpc('/report/check_wkhtmltopdf');
|
});
|
||||||
}
|
}
|
||||||
return wkhtmltopdfState;
|
return this.wkhtmltopdfState;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
odoo.define('web.action_manager_tests', function (require) {
|
odoo.define('web.action_manager_tests', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var ReportClientAction = require('report.client_action');
|
||||||
|
|
||||||
var ControlPanelMixin = require('web.ControlPanelMixin');
|
var ControlPanelMixin = require('web.ControlPanelMixin');
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var ReportClientAction = require('report.client_action');
|
var ReportService = require('web.ReportService');
|
||||||
var testUtils = require('web.test_utils');
|
var testUtils = require('web.test_utils');
|
||||||
var Widget = require('web.Widget');
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
@@ -1338,6 +1340,7 @@ QUnit.module('ActionManager', {
|
|||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
services: [ReportService],
|
||||||
session: {
|
session: {
|
||||||
get_file: function (params) {
|
get_file: function (params) {
|
||||||
assert.step(params.url);
|
assert.step(params.url);
|
||||||
@@ -1347,7 +1350,9 @@ QUnit.module('ActionManager', {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
actionManager.doAction(7, {
|
actionManager.doAction(7, {
|
||||||
on_close: assert.step.bind(assert, 'on_close'),
|
on_close: function () {
|
||||||
|
assert.step('on_close');
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.verifySteps([
|
assert.verifySteps([
|
||||||
@@ -1374,6 +1379,7 @@ QUnit.module('ActionManager', {
|
|||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
services: [ReportService],
|
||||||
session: {
|
session: {
|
||||||
get_file: function (params) {
|
get_file: function (params) {
|
||||||
assert.step(params.url);
|
assert.step(params.url);
|
||||||
@@ -1388,7 +1394,6 @@ QUnit.module('ActionManager', {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
actionManager.doAction(7);
|
actionManager.doAction(7);
|
||||||
|
|
||||||
assert.verifySteps([
|
assert.verifySteps([
|
||||||
'/web/action/load',
|
'/web/action/load',
|
||||||
'/report/check_wkhtmltopdf',
|
'/report/check_wkhtmltopdf',
|
||||||
@@ -1430,6 +1435,7 @@ QUnit.module('ActionManager', {
|
|||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
services: [ReportService],
|
||||||
session: {
|
session: {
|
||||||
get_file: function (params) {
|
get_file: function (params) {
|
||||||
assert.step(params.url); // should not be called
|
assert.step(params.url); // should not be called
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ var MockServer = Class.extend({
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Simulate a 'field_get' operation
|
* Simulate a 'fields_get' operation
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {string} modelName
|
* @param {string} modelName
|
||||||
@@ -464,7 +464,7 @@ var MockServer = Class.extend({
|
|||||||
if (!action) {
|
if (!action) {
|
||||||
// when the action doesn't exist, the real server doesn't crash, it
|
// when the action doesn't exist, the real server doesn't crash, it
|
||||||
// simply returns false
|
// simply returns false
|
||||||
console.warn("No action found for ID " + event.data.actionID);
|
console.warn("No action found for ID " + kwargs.action_id);
|
||||||
}
|
}
|
||||||
return action || false;
|
return action || false;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ var core = require('web.core');
|
|||||||
var dom = require('web.dom');
|
var dom = require('web.dom');
|
||||||
var session = require('web.session');
|
var session = require('web.session');
|
||||||
var MockServer = require('web.MockServer');
|
var MockServer = require('web.MockServer');
|
||||||
|
var utils = require('web.utils');
|
||||||
var Widget = require('web.Widget');
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
var DebouncedField = basic_fields.DebouncedField;
|
var DebouncedField = basic_fields.DebouncedField;
|
||||||
@@ -97,12 +98,6 @@ var createActionManager = function (params) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
addMockEnvironment(widget, _.defaults(params, {debounce: false}));
|
addMockEnvironment(widget, _.defaults(params, {debounce: false}));
|
||||||
intercept(widget, 'call_service', function (event) {
|
|
||||||
if (event.data.service === 'report') {
|
|
||||||
var state = widget._rpc({route: '/report/check_wkhtmltopdf'});
|
|
||||||
event.data.callback(state);
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
widget.appendTo($target);
|
widget.appendTo($target);
|
||||||
widget.$el.addClass('o_web_client');
|
widget.$el.addClass('o_web_client');
|
||||||
|
|
||||||
@@ -330,6 +325,7 @@ function addMockEnvironment(widget, params) {
|
|||||||
currentDate: params.currentDate,
|
currentDate: params.currentDate,
|
||||||
debug: params.debug,
|
debug: params.debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
// make sure the debounce value for input fields is set to 0
|
// make sure the debounce value for input fields is set to 0
|
||||||
var initialDebounceValue = DebouncedField.prototype.DEBOUNCE;
|
var initialDebounceValue = DebouncedField.prototype.DEBOUNCE;
|
||||||
DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0;
|
DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0;
|
||||||
@@ -406,11 +402,53 @@ function addMockEnvironment(widget, params) {
|
|||||||
widgetDestroy.call(this);
|
widgetDestroy.call(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
intercept(widget, 'call_service', function (event) {
|
// Dispatch service calls
|
||||||
if (event.data.service === 'ajax') {
|
// Note: some services could call other services at init,
|
||||||
var result = mockServer.performRpc(event.data.args[0], event.data.args[1]);
|
// Which is why we have to init services after that
|
||||||
event.data.callback(result);
|
var services = {};
|
||||||
|
intercept(widget, 'call_service', function (ev) {
|
||||||
|
var args, result;
|
||||||
|
if (ev.data.service === 'ajax') {
|
||||||
|
// ajax service is already mocked by the server
|
||||||
|
var route = ev.data.args[0];
|
||||||
|
args = ev.data.args[1];
|
||||||
|
result = mockServer.performRpc(route, args);
|
||||||
|
} else if (services[ev.data.service]) {
|
||||||
|
var service = services[ev.data.service];
|
||||||
|
args = (ev.data.args || []);
|
||||||
|
result = service[ev.data.method].apply(service, args);
|
||||||
}
|
}
|
||||||
|
ev.data.callback(result);
|
||||||
|
});
|
||||||
|
// Instantiate services
|
||||||
|
// Note: ensure topological sort of services based on their dependencies
|
||||||
|
var sortServices = function (services) {
|
||||||
|
// Create nodes (services), with ajax already loaded
|
||||||
|
var nodes = { ajax: [] };
|
||||||
|
_.each(services, function (Service) {
|
||||||
|
nodes[Service.prototype.name] = Service.prototype.dependencies;
|
||||||
|
});
|
||||||
|
var sorted;
|
||||||
|
try {
|
||||||
|
sorted = utils.topologicalSort(nodes);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('topologicalSort Error:', err.message);
|
||||||
|
sorted = nodes;
|
||||||
|
}
|
||||||
|
// Remove ajax from sorted
|
||||||
|
sorted = _.without(sorted, 'ajax');
|
||||||
|
// Sort services based on sorted
|
||||||
|
// Note: we convert sorted to an object key=>index for efficiency
|
||||||
|
var sortedObj = _.invert(_.object(_.pairs(sorted)));
|
||||||
|
sorted = _.sortBy(services, function (Service) {
|
||||||
|
return sortedObj[Service.prototype.name];
|
||||||
|
});
|
||||||
|
return sorted;
|
||||||
|
};
|
||||||
|
var sortedServices = sortServices(params.services);
|
||||||
|
_.each(sortedServices, function (Service) {
|
||||||
|
var service = new Service(widget);
|
||||||
|
services[service.name] = service;
|
||||||
});
|
});
|
||||||
|
|
||||||
intercept(widget, 'load_action', function (event) {
|
intercept(widget, 'load_action', function (event) {
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
<script type="text/javascript" src="/web/static/src/js/services/ajax_service.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/services/ajax_service.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/time.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/time.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/mixins.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/mixins.js"></script>
|
||||||
|
<script type="text/javascript" src="/web/static/src/js/core/service_mixins.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/rpc.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/rpc.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/widget.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/widget.js"></script>
|
||||||
<script type="text/javascript" src="/web/static/src/js/core/registry.js"></script>
|
<script type="text/javascript" src="/web/static/src/js/core/registry.js"></script>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
odoo.define('web_editor.BodyManager', function (require) {
|
odoo.define('web_editor.BodyManager', function (require) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var mixins = require('web.mixins');
|
|
||||||
var session = require('web.session');
|
|
||||||
var rootWidget = require('web_editor.root_widget');
|
var rootWidget = require('web_editor.root_widget');
|
||||||
|
var ServiceProviderMixin = require('web.ServiceProviderMixin');
|
||||||
|
var session = require('web.session');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element which is designed to be unique and that will be the top-most element
|
* Element which is designed to be unique and that will be the top-most element
|
||||||
@@ -11,13 +11,13 @@ var rootWidget = require('web_editor.root_widget');
|
|||||||
* this Class instance. Its main role will be to retrieve RPC demands from its
|
* this Class instance. Its main role will be to retrieve RPC demands from its
|
||||||
* children and handle them.
|
* children and handle them.
|
||||||
*/
|
*/
|
||||||
var BodyManager = rootWidget.RootWidget.extend(mixins.ServiceProvider, {
|
var BodyManager = rootWidget.RootWidget.extend(ServiceProviderMixin, {
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
init: function () {
|
init: function () {
|
||||||
mixins.ServiceProvider.init.call(this);
|
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
|
ServiceProviderMixin.init.call(this);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ var config = require('web.config');
|
|||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var mixins = require('web.mixins');
|
var mixins = require('web.mixins');
|
||||||
var rpc = require('web.rpc');
|
var rpc = require('web.rpc');
|
||||||
|
var ServiceProviderMixin = require('web.ServiceProviderMixin');
|
||||||
var session = require('web.session');
|
var session = require('web.session');
|
||||||
var TourManager = require('web_tour.TourManager');
|
var TourManager = require('web_tour.TourManager');
|
||||||
|
|
||||||
@@ -16,10 +17,10 @@ if (config.device.isMobile) {
|
|||||||
return $.Deferred().reject();
|
return $.Deferred().reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
var CallService = Class.extend(mixins.EventDispatcherMixin, mixins.ServiceProvider, {
|
var CallService = Class.extend(mixins.EventDispatcherMixin, ServiceProviderMixin, {
|
||||||
init: function () {
|
init: function () {
|
||||||
mixins.ServiceProvider.init.call(this);
|
|
||||||
mixins.EventDispatcherMixin.init.call(this);
|
mixins.EventDispatcherMixin.init.call(this);
|
||||||
|
ServiceProviderMixin.init.call(this);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<template id="assets_frontend" name="im_livechat assets frontend" inherit_id="website.assets_frontend">
|
<template id="assets_frontend" name="im_livechat assets frontend" inherit_id="website.assets_frontend">
|
||||||
<xpath expr="." position="inside">
|
<xpath expr="." position="inside">
|
||||||
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
|
<script type="text/javascript" src="/bus/static/src/js/bus.js"></script>
|
||||||
|
<script type="text/javascript" src="/bus/static/src/js/services/bus_service.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/chat_window.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/document_viewer.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/document_viewer.js"></script>
|
||||||
<script type="text/javascript" src="/mail/static/src/js/thread.js"></script>
|
<script type="text/javascript" src="/mail/static/src/js/thread.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user