mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(redis): add fuzzy key search toggle
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
Plus,
|
||||
KeyRound,
|
||||
TerminalSquare,
|
||||
Asterisk,
|
||||
} from "lucide-vue-next";
|
||||
import { RecycleScroller } from "vue-virtual-scroller";
|
||||
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
||||
@@ -42,6 +43,7 @@ import { isRedisClearScreenCommand, nextRedisCommandDb, redisKeyTextToRaw } from
|
||||
import { formatRedisCommandResult, formatRedisStringValue } from "@/lib/redisValuePresentation";
|
||||
import { isCancelSearchShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { useEditorFontFamilyStyle } from "@/composables/useEditorFontFamilyStyle";
|
||||
import { redisKeySearchPattern } from "@/lib/redisKeyPattern";
|
||||
|
||||
const { t } = useI18n();
|
||||
const connectionStore = useConnectionStore();
|
||||
@@ -72,6 +74,7 @@ const rootRef = ref<HTMLElement>();
|
||||
const commandTerminalRef = ref<HTMLElement>();
|
||||
const searchPattern = ref("");
|
||||
const searchMode = ref<RedisSearchMode>("key");
|
||||
const fuzzyKeySearch = ref(false);
|
||||
const selectedKeyRaw = ref<string | null>(null);
|
||||
const hasMore = ref(false);
|
||||
const scanCursor = ref(0);
|
||||
@@ -99,12 +102,18 @@ let redisBrowserIsActive = true;
|
||||
let redisDbFlushedListenerRegistered = false;
|
||||
|
||||
const valueQuery = computed(() => searchPattern.value.trim());
|
||||
const effectivePattern = computed(() => (searchMode.value === "key" ? searchPattern.value.trim() || "*" : "*"));
|
||||
const effectivePattern = computed(() =>
|
||||
searchMode.value === "key" ? redisKeySearchPattern(searchPattern.value, fuzzyKeySearch.value) : "*",
|
||||
);
|
||||
const isSearchMode = computed(() =>
|
||||
searchMode.value === "key" ? effectivePattern.value !== "*" : valueQuery.value !== "",
|
||||
);
|
||||
const searchPlaceholder = computed(() =>
|
||||
searchMode.value === "key" ? t("redis.pattern") : t("redis.valueSearchPlaceholder"),
|
||||
searchMode.value === "key"
|
||||
? fuzzyKeySearch.value
|
||||
? t("redis.fuzzyPattern")
|
||||
: t("redis.pattern")
|
||||
: t("redis.valueSearchPlaceholder"),
|
||||
);
|
||||
const loadingEmptyText = computed(() =>
|
||||
searchMode.value === "value" && valueQuery.value ? t("redis.searchingValues") : t("redis.loadingKeys"),
|
||||
@@ -550,6 +559,11 @@ function setSearchMode(mode: RedisSearchMode) {
|
||||
void loadKeys();
|
||||
}
|
||||
|
||||
function toggleFuzzyKeySearch() {
|
||||
fuzzyKeySearch.value = !fuzzyKeySearch.value;
|
||||
if (searchMode.value === "key") void loadKeys();
|
||||
}
|
||||
|
||||
function getSearchInput(): HTMLInputElement | null {
|
||||
return rootRef.value?.querySelector<HTMLInputElement>("[data-redis-search-input]") ?? null;
|
||||
}
|
||||
@@ -674,6 +688,19 @@ defineExpose({ focusSearch });
|
||||
@input="onSearchInput"
|
||||
@keydown="onSearchKeydown"
|
||||
/>
|
||||
<Button
|
||||
v-if="searchMode === 'key'"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 shrink-0 px-2 text-xs"
|
||||
:class="fuzzyKeySearch ? 'bg-accent text-accent-foreground' : 'text-muted-foreground'"
|
||||
:title="t('redis.fuzzyMatchTitle')"
|
||||
:aria-pressed="fuzzyKeySearch"
|
||||
@click="toggleFuzzyKeySearch"
|
||||
>
|
||||
<Asterisk class="h-3 w-3 mr-1" />
|
||||
{{ t("redis.fuzzyMatch") }}
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6 shrink-0" @click="loadKeys">
|
||||
<Loader2 v-if="loading" class="h-3 w-3 animate-spin" />
|
||||
<RefreshCw v-else class="h-3 w-3" />
|
||||
|
||||
@@ -1206,6 +1206,9 @@ export default {
|
||||
selectKey: "Select a key to view its value",
|
||||
noKeys: "No keys found",
|
||||
pattern: "pattern (e.g. user:*)",
|
||||
fuzzyPattern: "key contains...",
|
||||
fuzzyMatch: "Fuzzy",
|
||||
fuzzyMatchTitle: "Fuzzy match: search keys by plain text contains",
|
||||
valueSearchPlaceholder: "value contains...",
|
||||
searchByKey: "Key",
|
||||
searchByValue: "Value",
|
||||
|
||||
@@ -1098,6 +1098,9 @@ export default {
|
||||
selectKey: "Selecciona una clave para ver su valor",
|
||||
noKeys: "No se encontraron claves",
|
||||
pattern: "patrón (p. ej. usuario:*)",
|
||||
fuzzyPattern: "la clave contiene...",
|
||||
fuzzyMatch: "Difusa",
|
||||
fuzzyMatchTitle: "Coincidencia difusa: busca claves que contengan texto plano",
|
||||
valueSearchPlaceholder: "el valor contiene...",
|
||||
searchByKey: "Clave",
|
||||
searchByValue: "Valor",
|
||||
|
||||
@@ -1182,6 +1182,9 @@ export default {
|
||||
selectKey: "选择一个 key 查看值",
|
||||
noKeys: "未找到 key",
|
||||
pattern: "匹配模式 (如 user:*)",
|
||||
fuzzyPattern: "输入关键字搜索 key",
|
||||
fuzzyMatch: "模糊",
|
||||
fuzzyMatchTitle: "模糊匹配:自动按包含关系搜索 key",
|
||||
valueSearchPlaceholder: "按值内容搜索...",
|
||||
searchByKey: "键",
|
||||
searchByValue: "值",
|
||||
|
||||
@@ -1182,6 +1182,9 @@ export default {
|
||||
selectKey: "選擇一個 key 檢視值",
|
||||
noKeys: "未找到 key",
|
||||
pattern: "模式 (例如 user:*)",
|
||||
fuzzyPattern: "輸入關鍵字搜尋 key",
|
||||
fuzzyMatch: "模糊",
|
||||
fuzzyMatchTitle: "模糊匹配:自動按包含關係搜尋 key",
|
||||
valueSearchPlaceholder: "值包含……",
|
||||
searchByKey: "鍵",
|
||||
searchByValue: "值",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
const REDIS_GLOB_SPECIAL_CHARS = /[\\*?[\]]/g;
|
||||
|
||||
export function escapeRedisGlobText(value: string): string {
|
||||
return value.replace(REDIS_GLOB_SPECIAL_CHARS, "\\$&");
|
||||
}
|
||||
|
||||
export function redisKeySearchPattern(value: string, fuzzy: boolean): string {
|
||||
const pattern = value.trim();
|
||||
if (!pattern) return "*";
|
||||
return fuzzy ? `*${escapeRedisGlobText(pattern)}*` : pattern;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { escapeRedisGlobText, redisKeySearchPattern } from "../../apps/desktop/src/lib/redisKeyPattern.ts";
|
||||
|
||||
test("builds Redis key fuzzy search patterns from plain text", () => {
|
||||
assert.equal(redisKeySearchPattern("", true), "*");
|
||||
assert.equal(redisKeySearchPattern(" user ", true), "*user*");
|
||||
});
|
||||
|
||||
test("escapes Redis glob characters in fuzzy key search", () => {
|
||||
assert.equal(escapeRedisGlobText(String.raw`user:*?[1]`), String.raw`user:\*\?\[1\]`);
|
||||
assert.equal(redisKeySearchPattern(String.raw`user:*?[1]`, true), String.raw`*user:\*\?\[1\]*`);
|
||||
});
|
||||
|
||||
test("keeps Redis key pattern syntax when fuzzy search is off", () => {
|
||||
assert.equal(redisKeySearchPattern("user:*", false), "user:*");
|
||||
});
|
||||
Reference in New Issue
Block a user