From dca25637c92e56fe840fe2470469d2c4f61b5634 Mon Sep 17 00:00:00 2001 From: huzibaca Date: Tue, 19 Nov 2024 04:10:10 +0800 Subject: [PATCH] feat: add logger highlighting, support regular and case matching --- src/components/base/base-search-box.tsx | 23 ++++++++--------- src/components/log/log-item.tsx | 34 +++++++++++++++++++------ src/pages/logs.tsx | 7 ++--- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/components/base/base-search-box.tsx b/src/components/base/base-search-box.tsx index eb50a460..ec4b22d8 100644 --- a/src/components/base/base-search-box.tsx +++ b/src/components/base/base-search-box.tsx @@ -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 useRegularExpressionIcon from "@/assets/image/component/use_regular_expression.svg?react"; +export type SearchState = { + text: string; + matchCase: boolean; + matchWholeWord: boolean; + useRegularExpression: boolean; +}; + type SearchProps = { placeholder?: string; matchCase?: boolean; matchWholeWord?: boolean; useRegularExpression?: boolean; - onSearch: ( - match: (content: string) => boolean, - state: { - text: string; - matchCase: boolean; - matchWholeWord: boolean; - useRegularExpression: boolean; - } - ) => void; + onSearch: (match: (content: string) => boolean, state: SearchState) => void; }; export const BaseSearchBox = styled((props: SearchProps) => { @@ -28,10 +27,10 @@ export const BaseSearchBox = styled((props: SearchProps) => { const inputRef = useRef(null); const [matchCase, setMatchCase] = useState(props.matchCase ?? false); const [matchWholeWord, setMatchWholeWord] = useState( - props.matchWholeWord ?? false + props.matchWholeWord ?? false, ); const [useRegularExpression, setUseRegularExpression] = useState( - props.useRegularExpression ?? false + props.useRegularExpression ?? false, ); const [errorMessage, setErrorMessage] = useState(""); @@ -60,7 +59,7 @@ export const BaseSearchBox = styled((props: SearchProps) => { matchCase, matchWholeWord, useRegularExpression, - } + }, ); }; diff --git a/src/components/log/log-item.tsx b/src/components/log/log-item.tsx index e7891f9f..18e3a898 100644 --- a/src/components/log/log-item.tsx +++ b/src/components/log/log-item.tsx @@ -1,4 +1,5 @@ import { styled, Box } from "@mui/material"; +import { SearchState } from "@/components/base/base-search-box"; const Item = styled(Box)(({ theme: { palette, typography } }) => ({ padding: "8px 0", @@ -41,24 +42,41 @@ const Item = styled(Box)(({ theme: { palette, typography } }) => ({ interface Props { value: ILogItem; - searchText?: string; + searchState?: SearchState; } -const LogItem = ({ value, searchText }: Props) => { +const LogItem = ({ value, searchState }: Props) => { const renderHighlightText = (text: string) => { - if (!searchText?.trim()) return text; + if (!searchState?.text.trim()) return text; try { - const parts = text.split(new RegExp(`(${searchText})`, "gi")); - return parts.map((part, index) => - part.toLowerCase() === searchText.toLowerCase() ? ( + const searchText = searchState.text; + let pattern: string; + + 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 ? ( {part} ) : ( part - ), - ); + ); + }); } catch { return text; } diff --git a/src/pages/logs.tsx b/src/pages/logs.tsx index af6f8601..c454fd97 100644 --- a/src/pages/logs.tsx +++ b/src/pages/logs.tsx @@ -15,6 +15,7 @@ import LogItem from "@/components/log/log-item"; import { useTheme } from "@mui/material/styles"; import { BaseSearchBox } from "@/components/base/base-search-box"; import { BaseStyledSelect } from "@/components/base/base-styled-select"; +import { SearchState } from "@/components/base/base-search-box"; const LogPage = () => { const { t } = useTranslation(); @@ -27,7 +28,7 @@ const LogPage = () => { ); const [match, setMatch] = useState(() => (_: string) => true); const logData = useLogData(logLevel); - const [searchText, setSearchText] = useState(""); + const [searchState, setSearchState] = useState(); const filterLogs = useMemo(() => { return logData @@ -96,7 +97,7 @@ const LogPage = () => { { setMatch(() => matcher); - setSearchText(state.text); + setSearchState(state); }} /> @@ -114,7 +115,7 @@ const LogPage = () => { initialTopMostItemIndex={999} data={filterLogs} itemContent={(index, item) => ( - + )} followOutput={"smooth"} />