Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ const StyledWrapper = styled.div`
overflow-y: auto;
background: ${(props) => props.theme.console.contentBg};
min-height: 0;

&.cmd-ctrl-pressed .log-link:hover {
cursor: pointer;
color: ${(props) => props.theme.textLink};
text-decoration: underline;
}
}

.network-with-details {
Expand Down Expand Up @@ -346,6 +352,14 @@ const StyledWrapper = styled.div`
white-space: pre-wrap;
word-break: break-word;
flex: 1;

.log-link {
cursor: text;

&:hover {
text-decoration: underline;
}
}

.log-object {
margin: 4px 0;
Expand Down
67 changes: 64 additions & 3 deletions packages/bruno-app/src/components/Devtools/Console/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import ErrorDetailsPanel from './ErrorDetailsPanel';
import Performance from '../Performance';
import StyledWrapper from './StyledWrapper';
import { useResizablePanel } from 'hooks/useResizablePanel';
import { isMacOS } from 'utils/common/platform';

const MIN_DETAILS_PANEL_WIDTH = 280;
const DETAILS_PANEL_MAX_RATIO = 0.7;
Expand Down Expand Up @@ -132,6 +133,31 @@ const getBrunoTypeMetadata = (obj) => {
return {};
};

// Turns any http(s) URL inside a plain string into a marked, hoverable span.

const getLinkHint = () => (isMacOS() ? 'Hold Cmd and click to open link' : 'Hold Ctrl and click to open link');

const linkifyText = (text, key) => {
if (!text || typeof text !== 'string') return text;
const urlRegex = /(https?:\/\/[^\s"'()]*[^\s"'().,;!?])/g;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (!text.match(urlRegex)) return text;
const parts = text.split(urlRegex);
const linkHint = getLinkHint();
return (
<React.Fragment key={key}>
{parts.map((part, index) =>
part.match(urlRegex) ? (
<span key={index} className="log-link" data-url={part} title={linkHint}>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
{part}
</span>
) : (
part
)
)}
</React.Fragment>
);
};

const LogMessage = ({ message, args }) => {
const { displayedTheme } = useTheme();

Expand Down Expand Up @@ -172,10 +198,10 @@ const LogMessage = ({ message, args }) => {
</div>
);
}
return String(arg);
return linkifyText(String(arg), index);
});
}
return msg;
return linkifyText(msg, 'msg');
};

const formattedMessage = formatMessage(message, args);
Expand All @@ -192,6 +218,7 @@ const LogMessage = ({ message, args }) => {
const ConsoleTab = ({ logs, filters, logCounts, onFilterToggle, onToggleAll, onClearLogs }) => {
const logsEndRef = useRef(null);
const prevLogsCountRef = useRef(0);
const contentAreaRef = useRef(null);

useEffect(() => {
// Only scroll when new logs are added, not when switching tabs
Expand All @@ -201,11 +228,45 @@ const ConsoleTab = ({ logs, filters, logCounts, onFilterToggle, onToggleAll, onC
prevLogsCountRef.current = logs.length;
}, [logs]);

// Toggle a CSS-only class on the container while Cmd/Ctrl is held, so
// links visually become clickable

useEffect(() => {
const isCmdOrCtrlPressed = (event) => (isMacOS() ? event.metaKey : event.ctrlKey);
const updateCmdCtrlClass = (event) => {
const el = contentAreaRef.current;
if (!el) return;
el.classList.toggle('cmd-ctrl-pressed', isCmdOrCtrlPressed(event));
};
window.addEventListener('keydown', updateCmdCtrlClass);
window.addEventListener('keyup', updateCmdCtrlClass);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return () => {
window.removeEventListener('keydown', updateCmdCtrlClass);
window.removeEventListener('keyup', updateCmdCtrlClass);
};
}, []);

// Single delegated click handler for every .log-link in the list, rather
// than one listener per link. Only opens the URL when the modifier is held

const handleContentAreaClick = (event) => {
const linkEl = event.target.closest?.('.log-link');
if (!linkEl) return;

const modifierPressed = isMacOS() ? event.metaKey : event.ctrlKey;
if (!modifierPressed) return;

event.preventDefault();
event.stopPropagation();
const url = linkEl.getAttribute('data-url');
if (url) window?.ipcRenderer?.openExternal(url);
};

const filteredLogs = logs.filter((log) => filters[log.type]);

return (
<div className="tab-content">
<div className="tab-content-area">
<div className="tab-content-area" ref={contentAreaRef} onClick={handleContentAreaClick}>
{filteredLogs.length === 0 ? (
<div className="console-empty">
<IconTerminal2 size={48} strokeWidth={1} />
Expand Down