From 1f4e583ba20a01f4c44b0a4ada42c4d3bb074273 Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Thu, 7 Jul 2022 08:13:00 +0000 Subject: [PATCH] [FIX] web: CP lose init seach value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, in the HomeMenu, typing several characters very quickly could result in some not being present in the command palette when it was opened. Exemple: In the HomeMenu,if I type "a" "b" "c" quickly the command palette will open with the search value "/a" or "/ac". So we have lost the second and/or third character. Why: All characters typed between the first and the moment the command pallet is not yet mounted are lost because the search bar of the command pallet is not yet available. Solution: If you use openPalette and the command palette is already open. The configuration of the command pallet will be overwritten by the one given to openPalette. In our case, we can pass new configurations with the correct searchValue until the command palette is returned. closes odoo/odoo#95492 Task-id: 2906623 Related: odoo/enterprise#29230 Signed-off-by: Géry Debongnie --- addons/mail/static/src/js/m2x_avatar_user.js | 10 +-- .../src/core/commands/command_palette.js | 16 ++++- .../src/core/commands/command_service.js | 26 ++++--- .../web/static/src/core/debug/debug_menu.js | 2 +- .../src/legacy/js/fields/basic_fields.js | 8 +-- .../src/legacy/js/fields/relational_fields.js | 5 +- .../views/fields/priority/priority_field.js | 4 +- .../state_selection/state_selection_field.js | 4 +- .../views/fields/statusbar/statusbar_field.js | 4 +- .../core/commands/command_service_tests.js | 69 +++++++++++++++++++ addons/web/static/tests/helpers/mock_env.js | 1 + 11 files changed, 118 insertions(+), 31 deletions(-) diff --git a/addons/mail/static/src/js/m2x_avatar_user.js b/addons/mail/static/src/js/m2x_avatar_user.js index b76f8806ec9..7eecc1045a4 100644 --- a/addons/mail/static/src/js/m2x_avatar_user.js +++ b/addons/mail/static/src/js/m2x_avatar_user.js @@ -96,7 +96,7 @@ export const Many2OneAvatarUser = Many2OneAvatar.extend(M2XAvatarMixin, { hotkey: "alt+i", }, async action() { - return env.services.command.openPalette({ + return { configByNamespace: { default: { emptyMessage: env._t("No users found"), @@ -104,7 +104,7 @@ export const Many2OneAvatarUser = Many2OneAvatar.extend(M2XAvatarMixin, { }, placeholder: env._t("Select a user..."), providers: [{ provide }], - }); + }; }, }); core.bus.trigger("set_legacy_command", "web.Many2OneAvatar.assignTo", getCommandDefinition, self.el); @@ -277,7 +277,7 @@ export const Many2ManyAvatarUser = FieldMany2ManyTagsAvatar.extend(M2MAvatarMixi hotkey: "alt+i", }, action() { - return env.services.command.openPalette({ + return { configByNamespace: { default: { emptyMessage: env._t("No users found"), @@ -285,9 +285,9 @@ export const Many2ManyAvatarUser = FieldMany2ManyTagsAvatar.extend(M2MAvatarMixi }, placeholder: env._t("Select a user..."), providers: [{ provide }], - }); + }; }, - }) + }); core.bus.trigger("set_legacy_command", "web.FieldMany2ManyTagsAvatar.assignTo", getCommandDefinition) getCommandDefinition = (env) => ({ diff --git a/addons/web/static/src/core/commands/command_palette.js b/addons/web/static/src/core/commands/command_palette.js index ed27aaa2f77..0f98333d8a9 100644 --- a/addons/web/static/src/core/commands/command_palette.js +++ b/addons/web/static/src/core/commands/command_palette.js @@ -10,7 +10,15 @@ import { fuzzyLookup } from "@web/core/utils/search"; import { debounce } from "@web/core/utils/timing"; import { escapeRegExp } from "../utils/strings"; -const { Component, onWillStart, useRef, useState, markRaw, useExternalListener } = owl; +const { + Component, + onWillStart, + onWillDestroy, + useRef, + useState, + markRaw, + useExternalListener, +} = owl; const DEFAULT_PLACEHOLDER = _lt("Search..."); const DEFAULT_EMPTY_MESSAGE = _lt("No result found"); @@ -81,6 +89,12 @@ DefaultCommandItem.template = "web.DefaultCommandItem"; export class CommandPalette extends Component { setup() { + if (this.props.bus) { + const setConfig = ({ detail }) => this.setCommandPaletteConfig(detail); + this.props.bus.addEventListener(`SET-CONFIG`, setConfig); + onWillDestroy(() => this.props.bus.removeEventListener(`SET-CONFIG`, setConfig)); + } + this.keyId = 1; this.keepLast = new KeepLast(); this._sessionId = CommandPalette.lastSessionId++; diff --git a/addons/web/static/src/core/commands/command_service.js b/addons/web/static/src/core/commands/command_service.js index 4cdd831e309..3acdeb146b7 100644 --- a/addons/web/static/src/core/commands/command_service.js +++ b/addons/web/static/src/core/commands/command_service.js @@ -3,7 +3,7 @@ import { registry } from "@web/core/registry"; import { CommandPalette } from "./command_palette"; -const { Component, xml } = owl; +const { Component, xml, EventBus } = owl; /** * @typedef {import("./command_palette").CommandPaletteConfig} CommandPaletteConfig @@ -65,6 +65,7 @@ export const commandService = { const registeredCommands = new Map(); let nextToken = 0; let isPaletteOpened = false; + const bus = new EventBus(); hotkeyService.add("control+k", openMainPalette, { bypassEditableProtection: true, @@ -125,11 +126,11 @@ export const commandService = { /** * @param {CommandPaletteConfig} config * @param {Function} onClose called when the command palette is closed - * @returns config if the command palette is already open */ function openPalette(config, onClose) { if (isPaletteOpened) { - return config; + bus.trigger("SET-CONFIG", config); + return; } // Open Command Palette dialog @@ -138,6 +139,7 @@ export const commandService = { CommandPalette, { config, + bus, }, { onClose: () => { @@ -161,16 +163,18 @@ export const commandService = { } const registration = Object.assign({}, command, options); - if (registration.hotkey) { - registration.removeHotkey = hotkeyService.add( - registration.hotkey, - registration.action, - { - activeElement: registration.activeElement, - global: registration.global, + const action = async () => { + const commandService = env.services.command; + const config = await command.action(); + if (!isPaletteOpened && config) { + commandService.openPalette(config); } - ); + }; + registration.removeHotkey = hotkeyService.add(registration.hotkey, action, { + activeElement: registration.activeElement, + global: registration.global, + }); } const token = nextToken++; diff --git a/addons/web/static/src/core/debug/debug_menu.js b/addons/web/static/src/core/debug/debug_menu.js index fd65d218975..aca4019d6ab 100644 --- a/addons/web/static/src/core/debug/debug_menu.js +++ b/addons/web/static/src/core/debug/debug_menu.js @@ -50,7 +50,7 @@ export class DebugMenu extends DebugMenuBasic { configByNamespace, providers: [provider], }; - return this.command.openPalette(commandPaletteConfig); + return commandPaletteConfig; }, { category: "debug", diff --git a/addons/web/static/src/legacy/js/fields/basic_fields.js b/addons/web/static/src/legacy/js/fields/basic_fields.js index 62f720fa1c0..8f73d698107 100644 --- a/addons/web/static/src/legacy/js/fields/basic_fields.js +++ b/addons/web/static/src/legacy/js/fields/basic_fields.js @@ -2577,10 +2577,10 @@ var PriorityWidget = AbstractField.extend({ hotkey: "alt+r", }, action() { - return env.services.command.openPalette({ + return { placeholder: env._t("Set a priority..."), providers: [{ provide }], - }) + }; }, }); core.bus.trigger("set_legacy_command", "web.PriorityWidget.setPriority", getCommandDefinition); @@ -2784,10 +2784,10 @@ var StateSelectionWidget = AbstractField.extend({ hotkey: "alt+shift+r", }, action() { - return env.services.command.openPalette({ + return { placeholder: env._t("Set a kanban state..."), providers: [{ provide }], - }) + } }, }); core.bus.trigger("set_legacy_command", "web.StateSelectionWidget.setKanbanState", getCommandDefinition); diff --git a/addons/web/static/src/legacy/js/fields/relational_fields.js b/addons/web/static/src/legacy/js/fields/relational_fields.js index ad14a2d237b..4649aa2963e 100644 --- a/addons/web/static/src/legacy/js/fields/relational_fields.js +++ b/addons/web/static/src/legacy/js/fields/relational_fields.js @@ -3221,11 +3221,10 @@ var FieldStatus = AbstractField.extend({ hotkey: "alt+shift+x", }, action() { - return env.services.command.openPalette({ + return { placeholder: statusLabel, providers: [{ provide }], - }); - }, + } }, }); core.bus.trigger("set_legacy_command", "web.FieldStatus.moveToStage", getCommandDefinition); } diff --git a/addons/web/static/src/views/fields/priority/priority_field.js b/addons/web/static/src/views/fields/priority/priority_field.js index 13a1316a64e..d725ef6168d 100644 --- a/addons/web/static/src/views/fields/priority/priority_field.js +++ b/addons/web/static/src/views/fields/priority/priority_field.js @@ -56,10 +56,10 @@ export class PriorityField extends Component { }; const name = this.env._t("Set priority..."); const action = () => { - return commandService.openPalette({ + return { placeholder: this.env._t("Set a priority..."), providers: [{ provide }], - }); + }; }; const options = { category: "smart_action", diff --git a/addons/web/static/src/views/fields/state_selection/state_selection_field.js b/addons/web/static/src/views/fields/state_selection/state_selection_field.js index c10f32b86db..447ba6e7c76 100644 --- a/addons/web/static/src/views/fields/state_selection/state_selection_field.js +++ b/addons/web/static/src/views/fields/state_selection/state_selection_field.js @@ -57,10 +57,10 @@ export class StateSelectionField extends Component { }; const name = this.env._t("Set kanban state..."); const action = () => { - return commandService.openPalette({ + return { placeholder: this.env._t("Set a kanban state..."), providers: [{ provide }], - }); + }; }; const options = { category: "smart_action", diff --git a/addons/web/static/src/views/fields/statusbar/statusbar_field.js b/addons/web/static/src/views/fields/statusbar/statusbar_field.js index 761a17ec948..08009f55187 100644 --- a/addons/web/static/src/views/fields/statusbar/statusbar_field.js +++ b/addons/web/static/src/views/fields/statusbar/statusbar_field.js @@ -128,10 +128,10 @@ export class StatusBarField extends Component { }; const name = sprintf(this.env._t(`Move to %s...`), escape(this.props.displayName)); const action = () => { - return commandService.openPalette({ + return { placeholder: name, providers: [{ provide }], - }); + }; }; const options = { category: "smart_action", diff --git a/addons/web/static/tests/core/commands/command_service_tests.js b/addons/web/static/tests/core/commands/command_service_tests.js index 960030ffe7c..c937db4a69d 100644 --- a/addons/web/static/tests/core/commands/command_service_tests.js +++ b/addons/web/static/tests/core/commands/command_service_tests.js @@ -218,6 +218,39 @@ QUnit.test("global command with hotkey", async (assert) => { assert.verifySteps([globalHotkey]); }); +QUnit.test("open command palette with command config", async (assert) => { + const hotkey = "alt+a"; + const action = () => {}; + const provide = () => [ + { + name: "Command1", + action, + }, + ]; + const providers = [{ provide }]; + env.services.command.add( + "test", + () => { + return { + providers, + }; + }, + { + hotkey, + } + ); + + await mount(TestComponent, target, { env }); + + triggerHotkey("alt+a"); + await nextTick(); + assert.containsN(target, ".o_command", 1); + assert.deepEqual( + [...target.querySelectorAll(".o_command span:first-child")].map((el) => el.textContent), + ["Command1"] + ); +}); + QUnit.test("data-hotkey added to command palette", async (assert) => { assert.expect(8); @@ -1046,3 +1079,39 @@ QUnit.test("openMainPalette with onClose", async (assert) => { await nextTick(); assert.verifySteps(["onClose"]); }); + +QUnit.test("uses openPalette to modify the config used by the command palette", async (assert) => { + const action = () => {}; + env.services.command.add("Command1", action); + + await mount(TestComponent, target, { env }); + + triggerHotkey("control+k"); + await nextTick(); + assert.deepEqual(target.querySelector(".o_command_palette_search input").value, ""); + assert.containsN(target, ".o_command", 1); + assert.deepEqual( + [...target.querySelectorAll(".o_command span:first-child")].map((el) => el.textContent), + ["Command1"] + ); + + const provide = () => [ + { + name: "Command2", + action, + }, + ]; + const providers = [{ provide }]; + const configCustom = { + searchValue: "Command", + providers, + }; + env.services.command.openPalette(configCustom); + await nextTick(); + assert.containsN(target, ".o_command", 1); + assert.deepEqual( + [...target.querySelectorAll(".o_command span:first-child")].map((el) => el.textContent), + ["Command2"] + ); + assert.deepEqual(target.querySelector(".o_command_palette_search input").value, "Command"); +}); diff --git a/addons/web/static/tests/helpers/mock_env.js b/addons/web/static/tests/helpers/mock_env.js index 601abc719c8..f2c76adeafa 100644 --- a/addons/web/static/tests/helpers/mock_env.js +++ b/addons/web/static/tests/helpers/mock_env.js @@ -50,6 +50,7 @@ function prepareRegistriesWithCleanup() { cloneRegistryWithCleanup(registry.category("views")); cloneRegistryWithCleanup(registry.category("error_handlers")); cloneRegistryWithCleanup(registry.category("command_provider")); + cloneRegistryWithCleanup(registry.category("command_setup")); cloneRegistryWithCleanup(registry.category("view_widgets")); cloneRegistryWithCleanup(registry.category("fields"));