From e3d68312fa5fe269bf01883ce307f2817034c0c6 Mon Sep 17 00:00:00 2001 From: "Didier (did)" Date: Mon, 11 Sep 2023 14:45:56 +0200 Subject: [PATCH] [IMP] mail: improve escape key usage in discuss app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../mail/static/src/core/common/composer.js | 11 +++++----- .../static/src/core/common/composer_model.js | 1 + addons/mail/static/src/core/common/discuss.js | 14 ++++++++++++ .../src/discuss/core/web/channel_selector.js | 5 ++++- .../core/web/discuss_sidebar_categories.xml | 2 +- addons/mail/static/src/utils/common/hooks.js | 11 +++------- .../static/tests/discuss_app/discuss_tests.js | 22 +++++++++++++++++++ 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/addons/mail/static/src/core/common/composer.js b/addons/mail/static/src/core/common/composer.js index 06457014beb..db67322f070 100644 --- a/addons/mail/static/src/core/common/composer.js +++ b/addons/mail/static/src/core/common/composer.js @@ -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++; } } diff --git a/addons/mail/static/src/core/common/composer_model.js b/addons/mail/static/src/core/common/composer_model.js index 1bf759b8084..a18661b9e98 100644 --- a/addons/mail/static/src/core/common/composer_model.js +++ b/addons/mail/static/src/core/common/composer_model.js @@ -47,6 +47,7 @@ export class Composer extends Record { /** @type {boolean} */ forceCursorMove; isFocused = false; + autofocus = 0; } Composer.register(); diff --git a/addons/mail/static/src/core/common/discuss.js b/addons/mail/static/src/core/common/discuss.js index 8170a2a7bba..2c19756f738 100644 --- a/addons/mail/static/src/core/common/discuss.js +++ b/addons/mail/static/src/core/common/discuss.js @@ -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 ( diff --git a/addons/mail/static/src/discuss/core/web/channel_selector.js b/addons/mail/static/src/discuss/core/web/channel_selector.js index f6358cd69e0..f9193d682c3 100644 --- a/addons/mail/static/src/discuss/core/web/channel_selector.js +++ b/addons/mail/static/src/discuss/core/web/channel_selector.js @@ -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; } diff --git a/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.xml b/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.xml index b1f7d4521dc..6c5234e0814 100644 --- a/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.xml +++ b/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.xml @@ -28,7 +28,7 @@
- +
diff --git a/addons/mail/static/src/utils/common/hooks.js b/addons/mail/static/src/utils/common/hooks.js index fee6c6a2488..25ea4d12b51 100644 --- a/addons/mail/static/src/utils/common/hooks.js +++ b/addons/mail/static/src/utils/common/hooks.js @@ -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++; } }, }); diff --git a/addons/mail/static/tests/discuss_app/discuss_tests.js b/addons/mail/static/tests/discuss_app/discuss_tests.js index ff1886e45c2..99d2bb90150 100644 --- a/addons/mail/static/tests/discuss_app/discuss_tests.js +++ b/addons/mail/static/tests/discuss_app/discuss_tests.js @@ -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"); +});