mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 06:43:44 +08:00
feat: add logger highlighting, support regular and case matching
This commit is contained in:
parent
a7020fd46c
commit
dca25637c9
@ -7,20 +7,19 @@ import matchCaseIcon from "@/assets/image/component/match_case.svg?react";
|
|||||||
import matchWholeWordIcon from "@/assets/image/component/match_whole_word.svg?react";
|
import matchWholeWordIcon from "@/assets/image/component/match_whole_word.svg?react";
|
||||||
import useRegularExpressionIcon from "@/assets/image/component/use_regular_expression.svg?react";
|
import useRegularExpressionIcon from "@/assets/image/component/use_regular_expression.svg?react";
|
||||||
|
|
||||||
|
export type SearchState = {
|
||||||
|
text: string;
|
||||||
|
matchCase: boolean;
|
||||||
|
matchWholeWord: boolean;
|
||||||
|
useRegularExpression: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type SearchProps = {
|
type SearchProps = {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
matchCase?: boolean;
|
matchCase?: boolean;
|
||||||
matchWholeWord?: boolean;
|
matchWholeWord?: boolean;
|
||||||
useRegularExpression?: boolean;
|
useRegularExpression?: boolean;
|
||||||
onSearch: (
|
onSearch: (match: (content: string) => boolean, state: SearchState) => void;
|
||||||
match: (content: string) => boolean,
|
|
||||||
state: {
|
|
||||||
text: string;
|
|
||||||
matchCase: boolean;
|
|
||||||
matchWholeWord: boolean;
|
|
||||||
useRegularExpression: boolean;
|
|
||||||
}
|
|
||||||
) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BaseSearchBox = styled((props: SearchProps) => {
|
export const BaseSearchBox = styled((props: SearchProps) => {
|
||||||
@ -28,10 +27,10 @@ export const BaseSearchBox = styled((props: SearchProps) => {
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [matchCase, setMatchCase] = useState(props.matchCase ?? false);
|
const [matchCase, setMatchCase] = useState(props.matchCase ?? false);
|
||||||
const [matchWholeWord, setMatchWholeWord] = useState(
|
const [matchWholeWord, setMatchWholeWord] = useState(
|
||||||
props.matchWholeWord ?? false
|
props.matchWholeWord ?? false,
|
||||||
);
|
);
|
||||||
const [useRegularExpression, setUseRegularExpression] = useState(
|
const [useRegularExpression, setUseRegularExpression] = useState(
|
||||||
props.useRegularExpression ?? false
|
props.useRegularExpression ?? false,
|
||||||
);
|
);
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
|
||||||
@ -60,7 +59,7 @@ export const BaseSearchBox = styled((props: SearchProps) => {
|
|||||||
matchCase,
|
matchCase,
|
||||||
matchWholeWord,
|
matchWholeWord,
|
||||||
useRegularExpression,
|
useRegularExpression,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { styled, Box } from "@mui/material";
|
import { styled, Box } from "@mui/material";
|
||||||
|
import { SearchState } from "@/components/base/base-search-box";
|
||||||
|
|
||||||
const Item = styled(Box)(({ theme: { palette, typography } }) => ({
|
const Item = styled(Box)(({ theme: { palette, typography } }) => ({
|
||||||
padding: "8px 0",
|
padding: "8px 0",
|
||||||
@ -41,24 +42,41 @@ const Item = styled(Box)(({ theme: { palette, typography } }) => ({
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: ILogItem;
|
value: ILogItem;
|
||||||
searchText?: string;
|
searchState?: SearchState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogItem = ({ value, searchText }: Props) => {
|
const LogItem = ({ value, searchState }: Props) => {
|
||||||
const renderHighlightText = (text: string) => {
|
const renderHighlightText = (text: string) => {
|
||||||
if (!searchText?.trim()) return text;
|
if (!searchState?.text.trim()) return text;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parts = text.split(new RegExp(`(${searchText})`, "gi"));
|
const searchText = searchState.text;
|
||||||
return parts.map((part, index) =>
|
let pattern: string;
|
||||||
part.toLowerCase() === searchText.toLowerCase() ? (
|
|
||||||
|
if (searchState.useRegularExpression) {
|
||||||
|
try {
|
||||||
|
new RegExp(searchText);
|
||||||
|
pattern = searchText;
|
||||||
|
} catch {
|
||||||
|
pattern = searchText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const escaped = searchText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
pattern = searchState.matchWholeWord ? `\\b${escaped}\\b` : escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flags = searchState.matchCase ? "g" : "gi";
|
||||||
|
const parts = text.split(new RegExp(`(${pattern})`, flags));
|
||||||
|
|
||||||
|
return parts.map((part, index) => {
|
||||||
|
return index % 2 === 1 ? (
|
||||||
<span key={index} className="highlight">
|
<span key={index} className="highlight">
|
||||||
{part}
|
{part}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
part
|
part
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import LogItem from "@/components/log/log-item";
|
|||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { BaseSearchBox } from "@/components/base/base-search-box";
|
import { BaseSearchBox } from "@/components/base/base-search-box";
|
||||||
import { BaseStyledSelect } from "@/components/base/base-styled-select";
|
import { BaseStyledSelect } from "@/components/base/base-styled-select";
|
||||||
|
import { SearchState } from "@/components/base/base-search-box";
|
||||||
|
|
||||||
const LogPage = () => {
|
const LogPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -27,7 +28,7 @@ const LogPage = () => {
|
|||||||
);
|
);
|
||||||
const [match, setMatch] = useState(() => (_: string) => true);
|
const [match, setMatch] = useState(() => (_: string) => true);
|
||||||
const logData = useLogData(logLevel);
|
const logData = useLogData(logLevel);
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchState, setSearchState] = useState<SearchState>();
|
||||||
|
|
||||||
const filterLogs = useMemo(() => {
|
const filterLogs = useMemo(() => {
|
||||||
return logData
|
return logData
|
||||||
@ -96,7 +97,7 @@ const LogPage = () => {
|
|||||||
<BaseSearchBox
|
<BaseSearchBox
|
||||||
onSearch={(matcher, state) => {
|
onSearch={(matcher, state) => {
|
||||||
setMatch(() => matcher);
|
setMatch(() => matcher);
|
||||||
setSearchText(state.text);
|
setSearchState(state);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -114,7 +115,7 @@ const LogPage = () => {
|
|||||||
initialTopMostItemIndex={999}
|
initialTopMostItemIndex={999}
|
||||||
data={filterLogs}
|
data={filterLogs}
|
||||||
itemContent={(index, item) => (
|
itemContent={(index, item) => (
|
||||||
<LogItem value={item} searchText={searchText} />
|
<LogItem value={item} searchState={searchState} />
|
||||||
)}
|
)}
|
||||||
followOutput={"smooth"}
|
followOutput={"smooth"}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user