[IMP] im_livechat: go to oldest unread thread with tab
This PR adds the possibility to go the oldest unread livechat thread when pressing tab on an empty composer. part of task-3332872 closes odoo/odoo#125922 Signed-off-by: Matthieu Stockbauer (tsm) <tsm@odoo.com>
This commit is contained in:
@@ -8,4 +8,31 @@ patch(Composer.prototype, "im_livechat", {
|
||||
get allowUpload() {
|
||||
return this.thread?.type !== "livechat" && this._super();
|
||||
},
|
||||
|
||||
onKeydown(ev) {
|
||||
this._super(ev);
|
||||
if (
|
||||
ev.key === "Tab" &&
|
||||
this.thread?.type === "livechat" &&
|
||||
!this.props.composer.textInputContent
|
||||
) {
|
||||
const threadChanged = this.threadService.goToOldestUnreadLivechatThread();
|
||||
if (threadChanged) {
|
||||
// prevent chat window from switching to the next thread: as
|
||||
// we want to go to the oldest unread thread, not the next
|
||||
// one.
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
displayNextLivechatHint() {
|
||||
return (
|
||||
this.thread?.type === "livechat" &&
|
||||
!this.env.inChatWindow &&
|
||||
Object.values(this.store.discuss.livechat.threads).some((localId) => {
|
||||
return localId !== this.thread.localId && this.store.threads[localId].isUnread;
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="im_livechat.Composer" t-inherit="mail.Composer" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//Typing" position="replace">
|
||||
<div class="d-flex justify-content-between">
|
||||
<t>$0</t>
|
||||
<span t-if="displayNextLivechatHint()" class="text-muted fst-italic form-text">Tab to next livechat</span>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -63,7 +63,38 @@ patch(ThreadService.prototype, "im_livechat", {
|
||||
this.store.discuss.livechat.threads.sort((localId_1, localId_2) => {
|
||||
const thread1 = this.store.threads[localId_1];
|
||||
const thread2 = this.store.threads[localId_2];
|
||||
return thread2.lastInterestDateTime.ts - thread1.lastInterestDateTime.ts;
|
||||
return thread2.lastInterestDateTime?.ts - thread1.lastInterestDateTime?.ts;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} Whether the livechat thread changed.
|
||||
*/
|
||||
goToOldestUnreadLivechatThread() {
|
||||
const oldestUnreadThread =
|
||||
this.store.threads[
|
||||
Object.values(this.store.discuss.livechat.threads)
|
||||
.filter((localId) => this.store.threads[localId].isUnread)
|
||||
.sort(
|
||||
(localId_1, localId_2) =>
|
||||
this.store.threads[localId_1].lastInterestDateTime?.ts -
|
||||
this.store.threads[localId_2].lastInterestDateTime?.ts
|
||||
)[0]
|
||||
];
|
||||
if (!oldestUnreadThread) {
|
||||
return false;
|
||||
}
|
||||
if (this.store.discuss.isActive) {
|
||||
this.setDiscussThread(oldestUnreadThread);
|
||||
return true;
|
||||
}
|
||||
const chatWindow = this.chatWindowService.insert({ thread: oldestUnreadThread });
|
||||
if (chatWindow.hidden) {
|
||||
this.chatWindowService.makeVisible(chatWindow);
|
||||
} else if (chatWindow.folded) {
|
||||
this.chatWindowService.toggleFold(chatWindow);
|
||||
}
|
||||
this.chatWindowService.focus(chatWindow);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { startServer } from "@bus/../tests/helpers/mock_python_environment";
|
||||
|
||||
import { Command } from "@mail/../tests/helpers/command";
|
||||
import { nextTick, triggerHotkey } from "@web/../tests/helpers/utils";
|
||||
import { patchUiSize } from "@mail/../tests/helpers/patch_ui_size";
|
||||
|
||||
import { afterNextRender, insertText, start } from "@mail/../tests/helpers/test_utils";
|
||||
|
||||
QUnit.module("go to oldest unread livechat");
|
||||
|
||||
QUnit.test("tab on discuss composer goes to oldest unread livechat", async (assert) => {
|
||||
const pyEnv = await startServer();
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: pyEnv.currentPartnerId }),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
}),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 13",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
}),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 3",
|
||||
},
|
||||
]);
|
||||
pyEnv["mail.message"].create([
|
||||
{
|
||||
author_id: pyEnv.publicPartnerId,
|
||||
body: "Hello",
|
||||
model: "discuss.channel",
|
||||
res_id: channelIds[1],
|
||||
},
|
||||
{
|
||||
author_id: pyEnv.publicPartnerId,
|
||||
body: "Hello",
|
||||
model: "discuss.channel",
|
||||
res_id: channelIds[2],
|
||||
},
|
||||
]);
|
||||
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelIds[0]);
|
||||
|
||||
assert.containsOnce($, ".o-mail-DiscussCategoryItem.o-active:contains(Visitor 11)");
|
||||
assert.containsOnce(
|
||||
$,
|
||||
".o-mail-Composer-footer:contains(Tab to next livechat)"
|
||||
);
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(".o-mail-Composer-input").focus();
|
||||
triggerHotkey("Tab");
|
||||
});
|
||||
assert.containsOnce($, ".o-mail-DiscussCategoryItem.o-active:contains(Visitor 13)");
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(".o-mail-Composer-input").focus();
|
||||
triggerHotkey("Tab");
|
||||
});
|
||||
assert.containsOnce($, ".o-mail-DiscussCategoryItem.o-active:contains(Visitor 12)");
|
||||
});
|
||||
|
||||
QUnit.test("switching to folded chat window unfolds it", async (assert) => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
fold_state: "open",
|
||||
is_minimized: true,
|
||||
}),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
fold_state: "folded",
|
||||
is_minimized: true,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
}),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
assert.containsOnce(
|
||||
$,
|
||||
".o-mail-ChatWindow.o-folded .o-mail-ChatWindow-header:contains(Visitor 12)"
|
||||
);
|
||||
await afterNextRender(() => {
|
||||
$(".o-mail-ChatWindow:contains(Visitor 11) .o-mail-Composer-input").trigger("focus");
|
||||
triggerHotkey("Tab");
|
||||
});
|
||||
assert.containsOnce(
|
||||
$,
|
||||
".o-mail-ChatWindow:not(.o-folded) .o-mail-ChatWindow-header:contains(Visitor 12)"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
$(".o-mail-ChatWindow:contains(Visitor 12) .o-mail-Composer-input")[0]
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("switching to hidden chat window unhides it", async (assert) => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
is_minimized: true,
|
||||
}),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
is_minimized: true,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
}),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: pyEnv.currentPartnerId, is_minimized: true }),
|
||||
],
|
||||
},
|
||||
]);
|
||||
patchUiSize({ width: 900 }); // enough for 2 chat windows
|
||||
await start();
|
||||
assert.containsNone(
|
||||
$,
|
||||
".o-mail-ChatWindow.o-folded .o-mail-ChatWindow-header:contains(Visitor 12)"
|
||||
);
|
||||
await afterNextRender(() => {
|
||||
$(".o-mail-ChatWindow:contains(Visitor 11) .o-mail-Composer-input").trigger("focus");
|
||||
triggerHotkey("Tab");
|
||||
});
|
||||
assert.containsOnce(
|
||||
$,
|
||||
".o-mail-ChatWindow:not(.o-folded) .o-mail-ChatWindow-header:contains(Visitor 12)"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.activeElement,
|
||||
$(".o-mail-ChatWindow:contains(Visitor 12) .o-mail-Composer-input")[0]
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("tab on composer doesn't switch thread if user is typing", async (assert) => {
|
||||
const pyEnv = await startServer();
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: pyEnv.currentPartnerId }),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
}),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
]);
|
||||
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelIds[0]);
|
||||
await insertText(".o-mail-Composer-input", "Hello, ");
|
||||
triggerHotkey("Tab");
|
||||
await nextTick();
|
||||
assert.containsOnce($, ".o-mail-DiscussCategoryItem.o-active:contains(Visitor 11)");
|
||||
});
|
||||
|
||||
QUnit.test("tab on composer doesn't switch thread if no unread thread", async (assert) => {
|
||||
const pyEnv = await startServer();
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: pyEnv.currentPartnerId }),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: pyEnv.currentPartnerId }),
|
||||
Command.create({ partner_id: pyEnv.publicPartnerId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
]);
|
||||
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelIds[0]);
|
||||
document.querySelector(".o-mail-Composer-input").focus();
|
||||
triggerHotkey("Tab");
|
||||
await nextTick();
|
||||
assert.containsOnce($, ".o-mail-DiscussCategoryItem.o-active:contains(Visitor 11)");
|
||||
});
|
||||
Reference in New Issue
Block a user