diff --git a/addons/mail/static/src/composer/composer.js b/addons/mail/static/src/composer/composer.js index 0cca26d5663..4e107c2d594 100644 --- a/addons/mail/static/src/composer/composer.js +++ b/addons/mail/static/src/composer/composer.js @@ -100,6 +100,7 @@ export class Composer extends Component { }, }); this.suggestion = this.store.user ? useSuggestion() : undefined; + this.markEventHandled = markEventHandled; if (this.props.dropzoneRef && this.allowUpload) { useDropzone( this.props.dropzoneRef, diff --git a/addons/mail/static/src/composer/composer.xml b/addons/mail/static/src/composer/composer.xml index c3707c915f1..b48e4290a4c 100644 --- a/addons/mail/static/src/composer/composer.xml +++ b/addons/mail/static/src/composer/composer.xml @@ -41,6 +41,7 @@ t-on-input="onInput" t-on-focusin="onFocusin" t-on-focusout="() => this.props.composer.isFocused = false" + t-on-click="(ev) => markEventHandled(ev, 'composer.onClickTextarea')" t-on-paste="onPaste" t-model="props.composer.textInputContent" t-att-placeholder="placeholder" diff --git a/addons/mail/static/src/composer/navigable_list.js b/addons/mail/static/src/composer/navigable_list.js index d91a523a69b..178cfe320a2 100644 --- a/addons/mail/static/src/composer/navigable_list.js +++ b/addons/mail/static/src/composer/navigable_list.js @@ -13,7 +13,8 @@ import { useRef, useState, } from "@odoo/owl"; -import { markEventHandled } from "../utils/misc"; +import { markEventHandled, isEventHandled } from "../utils/misc"; +import { onExternalClick } from "@mail/utils/hooks"; export class NavigableList extends Component { static components = { ImStatus }; @@ -41,6 +42,17 @@ export class NavigableList extends Component { this.hotkeysToRemove = []; useExternalListener(window, "keydown", this.onKeydown, true); + onExternalClick("root", async (ev) => { + // Let event be handled by bubbling handlers first. + await new Promise(setTimeout); + if ( + isEventHandled(ev, "composer.onClickTextarea") || + isEventHandled(ev, "channelSelector.onClickInput") + ) { + return; + } + this.close(); + }); // position and size usePosition(() => this.props.anchorRef, { popper: "root", @@ -72,6 +84,9 @@ export class NavigableList extends Component { } open() { + if (this.state.isLoading) { + return; + } this.load().then(() => { this.state.open = true; this.navigate("first"); diff --git a/addons/mail/static/src/composer/navigable_list.xml b/addons/mail/static/src/composer/navigable_list.xml index 8a2488cc62f..2b359c18544 100644 --- a/addons/mail/static/src/composer/navigable_list.xml +++ b/addons/mail/static/src/composer/navigable_list.xml @@ -5,7 +5,7 @@
- + diff --git a/addons/mail/static/src/discuss/channel_selector.js b/addons/mail/static/src/discuss/channel_selector.js index 918c68481f9..dffe1083b1d 100644 --- a/addons/mail/static/src/discuss/channel_selector.js +++ b/addons/mail/static/src/discuss/channel_selector.js @@ -7,7 +7,7 @@ import { useService } from "@web/core/utils/hooks"; import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service"; import { Component, onMounted, useRef, useState } from "@odoo/owl"; import { cleanTerm } from "@mail/utils/format"; -import { createLocalId, isEventHandled } from "@mail/utils/misc"; +import { createLocalId, isEventHandled, markEventHandled } from "@mail/utils/misc"; import { _t } from "@web/core/l10n/translation"; export class ChannelSelector extends Component { @@ -30,10 +30,11 @@ export class ChannelSelector extends Component { if (this.props.autofocus) { onMounted(() => this.inputRef.el.focus()); } + this.markEventHandled = markEventHandled; } - async fetchSuggestions(term) { - const cleanedTerm = cleanTerm(term); + async fetchSuggestions() { + const cleanedTerm = cleanTerm(this.state.value); if (cleanedTerm) { if (this.props.category.id === "channels") { const domain = [ @@ -79,6 +80,13 @@ export class ChannelSelector extends Component { partner: this.store.self, }); } + if (suggestions.length === 0) { + suggestions.push({ + classList: "o-mail-ChannelSelector-suggestion", + label: _t("No results found"), + unselectable: true, + }); + } return suggestions; } } @@ -184,7 +192,7 @@ export class ChannelSelector extends Component { this.props.category.id === "channels" ? "mail.ChannelSelector.channel" : "mail.ChannelSelector.chat", - options: this.fetchSuggestions(this.state.value), + options: this.fetchSuggestions(), }; } } diff --git a/addons/mail/static/src/discuss/channel_selector.xml b/addons/mail/static/src/discuss/channel_selector.xml index b5402c14dee..5fe427a168a 100644 --- a/addons/mail/static/src/discuss/channel_selector.xml +++ b/addons/mail/static/src/discuss/channel_selector.xml @@ -9,6 +9,7 @@ t-att-placeholder="inputPlaceholder" t-model="state.value" t-ref="input" + t-on-click="(ev) => markEventHandled(ev, 'channelSelector.onClickInput')" t-on-keydown="onKeydownInput" type="text" /> diff --git a/addons/mail/static/src/web/messaging_menu/messaging_menu.xml b/addons/mail/static/src/web/messaging_menu/messaging_menu.xml index cbacc052b6b..7fd189f9348 100644 --- a/addons/mail/static/src/web/messaging_menu/messaging_menu.xml +++ b/addons/mail/static/src/web/messaging_menu/messaging_menu.xml @@ -34,7 +34,7 @@
- diff --git a/addons/mail/static/tests/chat_window/chat_window_new_message_tests.js b/addons/mail/static/tests/chat_window/chat_window_new_message_tests.js index 9472fb63d5d..678901c2ba6 100644 --- a/addons/mail/static/tests/chat_window/chat_window_new_message_tests.js +++ b/addons/mail/static/tests/chat_window/chat_window_new_message_tests.js @@ -20,7 +20,7 @@ QUnit.module("chat window: new message"); QUnit.test("basic rendering", async (assert) => { await start(); await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); assert.containsOnce($, ".o-mail-ChatWindow"); assert.containsOnce($, ".o-mail-ChatWindow-header"); assert.containsOnce($, ".o-mail-ChatWindow-header .o-mail-ChatWindow-name"); @@ -40,7 +40,7 @@ QUnit.test("basic rendering", async (assert) => { QUnit.test("focused on open [REQUIRE FOCUS]", async (assert) => { await start(); await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); assert.strictEqual( document.activeElement, $(".o-mail-ChatWindow .o-mail-ChannelSelector input")[0] @@ -50,7 +50,7 @@ QUnit.test("focused on open [REQUIRE FOCUS]", async (assert) => { QUnit.test("close", async (assert) => { await start(); await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); await click(".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Close chat window']"); assert.containsNone($, ".o-mail-ChatWindow"); }); @@ -58,7 +58,7 @@ QUnit.test("close", async (assert) => { QUnit.test("fold", async (assert) => { await start(); await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); assert.containsOnce($, ".o-mail-ChatWindow-content"); assert.containsOnce($, ".o-mail-ChannelSelector"); @@ -111,7 +111,7 @@ QUnit.test( // open "new message" chat window await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); assert.containsOnce($, ".o-mail-ChatWindow-header:contains(New message)"); assert.containsN($, ".o-mail-ChatWindow", 2); assert.containsOnce($, ".o-mail-ChatWindow .o-mail-ChannelSelector"); @@ -170,7 +170,7 @@ QUnit.test( }); await start(); await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); await insertText(".o-mail-ChannelSelector", "131"); await click(".o-mail-ChannelSelector-suggestion a"); assert.containsNone($, ".o-mail-ChatWindow-header:contains(New message)"); @@ -192,7 +192,7 @@ QUnit.test("new message autocomplete should automatically select first result", }); // open "new message" chat window await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); // search for a user in "new message" autocomplete await afterNextRender(async () => { await insertText(".o-mail-ChannelSelector", "131"); diff --git a/addons/mail/static/tests/discuss/discuss_tests.js b/addons/mail/static/tests/discuss/discuss_tests.js index ba671edbf50..8d0f615b2e9 100644 --- a/addons/mail/static/tests/discuss/discuss_tests.js +++ b/addons/mail/static/tests/discuss/discuss_tests.js @@ -223,6 +223,31 @@ QUnit.test("can create a group chat conversation", async (assert) => { assert.containsNone($, ".o-mail-Message"); }); +QUnit.test("chat search should display no result when no matches found", async (assert) => { + const { openDiscuss } = await start(); + await openDiscuss(); + await click(".o-mail-DiscussSidebar i[title='Start a conversation']"); + await insertText(".o-mail-ChannelSelector", "Rainbow Panda"); + assert.containsOnce($, ".o-mail-ChannelSelector-suggestion:contains(No results found)"); +}); + +QUnit.test( + "chat search should not be visible when clicking outside of the field", + async (assert) => { + const pyEnv = await startServer(); + const partnerId = pyEnv["res.partner"].create({ name: "Panda" }); + pyEnv["res.users"].create({ partner_id: partnerId }); + const { openDiscuss } = await start(); + await openDiscuss(); + assert.containsNone($, ".o-mail-DiscussCategoryItem"); + await click(".o-mail-DiscussSidebar i[title='Start a conversation']"); + await insertText(".o-mail-ChannelSelector", "Panda"); + assert.containsOnce($, ".o-mail-ChannelSelector-suggestion"); + await click(".o-mail-DiscussSidebar"); + assert.containsNone($, ".o-mail-ChannelSelector-suggestion"); + } +); + QUnit.test("Message following a notification should not be squashed", async (assert) => { const pyEnv = await startServer(); const channelId = pyEnv["mail.channel"].create({ diff --git a/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js b/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js index 75a7152c1c5..9702afcdaea 100644 --- a/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js +++ b/addons/mail/static/tests/messaging_menu/messaging_menu_tests.js @@ -127,7 +127,7 @@ QUnit.test("respond to notification prompt (granted)", async (assert) => { QUnit.test("Is closed after clicking on new message", async (assert) => { await start(); await click(".o_menu_systray i[aria-label='Messages']"); - await click(".o-mail-MessagingMenu-new"); + await click("button:contains(New Message)"); assert.containsNone($, ".o-mail-MessagingMenu"); }); @@ -582,7 +582,7 @@ QUnit.test("basic rendering", async (assert) => { assert.hasClass($('.o-mail-MessagingMenu button:contains("All")'), "fw-bolder"); assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Chats")'), "fw-bolder"); assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Channels")'), "fw-bolder"); - assert.containsOnce($, ".o-mail-MessagingMenu-new"); + assert.containsOnce($, "button:contains(New Message)"); assert.containsOnce($, '.o-mail-MessagingMenu:contains("No conversation yet...")'); await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])"); assert.doesNotHaveClass(