89c11fd683
* fix(extension): support searching installed plugins by display name
* fix: unify plugin search matching across installed and market tabs
* refactor(extension): optimize plugin search matcher and remove redundant checks
* refactor(extension-page): centralize search query normalization and text matching logic
- Extract `buildSearchQuery` to create normalized query objects from raw input
- Extract `matchesText` as a reusable text matching helper for normalized/loose/pinyin/initials matching
- Remove unused `marketCustomFilter` to eliminate dead code
- Simplify `matchesPluginSearch` to accept query object instead of pre-normalized string
- Replace Set with Array for candidates to simplify control flow
- Avoid redundant normalization by having callers pass raw strings to `buildSearchQuery`
* refactor: remove unused marketCustomFilter from extension page components
- Remove marketCustomFilter from destructuring in ExtensionPage.vue, InstalledPluginsTab.vue, and MarketPluginsTab.vue
* refactor(extension): extract plugin search utilities into shared module
- Create pluginSearch.js to centralize plugin search helpers
- Move `normalizeStr`, `normalizeLoose`, `toPinyinText`, and `toInitials` into the shared module
- Add `buildSearchQuery`, `matchesText`, and `matchesPluginSearch` for reusable search matching
- Refactor useExtensionPage.js to consume the shared utilities
- Simplify plugin search logic by consolidating normalization and matching in one place
* refactor(extension): add caching to pinyin utilities and extract search fields helper
- Add Map-based caching for `toPinyinText` and `toInitials` to avoid redundant pinyin computation
- Extract `getPluginSearchFields` function to retrieve plugin fields for searching
- Improve plugin search performance with caching and better code organization
* perf(extension): add bounded caching for plugin search
- cap normalization and pinyin caches with `MAX_SEARCH_CACHE_SIZE`
- add `setCacheValue()` for oldest-entry eviction
- cache normalized and loose text values to avoid repeated string processing
- skip pinyin matching for non-CJK text using Unicode `\p{Unified_Ideograph}` property
- improve search performance while keeping memory usage bounded
* refactor(extension): extract memoizeLRU helper for cache management
- Create `memoizeLRU` higher-order function to generate LRU-cached functions
- Replace manual cache implementation with `memoizeLRU` for cleaner code
- Optimize `matchesText` to lazily compute looseValue only when needed
- Simplify caching logic while maintaining bounded cache size
* refactor(extension): simplify memoization and remove LRU logic
- Rename `memoizeLRU` to `memoizeStringFn` and remove bounded cache size
- Simplify cache hit logic for cleaner code
- Remove `MAX_SEARCH_CACHE_SIZE` constant as it's no longer needed
103 lines
2.5 KiB
JavaScript
103 lines
2.5 KiB
JavaScript
import { pinyin } from "pinyin-pro";
|
|
|
|
const HAN_IDEOGRAPH_RE = /\p{Unified_Ideograph}/u;
|
|
|
|
export const normalizeStr = (s) => (s ?? "").toString().toLowerCase().trim();
|
|
|
|
const normalizeLooseFromNormalized = (normalized) =>
|
|
normalized.replace(/[\s_-]+/g, "").replace(/[()()【】\[\]{}·•]+/g, "");
|
|
|
|
export const normalizeLoose = (s) =>
|
|
normalizeLooseFromNormalized(normalizeStr(s));
|
|
|
|
const memoizeStringFn = (fn) => {
|
|
const cache = new Map();
|
|
|
|
return (raw) => {
|
|
const key = (raw ?? "").toString();
|
|
if (cache.has(key)) {
|
|
return cache.get(key);
|
|
}
|
|
|
|
const value = fn(key);
|
|
cache.set(key, value);
|
|
return value;
|
|
};
|
|
};
|
|
|
|
const getNormalizedText = memoizeStringFn(normalizeStr);
|
|
|
|
const getLooseText = memoizeStringFn((text) =>
|
|
normalizeLooseFromNormalized(getNormalizedText(text)),
|
|
);
|
|
|
|
export const toPinyinText = memoizeStringFn((text) =>
|
|
pinyin(text, { toneType: "none" })
|
|
.toLowerCase()
|
|
.replace(/\s+/g, ""),
|
|
);
|
|
|
|
export const toInitials = memoizeStringFn((text) =>
|
|
pinyin(text, { pattern: "first", toneType: "none" })
|
|
.toLowerCase()
|
|
.replace(/\s+/g, ""),
|
|
);
|
|
|
|
export const buildSearchQuery = (raw) => {
|
|
const norm = getNormalizedText(raw);
|
|
if (!norm) return null;
|
|
return {
|
|
norm,
|
|
loose: getLooseText(raw),
|
|
};
|
|
};
|
|
|
|
export const matchesText = (value, query) => {
|
|
if (value == null || !query?.norm) return false;
|
|
const text = String(value);
|
|
|
|
const normalizedValue = getNormalizedText(text);
|
|
const looseValue = query.loose ? getLooseText(text) : null;
|
|
|
|
if (normalizedValue.includes(query.norm)) return true;
|
|
if (query.loose && looseValue?.includes(query.loose)) return true;
|
|
|
|
if (!HAN_IDEOGRAPH_RE.test(text)) return false;
|
|
|
|
const pinyinValue = toPinyinText(text);
|
|
if (pinyinValue.includes(query.norm)) return true;
|
|
|
|
const initialsValue = toInitials(text);
|
|
if (initialsValue.includes(query.norm)) return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
export const getPluginSearchFields = (plugin) => {
|
|
const supportPlatforms = Array.isArray(plugin?.support_platforms)
|
|
? plugin.support_platforms.join(" ")
|
|
: "";
|
|
const tags = Array.isArray(plugin?.tags) ? plugin.tags.join(" ") : "";
|
|
|
|
return [
|
|
plugin?.name,
|
|
plugin?.trimmedName,
|
|
plugin?.display_name,
|
|
plugin?.desc,
|
|
plugin?.author,
|
|
plugin?.repo,
|
|
plugin?.version,
|
|
plugin?.astrbot_version,
|
|
supportPlatforms,
|
|
tags,
|
|
];
|
|
};
|
|
|
|
export const matchesPluginSearch = (plugin, query) => {
|
|
if (!query) return true;
|
|
|
|
return getPluginSearchFields(plugin).some((candidate) =>
|
|
matchesText(candidate, query),
|
|
);
|
|
};
|