feat: highlight search matches in log results
Recursive case-insensitive highlighting of the search query in collapsed message, expanded full message, and stack trace. Uses the project's amber accent color for the highlight mark. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -175,6 +175,13 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background: var(--amber);
|
||||
color: var(--bg-deep);
|
||||
border-radius: 2px;
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.linkBtn {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
@@ -36,11 +36,25 @@ function truncate(text: string, max: number): string {
|
||||
return text.length > max ? text.slice(0, max) + '\u2026' : text;
|
||||
}
|
||||
|
||||
interface LogEntryProps {
|
||||
entry: LogEntryResponse;
|
||||
function highlightText(text: string, query: string | undefined): React.ReactNode {
|
||||
if (!query || !text) return text;
|
||||
const idx = text.toLowerCase().indexOf(query.toLowerCase());
|
||||
if (idx === -1) return text;
|
||||
return (
|
||||
<>
|
||||
{text.slice(0, idx)}
|
||||
<mark className={styles.highlight}>{text.slice(idx, idx + query.length)}</mark>
|
||||
{highlightText(text.slice(idx + query.length), query)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function LogEntry({ entry }: LogEntryProps) {
|
||||
interface LogEntryProps {
|
||||
entry: LogEntryResponse;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
export function LogEntry({ entry, query }: LogEntryProps) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -68,7 +82,7 @@ export function LogEntry({ entry }: LogEntryProps) {
|
||||
<span className={styles.logger} title={entry.loggerName ?? ''}>
|
||||
{abbreviateLogger(entry.loggerName)}
|
||||
</span>
|
||||
<span className={styles.message}>{truncate(entry.message, 200)}</span>
|
||||
<span className={styles.message}>{highlightText(truncate(entry.message, 200), query)}</span>
|
||||
<span className={styles.chips}>
|
||||
{hasStack && <span className={styles.chip}>Stack</span>}
|
||||
{hasExchange && (
|
||||
@@ -98,10 +112,10 @@ export function LogEntry({ entry }: LogEntryProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.fullMessage}>{entry.message}</div>
|
||||
<div className={styles.fullMessage}>{highlightText(entry.message, query)}</div>
|
||||
|
||||
{hasStack && (
|
||||
<pre className={styles.stackTrace}>{entry.stackTrace}</pre>
|
||||
<pre className={styles.stackTrace}>{highlightText(entry.stackTrace!, query)}</pre>
|
||||
)}
|
||||
|
||||
{entry.mdc && Object.keys(entry.mdc).length > 0 && (
|
||||
|
||||
@@ -187,7 +187,7 @@ export function LogSearch({ defaultApplication, defaultRouteId }: LogSearchProps
|
||||
) : (
|
||||
<>
|
||||
{entries.map((entry, i) => (
|
||||
<LogEntry key={`${entry.timestamp}-${i}`} entry={entry} />
|
||||
<LogEntry key={`${entry.timestamp}-${i}`} entry={entry} query={debouncedQuery || undefined} />
|
||||
))}
|
||||
{!liveTail && hasMore && (
|
||||
<div className={styles.loadMore}>
|
||||
|
||||
Reference in New Issue
Block a user