[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:
@@ -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"/>
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user