[IMP] mail, im_livechat: improve Discuss sidebar
The commit is to refactor the discuss sidebar - threads are now organized in categories based on the thread type e.g. chat, channel - categories can be folded or unfolded by clicking the category title - for active thread, even if the category is folded, it remains under the category title - for channel category, a new cog button is added to view all channels - the active indicator bar is removed. The active item now is highlighted with a different background color - thread avatar is used for livechat, chat and channel - for livechat and chat, threads are now sorted by last activity time (pin or message exchange) closes odoo/odoo#70986 Task-id: 2440073 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
committed by
Sébastien Theys
parent
9d13bb4534
commit
463af6f61b
@@ -47,11 +47,11 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'im_livechat/static/src/js/im_livechat_channel_form_view.js',
|
||||
'im_livechat/static/src/js/im_livechat_channel_form_controller.js',
|
||||
'im_livechat/static/src/components/discuss/discuss.js',
|
||||
'im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js',
|
||||
'im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js',
|
||||
'im_livechat/static/src/components/notification_list/notification_list.js',
|
||||
'im_livechat/static/src/components/thread_needaction_preview/thread_needaction_preview.js',
|
||||
'im_livechat/static/src/components/thread_preview/thread_preview.js',
|
||||
'im_livechat/static/src/models/discuss/discuss.js',
|
||||
'im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js',
|
||||
'im_livechat/static/src/models/chat_window/chat_window.js',
|
||||
'im_livechat/static/src/models/messaging_initializer/messaging_initializer.js',
|
||||
'im_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js',
|
||||
@@ -61,6 +61,8 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'im_livechat/static/src/scss/im_livechat_form.scss',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'im_livechat/static/src/components/discuss_sidebar_category/tests/discuss_sidebar_category_tests.js',
|
||||
'im_livechat/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js',
|
||||
'im_livechat/static/src/components/chat_window_manager/chat_window_manager_tests.js',
|
||||
'im_livechat/static/src/components/composer/composer_tests.js',
|
||||
'im_livechat/static/src/components/discuss/discuss_tests.js',
|
||||
|
||||
@@ -5,5 +5,6 @@ from . import res_partner
|
||||
from . import im_livechat_channel
|
||||
from . import mail_channel
|
||||
from . import mail_channel_partner
|
||||
from . import res_users_settings
|
||||
from . import rating
|
||||
from . import digest
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResUsersSettings(models.Model):
|
||||
_inherit = 'res.users.settings'
|
||||
|
||||
is_discuss_sidebar_category_livechat_open = fields.Boolean("Is category livechat open", default=True)
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
start,
|
||||
} from '@mail/utils/test_utils';
|
||||
|
||||
import { datetime_to_str } from 'web.time';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss', {}, function () {
|
||||
@@ -44,18 +46,18 @@ QUnit.test('livechat in the sidebar: basic rendering', async function (assert) {
|
||||
assert.containsOnce(document.body, '.o_Discuss_sidebar',
|
||||
"should have a sidebar section"
|
||||
);
|
||||
const groupLivechat = document.querySelector('.o_DiscussSidebar_groupLivechat');
|
||||
const groupLivechat = document.querySelector('.o_DiscussSidebar_categoryLivechat');
|
||||
assert.ok(groupLivechat,
|
||||
"should have a channel group livechat"
|
||||
);
|
||||
const grouptitle = groupLivechat.querySelector('.o_DiscussSidebar_groupTitle');
|
||||
const titleText = groupLivechat.querySelector('.o_DiscussSidebarCategory_titleText');
|
||||
assert.strictEqual(
|
||||
grouptitle.textContent.trim(),
|
||||
titleText.textContent.trim(),
|
||||
"Livechat",
|
||||
"should have a channel group named 'Livechat'"
|
||||
);
|
||||
const livechat = groupLivechat.querySelector(`
|
||||
.o_DiscussSidebarItem[data-thread-local-id="${
|
||||
.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
@@ -95,10 +97,10 @@ QUnit.test('livechat in the sidebar: existing user with country', async function
|
||||
await this.start();
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should have a channel group livechat in the side bar"
|
||||
);
|
||||
const livechat = document.querySelector('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
|
||||
const livechat = document.querySelector('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem');
|
||||
assert.ok(
|
||||
livechat,
|
||||
"should have a livechat in sidebar"
|
||||
@@ -124,7 +126,7 @@ QUnit.test('do not add livechat in the sidebar on visitor opening his chat', asy
|
||||
await this.start();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should not have any livechat in the sidebar initially"
|
||||
);
|
||||
|
||||
@@ -141,7 +143,7 @@ QUnit.test('do not add livechat in the sidebar on visitor opening his chat', asy
|
||||
await nextAnimationFrame();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should still not have any livechat in the sidebar after visitor opened his chat"
|
||||
);
|
||||
});
|
||||
@@ -168,7 +170,7 @@ QUnit.test('do not add livechat in the sidebar on visitor typing', async functio
|
||||
await this.start();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should not have any livechat in the sidebar initially"
|
||||
);
|
||||
|
||||
@@ -187,7 +189,7 @@ QUnit.test('do not add livechat in the sidebar on visitor typing', async functio
|
||||
await nextAnimationFrame();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should still not have any livechat in the sidebar after visitor started typing"
|
||||
);
|
||||
});
|
||||
@@ -221,7 +223,7 @@ QUnit.test('add livechat in the sidebar on visitor sending first message', async
|
||||
await this.start();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should not have any livechat in the sidebar initially"
|
||||
);
|
||||
|
||||
@@ -239,33 +241,24 @@ QUnit.test('add livechat in the sidebar on visitor sending first message', async
|
||||
}));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should have a channel group livechat in the side bar after receiving first message"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat .o_DiscussSidebar_item',
|
||||
'.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem',
|
||||
"should have a livechat in the sidebar after receiving first message"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_DiscussSidebar_groupLivechat .o_DiscussSidebar_item').textContent,
|
||||
document.querySelector('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem .o_DiscussSidebarCategoryItem_name').textContent,
|
||||
"Visitor (Belgium)",
|
||||
"should have visitor name and country as livechat name"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechats are sorted by last message date in the sidebar: most recent at the top', async function (assert) {
|
||||
/**
|
||||
* For simplicity the code that is covered in this test is considering
|
||||
* messages to be more/less recent than others based on their ids instead of
|
||||
* their actual creation date.
|
||||
*/
|
||||
assert.expect(7);
|
||||
QUnit.test('livechats are sorted by last activity time in the sidebar: most recent at the top', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
this.data['mail.message'].records.push(
|
||||
{ id: 11, model: "mail.channel", res_id: 11 }, // least recent message due to smaller id
|
||||
{ id: 12, model: "mail.channel", res_id: 12 }, // most recent message due to higher id
|
||||
);
|
||||
this.data['mail.channel'].records.push(
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
@@ -273,6 +266,7 @@ QUnit.test('livechats are sorted by last message date in the sidebar: most recen
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
last_interest_dt: datetime_to_str(new Date(2021, 0, 1)), // less recent one
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
@@ -280,6 +274,7 @@ QUnit.test('livechats are sorted by last message date in the sidebar: most recen
|
||||
id: 12,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
last_interest_dt: datetime_to_str(new Date(2021, 0, 2)), // more recent one
|
||||
},
|
||||
);
|
||||
await this.start();
|
||||
@@ -291,122 +286,43 @@ QUnit.test('livechats are sorted by last message date in the sidebar: most recen
|
||||
id: 12,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
"should have a channel group livechat"
|
||||
);
|
||||
const initialLivechats = document.querySelectorAll('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
|
||||
const initialLivechats = document.querySelectorAll('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem');
|
||||
assert.strictEqual(
|
||||
initialLivechats.length,
|
||||
2,
|
||||
"should have 2 livechats in the sidebar"
|
||||
"should have 2 livechat items"
|
||||
);
|
||||
assert.strictEqual(
|
||||
initialLivechats[0].dataset.threadLocalId,
|
||||
livechat12.localId,
|
||||
"first livechat should be the one with the most recent message"
|
||||
"first livechat should be the one with the more recent last activity time"
|
||||
);
|
||||
assert.strictEqual(
|
||||
initialLivechats[1].dataset.threadLocalId,
|
||||
livechat11.localId,
|
||||
"second livechat should be the one with the least recent message"
|
||||
"second livechat should be the one with the less recent last activity time"
|
||||
);
|
||||
|
||||
// post a new message on the last channel
|
||||
await afterNextRender(() => initialLivechats[1].click());
|
||||
await afterNextRender(() => document.execCommand('insertText', false, "Blabla"));
|
||||
await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
|
||||
const livechats = document.querySelectorAll('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
|
||||
|
||||
const newLivechats = document.querySelectorAll('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem');
|
||||
assert.strictEqual(
|
||||
livechats.length,
|
||||
newLivechats.length,
|
||||
2,
|
||||
"should still have 2 livechats in the sidebar after posting a new message"
|
||||
"should have 2 livechat items"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechats[0].dataset.threadLocalId,
|
||||
newLivechats[0].dataset.threadLocalId,
|
||||
livechat11.localId,
|
||||
"first livechat should now be the one on which the new message was posted"
|
||||
"first livechat should be the one with the more recent last activity time"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechats[1].dataset.threadLocalId,
|
||||
newLivechats[1].dataset.threadLocalId,
|
||||
livechat12.localId,
|
||||
"second livechat should now be the one on which the message was not posted"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechats with no messages are sorted by creation date in the sidebar: most recent at the top', async function (assert) {
|
||||
/**
|
||||
* For simplicity the code that is covered in this test is considering
|
||||
* channels to be more/less recent than others based on their ids instead of
|
||||
* their actual creation date.
|
||||
*/
|
||||
assert.expect(5);
|
||||
|
||||
this.data['mail.message'].records.push(
|
||||
{ id: 13, model: "mail.channel", res_id: 13 },
|
||||
);
|
||||
this.data['mail.channel'].records.push(
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11, // least recent channel due to smallest id
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
channel_type: 'livechat',
|
||||
id: 12, // most recent channel that does not have a message
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 13",
|
||||
channel_type: 'livechat',
|
||||
id: 13, // most recent channel (but it has a message)
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
},
|
||||
);
|
||||
await this.start();
|
||||
const livechat11 = this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
const livechat12 = this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 12,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
const livechat13 = this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 13,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_groupLivechat',
|
||||
"should have a channel group livechat"
|
||||
);
|
||||
const initialLivechats = document.querySelectorAll('.o_DiscussSidebar_groupLivechat .o_DiscussSidebarItem');
|
||||
assert.strictEqual(
|
||||
initialLivechats.length,
|
||||
3,
|
||||
"should have 3 livechats in the sidebar"
|
||||
);
|
||||
assert.strictEqual(
|
||||
initialLivechats[0].dataset.threadLocalId,
|
||||
livechat12.localId,
|
||||
"first livechat should be the most recent channel without message"
|
||||
);
|
||||
assert.strictEqual(
|
||||
initialLivechats[1].dataset.threadLocalId,
|
||||
livechat11.localId,
|
||||
"second livechat should be the second most recent channel without message"
|
||||
);
|
||||
assert.strictEqual(
|
||||
initialLivechats[2].dataset.threadLocalId,
|
||||
livechat13.localId,
|
||||
"third livechat should be the channel with a message"
|
||||
"second livechat should be the one with the less recent last activity time"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { DiscussSidebar } from '@mail/components/discuss_sidebar/discuss_sidebar';
|
||||
|
||||
import { patch } from 'web.utils';
|
||||
|
||||
const components = { DiscussSidebar };
|
||||
|
||||
patch(components.DiscussSidebar.prototype, 'im_livechat/static/src/components/discuss_sidebar/discuss_sidebar.js', {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return the list of livechats that match the quick search value input.
|
||||
*
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
quickSearchOrderedAndPinnedLivechatList() {
|
||||
const allOrderedAndPinnedLivechats = this.messaging.models['mail.thread']
|
||||
.all(thread =>
|
||||
thread.channel_type === 'livechat' &&
|
||||
thread.isPinned &&
|
||||
thread.model === 'mail.channel'
|
||||
).sort((c1, c2) => {
|
||||
// sort by: last message id (desc), id (desc)
|
||||
if (c1.lastMessage && c2.lastMessage) {
|
||||
return c2.lastMessage.id - c1.lastMessage.id;
|
||||
}
|
||||
// a channel without a last message is assumed to be a new
|
||||
// channel just created with the intent of posting a new
|
||||
// message on it, in which case it should be moved up.
|
||||
if (!c1.lastMessage) {
|
||||
return -1;
|
||||
}
|
||||
if (!c2.lastMessage) {
|
||||
return 1;
|
||||
}
|
||||
return c2.id - c1.id;
|
||||
});
|
||||
if (!this.discuss.sidebarQuickSearchValue) {
|
||||
return allOrderedAndPinnedLivechats;
|
||||
}
|
||||
const qsVal = this.discuss.sidebarQuickSearchValue.toLowerCase();
|
||||
return allOrderedAndPinnedLivechats.filter(livechat => {
|
||||
const nameVal = livechat.displayName.toLowerCase();
|
||||
return nameVal.includes(qsVal);
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,24 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.DiscussSidebar" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@name='root']" position="inside">
|
||||
<t t-set="livechats" t-value="quickSearchOrderedAndPinnedLivechatList()"/>
|
||||
<t t-if="livechats and livechats.length">
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupLivechat">
|
||||
<div class="o_DiscussSidebar_groupHeader">
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle">
|
||||
Livechat
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_list">
|
||||
<t t-foreach="livechats" t-as="livechat" t-key="livechat.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="livechat.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<xpath expr="//DiscussSidebarCategory[contains(@class, 'o_DiscussSidebar_categoryChat')]" position="before">
|
||||
<t t-set="categoryLivechat" t-value="messaging.discuss.categoryLivechat"/>
|
||||
<t t-if="categoryLivechat and categoryLivechat.selectedChannels.length">
|
||||
<DiscussSidebarCategory
|
||||
class="o_DiscussSidebar_category o_DiscussSidebar_categoryLivechat"
|
||||
categoryLocalId="categoryLivechat.localId"
|
||||
/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
start,
|
||||
} from '@mail/utils/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_tests.js', {
|
||||
beforeEach() {
|
||||
beforeEach(this);
|
||||
|
||||
this.start = async params => {
|
||||
const { env, widget } = await start(Object.assign({}, params, {
|
||||
autoOpenDiscuss: true,
|
||||
data: this.data,
|
||||
hasDiscuss: true,
|
||||
}));
|
||||
this.env = env;
|
||||
this.widget = widget;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
afterEach(this);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('livechat - counter: should not have a counter if the category is unfolded and without unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
await this.start();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`,
|
||||
"should not have a counter if the category is unfolded and without unread messages",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - counter: should not have a counter if the category is unfolded and with unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
message_unread_counter: 10,
|
||||
});
|
||||
await this.start();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`,
|
||||
"should not have a counter if the category is unfolded and with unread messages",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - counter: should not have a counter if category is folded and without unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`,
|
||||
"should not have a counter if the category is folded and without unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - counter: should have correct value of unread threads if category is folded and with unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
message_unread_counter: 10,
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelector(`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`).textContent,
|
||||
"1",
|
||||
"should have correct value of unread threads if category is folded and with unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: close manually by clicking the title', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: true,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
// fold the livechat category
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
this.messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category livechat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: open manually by clicking the title', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
// open the livechat category
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
this.messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category livechat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: close should update the value on the server', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: true,
|
||||
});
|
||||
const currentUserId = this.data.currentUserId;
|
||||
await this.start();
|
||||
|
||||
const initalSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_livechat_open,
|
||||
true,
|
||||
"the value in server side should be true"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
this.messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
const newSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_livechat_open,
|
||||
false,
|
||||
"the value in server side should be false"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: open should update the value on the server', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
const currentUserId = this.data.currentUserId;
|
||||
await this.start();
|
||||
|
||||
const initalSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_livechat_open,
|
||||
false,
|
||||
"the value in server side should be false"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
this.messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
const newSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_livechat_open,
|
||||
true,
|
||||
"the value in server side should be true"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: close from the bus', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
const notif = [
|
||||
["dbName", "res.partner", this.messaging.currentPartner.id],
|
||||
{
|
||||
type: "res.users_settings_changed",
|
||||
payload: {
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
this.env.services.bus_service.trigger('notification', [notif]);
|
||||
});
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category livechat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: open from the bus', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
const notif = [
|
||||
["dbName", "res.partner", this.messaging.currentPartner.id],
|
||||
{
|
||||
type: "res.users_settings_changed",
|
||||
payload: {
|
||||
is_discuss_sidebar_category_livechat_open: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
this.env.services.bus_service.trigger('notification', [notif]);
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category livechat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('livechat - states: category item should be invisible if the catgory is closed', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
this.messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"inactive item should be invisible if the category is folded"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: the active category item should be visble even if the category is closed', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
const livechat = document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`);
|
||||
await afterNextRender(() => {
|
||||
livechat.click();
|
||||
});
|
||||
assert.ok(livechat.classList.contains('o-active'));
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
this.messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
'the active livechat item should remain even if the category is folded'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
start,
|
||||
} from '@mail/utils/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_item', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_item_tests.js', {
|
||||
beforeEach() {
|
||||
beforeEach(this);
|
||||
|
||||
this.start = async params => {
|
||||
const { env, widget } = await start(Object.assign({}, params, {
|
||||
autoOpenDiscuss: true,
|
||||
data: this.data,
|
||||
hasDiscuss: true,
|
||||
}));
|
||||
this.env = env;
|
||||
this.widget = widget;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
afterEach(this);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('livechat - avatar: should have a smiley face avatar for an anonymous livechat item', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.currentPartnerId],
|
||||
});
|
||||
await this.start();
|
||||
|
||||
const livechatItem = document.querySelector(`
|
||||
.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]
|
||||
`);
|
||||
assert.containsOnce(
|
||||
livechatItem,
|
||||
`.o_DiscussSidebarCategoryItem_image`,
|
||||
"should have an avatar"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
|
||||
'/mail/static/src/img/smiley/avatar.jpg',
|
||||
'should have the smiley face as the avatar for anonymous users'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - avatar: should have a partner profile picture for a livechat item linked with a partner', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['res.partner'].records.push({
|
||||
id: 10,
|
||||
name: "Jean",
|
||||
});
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, 10],
|
||||
});
|
||||
await this.start();
|
||||
|
||||
const livechatItem = document.querySelector(`
|
||||
.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]
|
||||
`);
|
||||
assert.containsOnce(
|
||||
livechatItem,
|
||||
`.o_DiscussSidebarCategoryItem_image`,
|
||||
"should have an avatar"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
|
||||
'/web/image/res.partner/10/avatar_128',
|
||||
'should have the partner profile picture as the avatar for partners'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { DiscussSidebarItem } from '@mail/components/discuss_sidebar_item/discuss_sidebar_item';
|
||||
|
||||
import { patch } from 'web.utils';
|
||||
|
||||
const components = { DiscussSidebarItem };
|
||||
|
||||
patch(components.DiscussSidebarItem.prototype, 'im_livechat/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js', {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
hasUnpin(...args) {
|
||||
const res = this._super(...args);
|
||||
return res || this.thread.channel_type === 'livechat';
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerFieldPatchModel, registerInstancePatchModel } from '@mail/model/model_core';
|
||||
import { one2one } from '@mail/model/model_field';
|
||||
|
||||
registerInstancePatchModel('mail.discuss', 'im_livechat/static/src/models/discuss/discuss.js', {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onInputQuickSearch(value) {
|
||||
if (!this.sidebarQuickSearchValue) {
|
||||
this.categoryLivechat.open();
|
||||
}
|
||||
return this._super(value);
|
||||
},
|
||||
});
|
||||
|
||||
registerFieldPatchModel('mail.discuss', 'im_livechat/static/src/models/discuss/discuss.js', {
|
||||
/**
|
||||
* Discuss sidebar category for `livechat` channel threads.
|
||||
*/
|
||||
categoryLivechat: one2one('mail.discuss_sidebar_category', {
|
||||
isCausal: true,
|
||||
}),
|
||||
});
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerInstancePatchModel } from '@mail/model/model_core';
|
||||
|
||||
registerInstancePatchModel('mail.discuss_sidebar_category_item', 'im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js', {
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Private
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeAvatarUrl() {
|
||||
if (this.channelType === 'livechat') {
|
||||
if (this.channel.correspondent && this.channel.correspondent.id > 0) {
|
||||
return this.channel.correspondent.avatarUrl;
|
||||
}
|
||||
return '/mail/static/src/img/smiley/avatar.jpg';
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeCounter() {
|
||||
if (this.channelType === 'livechat') {
|
||||
return this.channel.localMessageUnreadCounter;
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeHasUnpinCommand() {
|
||||
if (this.channelType === 'livechat') {
|
||||
return !this.channel.localMessageUnreadCounter;
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeHasThreadIcon() {
|
||||
if (this.channelType === 'livechat') {
|
||||
return false;
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
|
||||
});
|
||||
+21
-1
@@ -1,11 +1,31 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerInstancePatchModel } from '@mail/model/model_core';
|
||||
import { insert } from '@mail/model/model_field_command';
|
||||
import { create, insert } from '@mail/model/model_field_command';
|
||||
|
||||
registerInstancePatchModel('mail.messaging_initializer', 'im_livechat/static/src/models/messaging_initializer/messaging_initializer.js', {
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} resUsersSettings
|
||||
* @param {boolean} resUsersSettings.is_discuss_sidebar_category_livechat_open
|
||||
*/
|
||||
_initResUsersSettings({ is_discuss_sidebar_category_livechat_open }) {
|
||||
this.messaging.discuss.update({
|
||||
categoryLivechat: create({
|
||||
counterComputeMethod: 'unread',
|
||||
isServerOpen: is_discuss_sidebar_category_livechat_open,
|
||||
name: this.env._t("Livechat"),
|
||||
serverStateKey: 'is_discuss_sidebar_category_livechat_open',
|
||||
sortComputeMethod: 'last_action',
|
||||
supportedChannelTypes: ['livechat'],
|
||||
}),
|
||||
});
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Object[]} [param0.channel_livechat=[]]
|
||||
*/
|
||||
_initCommands() {
|
||||
this._super();
|
||||
|
||||
+14
@@ -8,6 +8,20 @@ registerInstancePatchModel('mail.messaging_notification_handler', 'im_livechat/s
|
||||
// Private
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {object} settings
|
||||
* @param {boolean} [settings.is_discuss_sidebar_category_livechat_open]
|
||||
*/
|
||||
_handleNotificationResUsersSettings(settings) {
|
||||
if ('is_discuss_sidebar_category_livechat_open' in settings) {
|
||||
this.messaging.discuss.categoryLivechat.update({
|
||||
isServerOpen: settings.is_discuss_sidebar_category_livechat_open,
|
||||
});
|
||||
}
|
||||
this._super(settings);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,9 @@ patch(MockModels, 'im_livechat/static/tests/helpers/mock_models.js', {
|
||||
livechat_channel_id: { string: "Channel", type: 'many2one', relation: 'im_livechat.channel' },
|
||||
livechat_operator_id: { string: "Operator", type: 'many2one', relation: 'res.partner' },
|
||||
});
|
||||
Object.assign(data['res.users.settings'].fields, {
|
||||
is_discuss_sidebar_category_livechat_open: { string: "Is Discuss Sidebar Category Livechat Open?", type: 'boolean', default: true },
|
||||
});
|
||||
Object.assign(data['res.users'].fields, {
|
||||
livechat_username: { string: 'Livechat Username', type: 'string' },
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
'security/ir.model.access.csv',
|
||||
'views/mail_alias_views.xml',
|
||||
'views/res_users_views.xml',
|
||||
'views/res_users_settings_views.xml',
|
||||
'views/mail_template_views.xml',
|
||||
'views/ir_actions_server_views.xml',
|
||||
'views/ir_model_views.xml',
|
||||
|
||||
@@ -30,6 +30,7 @@ from . import mail_template
|
||||
from . import mail_channel_partner
|
||||
from . import mail_channel
|
||||
from . import mail_shortcode
|
||||
from . import res_users_settings
|
||||
|
||||
# odoo models
|
||||
from . import ir_action_act_window
|
||||
|
||||
@@ -8,6 +8,7 @@ from uuid import uuid4
|
||||
from odoo import _, api, fields, models, modules, tools, Command
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -421,6 +422,20 @@ class Channel(models.Model):
|
||||
message_format_values = message.message_format()[0]
|
||||
bus_notifications = self._channel_message_notifications(message, message_format_values)
|
||||
self.env['bus.bus'].sudo().sendmany(bus_notifications)
|
||||
# Last interest is updated for a chat when posting a message.
|
||||
# So a notification is needed to update UI.
|
||||
if self.is_chat:
|
||||
notifications = []
|
||||
for channel_partners in self.channel_last_seen_partner_ids:
|
||||
notif = {
|
||||
'type': 'mail.channel_last_interest_dt_changed',
|
||||
'payload': {
|
||||
'id': self.id,
|
||||
'last_interest_dt': channel_partners.last_interest_dt,
|
||||
}
|
||||
}
|
||||
notifications.append([(self._cr.dbname, 'res.partner', channel_partners.partner_id.id), notif])
|
||||
self.env['bus.bus'].sendmany(notifications)
|
||||
return rdata
|
||||
|
||||
def _message_receive_bounce(self, email, partner):
|
||||
@@ -432,7 +447,10 @@ class Channel(models.Model):
|
||||
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, *, message_type='notification', **kwargs):
|
||||
self.filtered(lambda channel: channel.is_chat).mapped('channel_last_seen_partner_ids').sudo().write({'is_pinned': True})
|
||||
self.filtered(lambda channel: channel.is_chat).mapped('channel_last_seen_partner_ids').sudo().write({
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': fields.Datetime.now(),
|
||||
})
|
||||
|
||||
# mail_post_autofollow=False is necessary to prevent adding followers
|
||||
# when using mentions in channels. Followers should not be added to
|
||||
@@ -549,6 +567,7 @@ class Channel(models.Model):
|
||||
info['seen_message_id'] = partner_channel.seen_message_id.id
|
||||
info['custom_channel_name'] = partner_channel.custom_channel_name
|
||||
info['is_pinned'] = partner_channel.is_pinned
|
||||
info['last_interest_dt'] = partner_channel.last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
# add members infos
|
||||
if channel.channel_type != 'channel':
|
||||
# avoid sending potentially a lot of members for big channels
|
||||
@@ -616,7 +635,10 @@ class Channel(models.Model):
|
||||
channel = self.browse(result[0].get('channel_id'))
|
||||
# pin up the channel for the current partner
|
||||
if pin:
|
||||
self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', channel.id)]).write({'is_pinned': True})
|
||||
self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', channel.id)]).write({
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': fields.Datetime.now(),
|
||||
})
|
||||
channel._broadcast(self.env.user.partner_id.ids)
|
||||
else:
|
||||
# create a new one
|
||||
|
||||
@@ -23,6 +23,7 @@ class ChannelPartner(models.Model):
|
||||
fold_state = fields.Selection([('open', 'Open'), ('folded', 'Folded'), ('closed', 'Closed')], string='Conversation Fold State', default='open')
|
||||
is_minimized = fields.Boolean("Conversation is minimized")
|
||||
is_pinned = fields.Boolean("Is pinned on the interface", default=True)
|
||||
last_interest_dt = fields.Datetime("Last Interest", default=fields.Datetime.now, help="Contains the date and time of the last interesting event that happened in this channel for this partner. This includes: creating, joining, pinning, and new message posted.")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
||||
@@ -25,6 +25,7 @@ class Users(models.Model):
|
||||
help="Policy on how to handle Chatter notifications:\n"
|
||||
"- Handle by Emails: notifications are sent to your email address\n"
|
||||
"- Handle in Odoo: notifications appear in your Odoo Inbox")
|
||||
res_users_settings_ids = fields.One2many('res.users.settings', 'user_id')
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
@@ -95,6 +96,7 @@ class Users(models.Model):
|
||||
'channels': self.partner_id._get_channels_as_member().channel_info(),
|
||||
'current_partner': self.partner_id.mail_partner_format().get(self.partner_id),
|
||||
'current_user_id': self.id,
|
||||
'current_user_settings': self.env['res.users.settings']._find_or_create_for_user(self)._res_users_settings_format(),
|
||||
'mail_failures': self.partner_id._message_fetch_failed(),
|
||||
'menu_id': self.env['ir.model.data']._xmlid_to_res_id('mail.menu_root_discuss'),
|
||||
'needaction_inbox_counter': self.partner_id._get_needaction_count(),
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ResUsersSettings(models.Model):
|
||||
_name = 'res.users.settings'
|
||||
_description = 'User Settings'
|
||||
|
||||
user_id = fields.Many2one('res.users', string="User", required=True, readonly=True, ondelete='cascade')
|
||||
is_discuss_sidebar_category_channel_open = fields.Boolean(string="Is discuss sidebar category channel open?", default=True)
|
||||
is_discuss_sidebar_category_chat_open = fields.Boolean(string="Is discuss sidebar category chat open?", default=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_user_id', 'UNIQUE(user_id)', 'One user should only have one mail user settings.')
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _find_or_create_for_user(self, user):
|
||||
settings = user.res_users_settings_ids
|
||||
if not settings:
|
||||
settings = self.create({'user_id': user.id})
|
||||
return settings
|
||||
|
||||
def _res_users_settings_format(self):
|
||||
self.ensure_one()
|
||||
return self._read_format(fnames=[name for name, field in self._fields.items() if name == 'id' or not field.automatic])[0]
|
||||
|
||||
def set_res_users_settings(self, new_settings):
|
||||
self.ensure_one()
|
||||
changed_settings = {}
|
||||
for setting in new_settings.keys():
|
||||
if setting in self._fields and new_settings[setting] != self[setting]:
|
||||
changed_settings[setting] = new_settings[setting]
|
||||
self.write(changed_settings)
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.user_id.partner_id.id), {
|
||||
'type': 'res.users_settings_changed',
|
||||
'payload': changed_settings,
|
||||
})
|
||||
@@ -45,4 +45,6 @@ access_mail_resend_message,access.mail.resend.message,model_mail_resend_message,
|
||||
access_mail_resend_partner,access.mail.resend.partner,model_mail_resend_partner,base.group_user,1,1,1,0
|
||||
access_mail_template_preview,access.mail.template.preview,model_mail_template_preview,base.group_user,1,1,1,0
|
||||
access_mail_blacklist_remove_system,acesss.mail.blacklist.remove.system,model_mail_blacklist_remove,base.group_system,1,1,1,1
|
||||
access_res_users_settings_all,res.users.settings,model_res_users_settings,,0,0,0,0
|
||||
access_res_users_settings_user,res.users.settings,model_res_users_settings,base.group_user,1,1,1,1
|
||||
ir_actions_report_access_user,ir.actions.report.access.user,base.model_ir_actions_report,base.group_user,1,0,0,0
|
||||
|
||||
|
@@ -65,4 +65,26 @@
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="res_users_settings_rule_admin" model="ir.rule">
|
||||
<field name="name">Administrators can access all User Settings.</field>
|
||||
<field name="model_id" ref="model_res_users_settings"/>
|
||||
<field name="groups" eval="[Command.link(ref('base.group_system'))]"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="res_users_settings_rule_user" model="ir.rule">
|
||||
<field name="name">res.users.settings: access their own entries</field>
|
||||
<field name="model_id" ref="model_res_users_settings"/>
|
||||
<field name="groups" eval="[Command.link(ref('base.group_user'))]"/>
|
||||
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-if="!messaging.device.isMobile">
|
||||
<DiscussSidebar class="o_Discuss_sidebar"/>
|
||||
<DiscussSidebar class="o_Discuss_sidebar bg-light border-right"/>
|
||||
</t>
|
||||
<t t-if="messaging.device.isMobile" t-call="mail.Discuss.content"/>
|
||||
<t t-else="">
|
||||
|
||||
@@ -43,7 +43,7 @@ QUnit.test('sidebar: pinned channel 1: init with one pinned channel', async func
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarItem[data-thread-local-id="${
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
@@ -66,7 +66,7 @@ QUnit.test('sidebar: pinned channel 2: open pinned channel', async function (ass
|
||||
model: 'mail.channel',
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarItem[data-thread-local-id="${
|
||||
document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
threadGeneral.localId
|
||||
}"]`).click()
|
||||
);
|
||||
@@ -107,13 +107,13 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async
|
||||
model: 'mail.channel',
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarItem[data-thread-local-id="${
|
||||
document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
threadGeneral.localId
|
||||
}"]`).click()
|
||||
);
|
||||
assert.verifySteps([], "neither channel_fold nor action_unfollow are called yet");
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_DiscussSidebarItem_commandLeave').click()
|
||||
document.querySelector('.o_DiscussSidebarCategoryItem_commandLeave').click()
|
||||
);
|
||||
assert.verifySteps(
|
||||
[
|
||||
@@ -124,7 +124,7 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
"The channel must have been removed from discuss sidebar"
|
||||
);
|
||||
assert.containsOnce(
|
||||
@@ -153,12 +153,12 @@ QUnit.test('sidebar: unpin channel from bus', async function (assert) {
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
"1 channel is present in discuss sidebar and it is 'general'"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarItem[data-thread-local-id="${
|
||||
document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
threadGeneral.localId
|
||||
}"]`).click()
|
||||
);
|
||||
@@ -191,7 +191,7 @@ QUnit.test('sidebar: unpin channel from bus', async function (assert) {
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
"The channel must have been removed from discuss sidebar"
|
||||
);
|
||||
});
|
||||
@@ -217,12 +217,12 @@ QUnit.test('[technical] sidebar: channel group_based_subscription: mandatorily p
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${threadGeneral.localId}"]`,
|
||||
"The channel #General is in discuss sidebar"
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'o_DiscussSidebarItem_commandLeave',
|
||||
'o_DiscussSidebarCategoryItem_commandLeave',
|
||||
"The group_based_subscription channel is not unpinnable"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -52,13 +52,13 @@ QUnit.test('sidebar find shows channels matching search term', async function (a
|
||||
},
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebar_groupHeaderItemAdd`).click()
|
||||
document.querySelector(`.o_DiscussSidebarCategory_commandAdd`).click()
|
||||
);
|
||||
document.querySelector(`.o_DiscussSidebar_itemNew`).focus();
|
||||
document.querySelector(`.o_DiscussSidebarCategory_newItem`).focus();
|
||||
document.execCommand('insertText', false, "test");
|
||||
document.querySelector(`.o_DiscussSidebar_itemNew`)
|
||||
document.querySelector(`.o_DiscussSidebarCategory_newItem`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_DiscussSidebar_itemNew`)
|
||||
document.querySelector(`.o_DiscussSidebarCategory_newItem`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keyup'));
|
||||
|
||||
await searchReadDef;
|
||||
@@ -104,13 +104,13 @@ QUnit.test('sidebar find shows channels matching search term even when user is m
|
||||
},
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebar_groupHeaderItemAdd`).click()
|
||||
document.querySelector(`.o_DiscussSidebarCategory_commandAdd`).click()
|
||||
);
|
||||
document.querySelector(`.o_DiscussSidebar_itemNew`).focus();
|
||||
document.querySelector(`.o_DiscussSidebarCategory_newItem`).focus();
|
||||
document.execCommand('insertText', false, "test");
|
||||
document.querySelector(`.o_DiscussSidebar_itemNew`)
|
||||
document.querySelector(`.o_DiscussSidebarCategory_newItem`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_DiscussSidebar_itemNew`)
|
||||
document.querySelector(`.o_DiscussSidebarCategory_newItem`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keyup'));
|
||||
|
||||
await searchReadDef;
|
||||
@@ -145,7 +145,7 @@ QUnit.test('sidebar channels should be ordered case insensitive alphabetically',
|
||||
{ id: 22, name: "Xyz" }
|
||||
);
|
||||
await this.start();
|
||||
const results = document.querySelectorAll('.o_DiscussSidebar_groupChannel .o_DiscussSidebarItem_name');
|
||||
const results = document.querySelectorAll('.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategoryItem_name');
|
||||
assert.deepEqual(
|
||||
[results[0].textContent, results[1].textContent, results[2].textContent, results[3].textContent],
|
||||
["abc", "Abc", "Xyz", "Xyz"],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,12 +19,6 @@ export class DiscussSidebar extends Component {
|
||||
* chats based on this input content.
|
||||
*/
|
||||
this._quickSearchInputRef = useRef('quickSearchInput');
|
||||
|
||||
// bind since passed as props
|
||||
this._onAddChannelAutocompleteSelect = this._onAddChannelAutocompleteSelect.bind(this);
|
||||
this._onAddChannelAutocompleteSource = this._onAddChannelAutocompleteSource.bind(this);
|
||||
this._onAddChatAutocompleteSelect = this._onAddChatAutocompleteSelect.bind(this);
|
||||
this._onAddChatAutocompleteSource = this._onAddChatAutocompleteSource.bind(this);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -38,94 +32,6 @@ export class DiscussSidebar extends Component {
|
||||
return this.messaging && this.messaging.discuss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get FIND_OR_CREATE_CHANNEL() {
|
||||
return this.env._t("Find or create a channel...");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
get orderedMailboxes() {
|
||||
return this.messaging.models['mail.thread']
|
||||
.all(thread => thread.isPinned && thread.model === 'mail.box')
|
||||
.sort((mailbox1, mailbox2) => {
|
||||
if (mailbox1 === this.messaging.inbox) {
|
||||
return -1;
|
||||
}
|
||||
if (mailbox2 === this.messaging.inbox) {
|
||||
return 1;
|
||||
}
|
||||
if (mailbox1 === this.messaging.starred) {
|
||||
return -1;
|
||||
}
|
||||
if (mailbox2 === this.messaging.starred) {
|
||||
return 1;
|
||||
}
|
||||
const mailbox1Name = mailbox1.displayName;
|
||||
const mailbox2Name = mailbox2.displayName;
|
||||
mailbox1Name < mailbox2Name ? -1 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of chats that match the quick search value input.
|
||||
*
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
get quickSearchPinnedAndOrderedChats() {
|
||||
const allOrderedAndPinnedChats = this.messaging.models['mail.thread']
|
||||
.all(thread =>
|
||||
thread.channel_type === 'chat' &&
|
||||
thread.isPinned &&
|
||||
thread.model === 'mail.channel'
|
||||
)
|
||||
.sort((c1, c2) => c1.displayName < c2.displayName ? -1 : 1);
|
||||
if (!this.discuss.sidebarQuickSearchValue) {
|
||||
return allOrderedAndPinnedChats;
|
||||
}
|
||||
const qsVal = this.discuss.sidebarQuickSearchValue.toLowerCase();
|
||||
return allOrderedAndPinnedChats.filter(chat => {
|
||||
const nameVal = chat.displayName.toLowerCase();
|
||||
return nameVal.includes(qsVal);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of channels that match the quick search value input.
|
||||
*
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
get quickSearchOrderedAndPinnedMultiUserChannels() {
|
||||
const allOrderedAndPinnedMultiUserChannels = this.messaging.models['mail.thread']
|
||||
.all(thread =>
|
||||
thread.channel_type === 'channel' &&
|
||||
thread.isPinned &&
|
||||
thread.model === 'mail.channel'
|
||||
)
|
||||
.sort((c1, c2) => {
|
||||
if (c1.displayName && !c2.displayName) {
|
||||
return -1;
|
||||
} else if (!c1.displayName && c2.displayName) {
|
||||
return 1;
|
||||
} else if (c1.displayName && c2.displayName && c1.displayName !== c2.displayName) {
|
||||
return c1.displayName.toLowerCase() < c2.displayName.toLowerCase() ? -1 : 1;
|
||||
} else {
|
||||
return c1.id - c2.id;
|
||||
}
|
||||
});
|
||||
if (!this.discuss.sidebarQuickSearchValue) {
|
||||
return allOrderedAndPinnedMultiUserChannels;
|
||||
}
|
||||
const qsVal = this.discuss.sidebarQuickSearchValue.toLowerCase();
|
||||
return allOrderedAndPinnedMultiUserChannels.filter(channel => {
|
||||
const nameVal = channel.displayName.toLowerCase();
|
||||
return nameVal.includes(qsVal);
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -146,110 +52,13 @@ export class DiscussSidebar extends Component {
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
* @param {Object} ui
|
||||
* @param {Object} ui.item
|
||||
* @param {integer} ui.item.id
|
||||
*/
|
||||
_onAddChannelAutocompleteSelect(ev, ui) {
|
||||
this.discuss.handleAddChannelAutocompleteSelect(ev, ui);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} req
|
||||
* @param {string} req.term
|
||||
* @param {function} res
|
||||
*/
|
||||
_onAddChannelAutocompleteSource(req, res) {
|
||||
this.discuss.handleAddChannelAutocompleteSource(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
* @param {Object} ui
|
||||
* @param {Object} ui.item
|
||||
* @param {integer} ui.item.id
|
||||
*/
|
||||
_onAddChatAutocompleteSelect(ev, ui) {
|
||||
this.discuss.handleAddChatAutocompleteSelect(ev, ui);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} req
|
||||
* @param {string} req.term
|
||||
* @param {function} res
|
||||
*/
|
||||
_onAddChatAutocompleteSource(req, res) {
|
||||
this.discuss.handleAddChatAutocompleteSource(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking on add channel icon.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickChannelAdd(ev) {
|
||||
ev.stopPropagation();
|
||||
this.discuss.update({ isAddingChannel: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking on channel title.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickChannelTitle(ev) {
|
||||
ev.stopPropagation();
|
||||
return this.env.bus.trigger('do-action', {
|
||||
action: {
|
||||
name: this.env._t("Public Channels"),
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'mail.channel',
|
||||
views: [[false, 'kanban'], [false, 'form']],
|
||||
domain: [['public', '!=', 'private']]
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking on add chat icon.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickChatAdd(ev) {
|
||||
ev.stopPropagation();
|
||||
this.discuss.update({ isAddingChat: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
_onHideAddingItem(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!this.discuss) {
|
||||
return;
|
||||
}
|
||||
this.discuss.clearIsAddingItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
_onInputQuickSearch(ev) {
|
||||
ev.stopPropagation();
|
||||
this.discuss.update({
|
||||
sidebarQuickSearchValue: this._quickSearchInputRef.el.value,
|
||||
});
|
||||
this.discuss.onInputQuickSearch(this._quickSearchInputRef.el.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,42 +12,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_group {
|
||||
.o_DiscussSidebar_category {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_groupHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_groupHeaderItem {
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: $o-mail-discuss-sidebar-active-indicator-margin-right;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_itemNew {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_itemNewInput {
|
||||
flex: 1 1 auto;
|
||||
margin-left: $o-mail-discuss-sidebar-active-indicator-margin-right + 3px;
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_quickSearch {
|
||||
border-radius: 10px;
|
||||
margin: 0 $o-mail-discuss-sidebar-scrollbar-width 10px;
|
||||
@@ -62,49 +32,11 @@
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebar {
|
||||
background-color: gray('900');
|
||||
color: gray('300');
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_groupHeader {
|
||||
font-size: $font-size-sm;
|
||||
text-transform: uppercase;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_groupHeaderItemAdd {
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:hover) {
|
||||
color: gray('600');
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_groupTitle {
|
||||
|
||||
&:not(.o-clickable) {
|
||||
color: gray('600');
|
||||
}
|
||||
|
||||
&.o-clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:hover) {
|
||||
color: gray('600');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_itemNewInput {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_quickSearch {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_DiscussSidebar_separator {
|
||||
background-color: gray('600');
|
||||
background-color: $border-color;
|
||||
}
|
||||
|
||||
@@ -2,81 +2,24 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.DiscussSidebar" owl="1">
|
||||
<div class="o_DiscussSidebar">
|
||||
<t t-if="discuss" name="root">
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupMailbox">
|
||||
<t t-foreach="orderedMailboxes" t-as="mailbox" t-key="mailbox.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="mailbox.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<hr class="o_DiscussSidebar_separator"/>
|
||||
<t t-if="messaging.models['mail.thread'].all(thread => thread.isPinned and thread.model === 'mail.channel').length > 19">
|
||||
<input class="o_DiscussSidebar_quickSearch" t-on-input="_onInputQuickSearch" placeholder="Quick search..." t-ref="quickSearchInput" t-esc="discuss.sidebarQuickSearchValue"/>
|
||||
</t>
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupChannel">
|
||||
<div class="o_DiscussSidebar_groupHeader">
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle o-clickable" t-on-click="_onClickChannelTitle">
|
||||
Channels
|
||||
</div>
|
||||
<div class="o-autogrow"/>
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupHeaderItemAdd fa fa-plus" t-on-click="_onClickChannelAdd" title="Add or join a channel"/>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_list">
|
||||
<t t-if="discuss.isAddingChannel">
|
||||
<div class="o_DiscussSidebar_item o_DiscussSidebar_itemNew">
|
||||
<AutocompleteInput
|
||||
class="o_DiscussSidebar_itemNewInput"
|
||||
customClass="'o_DiscussSidebar_newChannelAutocompleteSuggestions'"
|
||||
isFocusOnMount="true"
|
||||
isHtml="true"
|
||||
placeholder="FIND_OR_CREATE_CHANNEL"
|
||||
select="_onAddChannelAutocompleteSelect"
|
||||
source="_onAddChannelAutocompleteSource"
|
||||
t-on-o-hide="_onHideAddingItem"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="quickSearchOrderedAndPinnedMultiUserChannels" t-as="channel" t-key="channel.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="channel.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupChat">
|
||||
<div class="o_DiscussSidebar_groupHeader">
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle">
|
||||
Direct Messages
|
||||
</div>
|
||||
<div class="o-autogrow"/>
|
||||
<div class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupHeaderItemAdd fa fa-plus" t-on-click="_onClickChatAdd" title="Start a conversation"/>
|
||||
</div>
|
||||
<div class="o_DiscussSidebar_list">
|
||||
<t t-if="discuss.isAddingChat">
|
||||
<div class="o_DiscussSidebar_item o_DiscussSidebar_itemNew">
|
||||
<AutocompleteInput
|
||||
class="o_DiscussSidebar_itemNewInput"
|
||||
isFocusOnMount="true"
|
||||
placeholder="'Find or start a conversation...'"
|
||||
select="_onAddChatAutocompleteSelect"
|
||||
source="_onAddChatAutocompleteSource"
|
||||
t-on-o-hide="_onHideAddingItem"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="quickSearchPinnedAndOrderedChats" t-as="chat" t-key="chat.localId">
|
||||
<DiscussSidebarItem
|
||||
class="o_DiscussSidebar_item"
|
||||
threadLocalId="chat.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div name="root" class="o_DiscussSidebar">
|
||||
<div class="o_DiscussSidebar_category o_DiscussSidebar_categoryMailbox">
|
||||
<DiscussSidebarMailbox threadLocalId="messaging.inbox.localId"/>
|
||||
<DiscussSidebarMailbox threadLocalId="messaging.starred.localId"/>
|
||||
<DiscussSidebarMailbox threadLocalId="messaging.history.localId"/>
|
||||
</div>
|
||||
<hr class="o_DiscussSidebar_separator"/>
|
||||
<t t-if="messaging.models['mail.thread'].all(thread => thread.isPinned and thread.model === 'mail.channel').length > 19">
|
||||
<input class="o_DiscussSidebar_quickSearch" t-on-input="_onInputQuickSearch" placeholder="Quick search..." t-ref="quickSearchInput" t-esc="discuss.sidebarQuickSearchValue"/>
|
||||
</t>
|
||||
<DiscussSidebarCategory
|
||||
class="o_DiscussSidebar_category o_DiscussSidebar_categoryChannel"
|
||||
categoryLocalId="discuss.categoryChannel.localId"
|
||||
/>
|
||||
<DiscussSidebarCategory
|
||||
class="o_DiscussSidebar_category o_DiscussSidebar_categoryChat"
|
||||
categoryLocalId="discuss.categoryChat.localId"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class DiscussSidebarCategory extends Component {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {mail.discuss_sidebar_category}
|
||||
*/
|
||||
get category() {
|
||||
return this.messaging.models['mail.discuss_sidebar_category'].get(this.props.categoryLocalId);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(DiscussSidebarCategory, {
|
||||
props: {
|
||||
categoryLocalId: String,
|
||||
},
|
||||
template: 'mail.DiscussSidebarCategory',
|
||||
});
|
||||
|
||||
registerMessagingComponent(DiscussSidebarCategory);
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
.o_DiscussSidebarCategory_command {
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-margin;
|
||||
margin-right:$o-mail-discuss-sidebar-category-item-margin;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_commands {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_headerItem {
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-margin;
|
||||
margin-right: $o-mail-discuss-sidebar-category-item-margin;
|
||||
|
||||
&:last-child {
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_itemNewInput {
|
||||
flex: 1 1 auto;
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-avatar-left-margin;
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_newItem {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-bottom: map-get($spacers, 2);
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_titleIcon {
|
||||
width: $o-mail-discuss-sidebar-category-title-icon-size;
|
||||
height: $o-mail-discuss-sidebar-category-title-icon-size;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebarCategory_command {
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:hover) {
|
||||
color: gray('600');
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_counter {
|
||||
background-color: $o-brand-primary;
|
||||
color: gray('300');
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_headerItem {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_itemNewInput {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_title {
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:hover)
|
||||
{
|
||||
color: gray('600');
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_titleIcon {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategory_titleText {
|
||||
font-size: $font-size-sm;
|
||||
text-transform: uppercase;
|
||||
font-weight: bolder;
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.DiscussSidebarCategory" owl="1">
|
||||
<div class="o_DiscussSidebarCategory" t-att-data-category-local-id="category.localId" t-att-data-category-type="category.type">
|
||||
<div class="o_DiscussSidebarCategory_header">
|
||||
<div class="o_DiscussSidebarCategory_title o_DiscussSidebarCategory_headerItem" t-on-click="category.onClick">
|
||||
<i class="o_DiscussSidebarCategory_titleIcon" t-att-class="category.isOpen ? 'fa fa-chevron-down' : 'fa fa-chevron-right'"/>
|
||||
<span class="o_DiscussSidebarCategory_titleText"><t t-esc="category.name"/></span>
|
||||
</div>
|
||||
<div class="o-autogrow o_DiscussSidebarCategory_headerItem"/>
|
||||
<div class="o_DiscussSidebarCategory_commands o_DiscussSidebarCategory_headerItem">
|
||||
<t t-if="category.hasViewCommand">
|
||||
<i class="o_DiscussSidebarCategory_command o_DiscussSidebarCategory_commandView fa fa-cog" title="View or join channels" t-on-click="category.onClickCommandView" role="img"/>
|
||||
</t>
|
||||
<t t-if="category.hasAddCommand and category.isOpen">
|
||||
<i class="o_DiscussSidebarCategory_command o_DiscussSidebarCategory_commandAdd fa fa-plus" t-on-click="category.onClickCommandAdd" t-att-title="category.commandAddTitleText" role="img"/>
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="!category.isOpen and category.counter > 0">
|
||||
<div class="o_DiscussSidebarCategory_counter o_DiscussSidebarCategory_headerItem badge badge-pill">
|
||||
<t t-esc="category.counter"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_DiscussSidebarCategory_content">
|
||||
<t t-if="category.isOpen">
|
||||
<t t-if="category.isAddingItem">
|
||||
<div class="o_DiscussSidebarCategory_newItem">
|
||||
<AutocompleteInput
|
||||
class="o_DiscussSidebarCategory_itemNewInput"
|
||||
customClass="category === messaging.discuss.categoryChannel ? 'o_DiscussSidebarCategory_newChannelAutocompleteSuggestions' : False"
|
||||
isFocusOnMount="true"
|
||||
isHtml="category === messaging.discuss.categoryChannel ? true : False"
|
||||
placeholder="category.newItemPlaceholderText"
|
||||
select="category.onAddItemAutocompleteSelect"
|
||||
source="category.onAddItemAutocompleteSource"
|
||||
t-on-o-hide="category.onHideAddingItem"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="category.categoryItems" t-as="item" t-key="item.localId">
|
||||
<DiscussSidebarCategoryItem
|
||||
class="o_DiscussSidebarCategory_item"
|
||||
categoryItemLocalId="item.localId"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="!category.isOpen and category.activeItem">
|
||||
<DiscussSidebarCategoryItem
|
||||
class="o_DiscussSidebarCategory_item"
|
||||
categoryItemLocalId="category.activeItem.localId"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
+834
@@ -0,0 +1,834 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
start,
|
||||
} from '@mail/utils/test_utils';
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_tests.js', {
|
||||
beforeEach() {
|
||||
beforeEach(this);
|
||||
|
||||
this.start = async params => {
|
||||
const { env, widget } = await start(Object.assign({}, params, {
|
||||
autoOpenDiscuss: true,
|
||||
data: this.data,
|
||||
hasDiscuss: true,
|
||||
}));
|
||||
this.env = env;
|
||||
this.widget = widget;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
afterEach(this);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('channel - counter: should not have a counter if the category is unfolded and without needaction messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
|
||||
await this.start();
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).length,
|
||||
0,
|
||||
"should not have a counter if the category is unfolded and without unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - counter: should not have a counter if the category is unfolded and with needaction messagens', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push(
|
||||
{ id: 20 },
|
||||
{ id: 30 },
|
||||
);
|
||||
this.data['mail.message'].records.push({
|
||||
body: "message 1",
|
||||
id: 100,
|
||||
model: "mail.channel",
|
||||
res_id: 20,
|
||||
}, {
|
||||
body: "message_2",
|
||||
id: 200,
|
||||
model: "mail.channel",
|
||||
res_id: 30,
|
||||
});
|
||||
this.data['mail.notification'].records.push({
|
||||
mail_message_id: 100,
|
||||
res_partner_id: this.data.currentPartnerId,
|
||||
}, {
|
||||
mail_message_id: 200,
|
||||
res_partner_id: this.data.currentPartnerId,
|
||||
});
|
||||
await this.start();
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).length,
|
||||
0,
|
||||
"should not have a counter if the category is unfolded and with needaction messages",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - counter: should not have a counter if category is folded and without needaction messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
});
|
||||
|
||||
await this.start();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).length,
|
||||
0,
|
||||
"should not have a counter if the category is folded and without unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - counter: should have correct value of needaction threads if category is folded and with needaction messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push(
|
||||
{ id: 20 },
|
||||
{ id: 30 },
|
||||
);
|
||||
this.data['mail.message'].records.push({
|
||||
body: "message 1",
|
||||
id: 100,
|
||||
model: "mail.channel",
|
||||
res_id: 20,
|
||||
}, {
|
||||
body: "message_2",
|
||||
id: 200,
|
||||
model: "mail.channel",
|
||||
res_id: 30,
|
||||
});
|
||||
this.data['mail.notification'].records.push({
|
||||
mail_message_id: 100,
|
||||
res_partner_id: this.data.currentPartnerId,
|
||||
}, {
|
||||
mail_message_id: 200,
|
||||
res_partner_id: this.data.currentPartnerId,
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_counter`).textContent,
|
||||
"2",
|
||||
"should have correct value of needaction threads if category is folded and with needaction messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - command: should have view command when category is unfolded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await this.start();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandView`).length,
|
||||
1,
|
||||
"should have view command when channel category is open"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - command: should have view command when category is folded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandView`).length,
|
||||
1,
|
||||
"should have view command when channel category is closed"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - command: should have add command when category is unfolded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await this.start();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length,
|
||||
1,
|
||||
"should have add command when channel category is open"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - command: should not have add command when category is folded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length,
|
||||
0,
|
||||
"should not have add command when channel category is closed"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - states: close manually by clicking the title', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: true,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category channel should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - states: open manually by clicking the title', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category channel should be open and the content should be visible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - states: close should update the value on the server', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: true,
|
||||
});
|
||||
const currentUserId = this.data.currentUserId;
|
||||
await this.start();
|
||||
|
||||
const initalSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_channel_open,
|
||||
true,
|
||||
"the vaule in server side should be true"
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
const newSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_channel_open,
|
||||
false,
|
||||
"the vaule in server side should be false"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - states: open should update the value on the server', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
});
|
||||
const currentUserId = this.data.currentUserId;
|
||||
await this.start();
|
||||
|
||||
const initalSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_channel_open,
|
||||
false,
|
||||
"the vaule in server side should be false"
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
const newSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_channel_open,
|
||||
true,
|
||||
"the vaule in server side should be false"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - states: close from the bus', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: true,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
const notif = [
|
||||
["dbName", "res.partner", this.messaging.currentPartner.id],
|
||||
{
|
||||
type: "res.users_settings_changed",
|
||||
payload: {
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
this.env.services.bus_service.trigger('notification', [notif]);
|
||||
});
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category channel should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - states: open from the bus', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_channel_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
const notif = [
|
||||
["dbName", "res.partner", this.messaging.currentPartner.id],
|
||||
{
|
||||
type: "res.users_settings_changed",
|
||||
payload: {
|
||||
is_discuss_sidebar_category_channel_open: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
this.env.services.bus_service.trigger('notification', [notif]);
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category channel should be open and the content should be visible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('channel - states: the active category item should be visble even if the category is closed', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
await this.start();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
const channel = document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`);
|
||||
await afterNextRender(() => {
|
||||
channel.click();
|
||||
});
|
||||
assert.ok(channel.classList.contains('o-active'));
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
'the active channel item should remain even if the category is folded'
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebarMailbox[data-thread-local-id="${
|
||||
this.messaging.inbox.localId
|
||||
}"]`).click();
|
||||
});
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"inactive item should be invisible if the category is folded"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - counter: should not have a counter if the category is unfolded and without unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 0,
|
||||
public: 'private',
|
||||
});
|
||||
|
||||
await this.start();
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).length,
|
||||
0,
|
||||
"should not have a counter if the category is unfolded and without unread messages",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - counter: should not have a counter if the category is unfolded and with unread messagens', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 10,
|
||||
public: 'private',
|
||||
});
|
||||
await this.start();
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).length,
|
||||
0,
|
||||
"should not have a counter if the category is unfolded and with unread messages",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - counter: should not have a counter if category is folded and without unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 0,
|
||||
public: 'private',
|
||||
});
|
||||
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).length,
|
||||
0,
|
||||
"should not have a counter if the category is folded and without unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - counter: should have correct value of unread threads if category is folded and with unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 10,
|
||||
public: 'private',
|
||||
}, {
|
||||
channel_type: 'chat',
|
||||
id: 20,
|
||||
message_unread_counter: 20,
|
||||
public: 'private',
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
assert.strictEqual(
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_counter`).textContent,
|
||||
"2",
|
||||
"should have correct value of unread threads if category is folded and with unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - command: should have add command when category is unfolded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await this.start();
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length,
|
||||
1,
|
||||
"should have add command when chat category is open"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - command: should not have add command when category is folded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_chat_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelectorAll(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_header .o_DiscussSidebarCategory_commandAdd`).length,
|
||||
0,
|
||||
"should not have add command when chat category is closed"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - states: close manually by clicking the title', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 0,
|
||||
public: 'private',
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_chat_open: true,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category chat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - states: open manually by clicking the title', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 0,
|
||||
public: 'private',
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_chat_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category chat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - states: close should call update server data', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_chat_open: true,
|
||||
});
|
||||
const currentUserId = this.data.currentUserId;
|
||||
await this.start();
|
||||
|
||||
const initalSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_chat_open,
|
||||
true,
|
||||
"the value in server side should be true"
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
const newSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_chat_open,
|
||||
false,
|
||||
"the value in server side should be false"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - states: open should call update server data', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_chat_open: false,
|
||||
});
|
||||
const currentUserId = this.data.currentUserId;
|
||||
await this.start();
|
||||
|
||||
const initalSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_chat_open,
|
||||
false,
|
||||
"the value in server side should be false"
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
const newSettings = await this.env.services.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_chat_open,
|
||||
true,
|
||||
"the value in server side should be true"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - states: close from the bus', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 0,
|
||||
public: 'private',
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_chat_open: true,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
const notif = [
|
||||
["dbName", "res.partner", this.messaging.currentPartner.id],
|
||||
{
|
||||
type: "res.users_settings_changed",
|
||||
payload: {
|
||||
is_discuss_sidebar_category_chat_open: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
this.env.services.bus_service.trigger('notification', [notif]);
|
||||
});
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category chat should be open and the content should be visible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - states: open from the bus', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 0,
|
||||
public: 'private',
|
||||
});
|
||||
this.data['res.users.settings'].records.push({
|
||||
user_id: this.data.currentUserId,
|
||||
is_discuss_sidebar_category_chat_open: false,
|
||||
});
|
||||
await this.start();
|
||||
|
||||
await afterNextRender(() => {
|
||||
const notif = [
|
||||
["dbName", "res.partner", this.messaging.currentPartner.id],
|
||||
{
|
||||
type: "res.users_settings_changed",
|
||||
payload: {
|
||||
is_discuss_sidebar_category_chat_open: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
this.env.services.bus_service.trigger('notification', [notif]);
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"Category chat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - states: the active category item should be visble even if the category is closed', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
message_unread_counter: 0,
|
||||
public: 'private',
|
||||
});
|
||||
await this.start();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`
|
||||
);
|
||||
|
||||
const chat = document.querySelector(`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`);
|
||||
await afterNextRender(() => {
|
||||
chat.click();
|
||||
});
|
||||
assert.ok(chat.classList.contains('o-active'));
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_title`).click();
|
||||
});
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
'the active chat item should remain even if the category is folded'
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_DiscussSidebarMailbox[data-thread-local-id="${
|
||||
this.messaging.inbox.localId
|
||||
}"]`).click();
|
||||
});
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"inactive item should be invisible if the category is folded"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class DiscussSidebarCategoryItem extends Component {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {mail.discuss_sidebar_category_item}
|
||||
*/
|
||||
get categoryItem() {
|
||||
return this.messaging.models['mail.discuss_sidebar_category_item'].get(this.props.categoryItemLocalId);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(DiscussSidebarCategoryItem, {
|
||||
props: {
|
||||
categoryItemLocalId: String,
|
||||
},
|
||||
template: 'mail.DiscussSidebarCategoryItem',
|
||||
});
|
||||
|
||||
registerMessagingComponent(DiscussSidebarCategoryItem);
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebarCategoryItem {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: map-get($spacers, 2) 0;
|
||||
|
||||
&:hover .o_DiscussSidebarCategoryItem_commands {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_command {
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-margin;
|
||||
margin-right: $o-mail-discuss-sidebar-category-item-margin;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_commands {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_image {
|
||||
object-fit: cover;
|
||||
width: map-get($sizes, 100);
|
||||
height: map-get($sizes, 100);
|
||||
}
|
||||
|
||||
|
||||
.o_DiscussSidebarCategoryItem_imageContainer {
|
||||
position: relative;
|
||||
width: $o-mail-discuss-sidebar-category-item-avatar-size;
|
||||
height: $o-mail-discuss-sidebar-category-item-avatar-size;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_item {
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-margin;
|
||||
margin-right: $o-mail-discuss-sidebar-category-item-margin;
|
||||
|
||||
&:first-child {
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-avatar-left-margin;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_name {
|
||||
@include text-truncate();
|
||||
margin-left: 10px;
|
||||
|
||||
&.o-editable {
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_nameInput {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_threadIcon {
|
||||
@include o-position-absolute($bottom: 0, $right: 0);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebarCategoryItem {
|
||||
|
||||
.o_DiscussSidebarCategoryItem_threadIcon {
|
||||
font-size: xx-small;
|
||||
background-color: gray('100');
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.o-active {
|
||||
background-color: gray('200');
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: gray('300');
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_command {
|
||||
|
||||
&:not(:hover) {
|
||||
color: gray('600');
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_counter {
|
||||
background-color: $o-brand-primary;
|
||||
color: gray('300');
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_name {
|
||||
&.o-item-unread {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarCategoryItem_nameInput{
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.DiscussSidebarCategoryItem" owl="1">
|
||||
<div class="o_DiscussSidebarCategoryItem"
|
||||
t-att-class="{
|
||||
'o-active': categoryItem.isActive,
|
||||
'o-unread': categoryItem.isUnread,
|
||||
}" t-on-click="categoryItem.onClick" t-att-data-thread-local-id="categoryItem.channel.localId" t-att-data-thread-name="categoryItem.channel.displayName"
|
||||
>
|
||||
<div class="o_DiscussSidebarCategoryItem_item o_DiscussSidebarCategoryItem_avatar">
|
||||
<div class="o_DiscussSidebarCategoryItem_imageContainer">
|
||||
<img class="o_DiscussSidebarCategoryItem_image rounded-circle" t-att-src="categoryItem.avatarUrl" alt="Thread Image"/>
|
||||
<t t-if="categoryItem.hasThreadIcon">
|
||||
<ThreadIcon class="o_DiscussSidebarCategoryItem_threadIcon" threadLocalId="categoryItem.channel.localId"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="categoryItem.isRenaming">
|
||||
<span class="o_DiscussSidebarCategoryItem_item o_DiscussSidebarCategoryItem_name o-editable">
|
||||
<EditableText
|
||||
class="o_DiscussSidebarCategoryItem_nameInput"
|
||||
placeholder="categoryItem.channel.correspondent ? categoryItem.channel.correspondent.name : categoryItem.channel.name"
|
||||
value="categoryItem.channel.displayName"
|
||||
t-on-o-cancel="categoryItem.onCancelRenaming"
|
||||
t-on-o-clicked="categoryItem.onClickedEditableText"
|
||||
t-on-o-validate="categoryItem.onValidateEditableText"
|
||||
/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="o_DiscussSidebarCategoryItem_item o_DiscussSidebarCategoryItem_name" t-att-class="{ 'o-item-unread': categoryItem.isUnread }">
|
||||
<t t-esc="categoryItem.channel.displayName"/>
|
||||
</span>
|
||||
</t>
|
||||
<div class="o-autogrow o_DiscussSidebarCategoryItem_item"/>
|
||||
<div class="o_DiscussSidebarCategoryItem_item o_DiscussSidebarCategoryItem_commands">
|
||||
<t t-if="categoryItem.hasSettingsCommand">
|
||||
<div class="o_DiscussSidebarCategoryItem_command o_DiscussSidebarCategoryItem_commandSettings fa fa-cog" t-on-click="categoryItem.onClickCommandSettings" title="Channel settings" role="img"/>
|
||||
</t>
|
||||
<t t-if="categoryItem.hasLeaveCommand">
|
||||
<div class="o_DiscussSidebarCategoryItem_command o_DiscussSidebarCategoryItem_commandLeave fa fa-times" t-on-click="categoryItem.onClickCommandLeave" title="Leave this channel" role="img"/>
|
||||
</t>
|
||||
<t t-if="categoryItem.hasRenameCommand">
|
||||
<div class="o_DiscussSidebarCategoryItem_command o_DiscussSidebarCategoryItem_commandRename fa fa-cog" t-on-click="categoryItem.onClickCommandRename" title="Rename conversation" role="img"/>
|
||||
</t>
|
||||
<t t-if="categoryItem.hasUnpinCommand">
|
||||
<div class="o_DiscussSidebarCategoryItem_command o_DiscussSidebarCategoryItem_commandUnpin fa fa-times" t-on-click="categoryItem.onClickCommandUnpin" title="Unpin Conversation" role="img"/>
|
||||
</t>
|
||||
</div>
|
||||
<t t-if="categoryItem.counter > 0">
|
||||
<div class="o_DiscussSidebarCategoryItem_counter o_DiscussSidebarCategoryItem_item badge badge-pill">
|
||||
<t t-esc="categoryItem.counter"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
start,
|
||||
} from '@mail/utils/test_utils';
|
||||
|
||||
import { datetime_to_str } from 'web.time';
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_item', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_item_tests.js', {
|
||||
beforeEach() {
|
||||
beforeEach(this);
|
||||
|
||||
this.start = async params => {
|
||||
const { env, widget } = await start(Object.assign({}, params, {
|
||||
autoOpenDiscuss: true,
|
||||
data: this.data,
|
||||
hasDiscuss: true,
|
||||
}));
|
||||
this.env = env;
|
||||
this.widget = widget;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
afterEach(this);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('channel - avatar: should have correct avatar', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 20 });
|
||||
await this.start();
|
||||
|
||||
const channelItem = document.querySelector(`
|
||||
.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]
|
||||
`);
|
||||
assert.strictEqual(
|
||||
channelItem.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_image`).length,
|
||||
1,
|
||||
"channel should have an avatar"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
channelItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
|
||||
'/web/image/mail.channel/20/image_128',
|
||||
'should link to the correct picture source'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - avatar: should have correct avatar', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 15, name: "Demo", im_status: 'offline' });
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
members: [this.data.currentPartnerId, 15],
|
||||
public: 'private',
|
||||
});
|
||||
await this.start();
|
||||
|
||||
const chatItem = document.querySelector(`
|
||||
.o_DiscussSidebarCategoryItem[data-thread-local-id="${
|
||||
this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]
|
||||
`);
|
||||
assert.strictEqual(
|
||||
chatItem.querySelectorAll(`:scope .o_DiscussSidebarCategoryItem_image`).length,
|
||||
1,
|
||||
"chat should have an avatar"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
chatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
|
||||
'/web/image/res.partner/15/avatar_128',
|
||||
'should link to the partner avatar'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('chat - sorting: should be sorted by last activity time', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'chat',
|
||||
id: 10,
|
||||
public: 'private',
|
||||
last_interest_dt: datetime_to_str(new Date(2021, 0, 1)), // less recent one
|
||||
}, {
|
||||
channel_type: 'chat',
|
||||
id: 20,
|
||||
public: 'private',
|
||||
last_interest_dt: datetime_to_str(new Date(2021, 0, 2)), // more recent one
|
||||
});
|
||||
await this.start();
|
||||
|
||||
const chat10 = this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 10,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
const chat20 = this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 20,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
const initialChats = document.querySelectorAll('.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategoryItem');
|
||||
assert.strictEqual(
|
||||
initialChats.length,
|
||||
2,
|
||||
"should have 2 livechat items"
|
||||
);
|
||||
assert.strictEqual(
|
||||
initialChats[0].dataset.threadLocalId,
|
||||
chat20.localId,
|
||||
"first livechat should be the one with the more recent last activity time"
|
||||
);
|
||||
assert.strictEqual(
|
||||
initialChats[1].dataset.threadLocalId,
|
||||
chat10.localId,
|
||||
"second chat should be the one with the less recent last activity time"
|
||||
);
|
||||
|
||||
// post a new message on the last channel
|
||||
await afterNextRender(() => initialChats[1].click());
|
||||
await afterNextRender(() => document.execCommand('insertText', false, "Blabla"));
|
||||
await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
|
||||
const newChats = document.querySelectorAll('.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategoryItem');
|
||||
assert.strictEqual(
|
||||
newChats.length,
|
||||
2,
|
||||
"should have 2 chat items"
|
||||
);
|
||||
assert.strictEqual(
|
||||
newChats[0].dataset.threadLocalId,
|
||||
chat10.localId,
|
||||
"first chat should be the one with the more recent last activity time"
|
||||
);
|
||||
assert.strictEqual(
|
||||
newChats[1].dataset.threadLocalId,
|
||||
chat20.localId,
|
||||
"second chat should be the one with the less recent last activity time"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,183 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { isEventHandled } from '@mail/utils/utils';
|
||||
|
||||
import Dialog from 'web.Dialog';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class DiscussSidebarItem extends Component {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get the counter of this discuss item, which is based on the thread type.
|
||||
*
|
||||
* @returns {integer}
|
||||
*/
|
||||
get counter() {
|
||||
if (this.thread.model === 'mail.box') {
|
||||
return this.thread.counter;
|
||||
} else if (this.thread.channel_type === 'channel') {
|
||||
return this.thread.message_needaction_counter;
|
||||
} else if (this.thread.channel_type === 'chat') {
|
||||
return this.thread.localMessageUnreadCounter;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {mail.discuss}
|
||||
*/
|
||||
get discuss() {
|
||||
return this.messaging && this.messaging.discuss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasUnpin() {
|
||||
return this.thread.channel_type === 'chat';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {mail.thread}
|
||||
*/
|
||||
get thread() {
|
||||
return this.messaging && this.messaging.models['mail.thread'].get(this.props.threadLocalId);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_askAdminConfirmation() {
|
||||
return new Promise(resolve => {
|
||||
Dialog.confirm(this,
|
||||
this.env._t("You are the administrator of this channel. Are you sure you want to leave?"),
|
||||
{
|
||||
buttons: [
|
||||
{
|
||||
text: this.env._t("Leave"),
|
||||
classes: 'btn-primary',
|
||||
close: true,
|
||||
click: resolve
|
||||
},
|
||||
{
|
||||
text: this.env._t("Discard"),
|
||||
close: true
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onCancelRenaming(ev) {
|
||||
this.discuss.cancelThreadRenaming(this.thread);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClick(ev) {
|
||||
if (isEventHandled(ev, 'EditableText.click')) {
|
||||
return;
|
||||
}
|
||||
this.thread.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop propagation to prevent selecting this item.
|
||||
*
|
||||
* @private
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
_onClickedEditableText(ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
async _onClickLeave(ev) {
|
||||
ev.stopPropagation();
|
||||
if (this.thread.creator === this.messaging.currentUser) {
|
||||
await this._askAdminConfirmation();
|
||||
}
|
||||
this.thread.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickRename(ev) {
|
||||
ev.stopPropagation();
|
||||
this.discuss.setThreadRenaming(this.thread);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickSettings(ev) {
|
||||
ev.stopPropagation();
|
||||
return this.env.bus.trigger('do-action', {
|
||||
action: {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: this.thread.model,
|
||||
res_id: this.thread.id,
|
||||
views: [[false, 'form']],
|
||||
target: 'current'
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickUnpin(ev) {
|
||||
ev.stopPropagation();
|
||||
this.thread.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CustomEvent} ev
|
||||
* @param {Object} ev.detail
|
||||
* @param {string} ev.detail.newName
|
||||
*/
|
||||
_onValidateEditableText(ev) {
|
||||
ev.stopPropagation();
|
||||
this.discuss.onValidateEditableText(this.thread, ev.detail.newName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(DiscussSidebarItem, {
|
||||
props: {
|
||||
threadLocalId: String,
|
||||
},
|
||||
template: 'mail.DiscussSidebarItem',
|
||||
});
|
||||
|
||||
registerMessagingComponent(DiscussSidebarItem);
|
||||
@@ -1,109 +0,0 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebarItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: map-get($spacers, 1) 0;
|
||||
|
||||
&:hover .o_DiscussSidebarItem_commands {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_activeIndicator {
|
||||
width: $o-mail-discuss-sidebar-active-indicator-width;
|
||||
align-self: stretch;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_command {
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_commands {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_item {
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0px;
|
||||
margin-right: $o-mail-discuss-sidebar-active-indicator-margin-right;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.o-editable {
|
||||
margin-left: $o-mail-discuss-sidebar-active-indicator-width + $o-mail-discuss-sidebar-active-indicator-margin-right;
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_nameInput {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebarItem {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: darken(gray('900'), 5%);
|
||||
}
|
||||
|
||||
&.o-starred-box {
|
||||
.o_DiscussSidebarItem_counter {
|
||||
border-color: gray('600');
|
||||
background-color: gray('600');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_activeIndicator.o-item-active {
|
||||
background-color: $o-brand-primary;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_command:not(:hover) {
|
||||
color: gray('600');
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_counter {
|
||||
background-color: $o-brand-primary;
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_name {
|
||||
|
||||
&.o-item-unread {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarItem_nameInput {
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.DiscussSidebarItem" owl="1">
|
||||
<div class="o_DiscussSidebarItem"
|
||||
t-att-class="{
|
||||
'o-active': thread and discuss.thread === thread,
|
||||
'o-starred-box': thread and thread === messaging.starred,
|
||||
'o-unread': thread and thread.localMessageUnreadCounter > 0,
|
||||
}" t-on-click="_onClick" t-att-data-thread-local-id="thread ? thread.localId : undefined" t-att-data-thread-name="thread ? thread.displayName : undefined"
|
||||
>
|
||||
<t t-if="thread">
|
||||
<div class=" o_DiscussSidebarItem_activeIndicator o_DiscussSidebarItem_item" t-att-class="{ 'o-item-active': discuss.thread === thread }"/>
|
||||
<ThreadIcon class="o_DiscussSidebarItem_item" threadLocalId="thread.localId"/>
|
||||
<t t-if="thread.channel_type === 'chat' and discuss.renamingThreads.includes(thread)">
|
||||
<div class="o_DiscussSidebarItem_item o_DiscussSidebarItem_name o-editable">
|
||||
<EditableText
|
||||
class="o_DiscussSidebarItem_nameInput"
|
||||
placeholder="thread.correspondent ? thread.correspondent.nameOrDisplayName : thread.name"
|
||||
value="thread.displayName"
|
||||
t-on-o-cancel="_onCancelRenaming"
|
||||
t-on-o-clicked="_onClickedEditableText"
|
||||
t-on-o-validate="_onValidateEditableText"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="o_DiscussSidebarItem_item o_DiscussSidebarItem_name" t-att-class="{ 'o-item-unread': thread.localMessageUnreadCounter > 0 }">
|
||||
<t t-esc="thread.displayName"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="o-autogrow o_DiscussSidebarItem_item"/>
|
||||
<t t-if="thread.model !== 'mail.box'">
|
||||
<div class="o_DiscussSidebarItem_commands o_DiscussSidebarItem_item">
|
||||
<t t-if="thread.channel_type === 'channel'">
|
||||
<div class="fa fa-cog o_DiscussSidebarItem_command o_DiscussSidebarItem_commandSettings" t-on-click="_onClickSettings" title="Channel settings" role="img"/>
|
||||
<t t-if="!thread.message_needaction_counter and !thread.group_based_subscription">
|
||||
<div class="o_DiscussSidebarItem_command o_DiscussSidebarItem_commandLeave fa fa-times" t-on-click="_onClickLeave" title="Leave this channel" role="img"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="thread.channel_type === 'chat'">
|
||||
<div class="o_DiscussSidebarItem_command o_DiscussSidebarItem_commandRename fa fa-cog" t-on-click="_onClickRename" title="Rename conversation" role="img"/>
|
||||
</t>
|
||||
<t t-if="hasUnpin()">
|
||||
<t t-if="!thread.localMessageUnreadCounter">
|
||||
<div class="fa fa-times o_DiscussSidebarItem_command o_DiscussSidebarItem_commandUnpin" t-on-click="_onClickUnpin" title="Unpin conversation" role="img"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="counter > 0">
|
||||
<div class="o_DiscussSidebarItem_counter o_DiscussSidebarItem_item badge badge-pill">
|
||||
<t t-esc="counter"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,29 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class DiscussSidebarMailbox extends Component {
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {mail.thread}
|
||||
*/
|
||||
get mailbox() {
|
||||
return this.messaging.models['mail.thread'].get(this.props.threadLocalId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(DiscussSidebarMailbox, {
|
||||
props: {
|
||||
threadLocalId: String,
|
||||
},
|
||||
template: 'mail.DiscussSidebarMailbox',
|
||||
});
|
||||
|
||||
registerMessagingComponent(DiscussSidebarMailbox);
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebarMailbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: map-get($spacers, 1) 0;
|
||||
}
|
||||
|
||||
|
||||
.o_DiscussSidebarMailbox_item {
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-margin;
|
||||
margin-right: $o-mail-discuss-sidebar-category-item-margin;
|
||||
|
||||
&:first-child {
|
||||
margin-left: $o-mail-discuss-sidebar-category-item-avatar-left-margin;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: $o-mail-discuss-sidebar-scrollbar-width;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.o_DiscussSidebarMailbox_name {
|
||||
@include text-truncate();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_DiscussSidebarMailbox {
|
||||
cursor: pointer;
|
||||
|
||||
&.o-active {
|
||||
background-color: gray('200')
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: gray('300');
|
||||
}
|
||||
|
||||
&.o-starred-box {
|
||||
.o_DiscussSidebarMailbox_counter {
|
||||
border-color: gray('400');
|
||||
background-color: gray('400');
|
||||
color: gray('300');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_DiscussSidebarMailbox_counter {
|
||||
background-color: $o-brand-primary;
|
||||
color: gray('300');
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.DiscussSidebarMailbox" owl="1">
|
||||
<div class="o_DiscussSidebarMailbox"
|
||||
t-att-class="{
|
||||
'o-active': mailbox and mailbox === messaging.discuss.thread,
|
||||
'o-starred-box': mailbox and mailbox === messaging.starred,
|
||||
}" t-on-click="mailbox.onClick" t-att-data-thread-local-id="mailbox.localId" t-att-data-thread-name="mailbox.displayName"
|
||||
>
|
||||
<ThreadIcon class="o_DiscussSidebarMailbox_item" threadLocalId="mailbox.localId"/>
|
||||
<div class="o_DiscussSidebarMailbox_item o_DiscussSidebarMailbox_name">
|
||||
<t t-esc="mailbox.displayName"/>
|
||||
</div>
|
||||
<div class="o-autogrow o_DiscussSidebarMailbox_item"/>
|
||||
<t t-if="mailbox.counter > 0">
|
||||
<div class="o_DiscussSidebarMailbox_counter o_DiscussSidebarMailbox_item badge badge-pill">
|
||||
<t t-esc="mailbox.counter"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,18 +1,18 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {_t} from 'web.core';
|
||||
import {Markup} from 'web.utils';
|
||||
import { _t } from 'web.core';
|
||||
import { Markup } from 'web.utils';
|
||||
import tour from 'web_tour.tour';
|
||||
|
||||
tour.register('mail_tour', {
|
||||
url: "/web#action=mail.widgets.discuss",
|
||||
sequence: 80,
|
||||
}, [{
|
||||
trigger: '.o_DiscussSidebar_groupChannel .o_DiscussSidebar_groupHeaderItemAdd',
|
||||
trigger: '.o_DiscussSidebar_categoryChannel .o_DiscussSidebarCategory_commandAdd',
|
||||
content: Markup(_t("<p>Channels make it easy to organize information across different topics and groups.</p> <p>Try to <b>create your first channel</b> (e.g. sales, marketing, product XYZ, after work party, etc).</p>")),
|
||||
position: 'bottom',
|
||||
}, {
|
||||
trigger: '.o_DiscussSidebar_itemNewInput',
|
||||
trigger: '.o_DiscussSidebarCategory_itemNewInput',
|
||||
content: Markup(_t("<p>Create a channel here.</p>")),
|
||||
position: 'bottom',
|
||||
auto: true,
|
||||
@@ -21,7 +21,7 @@ tour.register('mail_tour', {
|
||||
actions.text("SomeChannel_" + t, this.$anchor);
|
||||
},
|
||||
}, {
|
||||
trigger: ".o_DiscussSidebar_newChannelAutocompleteSuggestions",
|
||||
trigger: ".o_DiscussSidebarCategory_newChannelAutocompleteSuggestions",
|
||||
content: Markup(_t("<p>Create a public or private channel.</p>")),
|
||||
position: 'right',
|
||||
run() {
|
||||
@@ -45,11 +45,11 @@ tour.register('mail_tour', {
|
||||
content: Markup(_t("Messages can be <b>starred</b> to remind you to check back later.")),
|
||||
position: "bottom",
|
||||
}, {
|
||||
trigger: '.o_DiscussSidebarItem.o-starred-box',
|
||||
trigger: '.o_DiscussSidebarMailbox.o-starred-box',
|
||||
content: _t("Once a message has been starred, you can come back and review it at any time here."),
|
||||
position: "bottom",
|
||||
}, {
|
||||
trigger: '.o_DiscussSidebar_groupChat .o_DiscussSidebar_groupHeaderItemAdd',
|
||||
trigger: '.o_DiscussSidebar_categoryChat .o_DiscussSidebarCategory_commandAdd',
|
||||
content: Markup(_t("<p><b>Chat with coworkers</b> in real-time using direct messages.</p><p><i>You might need to invite users from the Settings app first.</i></p>")),
|
||||
position: 'bottom',
|
||||
}]);
|
||||
|
||||
@@ -246,6 +246,18 @@ function factory(dependencies) {
|
||||
return `${thread.model}_${thread.id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
onInputQuickSearch(value) {
|
||||
// Opens all categories only when user starts to search from empty search value.
|
||||
if (!this.sidebarQuickSearchValue) {
|
||||
this.categoryChat.open();
|
||||
this.categoryChannel.open();
|
||||
}
|
||||
this.update({ sidebarQuickSearchValue: value });
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Private
|
||||
//----------------------------------------------------------------------
|
||||
@@ -383,6 +395,18 @@ function factory(dependencies) {
|
||||
compute: '_computeAddingChannelValue',
|
||||
default: "",
|
||||
}),
|
||||
/**
|
||||
* Discuss sidebar category for `channel` type channel threads.
|
||||
*/
|
||||
categoryChannel: one2one('mail.discuss_sidebar_category', {
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Discuss sidebar category for `chat` type channel threads.
|
||||
*/
|
||||
categoryChat: one2one('mail.discuss_sidebar_category', {
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Determines whether `this.thread` should be displayed.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerNewModel } from '@mail/model/model_core';
|
||||
import { attr, many2one, one2many, one2one } from '@mail/model/model_field';
|
||||
import { clear, insertAndReplace, link, replace } from '@mail/model/model_field_command';
|
||||
import { OnChange } from '@mail/model/model_onchange';
|
||||
|
||||
function factory(dependencies) {
|
||||
class DiscussSidebarCategory extends dependencies['mail.model'] {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_created() {
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onHideAddingItem = this.onHideAddingItem.bind(this);
|
||||
this.onAddItemAutocompleteSelect = this.onAddItemAutocompleteSelect.bind(this);
|
||||
this.onAddItemAutocompleteSource = this.onAddItemAutocompleteSource.bind(this);
|
||||
this.onClickCommandAdd = this.onClickCommandAdd.bind(this);
|
||||
this.onClickCommandView = this.onClickCommandView.bind(this);
|
||||
return super._created();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Performs the `set_res_users_settings` RPC on `res.users.settings`.
|
||||
*
|
||||
* @static
|
||||
* @param {Object} resUsersSettings
|
||||
* @param {boolean} [resUsersSettings.is_category_channel_open]
|
||||
* @param {boolean} [resUsersSettings.is_category_chat_open]
|
||||
*/
|
||||
static async performRpcSetResUsersSettings(resUsersSettings) {
|
||||
return this.env.services.rpc(
|
||||
{
|
||||
model: 'res.users.settings',
|
||||
method: 'set_res_users_settings',
|
||||
args: [[this.messaging.currentUser.resUsersSettingsId]],
|
||||
kwargs: {
|
||||
new_settings: resUsersSettings,
|
||||
},
|
||||
},
|
||||
{ shadow: true },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the category and notity server to change the state
|
||||
*/
|
||||
async close() {
|
||||
this.update({ isPendingOpen: false });
|
||||
await this.messaging.models['mail.discuss_sidebar_category'].performRpcSetResUsersSettings({
|
||||
[this.serverStateKey]: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the category and notity server to change the state
|
||||
*/
|
||||
async open() {
|
||||
this.update({ isPendingOpen: true });
|
||||
await this.messaging.models['mail.discuss_sidebar_category'].performRpcSetResUsersSettings({
|
||||
[this.serverStateKey]: true,
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {mail.discuss_sidebar_category_item | undefined}
|
||||
*/
|
||||
_computeActiveItem() {
|
||||
const thread = this.messaging.discuss.thread;
|
||||
if (thread && this.supportedChannelTypes.includes(thread.channel_type)) {
|
||||
return insertAndReplace({ channelId: thread.id });
|
||||
}
|
||||
return clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {mail.discuss_sidebar_category_item[]}
|
||||
*/
|
||||
_computeCategoryItems() {
|
||||
let channels = this.selectedSortedChannels;
|
||||
const searchValue = this.messaging.discuss.sidebarQuickSearchValue;
|
||||
if (searchValue) {
|
||||
const qsVal = searchValue.toLowerCase();
|
||||
channels = channels.filter(t => {
|
||||
const nameVal = t.displayName.toLowerCase();
|
||||
return nameVal.includes(qsVal);
|
||||
});
|
||||
}
|
||||
return insertAndReplace(channels.map(c => ({ channelId: c.id })));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {integer}
|
||||
*/
|
||||
_computeCounter() {
|
||||
switch (this.counterComputeMethod) {
|
||||
case 'needaction':
|
||||
return this.selectedChannels.filter(thread => thread.message_needaction_counter > 0).length;
|
||||
case 'unread':
|
||||
return this.selectedChannels.filter(thread => thread.localMessageUnreadCounter > 0).length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeIsOpen() {
|
||||
return this.isPendingOpen !== undefined ? this.isPendingOpen : this.isServerOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
_computeSelectedChannels() {
|
||||
return replace(this.messaging.models['mail.thread'].all(thread =>
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.isPinned &&
|
||||
this.supportedChannelTypes.includes(thread.channel_type))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
_computeSelectedSortedChannels() {
|
||||
switch (this.sortComputeMethod) {
|
||||
case 'name':
|
||||
return replace(this._sortByDisplayName());
|
||||
case 'last_action':
|
||||
return replace(this._sortByLastInterestDateTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts `selectedChannels` by `displayName` in
|
||||
* case-insensitive alphabetical order.
|
||||
*
|
||||
* @private
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
_sortByDisplayName() {
|
||||
return this.selectedChannels.sort((t1, t2) => {
|
||||
if (t1.displayName && !t2.displayName) {
|
||||
return -1;
|
||||
} else if (!t1.displayName && t2.displayName) {
|
||||
return 1;
|
||||
} else if (t1.displayName && t2.displayName && t1.displayName !== t2.displayName) {
|
||||
return t1.displayName.toLowerCase() < t2.displayName.toLowerCase() ? -1 : 1;
|
||||
} else {
|
||||
return t1.id - t2.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts `selectedChannels` by `lastInterestDateTime`.
|
||||
* The most recent one will come first.
|
||||
*
|
||||
* @private
|
||||
* @returns {mail.thread[]}
|
||||
*/
|
||||
_sortByLastInterestDateTime() {
|
||||
return this.selectedChannels.sort((t1, t2) => {
|
||||
if (t1.lastInterestDateTime && !t2.lastInterestDateTime) {
|
||||
return -1;
|
||||
} else if (!t1.lastInterestDateTime && t2.lastInterestDateTime) {
|
||||
return 1;
|
||||
} else if (t1.lastInterestDateTime && t2.lastInterestDateTime && t1.lastInterestDateTime !== t2.lastInterestDateTime) {
|
||||
return t2.lastInterestDateTime - t1.lastInterestDateTime;
|
||||
} else {
|
||||
return t2.id - t1.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Changes the category open states when clicked.
|
||||
*/
|
||||
async onClick() {
|
||||
if (this.isOpen) {
|
||||
await this.close();
|
||||
} else {
|
||||
await this.open();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
onHideAddingItem(ev) {
|
||||
ev.stopPropagation();
|
||||
this.update({ isAddingItem: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} ev
|
||||
* @param {Object} ui
|
||||
* @param {Object} ui.item
|
||||
* @param {integer} ui.item.id
|
||||
*/
|
||||
onAddItemAutocompleteSelect(ev, ui) {
|
||||
switch (this.autocompleteMethod) {
|
||||
case 'channel':
|
||||
this.messaging.discuss.handleAddChannelAutocompleteSelect(ev, ui);
|
||||
break;
|
||||
case 'chat':
|
||||
this.messaging.discuss.handleAddChatAutocompleteSelect(ev, ui);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} req
|
||||
* @param {string} req.term
|
||||
* @param {function} res
|
||||
*/
|
||||
onAddItemAutocompleteSource(req, res) {
|
||||
switch (this.autocompleteMethod) {
|
||||
case 'channel':
|
||||
this.messaging.discuss.handleAddChannelAutocompleteSource(req, res);
|
||||
break;
|
||||
case 'chat':
|
||||
this.messaging.discuss.handleAddChatAutocompleteSource(req, res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickCommandAdd(ev) {
|
||||
ev.stopPropagation();
|
||||
this.update({ isAddingItem: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the public channels window when view command is clicked.
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickCommandView(ev) {
|
||||
ev.stopPropagation();
|
||||
return this.env.bus.trigger('do-action', {
|
||||
action: {
|
||||
name: this.env._t("Public Channels"),
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'mail.channel',
|
||||
views: [[false, 'kanban'], [false, 'form']],
|
||||
domain: [['public', '!=', 'private']],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles change of open state coming from the server. Useful to
|
||||
* clear pending state once server acknowledged the change.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onIsServerOpenChanged() {
|
||||
if (this.isServerOpen === this.isPendingOpen) {
|
||||
this.update({ isPendingOpen: clear() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DiscussSidebarCategory.fields = {
|
||||
/**
|
||||
* The category item which is active and belongs
|
||||
* to the category.
|
||||
*/
|
||||
activeItem: one2one('mail.discuss_sidebar_category_item', {
|
||||
compute: '_computeActiveItem',
|
||||
}),
|
||||
/**
|
||||
* Determines how the autocomplete of this category should behave.
|
||||
* Must be one of: 'channel', 'chat'.
|
||||
*/
|
||||
autocompleteMethod: attr(),
|
||||
/**
|
||||
* The title text in UI for command `add`
|
||||
*/
|
||||
commandAddTitleText: attr(),
|
||||
/**
|
||||
* The sorted category items which belong to the category.
|
||||
* These items are also filtered by `sidebarQuickSearchValue`.
|
||||
*/
|
||||
categoryItems: one2many('mail.discuss_sidebar_category_item', {
|
||||
compute: '_computeCategoryItems',
|
||||
}),
|
||||
/**
|
||||
* The total amount unread/action-needed threads in the category.
|
||||
*/
|
||||
counter: attr({
|
||||
compute: '_computeCounter',
|
||||
}),
|
||||
/**
|
||||
* Determines how the counter of this category should be computed.
|
||||
* Supported methods: 'needaction', 'unread'.
|
||||
*/
|
||||
counterComputeMethod: attr({
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Display name of the category.
|
||||
*/
|
||||
name: attr(),
|
||||
/**
|
||||
* Boolean that determines whether this category has a 'add' command.
|
||||
*/
|
||||
hasAddCommand: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Boolean that determines whether this category has a 'view' command.
|
||||
*/
|
||||
hasViewCommand: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Boolean that determines whether discuss is adding a new category item.
|
||||
*/
|
||||
isAddingItem: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Boolean that determines whether this category is open.
|
||||
*/
|
||||
isOpen: attr({
|
||||
compute: '_computeIsOpen',
|
||||
}),
|
||||
/**
|
||||
* Boolean that determines if there is a pending open state change,
|
||||
* which is requested by the client but not yet confirmed by the server.
|
||||
*
|
||||
* This field can be updated to immediately change the open state on the
|
||||
* interface and to notify the server of the new state.
|
||||
*/
|
||||
isPendingOpen: attr(),
|
||||
/**
|
||||
* Boolean that determines the last open state known by the server.
|
||||
*/
|
||||
isServerOpen: attr(),
|
||||
/**
|
||||
* The placeholder text used when a new item is being added in UI.
|
||||
*/
|
||||
newItemPlaceholderText: attr(),
|
||||
/**
|
||||
* Channels which belong to the category,
|
||||
*/
|
||||
selectedChannels: one2many('mail.thread', {
|
||||
compute: '_computeSelectedChannels',
|
||||
}),
|
||||
/**
|
||||
* Channels which belongs to the category,
|
||||
* and sorted based on the `supported_channel_type`.
|
||||
*/
|
||||
selectedSortedChannels: one2many('mail.thread', {
|
||||
compute: '_computeSelectedSortedChannels',
|
||||
}),
|
||||
/**
|
||||
* The key used in the server side for the category state
|
||||
*/
|
||||
serverStateKey: attr(),
|
||||
/**
|
||||
* Determines the sorting method of channels in this category.
|
||||
* Must be one of: 'name', 'last_action'.
|
||||
*/
|
||||
sortComputeMethod: attr({
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Channel type which is supported by the category.
|
||||
*/
|
||||
supportedChannelTypes: attr({
|
||||
required: true,
|
||||
readonly: true,
|
||||
}),
|
||||
};
|
||||
DiscussSidebarCategory.onChanges = [
|
||||
new OnChange({
|
||||
dependencies: ['isServerOpen'],
|
||||
methodName: ['_onIsServerOpenChanged'],
|
||||
}),
|
||||
];
|
||||
DiscussSidebarCategory.modelName = 'mail.discuss_sidebar_category';
|
||||
|
||||
return DiscussSidebarCategory;
|
||||
}
|
||||
|
||||
registerNewModel('mail.discuss_sidebar_category', factory);
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerNewModel } from '@mail/model/model_core';
|
||||
import { attr, one2one } from '@mail/model/model_field';
|
||||
import { clear, link } from '@mail/model/model_field_command';
|
||||
import { isEventHandled } from '@mail/utils/utils';
|
||||
|
||||
import Dialog from 'web.Dialog';
|
||||
|
||||
function factory(dependencies) {
|
||||
class DiscussSidebarCategoryItem extends dependencies['mail.model'] {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_created() {
|
||||
this.onCancelRenaming = this.onCancelRenaming.bind(this);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onClickCommandLeave = this.onClickCommandLeave.bind(this);
|
||||
this.onClickCommandRename = this.onClickCommandRename.bind(this);
|
||||
this.onClickCommandSettings = this.onClickCommandSettings.bind(this);
|
||||
this.onClickCommandUnpin = this.onClickCommandUnpin.bind(this);
|
||||
this.onClickedEditableText = this.onClickedEditableText.bind(this);
|
||||
this.onValidateEditableText = this.onValidateEditableText.bind(this);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
static _createRecordLocalId(data) {
|
||||
return `${this.modelName}_${data.channelId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeAvatarUrl() {
|
||||
switch (this.channelType) {
|
||||
case 'channel':
|
||||
return `/web/image/mail.channel/${this.channelId}/image_128`;
|
||||
case 'chat':
|
||||
return this.channel.correspondent.avatarUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {mail.thread}
|
||||
*/
|
||||
_computeChannel() {
|
||||
return link(this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: this.channelId,
|
||||
model: 'mail.channel',
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {integer}
|
||||
*/
|
||||
_computeCounter() {
|
||||
switch (this.channelType) {
|
||||
case 'channel':
|
||||
return this.channel.message_needaction_counter;
|
||||
case 'chat':
|
||||
return this.channel.localMessageUnreadCounter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeHasLeaveCommand() {
|
||||
return this.channelType === 'channel' &&
|
||||
!this.channel.message_needaction_counter &&
|
||||
!this.channel.group_based_subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeHasRenameCommand() {
|
||||
return this.channelType === 'chat';
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeHasSettingsCommand() {
|
||||
return this.channelType === 'channel';
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeHasUnpinCommand() {
|
||||
return this.channelType === 'chat' && !this.channel.localMessageUnreadCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeIsActive() {
|
||||
return this.messaging.discuss && this.channel === this.messaging.discuss.thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeIsRenaming() {
|
||||
return this.messaging.discuss && this.hasRenameCommand && this.messaging.discuss.renamingThreads.includes(this.channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeIsUnread() {
|
||||
if (!this.channel) {
|
||||
return clear();
|
||||
}
|
||||
return this.channel.localMessageUnreadCounter > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeHasThreadIcon() {
|
||||
switch (this.channelType) {
|
||||
case 'channel':
|
||||
case 'chat':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onCancelRenaming(ev) {
|
||||
this.messaging.discuss.cancelThreadRenaming(this.channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClick(ev) {
|
||||
if (isEventHandled(ev, 'EditableText.click')) {
|
||||
return;
|
||||
}
|
||||
this.channel.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
async onClickCommandLeave(ev) {
|
||||
ev.stopPropagation();
|
||||
if (this.channel.creator === this.messaging.currentUser) {
|
||||
await this._askAdminConfirmation();
|
||||
}
|
||||
this.channel.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickCommandRename(ev) {
|
||||
ev.stopPropagation();
|
||||
this.messaging.discuss.setThreadRenaming(this.channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to channel form page when `settings` command is clicked.
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickCommandSettings(ev) {
|
||||
ev.stopPropagation();
|
||||
return this.env.bus.trigger('do-action', {
|
||||
action: {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: this.channel.model,
|
||||
res_id: this.channel.id,
|
||||
views: [[false, 'form']],
|
||||
target: 'current',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickCommandUnpin(ev) {
|
||||
ev.stopPropagation();
|
||||
this.channel.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop propagation to prevent selecting this item.
|
||||
*
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
onClickedEditableText(ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CustomEvent} ev
|
||||
* @param {Object} ev.detail
|
||||
* @param {string} ev.detail.newName
|
||||
*/
|
||||
onValidateEditableText(ev) {
|
||||
ev.stopPropagation();
|
||||
this.messaging.discuss.onValidateEditableText(this.channel, ev.detail.newName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_askAdminConfirmation() {
|
||||
return new Promise(resolve => {
|
||||
Dialog.confirm(this,
|
||||
this.env._t("You are the administrator of this channel. Are you sure you want to leave?"),
|
||||
{
|
||||
buttons: [
|
||||
{
|
||||
text: this.env._t("Leave"),
|
||||
classes: 'btn-primary',
|
||||
close: true,
|
||||
click: resolve,
|
||||
},
|
||||
{
|
||||
text: this.env._t("Discard"),
|
||||
close: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
DiscussSidebarCategoryItem.fields = {
|
||||
/**
|
||||
* Image URL for the related channel thread.
|
||||
*/
|
||||
avatarUrl: attr({
|
||||
compute: '_computeAvatarUrl',
|
||||
}),
|
||||
/**
|
||||
* Amount of unread/action-needed messages
|
||||
*/
|
||||
counter: attr({
|
||||
compute: '_computeCounter',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether the item has a "leave" command
|
||||
*/
|
||||
hasLeaveCommand: attr({
|
||||
compute: '_computeHasLeaveCommand',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether the item has a "rename" command.
|
||||
*/
|
||||
hasRenameCommand: attr({
|
||||
compute: '_computeHasRenameCommand',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether the item has a "settings" command.
|
||||
*/
|
||||
hasSettingsCommand: attr({
|
||||
compute: '_computeHasSettingsCommand',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether ThreadIcon will be displayed in UI.
|
||||
*/
|
||||
hasThreadIcon: attr({
|
||||
compute: '_computeHasThreadIcon',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether the item has a "unpin" command.
|
||||
*/
|
||||
hasUnpinCommand: attr({
|
||||
compute: '_computeHasUnpinCommand',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether the item is currently active in discuss.
|
||||
*/
|
||||
isActive: attr({
|
||||
compute: '_computeIsActive',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether the item is currently being renamed.
|
||||
*/
|
||||
isRenaming: attr({
|
||||
compute: '_computeIsRenaming',
|
||||
}),
|
||||
/**
|
||||
* Boolean determines whether the item has any unread messages.
|
||||
*/
|
||||
isUnread: attr({
|
||||
compute: '_computeIsUnread',
|
||||
}),
|
||||
/**
|
||||
* The related channel thread.
|
||||
*/
|
||||
channel: one2one('mail.thread', {
|
||||
compute: '_computeChannel',
|
||||
}),
|
||||
/**
|
||||
* Id of the related channel thread.
|
||||
*/
|
||||
channelId: attr({
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* Type of the related channel thread.
|
||||
*/
|
||||
channelType: attr({
|
||||
related: 'channel.channel_type',
|
||||
}),
|
||||
|
||||
};
|
||||
|
||||
DiscussSidebarCategoryItem.modelName = 'mail.discuss_sidebar_category_item';
|
||||
|
||||
return DiscussSidebarCategoryItem;
|
||||
}
|
||||
|
||||
registerNewModel('mail.discuss_sidebar_category_item', factory);
|
||||
@@ -4,7 +4,6 @@ import { registerNewModel } from '@mail/model/model_core';
|
||||
import { executeGracefully } from '@mail/utils/utils';
|
||||
import { create, link, insert } from '@mail/model/model_field_command';
|
||||
|
||||
|
||||
function factory(dependencies) {
|
||||
|
||||
class MessagingInitializer extends dependencies['mail.model'] {
|
||||
@@ -67,6 +66,7 @@ function factory(dependencies) {
|
||||
* @param {Object[]} param0.channels
|
||||
* @param {Object} param0.current_partner
|
||||
* @param {integer} param0.current_user_id
|
||||
* @param {Object} param0.current_user_settings
|
||||
* @param {Object} [param0.mail_failures={}]
|
||||
* @param {integer} [param0.needaction_inbox_counter=0]
|
||||
* @param {Object} param0.partner_root
|
||||
@@ -79,6 +79,7 @@ function factory(dependencies) {
|
||||
commands = [],
|
||||
current_partner,
|
||||
current_user_id,
|
||||
current_user_settings,
|
||||
mail_failures = {},
|
||||
menu_id,
|
||||
needaction_inbox_counter = 0,
|
||||
@@ -101,6 +102,8 @@ function factory(dependencies) {
|
||||
needaction_inbox_counter,
|
||||
starred_counter,
|
||||
});
|
||||
// init mail user settings
|
||||
this._initResUsersSettings(current_user_settings);
|
||||
// various suggestions in no particular order
|
||||
this._initCannedResponses(shortcodes);
|
||||
this._initCommands();
|
||||
@@ -206,6 +209,43 @@ function factory(dependencies) {
|
||||
this.messaging.notificationGroupManager.computeGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} resUsersSettings
|
||||
* @param {integer} resUsersSettings.id
|
||||
* @param {boolean} resUsersSettings.is_discuss_sidebar_category_channel_open
|
||||
* @param {boolean} resUsersSettings.is_discuss_sidebar_category_chat_open
|
||||
*/
|
||||
_initResUsersSettings({ id, is_discuss_sidebar_category_channel_open, is_discuss_sidebar_category_chat_open }) {
|
||||
this.messaging.currentUser.update({ resUsersSettingsId: id });
|
||||
this.messaging.discuss.update({
|
||||
categoryChannel: create({
|
||||
autocompleteMethod: 'channel',
|
||||
commandAddTitleText: this.env._t("Add or join a channel"),
|
||||
counterComputeMethod: 'needaction',
|
||||
hasAddCommand: true,
|
||||
hasViewCommand: true,
|
||||
isServerOpen: is_discuss_sidebar_category_channel_open,
|
||||
name: this.env._t("Channels"),
|
||||
newItemPlaceholderText: this.env._t("Find or create a channel..."),
|
||||
serverStateKey: 'is_discuss_sidebar_category_channel_open',
|
||||
sortComputeMethod: 'name',
|
||||
supportedChannelTypes: ['channel'],
|
||||
}),
|
||||
categoryChat: create({
|
||||
autocompleteMethod: 'chat',
|
||||
commandAddTitleText: this.env._t("Start a conversation"),
|
||||
counterComputeMethod: 'unread',
|
||||
hasAddCommand: true,
|
||||
isServerOpen: is_discuss_sidebar_category_chat_open,
|
||||
name: this.env._t("Direct Messages"),
|
||||
newItemPlaceholderText: this.env._t("Find or start a conversation..."),
|
||||
serverStateKey: 'is_discuss_sidebar_category_chat_open',
|
||||
sortComputeMethod: 'last_action',
|
||||
supportedChannelTypes: ['chat'],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} current_partner
|
||||
|
||||
+43
@@ -4,6 +4,8 @@ import { registerNewModel } from '@mail/model/model_core';
|
||||
import { decrement, increment, insert, link } from '@mail/model/model_field_command';
|
||||
import { htmlToTextContentInline } from '@mail/js/utils';
|
||||
|
||||
import { str_to_datetime } from 'web.time';
|
||||
|
||||
const PREVIEW_MSG_MAX_SIZE = 350; // optimal for native English speakers
|
||||
|
||||
function factory(dependencies) {
|
||||
@@ -81,8 +83,12 @@ function factory(dependencies) {
|
||||
return this._handleNotificationChannelDescriptionChanged(message.payload);
|
||||
case 'mail.channel_joined':
|
||||
return this._handleNotificationChannelJoined(message.payload);
|
||||
case 'mail.channel_last_interest_dt_changed':
|
||||
return this._handleNotificationChannelLastInterestDateTimeChanged(message.payload);
|
||||
case 'mail.channel_rename':
|
||||
return this._handleNotificationChannelRenamed(message.payload);
|
||||
case 'res.users_settings_changed':
|
||||
return this._handleNotificationResUsersSettings(message.payload);
|
||||
}
|
||||
}
|
||||
const [, model, id] = channel;
|
||||
@@ -226,6 +232,24 @@ function factory(dependencies) {
|
||||
channel.update({ name });
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} payload
|
||||
* @param {integer} payload.id
|
||||
* @param {string} payload.last_interest_dt
|
||||
*/
|
||||
_handleNotificationChannelLastInterestDateTimeChanged({ id, last_interest_dt }) {
|
||||
const channel = this.messaging.models['mail.thread'].findFromIdentifyingData({
|
||||
id: id,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
if (channel) {
|
||||
channel.update({
|
||||
lastInterestDateTime: str_to_datetime(last_interest_dt),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {integer} channelId
|
||||
@@ -386,6 +410,25 @@ function factory(dependencies) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {object} settings
|
||||
* @param {boolean} [settings.is_discuss_sidebar_category_channel_open]
|
||||
* @param {boolean} [settings.is_discuss_sidebar_category_chat_open]
|
||||
*/
|
||||
_handleNotificationResUsersSettings(settings) {
|
||||
if ('is_discuss_sidebar_category_channel_open' in settings) {
|
||||
this.messaging.discuss.categoryChannel.update({
|
||||
isServerOpen: settings.is_discuss_sidebar_category_channel_open,
|
||||
});
|
||||
}
|
||||
if ('is_discuss_sidebar_category_chat_open' in settings) {
|
||||
this.messaging.discuss.categoryChat.update({
|
||||
isServerOpen: settings.is_discuss_sidebar_category_chat_open,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} data
|
||||
|
||||
@@ -9,6 +9,8 @@ import Timer from '@mail/utils/timer/timer';
|
||||
import { cleanSearchTerm } from '@mail/utils/utils';
|
||||
import * as mailUtils from '@mail/js/utils';
|
||||
|
||||
import { str_to_datetime } from 'web.time';
|
||||
|
||||
function factory(dependencies) {
|
||||
|
||||
class Thread extends dependencies['mail.model'] {
|
||||
@@ -99,6 +101,14 @@ function factory(dependencies) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_created() {
|
||||
this.onClick = this.onClick.bind(this);
|
||||
return super._created();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -185,6 +195,9 @@ function factory(dependencies) {
|
||||
if ('is_pinned' in data) {
|
||||
data2.isServerPinned = data.is_pinned;
|
||||
}
|
||||
if ('last_interest_dt' in data && data.last_interest_dt) {
|
||||
data2.lastInterestDateTime = str_to_datetime(data.last_interest_dt);
|
||||
}
|
||||
if ('last_message' in data && data.last_message) {
|
||||
const messageData = this.messaging.models['mail.message'].convertData({
|
||||
id: data.last_message.id,
|
||||
@@ -1640,6 +1653,13 @@ function factory(dependencies) {
|
||||
// Handlers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Event handler for clicking thread in discuss app.
|
||||
*/
|
||||
async onClick() {
|
||||
await this.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -1840,6 +1860,12 @@ function factory(dependencies) {
|
||||
isTemporary: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* States the date and time of the last interesting event that happened
|
||||
* in this channel for this partner. This includes: creating, joining,
|
||||
* pinning, and new message posted. It is contained as a Date object.
|
||||
*/
|
||||
lastInterestDateTime: attr(),
|
||||
lastCurrentPartnerMessageSeenByEveryone: many2one('mail.message', {
|
||||
compute: '_computeLastCurrentPartnerMessageSeenByEveryone',
|
||||
}),
|
||||
|
||||
@@ -224,6 +224,10 @@ function factory(dependencies) {
|
||||
partner: one2one('mail.partner', {
|
||||
inverse: 'user',
|
||||
}),
|
||||
/**
|
||||
* Id of this user's res.users.settings record.
|
||||
*/
|
||||
resUsersSettingsId: attr(),
|
||||
};
|
||||
|
||||
User.modelName = 'mail.user';
|
||||
|
||||
@@ -19,6 +19,10 @@ $o-mail-chat-window-header-height-mobile: 46px !default;
|
||||
$o-mail-discuss-message-highlight-indicator-width: 4px !default;
|
||||
$o-mail-discuss-sidebar-active-indicator-margin-right: 10px !default;
|
||||
$o-mail-discuss-sidebar-active-indicator-width: 3px !default;
|
||||
$o-mail-discuss-sidebar-category-title-icon-size: 15px !default;
|
||||
$o-mail-discuss-sidebar-category-item-margin: 3px !default;
|
||||
$o-mail-discuss-sidebar-category-item-avatar-left-margin: $o-mail-discuss-sidebar-category-title-icon-size + $o-mail-discuss-sidebar-category-item-margin !default;
|
||||
$o-mail-discuss-sidebar-category-item-avatar-size: 30px !default;
|
||||
$o-mail-discuss-sidebar-scrollbar-width: 15px !default;
|
||||
|
||||
$o-mail-message-sidebar-width: 50px;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { datetime_to_str } from 'web.time';
|
||||
|
||||
/**
|
||||
* Allows to generate mocked models that will be used by the mocked server.
|
||||
* This is defined as a class to allow patches by dependent modules and a new
|
||||
@@ -76,6 +78,10 @@ export class MockModels {
|
||||
is_minimized: { string: "isMinimized", type: "boolean", default: false },
|
||||
// In python this belongs to mail.channel.partner. Here for simplicity.
|
||||
is_pinned: { string: "isPinned", type: "boolean", default: true },
|
||||
// In python this belongs to mail.channel.partner. Here for simplicity.
|
||||
last_interest_dt: { string: "Last Interest", type: "datetime", default() {
|
||||
return datetime_to_str(new Date());
|
||||
} },
|
||||
members: { string: "Members", type: 'many2many', relation: 'res.partner', default() { return [this.currentPartnerId]; } },
|
||||
message_unread_counter: { string: "# unread messages", type: 'integer' },
|
||||
name: { string: "Name", type: "char", required: true },
|
||||
@@ -179,6 +185,14 @@ export class MockModels {
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
'res.users.settings': {
|
||||
fields: {
|
||||
is_discuss_sidebar_category_channel_open: { string: "Is Discuss Sidebar Category Channel Open?", type: 'boolean', default: true },
|
||||
is_discuss_sidebar_category_chat_open: { string: "Is Discuss Sidebar Category Chat Open?", type: 'boolean', default: true },
|
||||
user_id: { string: "User Id", type: 'many2one', relation: 'res.users' },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
'res.country': {
|
||||
fields: {
|
||||
code: { string: "Code", type: 'char' },
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { nextAnimationFrame } from '@mail/utils/test_utils';
|
||||
|
||||
import MockServer from 'web.MockServer';
|
||||
import { datetime_to_str } from 'web.time';
|
||||
|
||||
MockServer.include({
|
||||
/**
|
||||
@@ -246,6 +247,15 @@ MockServer.include({
|
||||
if (args.model === 'mail.message' && args.method === 'unstar_all') {
|
||||
return this._mockMailMessageUnstarAll();
|
||||
}
|
||||
if (args.model === 'res.users.settings' && args.method === '_find_or_create_for_user') {
|
||||
const user_id = args.args[0][0];
|
||||
return this._mockResUsersSettings_FindOrCreateForUser(user_id);
|
||||
}
|
||||
if (args.model === 'res.users.settings' && args.method === 'set_res_users_settings') {
|
||||
const id = args.args[0][0];
|
||||
const newSettings = args.kwargs.new_settings;
|
||||
return this._mockResUsersSettingsSetResUsersSettings(id, newSettings);
|
||||
}
|
||||
// res.partner methods
|
||||
if (args.method === 'get_mention_suggestions') {
|
||||
if (args.model === 'mail.channel') {
|
||||
@@ -710,6 +720,7 @@ MockServer.include({
|
||||
channel_type: 'chat',
|
||||
is_minimized: true,
|
||||
is_pinned: true,
|
||||
last_interest_dt: datetime_to_str(new Date()),
|
||||
members: [[6, 0, partners_to]],
|
||||
name: partners.map(partner => partner.name).join(", "),
|
||||
public: 'private',
|
||||
@@ -965,11 +976,14 @@ MockServer.include({
|
||||
_mockMailChannelMessagePost(id, kwargs, context) {
|
||||
const message_type = kwargs.message_type || 'notification';
|
||||
const channel = this._getRecords('mail.channel', [['id', '=', id]])[0];
|
||||
if (channel.channel_type !== 'channel' && !channel.is_pinned) {
|
||||
if (channel.channel_type !== 'channel') {
|
||||
// channel.partner not handled here for simplicity
|
||||
this._mockWrite('mail.channel', [
|
||||
[channel.id],
|
||||
{ is_pinned: true },
|
||||
{
|
||||
last_interest_dt: datetime_to_str(new Date()),
|
||||
is_pinned: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
const messageId = this._mockMailThreadMessagePost(
|
||||
@@ -1570,6 +1584,22 @@ MockServer.include({
|
||||
const channels = this._getRecords('mail.channel', [['id', '=', message.res_id]]);
|
||||
for (const channel of channels) {
|
||||
notifications.push([[false, 'mail.channel', channel.id], messageFormat]);
|
||||
|
||||
// notify update of last_interest_dt
|
||||
const now = datetime_to_str(new Date());
|
||||
this._mockWrite('mail.channel',
|
||||
[channel.id],
|
||||
{ last_interest_dt: now },
|
||||
);
|
||||
for (const member of channel.members) {
|
||||
notifications.push([[false, 'res.partner', member], {
|
||||
type: 'mail.channel_last_interest_dt_changed',
|
||||
payload: {
|
||||
id: channel.id,
|
||||
last_interest_dt: now,
|
||||
},
|
||||
}]);
|
||||
}
|
||||
}
|
||||
this._widget.call('bus_service', 'trigger', 'notification', notifications);
|
||||
},
|
||||
@@ -1623,6 +1653,50 @@ MockServer.include({
|
||||
...directMessages,
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulates `_find_or_create_for_user` on `res.users.settings`.
|
||||
*
|
||||
* @param {Object} user
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockResUsersSettings_FindOrCreateForUser(user_id) {
|
||||
let settings = this._getRecords('res.users.settings', [['user_id', '=', user_id]])[0];
|
||||
if (!settings) {
|
||||
const settingsId = this._mockCreate('res.users.settings', { user_id: user_id });
|
||||
settings = this._getRecords('res.users.settings', [['id', '=', settingsId]])[0];
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulates `set_res_users_settings` on `res.users.settings`.
|
||||
*
|
||||
* @param {integer} id
|
||||
* @param {Object} newSettings
|
||||
*/
|
||||
_mockResUsersSettingsSetResUsersSettings(id, newSettings) {
|
||||
const oldSettings = this._getRecords('res.users.settings', [['id', '=', id]])[0];
|
||||
const changedSettings = {};
|
||||
for (const setting in newSettings) {
|
||||
if (setting in oldSettings && newSettings[setting] !== oldSettings[setting]) {
|
||||
changedSettings[setting] = newSettings[setting];
|
||||
}
|
||||
}
|
||||
this._mockWrite('res.users.settings', [
|
||||
[id],
|
||||
changedSettings,
|
||||
]);
|
||||
const notification = [
|
||||
['dbName', 'res.partner', this.currentPartnerId],
|
||||
{
|
||||
type: 'res.users_settings_changed',
|
||||
payload: changedSettings,
|
||||
},
|
||||
];
|
||||
this._widget.call('bus_service', 'trigger', 'notification', [notification]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulates `get_mention_suggestions` on `res.partner`.
|
||||
*
|
||||
@@ -1806,6 +1880,7 @@ MockServer.include({
|
||||
channels: this._mockMailChannelChannelInfo(this._mockResPartner_GetChannelsAsMember(user.partner_id).map(channel => channel.id)),
|
||||
current_partner: this._mockResPartnerMailPartnerFormat(user.partner_id).get(user.partner_id),
|
||||
current_user_id: this.currentUserId,
|
||||
current_user_settings: this._mockResUsersSettings_FindOrCreateForUser(user.id),
|
||||
mail_failures: this._mockResPartner_MessageFetchFailed(user.partner_id),
|
||||
menu_id: false, // not useful in QUnit tests
|
||||
needaction_inbox_counter: this._mockResPartner_GetNeedactionCount(user.partner_id),
|
||||
|
||||
@@ -7,4 +7,5 @@ from . import test_mail_full_composer
|
||||
from . import test_mail_render
|
||||
from . import test_mail_tools
|
||||
from . import test_res_partner
|
||||
from . import test_res_users_settings
|
||||
from . import test_update_notification
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests import tagged, Form
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
|
||||
@tagged('mail_channel')
|
||||
@@ -196,6 +200,27 @@ class TestChannelInternals(MailCommon):
|
||||
self.assertEqual(channel.message_partner_ids, self.env['res.partner'])
|
||||
self.assertEqual(channel.channel_partner_ids, self.env['res.partner'])
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_channel_chat_message_post_should_update_last_interest_dt(self):
|
||||
channel_info = self.env['mail.channel'].with_user(self.user_admin).channel_get((self.partner_employee | self.user_admin.partner_id).ids)
|
||||
chat = self.env['mail.channel'].with_user(self.user_admin).browse(channel_info['id'])
|
||||
post_time = fields.Datetime.now()
|
||||
# Mocks the return value of field.Datetime.now(),
|
||||
# so we can see if the `last_interest_dt` is updated correctly
|
||||
with patch.object(fields.Datetime, 'now', lambda: post_time):
|
||||
chat.message_post(body="Test", message_type='comment', subtype_xmlid='mail.mt_comment')
|
||||
channel_partner_employee = self.env['mail.channel.partner'].search([
|
||||
('partner_id', '=', self.partner_employee.id),
|
||||
('channel_id', '=', chat.id),
|
||||
])
|
||||
channel_partner_admin = self.env['mail.channel.partner'].search([
|
||||
('partner_id', '=', self.partner_admin.id),
|
||||
('channel_id', '=', chat.id),
|
||||
])
|
||||
self.assertEqual(channel_partner_employee.last_interest_dt, post_time)
|
||||
self.assertEqual(channel_partner_admin.last_interest_dt, post_time)
|
||||
|
||||
@users('employee')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_channel_recipients_channel(self):
|
||||
@@ -290,6 +315,19 @@ class TestChannelInternals(MailCommon):
|
||||
same_solo_channel_info = self.env['mail.channel'].channel_get(partners_to=self.partner_employee_nomail.ids)
|
||||
self.assertEqual(same_solo_channel_info['id'], solo_channel_info['id'])
|
||||
|
||||
# `channel_get` will pin the channel by default and thus last interest will be updated.
|
||||
@users('employee')
|
||||
def test_channel_info_get_should_update_last_interest_dt(self):
|
||||
# create the channel via `channel_get`
|
||||
self.env['mail.channel'].channel_get(partners_to=self.partner_admin.ids)
|
||||
|
||||
retrieve_time = datetime(2021, 1, 1, 0, 0)
|
||||
with patch.object(fields.Datetime, 'now', lambda: retrieve_time):
|
||||
# `last_interest_dt` should be updated again when `channel_get` is called
|
||||
# because `channel_pin` is called.
|
||||
channel_info = self.env['mail.channel'].channel_get(partners_to=self.partner_admin.ids)
|
||||
self.assertEqual(channel_info['last_interest_dt'], retrieve_time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
|
||||
@users('employee')
|
||||
def test_channel_info_seen(self):
|
||||
""" In case of concurrent channel_seen RPC, ensure the oldest call has no effect. """
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestResUsersSettings(MailCommon):
|
||||
|
||||
@users('employee')
|
||||
def test_find_or_create_for_user_should_create_record_if_not_existing(self):
|
||||
settings = self.user_employee.res_users_settings_ids
|
||||
self.assertFalse(settings, "no records should exist")
|
||||
|
||||
self.env['res.users.settings']._find_or_create_for_user(self.user_employee)
|
||||
settings = self.user_employee.res_users_settings_ids
|
||||
self.assertTrue(settings, "a record should be created after _find_or_create_for_user is called")
|
||||
|
||||
@users('employee')
|
||||
def test_find_or_create_for_user_should_return_correct_res_users_settings(self):
|
||||
settings = self.env['res.users.settings'].create({
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
result = self.env['res.users.settings']._find_or_create_for_user(self.user_employee)
|
||||
self.assertEqual(result, settings, "Correct mail user settings should be returned")
|
||||
|
||||
@users('employee')
|
||||
def test_set_res_users_settings_should_send_notification_on_bus(self):
|
||||
settings = self.env['res.users.settings'].create({
|
||||
'is_discuss_sidebar_category_channel_open': False,
|
||||
'is_discuss_sidebar_category_chat_open': False,
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
|
||||
with self.assertBus(
|
||||
[(self.cr.dbname, 'res.partner', self.partner_employee.id)],
|
||||
[{
|
||||
"type": "res.users_settings_changed",
|
||||
"payload": {"is_discuss_sidebar_category_chat_open": True}
|
||||
}]):
|
||||
settings.set_res_users_settings({'is_discuss_sidebar_category_chat_open': True})
|
||||
|
||||
@users('employee')
|
||||
def test_set_res_users_settings_should_set_settings_properly(self):
|
||||
settings = self.env['res.users.settings'].create({
|
||||
'is_discuss_sidebar_category_channel_open': False,
|
||||
'is_discuss_sidebar_category_chat_open': False,
|
||||
'user_id': self.user_employee.id,
|
||||
})
|
||||
settings.set_res_users_settings({'is_discuss_sidebar_category_chat_open': True})
|
||||
self.assertEqual(
|
||||
settings.is_discuss_sidebar_category_chat_open,
|
||||
True,
|
||||
"category state should be updated correctly"
|
||||
)
|
||||
@@ -93,6 +93,12 @@
|
||||
parent="mail.mail_menu_technical"
|
||||
sequence="22"/>
|
||||
|
||||
<menuitem id="res_users_settings_menu"
|
||||
name="User Settings"
|
||||
action="res_users_settings_action"
|
||||
parent="mail.mail_menu_technical"
|
||||
sequence="50"/>
|
||||
|
||||
<!--
|
||||
This menuitem will be activated by integrations modules (like github, twitter, ...). It
|
||||
is a hook to ease other modules to plug into mail.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="res_users_settings_view_tree" model="ir.ui.view">
|
||||
<field name="name">res.users.settings.tree</field>
|
||||
<field name="model">res.users.settings</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="User Settings">
|
||||
<field name="id"/>
|
||||
<field name="user_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_users_settings_action" model="ir.actions.act_window">
|
||||
<field name="name">User Settings</field>
|
||||
<field name="res_model">res.users.settings</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -6,7 +6,7 @@ from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests.common import users, tagged, TransactionCase, warmup
|
||||
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
|
||||
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
@@ -59,6 +59,7 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
def test_init_messaging(self):
|
||||
"""Test performance of `_init_messaging`."""
|
||||
channel_general = self.env.ref('mail.channel_all_employees') # Unfortunately #general cannot be deleted. Assertions below assume data from a fresh db with demo.
|
||||
self.env['mail.channel'].search([('id', '!=', channel_general.id)]).unlink()
|
||||
user_root = self.env.ref('base.user_root')
|
||||
# create public channels
|
||||
channel_public_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='public 1', privacy='public')['id'])
|
||||
@@ -95,8 +96,8 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
|
||||
self.users[0].flush()
|
||||
self.users[0].invalidate_cache()
|
||||
with self.assertQueryCount(emp=33):
|
||||
init_messaging = self.users[0]._init_messaging()
|
||||
with self.assertQueryCount(emp=48):
|
||||
init_messaging = self.users[0].with_user(self.users[0])._init_messaging()
|
||||
|
||||
self.assertEqual(init_messaging, {
|
||||
'needaction_inbox_counter': 1,
|
||||
@@ -111,9 +112,10 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'id': channel_general.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_general.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_general._channel_last_message_ids()),
|
||||
'message_needaction_counter': 0,
|
||||
'message_unread_counter': 3,
|
||||
'message_unread_counter': 5,
|
||||
'name': 'general',
|
||||
'public': 'groups',
|
||||
'seen_message_id': False,
|
||||
@@ -123,100 +125,127 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
{
|
||||
'channel_type': 'channel',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_public_1.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_public_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_public_1._channel_last_message_ids()),
|
||||
'message_needaction_counter': 0,
|
||||
'message_needaction_counter': 1,
|
||||
'message_unread_counter': 0,
|
||||
'name': 'public 1',
|
||||
'public': 'public',
|
||||
'seen_message_id': next(res['message_id'] for res in channel_public_1._channel_last_message_ids()),
|
||||
'state': 'open',
|
||||
'uuid': channel_public_1.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'channel',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_public_2.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_public_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_public_2._channel_last_message_ids()),
|
||||
'message_needaction_counter': 0,
|
||||
'message_unread_counter': 0,
|
||||
'name': 'public 2',
|
||||
'public': 'public',
|
||||
'seen_message_id': next(res['message_id'] for res in channel_public_2._channel_last_message_ids()),
|
||||
'state': 'open',
|
||||
'uuid': channel_public_2.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'channel',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_group_1.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_group_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_group_1._channel_last_message_ids()),
|
||||
'message_needaction_counter': 0,
|
||||
'message_unread_counter': 0,
|
||||
'name': 'group 1',
|
||||
'public': 'groups',
|
||||
'seen_message_id': next(res['message_id'] for res in channel_group_1._channel_last_message_ids()),
|
||||
'state': 'open',
|
||||
'uuid': channel_group_1.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'channel',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_group_2.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_group_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_group_2._channel_last_message_ids()),
|
||||
'message_needaction_counter': 0,
|
||||
'message_unread_counter': 0,
|
||||
'name': 'group 2',
|
||||
'public': 'groups',
|
||||
'seen_message_id': next(res['message_id'] for res in channel_group_2._channel_last_message_ids()),
|
||||
'state': 'open',
|
||||
'uuid': channel_group_2.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'channel',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_private_1.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_private_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_private_1._channel_last_message_ids()),
|
||||
'message_needaction_counter': 0,
|
||||
'message_unread_counter': 0,
|
||||
'name': 'private 1',
|
||||
'public': 'private',
|
||||
'seen_message_id': next(res['message_id'] for res in channel_private_1._channel_last_message_ids()),
|
||||
'state': 'open',
|
||||
'uuid': channel_private_1.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'channel',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_private_2.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_private_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_private_2._channel_last_message_ids()),
|
||||
'message_needaction_counter': 0,
|
||||
'message_unread_counter': 0,
|
||||
'name': 'private 2',
|
||||
'public': 'private',
|
||||
'seen_message_id': next(res['message_id'] for res in channel_private_2._channel_last_message_ids()),
|
||||
'state': 'open',
|
||||
'uuid': channel_private_2.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'chat',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_dm_1.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_dm_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': False,
|
||||
'members': [
|
||||
{
|
||||
@@ -260,16 +289,20 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'seen_message_id': False,
|
||||
},
|
||||
],
|
||||
'seen_message_id': False,
|
||||
'state': 'open',
|
||||
'uuid': channel_dm_1.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'chat',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_dm_2.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_dm_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': False,
|
||||
'members': [
|
||||
{
|
||||
@@ -313,16 +346,20 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'seen_message_id': False,
|
||||
},
|
||||
],
|
||||
'seen_message_id': False,
|
||||
'state': 'open',
|
||||
'uuid': channel_dm_2.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'chat',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_dm_3.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_dm_3.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': False,
|
||||
'members': [
|
||||
{
|
||||
@@ -366,16 +403,20 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'seen_message_id': False,
|
||||
},
|
||||
],
|
||||
'seen_message_id': False,
|
||||
'state': 'open',
|
||||
'uuid': channel_dm_3.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'chat',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_dm_4.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_dm_4.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': False,
|
||||
'members': [
|
||||
{
|
||||
@@ -419,16 +460,20 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'seen_message_id': False,
|
||||
},
|
||||
],
|
||||
'seen_message_id': False,
|
||||
'state': 'open',
|
||||
'uuid': channel_dm_4.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'livechat',
|
||||
'create_uid': self.env.user.id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_livechat_1.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_livechat_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_livechat_1._channel_last_message_ids()),
|
||||
'livechat_visitor': {
|
||||
'country': False,
|
||||
@@ -478,16 +523,20 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'seen_message_id': next(res['message_id'] for res in channel_livechat_1._channel_last_message_ids()),
|
||||
},
|
||||
],
|
||||
'seen_message_id': False,
|
||||
'state': 'open',
|
||||
'uuid': channel_livechat_1.uuid,
|
||||
},
|
||||
{
|
||||
'channel_type': 'livechat',
|
||||
'create_uid': self.env.ref('base.public_user').id,
|
||||
'custom_channel_name': False,
|
||||
'description': False,
|
||||
'group_based_subscription': False,
|
||||
'id': channel_livechat_2.id,
|
||||
'is_minimized': False,
|
||||
'is_pinned': True,
|
||||
'last_interest_dt': channel_livechat_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'last_message_id': next(res['message_id'] for res in channel_livechat_2._channel_last_message_ids()),
|
||||
'livechat_visitor': {
|
||||
'country': (self.env.ref('base.be').id, 'Belgium'),
|
||||
@@ -537,6 +586,7 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'seen_message_id': False,
|
||||
},
|
||||
],
|
||||
'seen_message_id': False,
|
||||
'state': 'open',
|
||||
'uuid': channel_livechat_2.uuid,
|
||||
},
|
||||
@@ -591,4 +641,11 @@ class TestDiscussFullPerformance(TransactionCase):
|
||||
'user_id': self.users[0].id,
|
||||
},
|
||||
'current_user_id': self.users[0].id,
|
||||
'current_user_settings': {
|
||||
'id': self.env['res.users.settings']._find_or_create_for_user(self.users[0]).id,
|
||||
'is_discuss_sidebar_category_channel_open': True,
|
||||
'is_discuss_sidebar_category_chat_open': True,
|
||||
'is_discuss_sidebar_category_livechat_open': True,
|
||||
'user_id': (self.users[0].id, 'Ernest Employee'),
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user