Merge pull request #66 from Psypeal/fix/ctrl-r-shortcut

fix: prevent Ctrl+R page reload and show platform-aware shortcuts (#58)
This commit is contained in:
matt 2026-02-23 21:58:34 +08:00 committed by GitHub
commit c4975f1874
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 45 additions and 10 deletions

View file

@ -481,7 +481,16 @@ function createWindow(): void {
const ZOOM_OUT_KEYS = new Set(['-', '_']);
mainWindow.webContents.on('before-input-event', (event, input) => {
if (!mainWindow || mainWindow.isDestroyed()) return;
if (!input.meta || input.type !== 'keyDown') return;
if (input.type !== 'keyDown') return;
// Prevent Electron's default Ctrl+R / Cmd+R page reload so the renderer
// keyboard handler can use it as "Refresh Session" (fixes #58).
if ((input.control || input.meta) && input.key.toLowerCase() === 'r') {
event.preventDefault();
return;
}
if (!input.meta) return;
const currentLevel = mainWindow.webContents.getZoomLevel();

View file

@ -11,6 +11,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { api } from '@renderer/api';
import { useStore } from '@renderer/store';
import { formatShortcut } from '@renderer/utils/stringUtils';
import { createLogger } from '@shared/utils/logger';
import { useShallow } from 'zustand/react/shallow';
@ -75,7 +76,7 @@ const CommandSearch = ({ value, onChange }: Readonly<CommandSearchProps>): React
<button
onClick={() => openCommandPalette()}
className="flex shrink-0 items-center gap-1 transition-opacity hover:opacity-80"
title={selectedProjectId ? 'Search in sessions (⌘K)' : 'Search projects (⌘K)'}
title={selectedProjectId ? `Search in sessions (${formatShortcut('K')})` : `Search projects (${formatShortcut('K')})`}
>
<kbd className="flex h-5 items-center justify-center rounded border border-border bg-surface-overlay px-1.5 text-[10px] font-medium text-text-muted">
<Command className="size-2.5" />

View file

@ -16,7 +16,7 @@ import { useEffect, useRef, useState } from 'react';
import { isElectronMode } from '@renderer/api';
import { HEADER_ROW1_HEIGHT, HEADER_ROW2_HEIGHT } from '@renderer/constants/layout';
import { useStore } from '@renderer/store';
import { truncateMiddle } from '@renderer/utils/stringUtils';
import { formatShortcut, truncateMiddle } from '@renderer/utils/stringUtils';
import { Check, ChevronDown, GitBranch, PanelLeft } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
@ -369,7 +369,7 @@ export const SidebarHeader = (): React.JSX.Element => {
backgroundColor: isCollapseHovered ? 'var(--color-surface-raised)' : 'transparent',
} as React.CSSProperties
}
title="Collapse sidebar (⌘B)"
title={`Collapse sidebar (${formatShortcut('B')})`}
>
<PanelLeft className="size-4" />
</button>

View file

@ -14,6 +14,7 @@ import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortabl
import { isElectronMode } from '@renderer/api';
import { HEADER_ROW1_HEIGHT } from '@renderer/constants/layout';
import { useStore } from '@renderer/store';
import { formatShortcut } from '@renderer/utils/stringUtils';
import { Bell, PanelLeft, Plus, RefreshCw, Search, Settings } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
@ -339,7 +340,7 @@ export const TabBar = ({ paneId }: TabBarProps): React.JSX.Element => {
onMouseEnter={() => setRefreshHover(true)}
onMouseLeave={() => setRefreshHover(false)}
onClick={handleRefresh}
title="Refresh Session (Cmd+R)"
title={`Refresh Session (${formatShortcut('R')})`}
>
<RefreshCw className="size-4" />
</button>
@ -376,7 +377,7 @@ export const TabBar = ({ paneId }: TabBarProps): React.JSX.Element => {
color: searchHover ? 'var(--color-text)' : 'var(--color-text-muted)',
backgroundColor: searchHover ? 'var(--color-surface-raised)' : 'transparent',
}}
title="Search (Cmd+K)"
title={`Search (${formatShortcut('K')})`}
>
<Search className="size-4" />
</button>

View file

@ -7,6 +7,8 @@
import { useEffect, useRef } from 'react';
import { formatShortcut } from '@renderer/utils/stringUtils';
interface TabContextMenuProps {
x: number;
y: number;
@ -100,13 +102,13 @@ export const TabContextMenu = ({
onClick={handleClick(onCloseSelectedTabs)}
/>
) : (
<MenuItem label="Close Tab" shortcut="⌘W" onClick={handleClick(onCloseTab)} />
<MenuItem label="Close Tab" shortcut={formatShortcut('W')} onClick={handleClick(onCloseTab)} />
)}
<MenuItem label="Close Other Tabs" onClick={handleClick(onCloseOtherTabs)} />
<div className="mx-2 my-1 border-t" style={{ borderColor: 'var(--color-border)' }} />
<MenuItem
label="Split Right"
shortcut="⌘\"
shortcut={formatShortcut('\\')}
onClick={handleClick(onSplitRight)}
disabled={disableSplit}
/>
@ -127,7 +129,7 @@ export const TabContextMenu = ({
/>
)}
<div className="mx-2 my-1 border-t" style={{ borderColor: 'var(--color-border)' }} />
<MenuItem label="Close All Tabs" shortcut="⇧⌘W" onClick={handleClick(onCloseAllTabs)} />
<MenuItem label="Close All Tabs" shortcut={formatShortcut('W', { shift: true })} onClick={handleClick(onCloseAllTabs)} />
</div>
);
};

View file

@ -7,6 +7,7 @@
import { useEffect, useRef, useState } from 'react';
import { MAX_PANES } from '@renderer/types/panes';
import { formatShortcut } from '@renderer/utils/stringUtils';
import { Check, ClipboardCopy, Eye, EyeOff, Pin, PinOff, Terminal } from 'lucide-react';
interface SessionContextMenuProps {
@ -98,7 +99,7 @@ export const SessionContextMenu = ({
}}
>
<MenuItem label="Open in Current Pane" onClick={handleClick(onOpenInCurrentPane)} />
<MenuItem label="Open in New Tab" shortcut="⌘ Click" onClick={handleClick(onOpenInNewTab)} />
<MenuItem label="Open in New Tab" shortcut={`${formatShortcut('')}Click`} onClick={handleClick(onOpenInNewTab)} />
<div className="mx-2 my-1 border-t" style={{ borderColor: 'var(--color-border)' }} />
<MenuItem
label="Split Right and Open"

View file

@ -2,6 +2,27 @@
* String utilities for display formatting.
*/
const isMacPlatform =
typeof window !== 'undefined' && window.navigator.userAgent.includes('Macintosh');
/** Returns '⌘' on macOS, 'Ctrl' on Windows/Linux. */
export const modKey = isMacPlatform ? '⌘' : 'Ctrl+';
/** Returns '⇧' on macOS, 'Shift+' on Windows/Linux. */
export const shiftKey = isMacPlatform ? '⇧' : 'Shift+';
/**
* Formats a keyboard shortcut for the current platform.
* @example formatShortcut('R') '⌘R' on Mac, 'Ctrl+R' on Windows/Linux
* @example formatShortcut('W', { shift: true }) '⇧⌘W' on Mac, 'Ctrl+Shift+W' on Windows/Linux
*/
export function formatShortcut(key: string, opts?: { shift?: boolean }): string {
if (opts?.shift) {
return isMacPlatform ? `${shiftKey}${modKey}${key}` : `${modKey}${shiftKey}${key}`;
}
return `${modKey}${key}`;
}
/**
* Truncates a string in the middle to preserve both the beginning and end.
* Useful for branch names where the unique identifier is often at the end.