diff --git a/addons/mail/static/src/webclient/commands/mail_providers.js b/addons/mail/static/src/webclient/commands/mail_providers.js
index 87fd0fbd1b1..bc5e95e4829 100644
--- a/addons/mail/static/src/webclient/commands/mail_providers.js
+++ b/addons/mail/static/src/webclient/commands/mail_providers.js
@@ -8,7 +8,7 @@ const { Component, xml } = owl;
class DialogCommand extends Component {}
DialogCommand.template = xml`
-
+
`;
diff --git a/addons/web/static/src/core/commands/command_items.xml b/addons/web/static/src/core/commands/command_items.xml
new file mode 100644
index 00000000000..cf752265f2b
--- /dev/null
+++ b/addons/web/static/src/core/commands/command_items.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
diff --git a/addons/web/static/src/core/commands/command_palette.js b/addons/web/static/src/core/commands/command_palette.js
index 7736bb683cd..1b0657a89be 100644
--- a/addons/web/static/src/core/commands/command_palette.js
+++ b/addons/web/static/src/core/commands/command_palette.js
@@ -1,12 +1,13 @@
/** @odoo-module **/
-import { useAutofocus, useService } from "@web/core/utils/hooks";
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
+import { _lt } from "@web/core/l10n/translation";
import { KeepLast } from "@web/core/utils/concurrency";
+import { useAutofocus, useService } from "@web/core/utils/hooks";
import { scrollTo } from "@web/core/utils/scrolling";
import { fuzzyLookup } from "@web/core/utils/search";
import { debounce } from "@web/core/utils/timing";
-import { _lt } from "@web/core/l10n/translation";
+import { escapeRegExp } from "../utils/strings";
const { Component, onWillStart, useRef, useState } = owl;
@@ -59,6 +60,11 @@ function commandsWithinCategory(categoryName, categories) {
};
}
+function splitCommandName(name, searchValue) {
+ const splitName = name.split(new RegExp(`(${escapeRegExp(searchValue)})`, "ig"));
+ return searchValue.length && splitName.length > 1 ? splitName : [name];
+}
+
export class DefaultCommandItem extends Component {}
DefaultCommandItem.template = "web.DefaultCommandItem";
@@ -175,11 +181,13 @@ export class CommandPalette extends Component {
this.state.commands = commands.map((command) => ({
...command,
keyId: this.keyId++,
+ splitName: splitCommandName(command.name, options.searchValue),
}));
this.selectCommand(this.state.commands.length ? 0 : -1);
this.mouseSelectionActive = false;
this.state.emptyMessage =
this.emptyMessageByNamespace[namespace] || DEFAULT_EMPTY_MESSAGE.toString();
+ this.clearSearchValue = options.searchValue;
}
selectCommand(index) {
diff --git a/addons/web/static/src/core/commands/command_palette.scss b/addons/web/static/src/core/commands/command_palette.scss
index 31e59681296..2d09af1b9ee 100644
--- a/addons/web/static/src/core/commands/command_palette.scss
+++ b/addons/web/static/src/core/commands/command_palette.scss
@@ -91,6 +91,12 @@
}
}
+ .o_command_name {
+ > b {
+ color: $o-brand-primary;
+ }
+ }
+
.o_app_icon {
position: relative;
border-radius: 4%;
diff --git a/addons/web/static/src/core/commands/command_palette.xml b/addons/web/static/src/core/commands/command_palette.xml
index cd65c01cfeb..c51e3de52d0 100644
--- a/addons/web/static/src/core/commands/command_palette.xml
+++ b/addons/web/static/src/core/commands/command_palette.xml
@@ -17,7 +17,16 @@
-
+
+
+
+
+
+
+
+
+
+
@@ -35,32 +44,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
- +
-
-
-
-
-
-
-
-
-
![]()
-
-
-
-
-
-
diff --git a/addons/web/static/src/webclient/menus/menu_command_item.xml b/addons/web/static/src/webclient/menus/menu_command_item.xml
new file mode 100644
index 00000000000..7a62b54da9c
--- /dev/null
+++ b/addons/web/static/src/webclient/menus/menu_command_item.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
diff --git a/addons/web/static/src/webclient/menus/menu_providers.js b/addons/web/static/src/webclient/menus/menu_providers.js
index 3c0bcd62815..188a2446e37 100644
--- a/addons/web/static/src/webclient/menus/menu_providers.js
+++ b/addons/web/static/src/webclient/menus/menu_providers.js
@@ -2,8 +2,8 @@
import { _lt } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
-import { computeAppsAndMenuItems } from "@web/webclient/menus/menu_helpers";
import { fuzzyLookup } from "@web/core/utils/search";
+import { computeAppsAndMenuItems } from "@web/webclient/menus/menu_helpers";
const { Component } = owl;
diff --git a/addons/web/static/tests/core/commands/command_palette_tests.js b/addons/web/static/tests/core/commands/command_palette_tests.js
index b0c7605a698..c763e980b15 100644
--- a/addons/web/static/tests/core/commands/command_palette_tests.js
+++ b/addons/web/static/tests/core/commands/command_palette_tests.js
@@ -1120,3 +1120,91 @@ QUnit.test("navigate in the command palette with an empty list", async (assert)
assert.containsNone(target, ".o_command");
assert.containsOnce(target, ".o_command_palette_listbox_empty");
});
+
+QUnit.test("bold the searchValue on the commands", async (assert) => {
+ testComponent = await mount(TestComponent, { env, target });
+ const action = () => {};
+ const providers = [
+ {
+ namespace: "@",
+ provide: () => [
+ {
+ name: "Test",
+ action,
+ },
+ {
+ name: "test hello",
+ action,
+ },
+ {
+ name: "hello test",
+ action,
+ },
+ {
+ name: "hello Test hello",
+ action,
+ },
+ {
+ name: "TeSt hello Test hello TEST",
+ action,
+ },
+ ],
+ },
+ ];
+ const config = {
+ searchValue: "@",
+ providers,
+ };
+ env.services.dialog.add(CommandPaletteDialog, {
+ config,
+ });
+ await nextTick();
+ assert.containsOnce(target, ".o_command_palette");
+ assert.containsN(target, ".o_command", 5);
+ assert.deepEqual(
+ [...target.querySelectorAll(".o_command b")].map((el) => el.textContent),
+ []
+ );
+
+ await editSearchBar("@test");
+ assert.containsN(target, ".o_command", 5);
+ assert.deepEqual(
+ [...target.querySelectorAll(".o_command")].map((command) => {
+ return [...command.querySelectorAll(".o_command_name b")].map((el) => el.textContent);
+ }),
+ [["Test"], ["test"], ["test"], ["Test"], ["TeSt", "Test", "TEST"]]
+ );
+});
+
+QUnit.test("bold the searchValue on the commands with special char", async (assert) => {
+ testComponent = await mount(TestComponent, { env, target });
+ const action = () => {};
+ const providers = [
+ {
+ provide: () => [
+ {
+ name: "Test&",
+ action,
+ },
+ ],
+ },
+ ];
+ const config = {
+ searchValue: "&",
+ providers,
+ };
+ env.services.dialog.add(CommandPaletteDialog, {
+ config,
+ });
+ await nextTick();
+ assert.containsOnce(target, ".o_command_palette");
+ assert.containsN(target, ".o_command", 1);
+ assert.deepEqual(
+ [...target.querySelectorAll(".o_command")].map((el) => el.textContent),
+ ["Test&"]
+ );
+ assert.deepEqual(
+ [...target.querySelectorAll(".o_command b")].map((el) => el.textContent),
+ ["&"]
+ );
+});