UX: improved settings search

- Marching shortcuts now appear in the search results
This commit is contained in:
aleksey-hoffman committed 2026-08-21 22:03:12 +03:00
1 parent 5b3137c8c7
commit d55295a91e
16 files changed
+591 -60

No files matched your search

+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "文件已在网络上共享。当前共享将被停止并替换为新的共享。",
"replaceShareDialogConfirm": "替换"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "فایل‌ها از قبل از طریق شبکه به اشتراک گذاشته شده‌اند. اشتراک‌گذاری فعلی متوقف و با مورد جدید جایگزین خواهد شد.",
"replaceShareDialogConfirm": "جایگزینی"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "קבצים כבר משותפים ברשת. השיתוף הנוכחי ייעצר ויוחלף בחדש.",
"replaceShareDialogConfirm": "החלף"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "फ़ाइलें पहले से ही नेटवर्क पर साझा की जा रही हैं. वर्तमान शेयर बंद कर दिया जाएगा और उसके स्थान पर नया शेयर ले लिया जाएगा।",
"replaceShareDialogConfirm": "प्रतिस्थापित करें"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "Ֆայլերն արդեն կիսվում են ցանցում: Ընթացիկ կիսումը կդադարեցվի և կփոխարինվի նորով:",
"replaceShareDialogConfirm": "Փոխարինել"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "Datoteke se že delijo po omrežju. Trenutno deljenje bo ustavljeno in zamenjano z novim.",
"replaceShareDialogConfirm": "Zamenjaj"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "Ağ üzerinden zaten dosyalar paylaşılıyor. Mevcut paylaşım durdurulup yenisiyle değiştirilecek.",
"replaceShareDialogConfirm": "Değiştir"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "فائلیں پہلے سے نیٹ ورک پر ہیں۔ موجودہ شیئر بند ہو کر نئے سے بدل جائے گا۔",
"replaceShareDialogConfirm": "بدل دیں"
}
}
}
+1 -1
View File
@@ -1242,4 +1242,4 @@
"replaceShareDialogDescription": "Các tập tin đang được chia sẻ qua mạng. Chia sẻ hiện tại sẽ bị dừng và thay thế bằng chia sẻ mới.",
"replaceShareDialogConfirm": "Thay thế"
}
}
}
+1 -1
View File
@@ -50,7 +50,7 @@ watch(
>
<SettingsActions />
<SettingsSearch />
<SettingsHeader />
<SettingsHeader v-if="!settingsStore.search" />
<SettingsContent />
</PageDefaultLayout>
</template>
@@ -89,8 +89,14 @@ import { useExtensionsStorageStore } from '@/stores/storage/extensions';
import { BlocksIcon } from '@lucide/vue';
import type { ExtensionKeybindingOverride } from '@/types/extension';
import { SettingsItem } from '@/modules/settings';
import { useSettingsStore } from '@/stores/runtime/settings';
import {
filterItemsForSettingsSearch,
shortcutSearchItemMatches,
} from '@/modules/settings/utils/settings-search';
const { t } = useI18n();
const settingsStore = useSettingsStore();
const shortcutsStore = useShortcutsStore();
const globalShortcutsStore = useGlobalShortcutsStore();
const unassignedShortcutLabel = '—';
@@ -1151,6 +1157,64 @@ const globalExtensionShortcuts = computed(() => {
keybindingLabel: formatKeybindingKeys(shortcut.keys),
}));
});
function filterShortcutRows<T>(
rows: T[],
rowMatches: (row: T) => boolean,
): T[] {
return filterItemsForSettingsSearch(
rows,
settingsStore.search,
rowMatches,
);
}
const visibleGlobalAppShortcuts = computed(() =>
filterShortcutRows(
globalShortcutsStore.definitions,
definition => shortcutSearchItemMatches({
labelKey: definition.labelKey,
combinations: [globalShortcutsStore.getShortcutLabel(definition.id)],
}, settingsStore.search),
),
);
const visibleGlobalExtensionShortcuts = computed(() =>
filterShortcutRows(
globalExtensionShortcuts.value,
shortcut => shortcutSearchItemMatches({
titles: [shortcut.commandTitle],
combinations: [shortcut.keybindingLabel, formatShortcutKeys(shortcut.keys)],
}, settingsStore.search),
),
);
const visibleLocalShortcuts = computed(() =>
filterShortcutRows(
shortcutsStore.definitions,
definition => shortcutSearchItemMatches({
labelKey: definition.labelKey,
combinations: [shortcutsStore.getShortcutBindingLabel(definition)],
}, settingsStore.search),
),
);
const visibleExtensionKeybindings = computed(() =>
filterShortcutRows(
extensionKeybindings.value,
keybinding => shortcutSearchItemMatches({
titles: [keybinding.commandTitle],
combinations: [keybinding.keybindingLabel, formatShortcutKeys(keybinding.keys)],
}, settingsStore.search),
),
);
const hasVisibleGlobalShortcuts = computed(() =>
visibleGlobalAppShortcuts.value.length > 0
|| visibleGlobalExtensionShortcuts.value.length > 0,
);
const hasVisibleLocalShortcuts = computed(() => visibleLocalShortcuts.value.length > 0);
</script>
<template>
@@ -1172,7 +1236,10 @@ const globalExtensionShortcuts = computed(() => {
</template>
<div class="shortcuts-section">
<div class="shortcuts-section__group">
<div
v-if="hasVisibleGlobalShortcuts"
class="shortcuts-section__group"
>
<div class="shortcuts-section__group-header">
<div class="shortcuts-section__group-info">
<h4 class="shortcuts-section__group-title">
@@ -1200,7 +1267,7 @@ const globalExtensionShortcuts = computed(() => {
<div class="shortcuts-table__body">
<ContextMenu
v-for="definition in globalShortcutsStore.definitions"
v-for="definition in visibleGlobalAppShortcuts"
:key="definition.id"
>
<ContextMenuTrigger as-child>
@@ -1273,7 +1340,7 @@ const globalExtensionShortcuts = computed(() => {
</ContextMenu>
<ContextMenu
v-for="shortcut in globalExtensionShortcuts"
v-for="shortcut in visibleGlobalExtensionShortcuts"
:key="`${shortcut.extensionId}-${shortcut.commandId}`"
>
<ContextMenuTrigger as-child>
@@ -1351,7 +1418,10 @@ const globalExtensionShortcuts = computed(() => {
</div>
</div>
<div class="shortcuts-section__group">
<div
v-if="hasVisibleLocalShortcuts"
class="shortcuts-section__group"
>
<div class="shortcuts-section__group-header">
<div class="shortcuts-section__group-info">
<h4 class="shortcuts-section__group-title">
@@ -1378,7 +1448,7 @@ const globalExtensionShortcuts = computed(() => {
<div class="shortcuts-table__body">
<ContextMenu
v-for="definition in shortcutsStore.definitions"
v-for="definition in visibleLocalShortcuts"
:key="`${definition.id}-${definition.bindingSlot ?? 0}`"
>
<ContextMenuTrigger
@@ -1489,7 +1559,7 @@ const globalExtensionShortcuts = computed(() => {
</div>
</div>
<template v-if="extensionKeybindings.length > 0">
<template v-if="visibleExtensionKeybindings.length > 0">
<div class="shortcuts-table shortcuts-table--extensions">
<div class="shortcuts-table__section-header">
<BlocksIcon :size="16" />
@@ -1512,7 +1582,7 @@ const globalExtensionShortcuts = computed(() => {
<div class="shortcuts-table__body">
<ContextMenu
v-for="keybinding in extensionKeybindings"
v-for="keybinding in visibleExtensionKeybindings"
:key="`${keybinding.extensionId}-${keybinding.commandId}`"
>
<ContextMenuTrigger as-child>
+3 -1
View File
@@ -25,6 +25,7 @@ import StorageCategory from './categories/storage/index.vue';
import TabsCategory from './categories/tabs/index.vue';
import { Button } from '@/components/ui/button';
import { DeleteIcon } from '@lucide/vue';
const settingsStore = useSettingsStore();
const scrollStore = useScrollRestorationStore();
@@ -85,11 +86,12 @@ watch(
</div>
<Button
v-if="settingsStore.search"
variant="tertiary"
variant="outline"
class="settings-view__clear-search"
@click="settingsStore.clearSearch"
>
{{ t('globalSearch.clearSearchField') }}
<DeleteIcon :size="18" />
</Button>
</div>
</div>
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// License: GNU GPLv3 or later. See the license file in the project root for more information.
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
import { describe, expect, it } from 'vitest';
import {
filterItemsForSettingsSearch,
filterSettingsSections,
matchesAnyLocale,
matchesShortcutCombination,
shortcutSearchItemMatches,
} from '@/modules/settings/utils/settings-search';
describe('settings search', () => {
it('matches setting titles in any locale', () => {
expect(matchesAnyLocale('shortcuts.setSelectedItemsForCopying', 'copy')).toBe(true);
expect(matchesAnyLocale('shortcuts.setSelectedItemsForCopying', 'копирован')).toBe(true);
expect(matchesAnyLocale('shortcuts.focusAppWindow', 'показать окно')).toBe(true);
expect(matchesAnyLocale('shortcuts.focusAppWindow', 'theme')).toBe(false);
});
it('matches shortcut combinations with partial and alternate spellings', () => {
expect(matchesShortcutCombination('Ctrl+C', 'ctrl+c')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'ctrl + c')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'ctrl-c')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'control+c')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'ctrlc')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'ctrl+')).toBe(true);
expect(matchesShortcutCombination('Ctrl+Shift+N', 'ctrl+sh')).toBe(true);
expect(matchesShortcutCombination('Ctrl+Shift+N', 'ctrl shift n')).toBe(true);
expect(matchesShortcutCombination('Win+Shift+E', 'super+shift')).toBe(true);
expect(matchesShortcutCombination('Win+Shift+E', 'cmd+shift+e')).toBe(true);
expect(matchesShortcutCombination('Meta+K', 'win+k')).toBe(true);
expect(matchesShortcutCombination('F5', 'f5')).toBe(true);
expect(matchesShortcutCombination('Del', 'delete')).toBe(true);
expect(matchesShortcutCombination('Alt+↑', 'arrowup')).toBe(true);
expect(matchesShortcutCombination('Alt+↑', 'up')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'c')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'ctrl+v')).toBe(false);
expect(matchesShortcutCombination('Ctrl+A', 'c')).toBe(false);
expect(matchesShortcutCombination('Ctrl+C', '+')).toBe(false);
expect(matchesShortcutCombination('Ctrl+C', '-')).toBe(false);
expect(matchesShortcutCombination('Ctrl+-', '-')).toBe(true);
expect(matchesShortcutCombination('Ctrl++', '+')).toBe(true);
expect(matchesShortcutCombination('Ctrl++', 'ctrl++')).toBe(true);
expect(matchesShortcutCombination('Ctrl+C', 'ctrl++')).toBe(false);
expect(matchesShortcutCombination('', 'ctrl')).toBe(false);
});
it('matches shortcut search items by locale title or combination', () => {
expect(shortcutSearchItemMatches({
labelKey: 'shortcuts.setSelectedItemsForCopying',
combinations: ['Ctrl+C'],
}, 'копирован')).toBe(true);
expect(shortcutSearchItemMatches({
labelKey: 'shortcuts.setSelectedItemsForCopying',
combinations: ['Ctrl+C'],
}, 'ctrl+c')).toBe(true);
expect(shortcutSearchItemMatches({
titles: ['Convert video'],
combinations: ['Alt+Shift+V'],
}, 'convert')).toBe(true);
expect(shortcutSearchItemMatches({
labelKey: 'shortcuts.setSelectedItemsForCopying',
combinations: ['Ctrl+C'],
}, 'theme')).toBe(false);
});
it('shows the shortcuts section for title, tag, or matching shortcut data', () => {
const sections = [
{
key: 'theme',
titleKey: 'settings.homeBannerEffects.theme.title',
tags: 'settingsTags.theme',
category: 'appearance',
},
{
key: 'shortcuts',
titleKey: 'settingsTabs.shortcuts',
tags: 'settingsTags.shortcuts',
category: 'shortcuts',
},
];
expect(filterSettingsSections(sections, '', 'appearance').map(section => section.key)).toEqual(['theme']);
expect(filterSettingsSections(sections, 'shortcuts', 'appearance').map(section => section.key)).toEqual(['shortcuts']);
expect(filterSettingsSections(sections, 'шорткаты', 'appearance').map(section => section.key)).toEqual(['shortcuts']);
expect(filterSettingsSections(sections, 'ctrl+c', 'appearance', {
shortcuts: () => true,
}).map(section => section.key)).toEqual(['shortcuts']);
expect(filterSettingsSections(sections, 'ctrl+c', 'appearance', {
shortcuts: () => false,
})).toEqual([]);
});
it('filters shortcut rows unless the query matches the shortcuts section itself', () => {
const rows = [
{
id: 'copy',
labelKey: 'shortcuts.setSelectedItemsForCopying',
combo: 'Ctrl+C',
},
{
id: 'paste',
labelKey: 'shortcuts.transferPreparedForCopying',
combo: 'Ctrl+V',
},
{
id: 'rename',
labelKey: 'shortcuts.renameSelectedItems',
combo: 'F2',
},
];
const matchingCopy = filterItemsForSettingsSearch(
rows,
'ctrl+c',
row => shortcutSearchItemMatches({
labelKey: row.labelKey,
combinations: [row.combo],
}, 'ctrl+c'),
);
expect(matchingCopy.map(row => row.id)).toEqual(['copy']);
const matchingSection = filterItemsForSettingsSearch(
rows,
'shortcuts',
row => shortcutSearchItemMatches({
labelKey: row.labelKey,
combinations: [row.combo],
}, 'shortcuts'),
);
expect(matchingSection.map(row => row.id)).toEqual(['copy', 'paste', 'rename']);
});
});
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// License: GNU GPLv3 or later. See the license file in the project root for more information.
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
import { messages } from '@/localization/data';
export const SHORTCUTS_SECTION_SEARCH_KEYS = [
'settingsTabs.shortcuts',
'settingsTags.shortcuts',
] as const;
export type SettingsSectionSearchFields = {
key: string;
titleKey: string;
tags: string;
category: string;
};
export type SettingsShortcutSearchItem = {
labelKey?: string;
titles?: string[];
combinations: string[];
};
const SHORTCUT_DISPLAY_ALIASES: Array<[string, string]> = [
['↑', 'ArrowUp'],
['↓', 'ArrowDown'],
['←', 'ArrowLeft'],
['→', 'ArrowRight'],
['Del', 'Delete'],
['Mouse Button 4', 'MouseButton4'],
['Mouse Button 5', 'MouseButton5'],
];
function getNestedValue(obj: Record<string, unknown>, path: string): string | undefined {
const keys = path.split('.');
let current: unknown = obj;
for (const key of keys) {
if (current === null || current === undefined || typeof current !== 'object') {
return undefined;
}
current = (current as Record<string, unknown>)[key];
}
return typeof current === 'string' ? current : undefined;
}
export function getAllTranslations(key: string): string[] {
const translations: string[] = [];
for (const locale of Object.keys(messages)) {
const localeMessages = messages[locale as keyof typeof messages];
const value = getNestedValue(localeMessages as Record<string, unknown>, key);
if (value) {
translations.push(value.toLowerCase());
}
}
return translations;
}
export function normalizeSettingsSearchTerm(searchTerm: string): string {
return searchTerm.trim().toLowerCase();
}
export function matchesAnyLocale(key: string, searchTerm: string): boolean {
const normalizedSearch = normalizeSettingsSearchTerm(searchTerm);
if (!normalizedSearch) {
return true;
}
return getAllTranslations(key).some(translation => translation.includes(normalizedSearch));
}
function applyShortcutModifierAliases(value: string): string {
return value
.replaceAll('commandorcontrol', 'ctrl')
.replaceAll('control', 'ctrl')
.replaceAll('windows', 'win')
.replaceAll('command', 'win')
.replaceAll('super', 'win')
.replaceAll('meta', 'win')
.replaceAll('cmd', 'win');
}
function normalizeShortcutCombinationText(value: string): string {
const compact = value
.toLowerCase()
.replace(/\s+/g, '')
.replace(/(?<=[\p{L}\p{N}])-(?=[\p{L}\p{N}])/gu, '+');
return applyShortcutModifierAliases(compact);
}
function splitShortcutComboParts(normalizedCombo: string): string[] {
const parts: string[] = [];
let currentPart = '';
for (const character of normalizedCombo) {
if (character === '+') {
if (currentPart) {
parts.push(currentPart);
currentPart = '';
}
else {
parts.push('+');
}
}
else {
currentPart += character;
}
}
if (currentPart) {
parts.push(currentPart);
}
return parts;
}
export function getShortcutCombinationSearchTexts(formattedLabel: string): string[] {
if (!formattedLabel) {
return [];
}
const texts = [formattedLabel];
for (const [displayToken, alias] of SHORTCUT_DISPLAY_ALIASES) {
if (formattedLabel.includes(displayToken)) {
texts.push(formattedLabel.replaceAll(displayToken, alias));
}
}
return texts;
}
function isShortcutPartMatch(comboPart: string, queryPart: string): boolean {
if (comboPart === queryPart) {
return true;
}
return queryPart.length >= 2 && comboPart.startsWith(queryPart);
}
function matchesNormalizedShortcutCombination(formattedKeys: string, searchTerm: string): boolean {
if (!formattedKeys) {
return false;
}
const normalizedQuery = normalizeShortcutCombinationText(normalizeSettingsSearchTerm(searchTerm));
if (!normalizedQuery) {
return false;
}
const normalizedCombo = normalizeShortcutCombinationText(formattedKeys);
if (normalizedCombo === normalizedQuery) {
return true;
}
const compactCombo = normalizedCombo.replaceAll('+', '');
const compactQuery = normalizedQuery.replaceAll('+', '');
if (normalizedQuery.includes('+') && compactQuery.length > 0 && normalizedCombo.includes(normalizedQuery)) {
return true;
}
if (!normalizedQuery.includes('+') && compactQuery.length >= 3 && compactCombo.includes(compactQuery)) {
return true;
}
const queryParts = splitShortcutComboParts(normalizedQuery);
const comboParts = splitShortcutComboParts(normalizedCombo);
if (queryParts.length === 0 || comboParts.length === 0) {
return false;
}
if (queryParts.length > 1) {
return queryParts.every(queryPart =>
comboParts.some(comboPart => isShortcutPartMatch(comboPart, queryPart)),
);
}
const [singlePart] = queryParts;
const mainKey = comboParts[comboParts.length - 1];
if (singlePart.length >= 3) {
return comboParts.some(comboPart => isShortcutPartMatch(comboPart, singlePart) || comboPart.includes(singlePart));
}
return comboParts.some(comboPart => comboPart === singlePart)
|| (singlePart.length >= 2 && mainKey.includes(singlePart));
}
export function matchesShortcutCombination(formattedKeys: string, searchTerm: string): boolean {
return getShortcutCombinationSearchTexts(formattedKeys).some(combinationText =>
matchesNormalizedShortcutCombination(combinationText, searchTerm),
);
}
export function shortcutSearchItemMatches(
item: SettingsShortcutSearchItem,
searchTerm: string,
): boolean {
const normalizedSearch = normalizeSettingsSearchTerm(searchTerm);
if (!normalizedSearch) {
return true;
}
if (item.labelKey && matchesAnyLocale(item.labelKey, normalizedSearch)) {
return true;
}
if (item.titles?.some(title => title.toLowerCase().includes(normalizedSearch))) {
return true;
}
return item.combinations.some(combination =>
matchesShortcutCombination(combination, normalizedSearch),
);
}
export function filterItemsForSettingsSearch<T>(
items: T[],
searchTerm: string,
itemMatches: (item: T) => boolean,
sectionKeys: readonly string[] = SHORTCUTS_SECTION_SEARCH_KEYS,
): T[] {
const normalizedSearch = normalizeSettingsSearchTerm(searchTerm);
if (!normalizedSearch) {
return items;
}
const matchingItems = items.filter(itemMatches);
if (matchingItems.length > 0) {
return matchingItems;
}
const sectionMatches = sectionKeys.some(key => matchesAnyLocale(key, normalizedSearch));
return sectionMatches ? items : matchingItems;
}
export function filterSettingsSections<T extends SettingsSectionSearchFields>(
sections: T[],
searchTerm: string,
currentTab: string,
extraSectionMatchers: Partial<Record<string, (normalizedSearch: string) => boolean>> = {},
): T[] {
const normalizedSearch = normalizeSettingsSearchTerm(searchTerm);
if (!normalizedSearch) {
return sections.filter(section => section.category === currentTab);
}
return sections.filter((section) => {
if (matchesAnyLocale(section.titleKey, normalizedSearch)) {
return true;
}
if (matchesAnyLocale(section.tags, normalizedSearch)) {
return true;
}
return extraSectionMatchers[section.key]?.(normalizedSearch) ?? false;
});
}
+81 -40
View File
@@ -10,10 +10,20 @@ import {
markRaw,
type Component,
} from 'vue';
import { messages } from '@/localization/data';
import { i18n } from '@/localization';
import { formatKeybindingKeys } from '@/modules/extensions/api';
import { useUserSettingsStore } from '@/stores/storage/user-settings';
import { usePlatformStore } from '@/stores/runtime/platform';
import {
formatShortcutKeys,
useShortcutsStore,
} from '@/stores/runtime/shortcuts';
import { useGlobalShortcutsStore } from '@/stores/runtime/global-shortcuts';
import { useExtensionsStore } from '@/stores/runtime/extensions';
import {
filterSettingsSections,
shortcutSearchItemMatches,
} from '@/modules/settings/utils/settings-search';
export interface SettingsSection {
key: string;
@@ -71,39 +81,76 @@ function normalizeSettingsTabName(tabName: string): string {
return settingsTabs.some(tab => tab.name === tabName) ? tabName : 'general';
}
function getNestedValue(obj: Record<string, unknown>, path: string): string | undefined {
const keys = path.split('.');
let current: unknown = obj;
function getExtensionCommandSearchTitles(extensionId: string, commandId: string): string[] {
const extensionsStore = useExtensionsStore();
const titles = [commandId];
const command = extensionsStore.commands.find(registeredCommand => registeredCommand.command.id === commandId);
for (const key of keys) {
if (current === null || current === undefined || typeof current !== 'object') {
return undefined;
}
current = (current as Record<string, unknown>)[key];
if (command?.command.title) {
titles.push(command.command.title);
}
return typeof current === 'string' ? current : undefined;
}
const contextMenuItem = extensionsStore.contextMenuItems.find(
item => item.extensionId === extensionId && item.item.id === commandId,
);
function getAllTranslations(key: string): string[] {
const translations: string[] = [];
for (const locale of Object.keys(messages)) {
const localeMessages = messages[locale as keyof typeof messages];
const value = getNestedValue(localeMessages as Record<string, unknown>, key);
if (value) {
translations.push(value.toLowerCase());
}
if (contextMenuItem?.item.title) {
titles.push(contextMenuItem.item.title);
}
return translations;
return titles;
}
function matchesAnyLocale(key: string, searchTerm: string): boolean {
const translations = getAllTranslations(key);
return translations.some(translation => translation.includes(searchTerm));
function shortcutsSectionMatchesSearch(searchTerm: string): boolean {
const shortcutsStore = useShortcutsStore();
const globalShortcutsStore = useGlobalShortcutsStore();
const extensionsStore = useExtensionsStore();
const appShortcutMatches = shortcutsStore.definitions.some(definition =>
shortcutSearchItemMatches({
labelKey: definition.labelKey,
combinations: [shortcutsStore.getShortcutBindingLabel(definition)],
}, searchTerm),
);
if (appShortcutMatches) {
return true;
}
const globalShortcutMatches = globalShortcutsStore.definitions.some(definition =>
shortcutSearchItemMatches({
labelKey: definition.labelKey,
combinations: [globalShortcutsStore.getShortcutLabel(definition.id)],
}, searchTerm),
);
if (globalShortcutMatches) {
return true;
}
const extensionLocalMatches = extensionsStore.keybindings.some(keybinding =>
shortcutSearchItemMatches({
titles: getExtensionCommandSearchTitles(keybinding.extensionId, keybinding.commandId),
combinations: [
formatKeybindingKeys(keybinding.keys),
formatShortcutKeys(keybinding.keys),
],
}, searchTerm),
);
if (extensionLocalMatches) {
return true;
}
return globalShortcutsStore.extensionDefinitions.some(shortcut =>
shortcutSearchItemMatches({
titles: [shortcut.commandTitle],
combinations: [
formatKeybindingKeys(shortcut.keys),
formatShortcutKeys(shortcut.keys),
],
}, searchTerm),
);
}
export const useSettingsStore = defineStore('settings', () => {
@@ -439,20 +486,14 @@ export const useSettingsStore = defineStore('settings', () => {
return sections.value.filter(section => section.category === category);
}
const filteredSections = computed(() => {
if (!search.value) {
return sections.value.filter(section => section.category === currentTab.value);
}
const searchTerm = search.value.toLowerCase();
return sections.value.filter((section) => {
const titleMatches = matchesAnyLocale(section.titleKey, searchTerm);
const tagsMatch = matchesAnyLocale(section.tags, searchTerm);
return titleMatches || tagsMatch;
});
});
const filteredSections = computed(() => filterSettingsSections(
sections.value,
search.value,
currentTab.value,
{
shortcuts: shortcutsSectionMatchesSearch,
},
));
const currentTabSections = computed(() => filteredSections.value);
+5 -2
View File
@@ -4,8 +4,11 @@
"exclude": [],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
"lib": [],
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"types": ["node", "jsdom"]
}
}