diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss index 545dd0221c..4b005f99d0 100644 --- a/src/renderer/components/dock/logs/log-list.module.scss +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -17,11 +17,11 @@ width: 100%; font-family: var(--font-monospace); font-size: smaller; - padding: 0 16px; line-height: 120%; > * { -webkit-font-smoothing: auto; // Better readability on non-retina screens + padding: 2px 16px; } } diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index f6c89b5804..2bcdf239b3 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -1,13 +1,10 @@ import styles from "./log-list.module.scss"; import { useVirtualizer } from '@tanstack/react-virtual'; -import AnsiUp from 'ansi_up'; -import DOMPurify from 'dompurify'; import { observer } from 'mobx-react'; import React, { useEffect, useRef } from 'react'; -import { SearchStore } from '../../../search-store/search-store'; -import { cssNames } from '../../../utils'; import type { LogTabViewModel } from './logs-view-model'; +import { LogRow } from "./log-row"; export interface LogListProps { model: LogTabViewModel; @@ -118,51 +115,3 @@ export const LogList = observer(({ model }: LogListProps) => { ) }); -const colorConverter = new AnsiUp(); - -function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabViewModel }) { - const { searchQuery, isActiveOverlay } = model.searchStore; - const log = model.visibleLogs.get()[rowIndex]; - const contents: React.ReactElement[] = []; - const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); - - if (searchQuery) { // If search is enabled, replace keyword with backgrounded - // Case-insensitive search (lowercasing query and keywords in line) - const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi"); - const matches = log.matchAll(regex); - const modified = log.replace(regex, match => match.toLowerCase()); - // Splitting text line by keyword - const pieces = modified.split(searchQuery.toLowerCase()); - - pieces.forEach((piece, index) => { - const active = isActiveOverlay(rowIndex, index); - const lastItem = index === pieces.length - 1; - const overlayValue = matches.next().value; - const overlay = !lastItem - ? ( - - ) - : null; - - contents.push( - - - {overlay} - , - ); - }); - } - - return ( -
- {contents.length > 1 ? contents : ( - - )} - {/* For preserving copy-paste experience and keeping line breaks */} -
-
- ); -} \ No newline at end of file diff --git a/src/renderer/components/dock/logs/log-row.module.scss b/src/renderer/components/dock/logs/log-row.module.scss new file mode 100644 index 0000000000..a1f64a7ab1 --- /dev/null +++ b/src/renderer/components/dock/logs/log-row.module.scss @@ -0,0 +1,20 @@ +.overlay { + --overlay-bg: #8cc474b8; + --overlay-active-bg: orange; + + border-radius: 2px; + -webkit-font-smoothing: auto; + background-color: var(--overlay-bg); + + span { + background-color: var(--overlay-bg)!important; // Rewriting inline styles from AnsiUp library + } + + &.active { + background-color: var(--overlay-active-bg); + + span { + background-color: var(--overlay-active-bg)!important; // Rewriting inline styles from AnsiUp library + } + } +} diff --git a/src/renderer/components/dock/logs/log-row.tsx b/src/renderer/components/dock/logs/log-row.tsx new file mode 100644 index 0000000000..bdba64ba48 --- /dev/null +++ b/src/renderer/components/dock/logs/log-row.tsx @@ -0,0 +1,57 @@ +import styles from "./log-row.module.scss"; + +import AnsiUp from 'ansi_up'; +import DOMPurify from 'dompurify'; +import React from 'react'; +import { SearchStore } from '../../../search-store/search-store'; +import { cssNames } from '../../../utils'; +import type { LogTabViewModel } from './logs-view-model'; + +const colorConverter = new AnsiUp(); + +export function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabViewModel }) { + const { searchQuery, isActiveOverlay } = model.searchStore; + const log = model.visibleLogs.get()[rowIndex]; + const contents: React.ReactElement[] = []; + const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); + + if (searchQuery) { // If search is enabled, replace keyword with backgrounded + // Case-insensitive search (lowercasing query and keywords in line) + const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi"); + const matches = log.matchAll(regex); + const modified = log.replace(regex, match => match.toLowerCase()); + // Splitting text line by keyword + const pieces = modified.split(searchQuery.toLowerCase()); + + pieces.forEach((piece, index) => { + const active = isActiveOverlay(rowIndex, index); + const lastItem = index === pieces.length - 1; + const overlayValue = matches.next().value; + const overlay = !lastItem + ? ( + + ) + : null; + + contents.push( + + + {overlay} + , + ); + }); + } + + return ( +
+ {contents.length > 1 ? contents : ( + + )} + {/* For preserving copy-paste experience and keeping line breaks */} +
+
+ ); +} \ No newline at end of file