2025-10-14 22:13:14 +08:00

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;