mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-14 14:00:30 +08:00
124 lines
3.1 KiB
TypeScript
124 lines
3.1 KiB
TypeScript
import { styled, Box } from "@mui/material";
|
|
import type { ReactNode } from "react";
|
|
|
|
import { SearchState } from "@/components/base/base-search-box";
|
|
|
|
const Item = styled(Box)(({ theme: { palette, typography } }) => ({
|
|
padding: "8px 0",
|
|
margin: "0 12px",
|
|
lineHeight: 1.35,
|
|
borderBottom: `1px solid ${palette.divider}`,
|
|
fontSize: "0.875rem",
|
|
fontFamily: typography.fontFamily,
|
|
userSelect: "text",
|
|
"& .time": {
|
|
color: palette.text.secondary,
|
|
},
|
|
"& .type": {
|
|
display: "inline-block",
|
|
marginLeft: 8,
|
|
textAlign: "center",
|
|
borderRadius: 2,
|
|
textTransform: "uppercase",
|
|
fontWeight: "600",
|
|
},
|
|
'& .type[data-type="error"], & .type[data-type="err"]': {
|
|
color: palette.error.main,
|
|
},
|
|
'& .type[data-type="warning"], & .type[data-type="warn"]': {
|
|
color: palette.warning.main,
|
|
},
|
|
'& .type[data-type="info"], & .type[data-type="inf"]': {
|
|
color: palette.info.main,
|
|
},
|
|
"& .data": {
|
|
color: palette.text.primary,
|
|
overflowWrap: "anywhere",
|
|
},
|
|
"& .highlight": {
|
|
backgroundColor: palette.mode === "dark" ? "#ffeb3b40" : "#ffeb3b90",
|
|
borderRadius: 2,
|
|
padding: "0 2px",
|
|
},
|
|
}));
|
|
|
|
interface Props {
|
|
value: ILogItem;
|
|
searchState?: SearchState;
|
|
}
|
|
|
|
const LogItem = ({ value, searchState }: Props) => {
|
|
const renderHighlightText = (text: string) => {
|
|
if (!searchState?.text.trim()) return text;
|
|
|
|
try {
|
|
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 regex = new RegExp(pattern, flags);
|
|
const elements: ReactNode[] = [];
|
|
let lastIndex = 0;
|
|
let match: RegExpExecArray | null;
|
|
|
|
while ((match = regex.exec(text)) !== null) {
|
|
const start = match.index;
|
|
const matchText = match[0];
|
|
|
|
if (matchText === "") {
|
|
regex.lastIndex += 1;
|
|
continue;
|
|
}
|
|
|
|
if (start > lastIndex) {
|
|
elements.push(text.slice(lastIndex, start));
|
|
}
|
|
|
|
elements.push(
|
|
<span key={`highlight-${start}`} className="highlight">
|
|
{matchText}
|
|
</span>,
|
|
);
|
|
|
|
lastIndex = start + matchText.length;
|
|
}
|
|
|
|
if (lastIndex < text.length) {
|
|
elements.push(text.slice(lastIndex));
|
|
}
|
|
|
|
return elements.length ? elements : text;
|
|
} catch {
|
|
return text;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Item>
|
|
<div>
|
|
<span className="time">{renderHighlightText(value.time || "")}</span>
|
|
<span className="type" data-type={value.type.toLowerCase()}>
|
|
{renderHighlightText(value.type)}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="data">{renderHighlightText(value.payload)}</span>
|
|
</div>
|
|
</Item>
|
|
);
|
|
};
|
|
|
|
export default LogItem;
|