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"));