[IMP] mail, *: introduce public livechat bundle
*: hr, im_livechat, snailmail, website_livechat This commit introduces the public livechat bundle, which contains the models that are specifically used in public livechat. Doing so reduces the amount of model and data in frontend and external lib, which makes the page load faster. Task-2990182 Task-2990191 closes odoo/odoo#100768 Related: odoo/enterprise#31608 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { patchRecordMethods } from '@mail/model/model_core';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/messaging';
|
||||
import '@mail/core_models/messaging';
|
||||
|
||||
patchRecordMethods('Messaging', {
|
||||
/**
|
||||
|
||||
@@ -50,11 +50,16 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'im_livechat/static/src/components/*/*',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
('include', 'mail.assets_public_livechat'),
|
||||
'im_livechat/static/src/public/main.js',
|
||||
'im_livechat/static/src/services/*.js',
|
||||
'im_livechat/static/src/legacy/public_livechat_chatbot.js',
|
||||
'im_livechat/static/src/legacy/public_livechat.scss',
|
||||
'im_livechat/static/src/legacy/public_livechat_chatbot.scss',
|
||||
'mail/static/src/utils/*.js',
|
||||
'mail/static/src/js/utils.js',
|
||||
'mail/static/src/component_hooks/*.js',
|
||||
'mail/static/src/services/messaging_service.js',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'im_livechat/static/src/js/im_livechat_channel_form_view.js',
|
||||
@@ -74,16 +79,19 @@ Help your customers with this chat, and analyse their feedback.
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'im_livechat/static/tests/qunit_suite_tests/components/**/*.js',
|
||||
'im_livechat/static/src/legacy/public_livechat_chatbot.js',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'im_livechat/static/tests/tours/**/*',
|
||||
],
|
||||
'mail.assets_messaging': [
|
||||
'im_livechat/static/src/models/*.js',
|
||||
],
|
||||
'mail.assets_public_livechat': [
|
||||
('include', 'mail.assets_core_messaging'),
|
||||
'im_livechat/static/src/legacy/models/*',
|
||||
'im_livechat/static/src/legacy/widgets/*',
|
||||
'im_livechat/static/src/legacy/widgets/*/*',
|
||||
'im_livechat/static/src/models/*.js',
|
||||
'im_livechat/static/src/public_models/*.js',
|
||||
],
|
||||
# Bundle of External Librairies of the Livechat (Odoo + required modules)
|
||||
'im_livechat.external_lib': [
|
||||
@@ -198,7 +206,7 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'mail/static/src/utils/*.js',
|
||||
'mail/static/src/js/emojis.js',
|
||||
'mail/static/src/component_hooks/*.js',
|
||||
('include', 'mail.assets_messaging'),
|
||||
('include', 'mail.assets_public_livechat'),
|
||||
'mail/static/src/services/messaging_service.js',
|
||||
# Framework JS
|
||||
'bus/static/src/*.js',
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addFields, patchFields } from '@mail/model/model_core';
|
||||
import { many, one } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
import { addFields } from '@mail/model/model_core';
|
||||
import { many } from '@mail/model/model_field';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/messaging';
|
||||
import '@mail/core_models/messaging';
|
||||
|
||||
addFields('Messaging', {
|
||||
/**
|
||||
@@ -14,18 +13,4 @@ addFields('Messaging', {
|
||||
inverse: 'messagingAsPinnedLivechat',
|
||||
readonly: true,
|
||||
}),
|
||||
publicLivechatGlobal: one('PublicLivechatGlobal', {
|
||||
isCausal: true,
|
||||
}),
|
||||
});
|
||||
|
||||
patchFields('Messaging', {
|
||||
notificationHandler: {
|
||||
compute() {
|
||||
if (this.publicLivechatGlobal) {
|
||||
return clear();
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,16 +6,6 @@ import { insert } from '@mail/model/model_field_command';
|
||||
import '@mail/models/messaging_initializer';
|
||||
|
||||
patchRecordMethods('MessagingInitializer', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async performInitRpc() {
|
||||
if (this.messaging.publicLivechatGlobal) {
|
||||
return {};
|
||||
} else {
|
||||
return this._super();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {Object[]} [param0.channel_livechat=[]]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addFields } from '@mail/model/model_core';
|
||||
import { one } from '@mail/model/model_field';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/core_models/messaging';
|
||||
|
||||
addFields('Messaging', {
|
||||
publicLivechatGlobal: one('PublicLivechatGlobal', {
|
||||
isCausal: true,
|
||||
}),
|
||||
});
|
||||
@@ -114,8 +114,12 @@ For more specific needs, you may also assign custom-defined actions
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'assets': {
|
||||
'mail.assets_messaging': [
|
||||
'mail.assets_core_messaging': [
|
||||
'mail/static/src/model/*.js',
|
||||
'mail/static/src/core_models/*.js',
|
||||
],
|
||||
'mail.assets_messaging': [
|
||||
('include', 'mail.assets_core_messaging'),
|
||||
'mail/static/src/models/*.js',
|
||||
'mail/static/src/models_data/*.js',
|
||||
'mail/static/lib/selfie_segmentation/selfie_segmentation.js',
|
||||
@@ -173,14 +177,6 @@ For more specific needs, you may also assign custom-defined actions
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/xml/base.xml',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'mail/static/src/utils/*.js',
|
||||
'mail/static/src/js/emojis.js',
|
||||
'mail/static/src/js/utils.js',
|
||||
'mail/static/src/component_hooks/*.js',
|
||||
('include', 'mail.assets_messaging'),
|
||||
'mail/static/src/services/messaging_service.js',
|
||||
],
|
||||
'web._assets_primary_variables': [
|
||||
'mail/static/src/scss/variables/primary_variables.scss',
|
||||
],
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many, one } from '@mail/model/model_field';
|
||||
import { makeDeferred } from '@mail/utils/deferred';
|
||||
|
||||
import { browser } from '@web/core/browser/browser';
|
||||
|
||||
const { EventBus } = owl;
|
||||
|
||||
registerModel({
|
||||
name: 'Messaging',
|
||||
lifecycleHooks: {
|
||||
_created() {
|
||||
odoo.__DEBUG__.messaging = this;
|
||||
},
|
||||
_willDelete() {
|
||||
delete odoo.__DEBUG__.messaging;
|
||||
},
|
||||
},
|
||||
recordMethods: {
|
||||
/**
|
||||
* Perform a rpc call and return a promise resolving to the result.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @return {any}
|
||||
*/
|
||||
async rpc(params, options = {}) {
|
||||
if (params.route) {
|
||||
const { route, params: rpcParameters } = params;
|
||||
const { shadow: silent, ...rpcSettings } = options;
|
||||
return this.env.services.rpc(route, rpcParameters, { silent, ...rpcSettings });
|
||||
} else {
|
||||
const { args, method, model, kwargs = {} } = params;
|
||||
const { domain, fields, groupBy } = kwargs;
|
||||
|
||||
const ormService = 'shadow' in options ? this.env.services.orm.silent : this.env.services.orm;
|
||||
switch (method) {
|
||||
case 'create':
|
||||
return ormService.create(model, args[0], kwargs);
|
||||
case 'read':
|
||||
return ormService.read(model, args[0], args.length > 1 ? args[1] : undefined, kwargs);
|
||||
case 'read_group':
|
||||
return ormService.readGroup(model, domain, fields, groupBy, kwargs);
|
||||
case 'search':
|
||||
return ormService.search(model, args[0], kwargs);
|
||||
case 'search_read':
|
||||
return ormService.searchRead(model, domain, fields, kwargs);
|
||||
case 'unlink':
|
||||
return ormService.unlink(model, args[0], kwargs);
|
||||
case 'write':
|
||||
return ormService.write(model, args[0], args[1], kwargs);
|
||||
default:
|
||||
return ormService.call(model, method, args, kwargs);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Starts messaging and related records.
|
||||
*/
|
||||
async start() {
|
||||
this.update({ isInitialized: true });
|
||||
this.initializedPromise.resolve();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
/**
|
||||
* Inverse of the messaging field present on all models. This field
|
||||
* therefore contains all existing records.
|
||||
*/
|
||||
allRecords: many('Record', {
|
||||
inverse: 'messaging',
|
||||
isCausal: true,
|
||||
}),
|
||||
browser: attr({
|
||||
compute() {
|
||||
return browser;
|
||||
},
|
||||
}),
|
||||
device: one('Device', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Promise that will be resolved when messaging is initialized.
|
||||
*/
|
||||
initializedPromise: attr({
|
||||
compute() {
|
||||
return makeDeferred();
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
isInitialized: attr({
|
||||
default: false,
|
||||
}),
|
||||
locale: one('Locale', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines the bus that is used to communicate messaging events.
|
||||
*/
|
||||
messagingBus: attr({
|
||||
compute() {
|
||||
if (this.messagingBus) {
|
||||
return; // avoid overwrite if already provided (example in tests)
|
||||
}
|
||||
return new EventBus();
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -1,496 +1,412 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { addFields, addOnChanges, addRecordMethods, patchLifecycleHooks, patchRecordMethods } from '@mail/model/model_core';
|
||||
import { attr, many, one } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
import { makeDeferred } from '@mail/utils/deferred';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/core_models/messaging';
|
||||
|
||||
import { browser } from '@web/core/browser/browser';
|
||||
|
||||
const { EventBus } = owl;
|
||||
|
||||
registerModel({
|
||||
name: 'Messaging',
|
||||
lifecycleHooks: {
|
||||
_created() {
|
||||
odoo.__DEBUG__.messaging = this;
|
||||
this.refreshIsNotificationPermissionDefault();
|
||||
},
|
||||
_willDelete() {
|
||||
this.env.services['im_status'].unregisterFromImStatus('res.partner');
|
||||
this.env.services['im_status'].unregisterFromImStatus('mail.guest');
|
||||
this.env.bus.removeEventListener('window_focus', this._handleGlobalWindowFocus);
|
||||
delete odoo.__DEBUG__.messaging;
|
||||
},
|
||||
patchLifecycleHooks('Messaging', {
|
||||
_created() {
|
||||
this._super();
|
||||
this.refreshIsNotificationPermissionDefault();
|
||||
},
|
||||
recordMethods: {
|
||||
/**
|
||||
* Starts messaging and related records.
|
||||
*/
|
||||
async start() {
|
||||
this.env.bus.addEventListener('window_focus', this._handleGlobalWindowFocus);
|
||||
await this.initializer.start();
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
_willDelete() {
|
||||
this.env.services['im_status'].unregisterFromImStatus('res.partner');
|
||||
this.env.services['im_status'].unregisterFromImStatus('mail.guest');
|
||||
this.env.bus.removeEventListener('window_focus', this._handleGlobalWindowFocus);
|
||||
this._super();
|
||||
},
|
||||
});
|
||||
|
||||
patchRecordMethods('Messaging', {
|
||||
/**
|
||||
* Starts messaging and related records.
|
||||
*/
|
||||
async start() {
|
||||
this.env.bus.addEventListener('window_focus', this._handleGlobalWindowFocus);
|
||||
await this.initializer.start();
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
if (this.notificationHandler) {
|
||||
this.notificationHandler.start();
|
||||
}
|
||||
await this._super();
|
||||
},
|
||||
});
|
||||
|
||||
addRecordMethods('Messaging', {
|
||||
/**
|
||||
* Executes the provided functions in order, but with a potential delay between
|
||||
* them if they take too much time. This is done in order to avoid blocking the
|
||||
* main thread for too long.
|
||||
*
|
||||
* @param {function[]} functions
|
||||
*/
|
||||
async executeGracefully(functions) {
|
||||
let date = new Date();
|
||||
for (const func of functions) {
|
||||
if (new Date() - date > 100) {
|
||||
await new Promise(resolve => setTimeout(resolve, this.isInQUnitTest ? 0 : 50));
|
||||
date = new Date();
|
||||
}
|
||||
if (this.notificationHandler) {
|
||||
this.notificationHandler.start();
|
||||
}
|
||||
this.update({ isInitialized: true });
|
||||
this.initializedPromise.resolve();
|
||||
},
|
||||
/**
|
||||
* Executes the provided functions in order, but with a potential delay between
|
||||
* them if they take too much time. This is done in order to avoid blocking the
|
||||
* main thread for too long.
|
||||
*
|
||||
* @param {function[]} functions
|
||||
*/
|
||||
async executeGracefully(functions) {
|
||||
let date = new Date();
|
||||
for (const func of functions) {
|
||||
if (new Date() - date > 100) {
|
||||
await new Promise(resolve => setTimeout(resolve, this.isInQUnitTest ? 0 : 50));
|
||||
date = new Date();
|
||||
}
|
||||
await func();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Open the form view of the record with provided id and model.
|
||||
* Gets the chat with the provided person and returns it.
|
||||
*
|
||||
* If a chat is not appropriate, a notification is displayed instead.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {integer} [param0.partnerId]
|
||||
* @param {integer} [param0.userId]
|
||||
* @param {Object} [options]
|
||||
* @returns {Channel|undefined}
|
||||
*/
|
||||
async getChat({ partnerId, userId }) {
|
||||
if (userId) {
|
||||
const user = this.messaging.models['User'].insert({ id: userId });
|
||||
return user.getChat();
|
||||
}
|
||||
if (partnerId) {
|
||||
const partner = this.messaging.models['Partner'].insert({ id: partnerId });
|
||||
return partner.getChat();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Display a notification to the user.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {string} [params.message]
|
||||
* @param {string} [params.subtitle]
|
||||
* @param {Object[]} [params.buttons]
|
||||
* @param {boolean} [params.sticky]
|
||||
* @param {string} [params.type]
|
||||
* @param {string} [params.className]
|
||||
* @param {function} [params.onClose]
|
||||
* @return {number} the id of the notification.
|
||||
*/
|
||||
notify(params) {
|
||||
const { message, ...options } = params;
|
||||
return this.env.services.notification.add(message, options);
|
||||
},
|
||||
/**
|
||||
* Opens a chat with the provided person and returns it.
|
||||
*
|
||||
* If a chat is not appropriate, a notification is displayed instead.
|
||||
*
|
||||
* @param {Object} person forwarded to @see `getChat()`
|
||||
* @param {Object} [options] forwarded to @see `Thread:open()`
|
||||
*/
|
||||
async openChat(person, options) {
|
||||
const chat = await this.getChat(person);
|
||||
if (!this.exists() || !chat) {
|
||||
return;
|
||||
}
|
||||
await chat.thread.open(options);
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Opens the form view of the record with provided id and model.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {integer} param0.id
|
||||
* @param {string} param0.model
|
||||
*/
|
||||
async openDocument({ id, model }) {
|
||||
this.env.services.action.doAction({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: model,
|
||||
views: [[false, 'form']],
|
||||
res_id: id,
|
||||
});
|
||||
if (this.messaging.device.isSmall) {
|
||||
// When opening documents chat windows need to be closed
|
||||
this.messaging.chatWindowManager.closeAll();
|
||||
// messaging menu has a higher z-index than views so it must
|
||||
// be closed to ensure the visibility of the view
|
||||
this.messaging.messagingMenu.close();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Opens the most appropriate view that is a profile for provided id and
|
||||
* model.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {integer} param0.id
|
||||
* @param {string} param0.model
|
||||
*/
|
||||
async openProfile({ id, model }) {
|
||||
if (model === 'res.partner') {
|
||||
const partner = this.messaging.models['Partner'].insert({ id });
|
||||
return partner.openProfile();
|
||||
}
|
||||
if (model === 'res.users') {
|
||||
const user = this.messaging.models['User'].insert({ id });
|
||||
return user.openProfile();
|
||||
}
|
||||
if (model === 'mail.channel') {
|
||||
let channel = this.messaging.models['Thread'].findFromIdentifyingData({ id, model: 'mail.channel' });
|
||||
if (!channel) {
|
||||
const res = await this.messaging.models['Thread'].performRpcChannelInfo({ ids: [id] });
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
channel = res[0];
|
||||
}
|
||||
if (!channel) {
|
||||
this.messaging.notify({
|
||||
message: this.env._t("You can only open the profile of existing channels."),
|
||||
type: 'warning',
|
||||
});
|
||||
await func();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Open the form view of the record with provided id and model.
|
||||
* Gets the chat with the provided person and returns it.
|
||||
*
|
||||
* If a chat is not appropriate, a notification is displayed instead.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {integer} [param0.partnerId]
|
||||
* @param {integer} [param0.userId]
|
||||
* @param {Object} [options]
|
||||
* @returns {Channel|undefined}
|
||||
*/
|
||||
async getChat({ partnerId, userId }) {
|
||||
if (userId) {
|
||||
const user = this.messaging.models['User'].insert({ id: userId });
|
||||
return user.getChat();
|
||||
}
|
||||
if (partnerId) {
|
||||
const partner = this.messaging.models['Partner'].insert({ id: partnerId });
|
||||
return partner.getChat();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Display a notification to the user.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {string} [params.message]
|
||||
* @param {string} [params.subtitle]
|
||||
* @param {Object[]} [params.buttons]
|
||||
* @param {boolean} [params.sticky]
|
||||
* @param {string} [params.type]
|
||||
* @param {string} [params.className]
|
||||
* @param {function} [params.onClose]
|
||||
* @return {number} the id of the notification.
|
||||
*/
|
||||
notify(params) {
|
||||
const { message, ...options } = params;
|
||||
return this.env.services.notification.add(message, options);
|
||||
},
|
||||
/**
|
||||
* Opens a chat with the provided person and returns it.
|
||||
*
|
||||
* If a chat is not appropriate, a notification is displayed instead.
|
||||
*
|
||||
* @param {Object} person forwarded to @see `getChat()`
|
||||
* @param {Object} [options] forwarded to @see `Thread:open()`
|
||||
*/
|
||||
async openChat(person, options) {
|
||||
const chat = await this.getChat(person);
|
||||
if (!this.exists() || !chat) {
|
||||
return;
|
||||
}
|
||||
await chat.thread.open(options);
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Opens the form view of the record with provided id and model.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {integer} param0.id
|
||||
* @param {string} param0.model
|
||||
*/
|
||||
async openDocument({ id, model }) {
|
||||
this.env.services.action.doAction({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: model,
|
||||
views: [[false, 'form']],
|
||||
res_id: id,
|
||||
});
|
||||
if (this.messaging.device.isSmall) {
|
||||
// When opening documents chat windows need to be closed
|
||||
this.messaging.chatWindowManager.closeAll();
|
||||
// messaging menu has a higher z-index than views so it must
|
||||
// be closed to ensure the visibility of the view
|
||||
this.messaging.messagingMenu.close();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Opens the most appropriate view that is a profile for provided id and
|
||||
* model.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {integer} param0.id
|
||||
* @param {string} param0.model
|
||||
*/
|
||||
async openProfile({ id, model }) {
|
||||
if (model === 'res.partner') {
|
||||
const partner = this.messaging.models['Partner'].insert({ id });
|
||||
return partner.openProfile();
|
||||
}
|
||||
if (model === 'res.users') {
|
||||
const user = this.messaging.models['User'].insert({ id });
|
||||
return user.openProfile();
|
||||
}
|
||||
if (model === 'mail.channel') {
|
||||
let channel = this.messaging.models['Thread'].findFromIdentifyingData({ id, model: 'mail.channel' });
|
||||
if (!channel) {
|
||||
const res = await this.messaging.models['Thread'].performRpcChannelInfo({ ids: [id] });
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
return channel.openProfile();
|
||||
channel = res[0];
|
||||
}
|
||||
return this.messaging.openDocument({ id, model });
|
||||
},
|
||||
/**
|
||||
* Perform a rpc call and return a promise resolving to the result.
|
||||
*
|
||||
* @param {Object} params
|
||||
* @return {any}
|
||||
*/
|
||||
async rpc(params, options = {}) {
|
||||
if (params.route) {
|
||||
const { route, params: rpcParameters } = params;
|
||||
const { shadow: silent, ...rpcSettings } = options;
|
||||
return this.env.services.rpc(route, rpcParameters, { silent, ...rpcSettings });
|
||||
} else {
|
||||
const { args, method, model, kwargs = {} } = params;
|
||||
const { domain, fields, groupBy } = kwargs;
|
||||
|
||||
const ormService = 'shadow' in options ? this.env.services.orm.silent : this.env.services.orm;
|
||||
switch (method) {
|
||||
case 'create':
|
||||
return ormService.create(model, args[0], kwargs);
|
||||
case 'read':
|
||||
return ormService.read(model, args[0], args.length > 1 ? args[1] : undefined, kwargs);
|
||||
case 'read_group':
|
||||
return ormService.readGroup(model, domain, fields, groupBy, kwargs);
|
||||
case 'search':
|
||||
return ormService.search(model, args[0], kwargs);
|
||||
case 'search_read':
|
||||
return ormService.searchRead(model, domain, fields, kwargs);
|
||||
case 'unlink':
|
||||
return ormService.unlink(model, args[0], kwargs);
|
||||
case 'write':
|
||||
return ormService.write(model, args[0], args[1], kwargs);
|
||||
default:
|
||||
return ormService.call(model, method, args, kwargs);
|
||||
}
|
||||
if (!channel) {
|
||||
this.messaging.notify({
|
||||
message: this.env._t("You can only open the profile of existing channels."),
|
||||
type: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Refreshes the value of `isNotificationPermissionDefault`.
|
||||
*
|
||||
* Must be called in flux-specific way because the browser does not
|
||||
* provide an API to detect when this value changes.
|
||||
*/
|
||||
refreshIsNotificationPermissionDefault() {
|
||||
const browserNotification = this.messaging.browser.Notification;
|
||||
this.update({
|
||||
isNotificationPermissionDefault: Boolean(browserNotification) && browserNotification.permission === 'default',
|
||||
});
|
||||
},
|
||||
updateImStatusRegistration() {
|
||||
const partnerIds = [];
|
||||
for (const partner of this.models['Partner'].all()) {
|
||||
if (partner.im_status !== 'im_partner' && !partner.is_public) {
|
||||
partnerIds.push(partner.id);
|
||||
}
|
||||
}
|
||||
const guestIds = [];
|
||||
for (const guest of this.models['Guest'].all()) {
|
||||
guestIds.push(guest.id);
|
||||
}
|
||||
this.env.services['im_status'].registerToImStatus('res.partner', partnerIds);
|
||||
this.env.services['im_status'].registerToImStatus('mail.guest', guestIds);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_handleGlobalWindowFocus() {
|
||||
this.update({ outOfFocusUnreadMessageCounter: 0 });
|
||||
this.env.bus.trigger('set_title_part', {
|
||||
part: '_chat',
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeAllCurrentClientThreads() {
|
||||
if (this.isInitialized) {
|
||||
this.env.services.bus_service.forceUpdateChannels();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeAllPersonas() {
|
||||
if (this.isInitialized) {
|
||||
this.updateImStatusRegisterThrottle.do();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeRingingThreads() {
|
||||
if (this.ringingThreads && this.ringingThreads.length > 0) {
|
||||
this.soundEffects.incomingCall.play({ loop: true });
|
||||
} else {
|
||||
this.soundEffects.incomingCall.stop();
|
||||
}
|
||||
},
|
||||
return channel.openProfile();
|
||||
}
|
||||
return this.messaging.openDocument({ id, model });
|
||||
},
|
||||
fields: {
|
||||
allMailboxes: many('Mailbox', {
|
||||
inverse: 'messagingAsAnyMailbox',
|
||||
}),
|
||||
allPersonas: many('Persona', {
|
||||
inverse: 'messagingAsAnyPersona',
|
||||
}),
|
||||
/**
|
||||
* Inverse of the messaging field present on all models. This field
|
||||
* therefore contains all existing records.
|
||||
*/
|
||||
allRecords: many('Record', {
|
||||
inverse: 'messaging',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* This field contains all current client channels.
|
||||
*/
|
||||
allCurrentClientThreads: many('Thread', {
|
||||
inverse: 'messagingAsAllCurrentClientThreads',
|
||||
}),
|
||||
browser: attr({
|
||||
compute() {
|
||||
return browser;
|
||||
},
|
||||
}),
|
||||
cannedResponses: many('CannedResponse'),
|
||||
chatWindowManager: one('ChatWindowManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines which message view is currently clicked, if any.
|
||||
*/
|
||||
clickedMessageView: one('MessageView', {
|
||||
inverse: 'messagingAsClickedMessageView',
|
||||
}),
|
||||
commands: many('ChannelCommand'),
|
||||
companyName: attr(),
|
||||
currentGuest: one('Guest'),
|
||||
currentPartner: one('Partner'),
|
||||
currentUser: one('User'),
|
||||
device: one('Device', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
dialogManager: one('DialogManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines whether animations should be disabled.
|
||||
*/
|
||||
disableAnimation: attr({
|
||||
default: false,
|
||||
}),
|
||||
discuss: one('Discuss', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
emojiRegistry: one('EmojiRegistry', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
hasLinkPreviewFeature: attr(),
|
||||
history: one('Mailbox', {
|
||||
default: {},
|
||||
inverse: 'messagingAsHistory',
|
||||
}),
|
||||
inbox: one('Mailbox', {
|
||||
default: {},
|
||||
inverse: 'messagingAsInbox',
|
||||
}),
|
||||
/**
|
||||
* Promise that will be resolved when messaging is initialized.
|
||||
*/
|
||||
initializedPromise: attr({
|
||||
compute() {
|
||||
return makeDeferred();
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
initializer: one('MessagingInitializer', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
internalUserGroupId: attr(),
|
||||
isCurrentUserGuest: attr({
|
||||
compute() {
|
||||
return Boolean(!this.currentPartner && this.currentGuest);
|
||||
},
|
||||
}),
|
||||
isInitialized: attr({
|
||||
default: false,
|
||||
}),
|
||||
isInQUnitTest: attr({
|
||||
default: false,
|
||||
}),
|
||||
isNotificationBlocked: attr({
|
||||
compute() {
|
||||
const windowNotification = this.browser.Notification;
|
||||
return (
|
||||
windowNotification &&
|
||||
windowNotification.permission !== 'granted' &&
|
||||
!this.isNotificationPermissionDefault
|
||||
);
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* States whether browser Notification Permission is currently in its
|
||||
* 'default' state. This means it is allowed to make a request to the
|
||||
* user to enable notifications.
|
||||
*/
|
||||
isNotificationPermissionDefault: attr(),
|
||||
locale: one('Locale', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines after how much time in ms a "loading" indicator should be
|
||||
* shown. Useful to avoid flicker for almost instant loading.
|
||||
*/
|
||||
loadingBaseDelayDuration: attr({
|
||||
default: 400,
|
||||
}),
|
||||
/**
|
||||
* Determines the bus that is used to communicate messaging events.
|
||||
*/
|
||||
messagingBus: attr({
|
||||
compute() {
|
||||
if (this.messagingBus) {
|
||||
return; // avoid overwrite if already provided (example in tests)
|
||||
}
|
||||
return new EventBus();
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
messagingMenu: one('MessagingMenu', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
}),
|
||||
notificationHandler: one('MessagingNotificationHandler', {
|
||||
compute() {
|
||||
return {};
|
||||
},
|
||||
isCausal: true,
|
||||
}),
|
||||
outOfFocusUnreadMessageCounter: attr({
|
||||
default: 0,
|
||||
}),
|
||||
partnerRoot: one('Partner'),
|
||||
popoverManager: one('PopoverManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Threads for which the current partner has a pending invitation.
|
||||
* It is computed from the inverse relation for performance reasons.
|
||||
*/
|
||||
ringingThreads: many('Thread', {
|
||||
inverse: 'messagingAsRingingThread',
|
||||
}),
|
||||
rtc: one('Rtc', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
callInviteRequestPopups: many('CallInviteRequestPopup', {
|
||||
compute() {
|
||||
if (this.ringingThreads.length === 0) {
|
||||
return clear();
|
||||
}
|
||||
return this.ringingThreads.map(thread => thread.callInviteRequestPopup);
|
||||
},
|
||||
isCausal: true,
|
||||
}),
|
||||
soundEffects: one('SoundEffects', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
starred: one('Mailbox', {
|
||||
default: {},
|
||||
inverse: 'messagingAsStarred',
|
||||
}),
|
||||
userNotificationManager: one('UserNotificationManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
updateImStatusRegisterThrottle: one('Throttle', {
|
||||
compute() {
|
||||
return { func: this.updateImStatusRegistration };
|
||||
},
|
||||
inverse: 'messagingAsUpdateImStatusRegister',
|
||||
}),
|
||||
userSetting: one('UserSetting', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Refreshes the value of `isNotificationPermissionDefault`.
|
||||
*
|
||||
* Must be called in flux-specific way because the browser does not
|
||||
* provide an API to detect when this value changes.
|
||||
*/
|
||||
refreshIsNotificationPermissionDefault() {
|
||||
const browserNotification = this.messaging.browser.Notification;
|
||||
this.update({
|
||||
isNotificationPermissionDefault: Boolean(browserNotification) && browserNotification.permission === 'default',
|
||||
});
|
||||
},
|
||||
updateImStatusRegistration() {
|
||||
const partnerIds = [];
|
||||
for (const partner of this.models['Partner'].all()) {
|
||||
if (partner.im_status !== 'im_partner' && !partner.is_public) {
|
||||
partnerIds.push(partner.id);
|
||||
}
|
||||
}
|
||||
const guestIds = [];
|
||||
for (const guest of this.models['Guest'].all()) {
|
||||
guestIds.push(guest.id);
|
||||
}
|
||||
this.env.services['im_status'].registerToImStatus('res.partner', partnerIds);
|
||||
this.env.services['im_status'].registerToImStatus('mail.guest', guestIds);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_handleGlobalWindowFocus() {
|
||||
this.update({ outOfFocusUnreadMessageCounter: 0 });
|
||||
this.env.bus.trigger('set_title_part', {
|
||||
part: '_chat',
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeAllCurrentClientThreads() {
|
||||
if (this.isInitialized) {
|
||||
this.env.services.bus_service.forceUpdateChannels();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeAllPersonas() {
|
||||
if (this.isInitialized) {
|
||||
this.updateImStatusRegisterThrottle.do();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeRingingThreads() {
|
||||
if (this.ringingThreads && this.ringingThreads.length > 0) {
|
||||
this.soundEffects.incomingCall.play({ loop: true });
|
||||
} else {
|
||||
this.soundEffects.incomingCall.stop();
|
||||
}
|
||||
},
|
||||
onChanges: [
|
||||
{
|
||||
dependencies: ['ringingThreads'],
|
||||
methodName: '_onChangeRingingThreads',
|
||||
},
|
||||
{
|
||||
dependencies: ['allCurrentClientThreads'],
|
||||
methodName: '_onChangeAllCurrentClientThreads',
|
||||
},
|
||||
{
|
||||
dependencies: ['allPersonas'],
|
||||
methodName: '_onChangeAllPersonas',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
addFields('Messaging', {
|
||||
allMailboxes: many('Mailbox', {
|
||||
inverse: 'messagingAsAnyMailbox',
|
||||
}),
|
||||
allPersonas: many('Persona', {
|
||||
inverse: 'messagingAsAnyPersona',
|
||||
}),
|
||||
/**
|
||||
* This field contains all current client channels.
|
||||
*/
|
||||
allCurrentClientThreads: many('Thread', {
|
||||
inverse: 'messagingAsAllCurrentClientThreads',
|
||||
}),
|
||||
cannedResponses: many('CannedResponse'),
|
||||
chatWindowManager: one('ChatWindowManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines which message view is currently clicked, if any.
|
||||
*/
|
||||
clickedMessageView: one('MessageView', {
|
||||
inverse: 'messagingAsClickedMessageView',
|
||||
}),
|
||||
commands: many('ChannelCommand'),
|
||||
companyName: attr(),
|
||||
currentGuest: one('Guest'),
|
||||
currentPartner: one('Partner'),
|
||||
currentUser: one('User'),
|
||||
dialogManager: one('DialogManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Determines whether animations should be disabled.
|
||||
*/
|
||||
disableAnimation: attr({
|
||||
default: false,
|
||||
}),
|
||||
discuss: one('Discuss', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
emojiRegistry: one('EmojiRegistry', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
hasLinkPreviewFeature: attr(),
|
||||
history: one('Mailbox', {
|
||||
default: {},
|
||||
inverse: 'messagingAsHistory',
|
||||
}),
|
||||
inbox: one('Mailbox', {
|
||||
default: {},
|
||||
inverse: 'messagingAsInbox',
|
||||
}),
|
||||
initializer: one('MessagingInitializer', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
internalUserGroupId: attr(),
|
||||
isCurrentUserGuest: attr({
|
||||
compute() {
|
||||
return Boolean(!this.currentPartner && this.currentGuest);
|
||||
},
|
||||
}),
|
||||
isInQUnitTest: attr({
|
||||
default: false,
|
||||
}),
|
||||
isNotificationBlocked: attr({
|
||||
compute() {
|
||||
const windowNotification = this.browser.Notification;
|
||||
return (
|
||||
windowNotification &&
|
||||
windowNotification.permission !== 'granted' &&
|
||||
!this.isNotificationPermissionDefault
|
||||
);
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* States whether browser Notification Permission is currently in its
|
||||
* 'default' state. This means it is allowed to make a request to the
|
||||
* user to enable notifications.
|
||||
*/
|
||||
isNotificationPermissionDefault: attr(),
|
||||
/**
|
||||
* Determines after how much time in ms a "loading" indicator should be
|
||||
* shown. Useful to avoid flicker for almost instant loading.
|
||||
*/
|
||||
loadingBaseDelayDuration: attr({
|
||||
default: 400,
|
||||
}),
|
||||
messagingMenu: one('MessagingMenu', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
}),
|
||||
notificationHandler: one('MessagingNotificationHandler', {
|
||||
compute() {
|
||||
return {};
|
||||
},
|
||||
isCausal: true,
|
||||
}),
|
||||
outOfFocusUnreadMessageCounter: attr({
|
||||
default: 0,
|
||||
}),
|
||||
partnerRoot: one('Partner'),
|
||||
popoverManager: one('PopoverManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* Threads for which the current partner has a pending invitation.
|
||||
* It is computed from the inverse relation for performance reasons.
|
||||
*/
|
||||
ringingThreads: many('Thread', {
|
||||
inverse: 'messagingAsRingingThread',
|
||||
}),
|
||||
rtc: one('Rtc', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
callInviteRequestPopups: many('CallInviteRequestPopup', {
|
||||
compute() {
|
||||
if (this.ringingThreads.length === 0) {
|
||||
return clear();
|
||||
}
|
||||
return this.ringingThreads.map(thread => thread.callInviteRequestPopup);
|
||||
},
|
||||
isCausal: true,
|
||||
}),
|
||||
soundEffects: one('SoundEffects', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
starred: one('Mailbox', {
|
||||
default: {},
|
||||
inverse: 'messagingAsStarred',
|
||||
}),
|
||||
userNotificationManager: one('UserNotificationManager', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
updateImStatusRegisterThrottle: one('Throttle', {
|
||||
compute() {
|
||||
return { func: this.updateImStatusRegistration };
|
||||
},
|
||||
inverse: 'messagingAsUpdateImStatusRegister',
|
||||
}),
|
||||
userSetting: one('UserSetting', {
|
||||
default: {},
|
||||
isCausal: true,
|
||||
}),
|
||||
});
|
||||
|
||||
addOnChanges('Messaging', [
|
||||
{
|
||||
dependencies: ['ringingThreads'],
|
||||
methodName: '_onChangeRingingThreads',
|
||||
},
|
||||
{
|
||||
dependencies: ['allCurrentClientThreads'],
|
||||
methodName: '_onChangeAllCurrentClientThreads',
|
||||
},
|
||||
{
|
||||
dependencies: ['allPersonas'],
|
||||
methodName: '_onChangeAllPersonas',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { addFields, addRecordMethods } from '@mail/model/model_core';
|
||||
import { attr } from '@mail/model/model_field';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/messaging';
|
||||
import '@mail/core_models/messaging';
|
||||
|
||||
addRecordMethods('Messaging', {
|
||||
async fetchSnailmailCreditsUrl() {
|
||||
|
||||
@@ -28,6 +28,9 @@ Allow website visitors to chat with the collaborators. This module also brings a
|
||||
'mail.assets_messaging': [
|
||||
'website_livechat/static/src/models/*.js',
|
||||
],
|
||||
'mail.assets_public_livechat': [
|
||||
'website_livechat/static/src/public_models/*.js',
|
||||
],
|
||||
'mail.assets_discuss_public': [
|
||||
'website_livechat/static/src/components/*/*',
|
||||
],
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
import { addFields, patchFields } from '@mail/model/model_core';
|
||||
import { attr } from '@mail/model/model_field';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@im_livechat/models/chatbot';
|
||||
import '@im_livechat/public_models/chatbot';
|
||||
|
||||
addFields('Chatbot', {
|
||||
isWebsiteLivechatTourFlow: attr({
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
import { patchFields, patchRecordMethods } from '@mail/model/model_core';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@im_livechat/models/livechat_button_view';
|
||||
import '@im_livechat/public_models/livechat_button_view';
|
||||
|
||||
import { set_cookie, unaccent } from 'web.utils';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
import { patchRecordMethods } from '@mail/model/model_core';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@im_livechat/models/public_livechat_global';
|
||||
import '@im_livechat/public_models/public_livechat_global';
|
||||
|
||||
import { set_cookie } from 'web.utils';
|
||||
|
||||
@@ -4,7 +4,7 @@ import tour from "web_tour.tour";
|
||||
|
||||
import { patchRecordMethods } from '@mail/model/model_core';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@im_livechat/models/livechat_button_view';
|
||||
import '@im_livechat/public_models/livechat_button_view';
|
||||
|
||||
patchRecordMethods('LivechatButtonView', {
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ odoo.define('website_livechat.tour_common', function (require) {
|
||||
|
||||
const { patchRecordMethods } = require('@mail/model/model_core');
|
||||
// ensure that the model definition is loaded before the patch
|
||||
require('@im_livechat/models/livechat_button_view');
|
||||
require('@im_livechat/public_models/livechat_button_view');
|
||||
|
||||
const { Markup } = require('web.utils');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user