mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-26 14:12:10 +08:00
feat(proxy): unify filter search box and sync filter options (#5819)
* feat(proxy): unify filter search box and sync filter options * docs: fix JSDoc placement * refactor(search): unify string matcher and stabilize regex filtering * fix(search): treat invalid regex as matching nothing * docs: update changelog to include advanced filter search feature for proxy page --------- Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import { Box, IconButton, TextField, SxProps } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseSearchBox } from "@/components/base/base-search-box";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import delayManager from "@/services/delay";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
@@ -43,7 +44,16 @@ export const ProxyHead = ({
|
||||
onLocation,
|
||||
onCheckDelay,
|
||||
}: Props) => {
|
||||
const { showType, sortType, filterText, textState, testUrl } = headState;
|
||||
const {
|
||||
showType,
|
||||
sortType,
|
||||
filterText,
|
||||
textState,
|
||||
testUrl,
|
||||
filterMatchCase,
|
||||
filterMatchWholeWord,
|
||||
filterUseRegularExpression,
|
||||
} = headState;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [autoFocus, setAutoFocus] = useState(false);
|
||||
@@ -154,17 +164,25 @@ export const ProxyHead = ({
|
||||
</IconButton>
|
||||
|
||||
{textState === "filter" && (
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
autoFocus={autoFocus}
|
||||
hiddenLabel
|
||||
value={filterText}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
placeholder={t("shared.placeholders.filter")}
|
||||
onChange={(e) => onHeadState({ filterText: e.target.value })}
|
||||
sx={{ ml: 0.5, flex: "1 1 auto", input: { py: 0.65, px: 1 } }}
|
||||
/>
|
||||
<Box sx={{ ml: 0.5, flex: "1 1 auto" }}>
|
||||
<BaseSearchBox
|
||||
autoFocus={autoFocus}
|
||||
value={filterText}
|
||||
searchState={{
|
||||
matchCase: filterMatchCase,
|
||||
matchWholeWord: filterMatchWholeWord,
|
||||
useRegularExpression: filterUseRegularExpression,
|
||||
}}
|
||||
onSearch={(_, state) =>
|
||||
onHeadState({
|
||||
filterText: state.text,
|
||||
filterMatchCase: state.matchCase,
|
||||
filterMatchWholeWord: state.matchWholeWord,
|
||||
filterUseRegularExpression: state.useRegularExpression,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{textState === "url" && (
|
||||
|
||||
@@ -2,15 +2,23 @@ import { useEffect, useMemo, useReducer } from "react";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import delayManager from "@/services/delay";
|
||||
import { compileStringMatcher } from "@/utils/search-matcher";
|
||||
|
||||
// default | delay | alphabet
|
||||
export type ProxySortType = 0 | 1 | 2;
|
||||
|
||||
export type ProxySearchState = {
|
||||
matchCase?: boolean;
|
||||
matchWholeWord?: boolean;
|
||||
useRegularExpression?: boolean;
|
||||
};
|
||||
|
||||
export default function useFilterSort(
|
||||
proxies: IProxyItem[],
|
||||
groupName: string,
|
||||
filterText: string,
|
||||
sortType: ProxySortType,
|
||||
searchState?: ProxySearchState,
|
||||
) {
|
||||
const { verge } = useVerge();
|
||||
const [_, bumpRefresh] = useReducer((count: number) => count + 1, 0);
|
||||
@@ -33,7 +41,7 @@ export default function useFilterSort(
|
||||
}, [groupName]);
|
||||
|
||||
return useMemo(() => {
|
||||
const fp = filterProxies(proxies, groupName, filterText);
|
||||
const fp = filterProxies(proxies, groupName, filterText, searchState);
|
||||
const sp = sortProxies(
|
||||
fp,
|
||||
groupName,
|
||||
@@ -46,6 +54,7 @@ export default function useFilterSort(
|
||||
groupName,
|
||||
filterText,
|
||||
sortType,
|
||||
searchState,
|
||||
verge?.default_latency_timeout,
|
||||
]);
|
||||
}
|
||||
@@ -56,8 +65,9 @@ export function filterSort(
|
||||
filterText: string,
|
||||
sortType: ProxySortType,
|
||||
latencyTimeout?: number,
|
||||
searchState?: ProxySearchState,
|
||||
) {
|
||||
const fp = filterProxies(proxies, groupName, filterText);
|
||||
const fp = filterProxies(proxies, groupName, filterText, searchState);
|
||||
const sp = sortProxies(fp, groupName, sortType, latencyTimeout);
|
||||
return sp;
|
||||
}
|
||||
@@ -76,10 +86,12 @@ function filterProxies(
|
||||
proxies: IProxyItem[],
|
||||
groupName: string,
|
||||
filterText: string,
|
||||
searchState?: ProxySearchState,
|
||||
) {
|
||||
if (!filterText) return proxies;
|
||||
const query = filterText.trim();
|
||||
if (!query) return proxies;
|
||||
|
||||
const res1 = regex1.exec(filterText);
|
||||
const res1 = regex1.exec(query);
|
||||
if (res1) {
|
||||
const symbol = res1[1];
|
||||
const symbol2 = res1[2].toLowerCase();
|
||||
@@ -100,13 +112,25 @@ function filterProxies(
|
||||
});
|
||||
}
|
||||
|
||||
const res2 = regex2.exec(filterText);
|
||||
const res2 = regex2.exec(query);
|
||||
if (res2) {
|
||||
const type = res2[1].toLowerCase();
|
||||
return proxies.filter((p) => p.type.toLowerCase().includes(type));
|
||||
}
|
||||
|
||||
return proxies.filter((p) => p.name.includes(filterText.trim()));
|
||||
const {
|
||||
matchCase = false,
|
||||
matchWholeWord = false,
|
||||
useRegularExpression = false,
|
||||
} = searchState ?? {};
|
||||
const compiled = compileStringMatcher(query, {
|
||||
matchCase,
|
||||
matchWholeWord,
|
||||
useRegularExpression,
|
||||
});
|
||||
|
||||
if (!compiled.isValid) return [];
|
||||
return proxies.filter((p) => compiled.matcher(p.name));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,9 @@ export interface HeadState {
|
||||
showType: boolean;
|
||||
sortType: ProxySortType;
|
||||
filterText: string;
|
||||
filterMatchCase?: boolean;
|
||||
filterMatchWholeWord?: boolean;
|
||||
filterUseRegularExpression?: boolean;
|
||||
textState: "url" | "filter" | null;
|
||||
testUrl: string;
|
||||
}
|
||||
@@ -21,6 +24,9 @@ export const DEFAULT_STATE: HeadState = {
|
||||
showType: true,
|
||||
sortType: 0,
|
||||
filterText: "",
|
||||
filterMatchCase: false,
|
||||
filterMatchWholeWord: false,
|
||||
filterUseRegularExpression: false,
|
||||
textState: null,
|
||||
testUrl: "",
|
||||
};
|
||||
|
||||
@@ -390,6 +390,11 @@ export const useRenderList = (
|
||||
headState.filterText,
|
||||
headState.sortType,
|
||||
latencyTimeout,
|
||||
{
|
||||
matchCase: headState.filterMatchCase,
|
||||
matchWholeWord: headState.filterMatchWholeWord,
|
||||
useRegularExpression: headState.filterUseRegularExpression,
|
||||
},
|
||||
);
|
||||
|
||||
ret.push({
|
||||
|
||||
Reference in New Issue
Block a user