[IMP] mail: improve escape key usage in discuss app

This PR introduce multiple usage of the escape key:

- Channel selector can now be closed using the escape key
- Using the escape key will focus the composer automatically

Task-3456518

closes odoo/odoo#135031

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Didier (did)
2023-10-05 13:37:50 +00:00
parent a1949ca541
commit e3d68312fa
7 changed files with 50 additions and 16 deletions
@@ -96,7 +96,6 @@ export class Composer extends Component {
this.fakeTextarea = useRef("fakeTextarea");
this.emojiButton = useRef("emoji-button");
this.state = useState({
autofocus: 0,
active: true,
});
this.selection = useSelection({
@@ -135,12 +134,12 @@ export class Composer extends Component {
this.ref.el.focus();
}
},
() => [this.props.autofocus + this.state.autofocus, this.props.placeholder]
() => [this.props.autofocus + this.props.composer.autofocus, this.props.placeholder]
);
useEffect(
(rThread, cThread) => {
if (cThread && cThread.eq(rThread)) {
this.state.autofocus++;
this.props.composer.autofocus++;
}
},
() => [this.props.messageToReplyTo?.thread, this.props.composer.thread]
@@ -172,7 +171,7 @@ export class Composer extends Component {
buttons: [this.emojiButton],
close: () => {
if (!this.ui.isSmall) {
this.state.autofocus++;
this.props.composer.autofocus++;
}
},
pickers: { emoji: (emoji) => this.addEmoji(emoji) },
@@ -392,7 +391,7 @@ export class Composer extends Component {
onClickAddAttachment(ev) {
markEventHandled(ev, "composer.clickOnAddAttachment");
this.state.autofocus++;
this.props.composer.autofocus++;
}
async onClickFullComposer(ev) {
@@ -576,7 +575,7 @@ export class Composer extends Component {
this.props.composer.textInputContent = firstPart + str + secondPart;
this.selection.moveCursor((firstPart + str).length);
if (!this.ui.isSmall) {
this.state.autofocus++;
this.props.composer.autofocus++;
}
}
@@ -47,6 +47,7 @@ export class Composer extends Record {
/** @type {boolean} */
forceCursorMove;
isFocused = false;
autofocus = 0;
}
Composer.register();
@@ -21,7 +21,9 @@ import {
useRef,
useState,
useEffect,
useExternalListener,
} from "@odoo/owl";
import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks";
@@ -60,6 +62,18 @@ export class Discuss extends Component {
});
this.notification = useService("notification");
this.threadActions = useThreadActions();
useExternalListener(
window,
"keydown",
(ev) => {
if (getActiveHotkey(ev) === "escape" && !this.thread?.composer?.isFocused) {
if (this.thread?.composer) {
this.thread.composer.autofocus++;
}
}
},
{ capture: true }
);
useEffect(
() => {
if (
@@ -14,7 +14,7 @@ import { useSequential } from "@mail/utils/common/hooks";
export class ChannelSelector extends Component {
static components = { TagsList, NavigableList };
static props = ["category", "onValidate?", "autofocus?", "multiple?"];
static props = ["category", "onValidate?", "autofocus?", "multiple?", "close?"];
static defaultProps = { multiple: true };
static template = "discuss.ChannelSelector";
@@ -207,6 +207,9 @@ export class ChannelSelector extends Component {
this.state.selectedPartners.pop();
}
return;
case "escape":
this.props.close?.();
return;
default:
return;
}
@@ -28,7 +28,7 @@
</div>
<t t-if="category.isOpen">
<div t-if="state.editing === category.id" class="p-2" t-ref="selector">
<ChannelSelector category="category" onValidate.bind="stopEditing" autofocus="true"/>
<ChannelSelector category="category" onValidate.bind="stopEditing" autofocus="true" close.bind="stopEditing" />
</div>
<t t-foreach="filteredThreads(category)" t-as="thread" t-key="thread.localId" t-call="mail.DiscussSidebarChannel">
<t t-set="thread" t-value="thread"/>
+3 -8
View File
@@ -391,21 +391,16 @@ export function useScrollPosition(refName, model, clearOn) {
return self;
}
/**
* @typedef {Object} MessageEdition
* @property {composerOfThread} composerOfThread
* @property {editingMessage} editingMessage
* @property {function} exitEditMode
* @returns {MessageEdition}
*/
export function useMessageEdition() {
const state = useState({
/** @type {import('@mail/core/common/composer').Composer} */
composerOfThread: null,
/** @type {import('@mail/core/common/message_model').Message} */
editingMessage: null,
exitEditMode() {
state.editingMessage = null;
if (state.composerOfThread) {
state.composerOfThread.state.autofocus++;
state.composerOfThread.props.composer.autofocus++;
}
},
});
@@ -1814,3 +1814,25 @@ QUnit.test(
assert.verifySteps(["Second RPC"]);
}
);
QUnit.test("Escape key should close the channel selector and focus the composer", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await click("i[title='Add or join a channel']");
await contains(".o-discuss-ChannelSelector");
triggerHotkey("escape");
await contains(".o-discuss-ChannelSelector", { count: 0 });
await contains(".o-mail-Composer-input:focus");
});
QUnit.test("Escape key should focus the composer if it's not focused", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await click("button[title='Pinned Messages']");
triggerHotkey("escape");
await contains(".o-mail-Composer-input:focus");
});