[IMP] mail: ChannelSelector improvements
1. Display no result when having no matched results. 2. Hidden when clicking outside of the field. 3. Be visible when clicking inside the field. 4. Add related tests task-3208033 closes odoo/odoo#116214 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="o-mail-NavigableList bg-white m-0 p-0" t-ref="root" t-att-class="props.class">
|
||||
<div t-if="show" class="o-open border" t-on-mousedown.prevent="">
|
||||
<div t-if="state.isLoading" class="o-mail-NavigableList-item">
|
||||
<a href="#" class="">
|
||||
<a href="#" class="d-flex align-items-center w-100 py-2 px-4">
|
||||
<i class="fa fa-spin fa-circle-o-notch"/>
|
||||
<t t-esc="props.placeholder"/>
|
||||
</a>
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<ChannelSelector category="category" autofocus="true"/>
|
||||
</div>
|
||||
</t>
|
||||
<button t-if="!env.inDiscussApp or (env.inDiscussApp and store.discuss.activeTab === 'chat' and !state.addingChat)" t-att-class="env.inDiscussApp ? 'w-100 p-2 btn btn-secondary border-bottom bg-light' : 'btn btn-link o-mail-MessagingMenu-new'" t-on-click.stop="onClickNewMessage">
|
||||
<button t-if="!env.inDiscussApp or (env.inDiscussApp and store.discuss.activeTab === 'chat' and !state.addingChat)" t-att-class="env.inDiscussApp ? 'w-100 p-2 btn btn-secondary border-bottom bg-light' : 'btn btn-link'" t-on-click.stop="onClickNewMessage">
|
||||
<t t-if="env.inDiscussApp">Start a conversation</t>
|
||||
<t t-else="">New Message</t>
|
||||
</button>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user