Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions web/src/components/ChatView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3087,6 +3087,7 @@ export function ChatView({ sessionRef, sessionId, onSessionPromoted, onCommandWo
onToggleMaximize={onToggleSidePanelMaximized}
onSelectChangeFile={openSessionChangePreview}
onSelectGitChangeFile={openGitChangePreview}
onOpenFilePreview={openFilePreview}
/>
</div>
)}
Expand Down
82 changes: 46 additions & 36 deletions web/src/components/GitChangeReview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function statusBadgeClass(status) {
}
}

function GitChangeFileRow({ row, open, selected, onToggle }) {
function GitChangeFileRow({ row, open, selected, onToggle, onOpenFile }) {
const name = row.path.split(/[\\/]/).pop();
const parent = row.path.includes('/') ? row.path.slice(0, row.path.lastIndexOf('/')) : '';
return (
Expand Down Expand Up @@ -89,6 +89,25 @@ function GitChangeFileRow({ row, open, selected, onToggle }) {
<span className="text-fg-mute">{row.statLabel}</span>
)}
</span>
{!!onOpenFile && (
// 已删除文件磁盘上不存在,按钮隐形占位保持各行 ± 计数对齐。
<span
role="button"
tabIndex={-1}
className={clsx('ace-change-row-action', row.status === 'D' && 'is-hidden')}
title="转到文件"
aria-label={`转到文件 ${row.path}`}
onPointerDown={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
if (row.status !== 'D') onOpenFile(row.path);
}}
>
<VsIcon name="openFile" size={13} />
</span>
)}
</button>
);
}
Expand All @@ -101,6 +120,7 @@ export function GitChangeDetails({
expandedFileRevision = 0,
busy = false,
onSelectFile,
onOpenFilePreview,
}) {
const [list, setList] = useState(null);
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -315,41 +335,30 @@ export function GitChangeDetails({

return (
<div className="ace-review-panel" data-change-region="preview-panel" ref={panelRef}>
<div className="ace-review-summary" data-desktop-review-kind="summary">
<div className="ace-review-title min-w-0">
<VsIcon name="editWindow" size={15} />
<span>变更</span>
{(list?.branch) && (
<>
<span className="text-fg-mute font-normal truncate max-w-[120px]" title={list.branch}>{list.branch}</span>
<span className="text-fg-mute opacity-60" aria-hidden="true">→</span>
</>
)}
<span className="text-fg-mute font-normal truncate max-w-[140px]" title={`比较基线:${base}`}>{base}</span>
</div>
<div className="flex items-center gap-2 shrink-0">
{loading && <span className="ace-spinner w-3 h-3" />}
{list && (
<span className="text-[11px]">
<span className="ace-change-add">+{list.total_additions ?? 0}</span>{' '}
<span className="ace-change-del">-{list.total_deletions ?? 0}</span>
</span>
)}
<button
type="button"
className="ace-side-panel-refresh-btn !static"
title="刷新变更"
aria-label="刷新变更"
onClick={() => { changesCache.markStale(cwdRef.current); setPatches({}); fetchList(true); }}
>
<VsIcon name="refresh" size={14} />
</button>
</div>
</div>

{list && rows.length > 0 && (
<div className="px-3 py-1 text-[11px] text-fg-mute border-b border-border/50 truncate">
{buildSummaryLabel(list)}
{(list || loading) && (
<div
className="px-3 py-1 text-[11px] text-fg-mute border-b border-border/50 flex items-center gap-2 shrink-0"
data-desktop-review-kind="summary"
>
<span className="truncate">{buildSummaryLabel(list)}</span>
{loading && <span className="ace-spinner w-3 h-3 shrink-0" />}
<span className="ml-auto shrink-0 inline-flex items-center gap-1.5">
{list && (
<span>
<span className="ace-change-add">+{list.total_additions ?? 0}</span>{' '}
<span className="ace-change-del">-{list.total_deletions ?? 0}</span>
</span>
)}
<button
type="button"
className="ace-review-collapse-all-btn"
title="折叠全部 diff"
aria-label="折叠全部 diff"
onClick={() => setOpenFiles(new Set())}
>
<VsIcon name="collapseAll" size={14} />
</button>
</span>
</div>
)}

Expand Down Expand Up @@ -387,6 +396,7 @@ export function GitChangeDetails({
open={open}
selected={selected}
onToggle={() => toggleFile(row.path, open)}
onOpenFile={onOpenFilePreview}
/>
{open && (
<div className="ace-review-diff-scroll">
Expand Down
20 changes: 20 additions & 0 deletions web/src/components/GitChangesPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function GitChangesPanel({
visible = true,
selectedFile = '',
onOpenFile,
onOpenFilePreview,
onBaseChange,
}) {
const { candidates, initial } = useMemo(
Expand Down Expand Up @@ -260,6 +261,25 @@ export function GitChangesPanel({
)
: <span className="text-fg-mute">{row.statLabel}</span>}
</span>
{!!onOpenFilePreview && (
// 已删除文件磁盘上不存在,按钮隐形占位保持各行 ± 计数对齐。
<span
role="button"
tabIndex={-1}
className={clsx('ace-change-row-action', row.status === 'D' && 'is-hidden')}
title="转到文件"
aria-label={`转到文件 ${row.path}`}
onPointerDown={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
if (row.status !== 'D') onOpenFilePreview(row.path);
}}
>
<VsIcon name="openFile" size={13} />
</span>
)}
</button>
))}
</div>
Expand Down
4 changes: 3 additions & 1 deletion web/src/components/PreviewDetailsPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export function PreviewDetailsPanel({
onToggleMaximize,
onSelectChangeFile,
onSelectGitChangeFile,
onOpenFilePreview,
}) {
const tabListRef = useRef(null);
const tabDragRef = useRef(null);
Expand Down Expand Up @@ -250,6 +251,7 @@ export function PreviewDetailsPanel({
expandedFileRevision={active.expandedFileRevision || 0}
busy={busy}
onSelectFile={onSelectGitChangeFile}
onOpenFilePreview={onOpenFilePreview}
/>
);
}
Expand All @@ -263,7 +265,7 @@ export function PreviewDetailsPanel({
onToggleWrapPreview={() => setWrapPreview((prev) => !prev)}
/>
);
}, [active, api, busy, changeGroups, changeSummary, cwd, onSelectChangeFile, onSelectGitChangeFile, setWrapPreview, wrapPreview]);
}, [active, api, busy, changeGroups, changeSummary, cwd, onOpenFilePreview, onSelectChangeFile, onSelectGitChangeFile, setWrapPreview, wrapPreview]);

const handleTabWheel = useCallback((event) => {
const el = tabListRef.current;
Expand Down
1 change: 1 addition & 0 deletions web/src/components/SidePanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ export function SidePanel({
visible={!collapsed && activeTab === 'changes'}
selectedFile={selectedGitChangeFile}
onOpenFile={onOpenGitChangePreview}
onOpenFilePreview={onOpenFilePreview}
onBaseChange={onGitBaseChange}
/>
) : (
Expand Down
56 changes: 56 additions & 0 deletions web/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,8 @@ body {
min-height: 28px;
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
grid-auto-columns: max-content;
grid-auto-flow: column;
align-items: center;
gap: 5px;
padding: 3px 8px;
Expand Down Expand Up @@ -2323,6 +2325,60 @@ body {
white-space: nowrap;
}

/* 变更文件行的行内动作按钮(转到文件):hover / 选中 / 键盘聚焦时浮现,
平时透明占位保持 ± 计数各行对齐;is-hidden = 已删除文件的隐形占位。 */
.ace-change-row-action {
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border-radius: 4px;
color: var(--ace-fg-mute);
opacity: 0;
transition: opacity .12s ease, color .15s ease, background .15s ease;
}
.ace-change-compact-row:hover .ace-change-row-action,
.ace-change-compact-row.is-selected .ace-change-row-action,
.ace-change-file-row:hover .ace-change-row-action,
.ace-change-file-row.is-selected .ace-change-row-action,
.ace-change-row-action:focus-visible {
opacity: 1;
}
.ace-change-row-action:hover {
color: var(--ace-fg);
background: var(--ace-surface-alt);
}
.ace-change-row-action.is-hidden {
visibility: hidden;
pointer-events: none;
}

/* 变更详情栏「N 个文件已更改」行右端的折叠全部 diff 按钮。 */
.ace-review-collapse-all-btn {
width: 24px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 4px;
background: transparent;
color: var(--ace-fg-mute);
cursor: pointer;
flex-shrink: 0;
transition: color .15s ease, background .15s ease;
}
.ace-review-collapse-all-btn:hover {
color: var(--ace-fg);
background: var(--ace-surface-alt);
}
.ace-review-collapse-all-btn:focus-visible {
outline: 2px solid var(--ace-accent);
outline-offset: -2px;
}

.ace-review-panel {
flex: 1;
min-height: 0;
Expand Down
Loading