mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
feat(desktop): collapse long change-file lists in the turn card (show N more)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0c24a5da3e
commit
f2f230914d
@ -251,3 +251,49 @@ describe('CurrentTurnChangeCard – open-with buttons', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('CurrentTurnChangeCard – collapse long file lists', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
ensureTargetsMock.mockResolvedValue(undefined)
|
||||
openPreviewSpy.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
function makeFiles(count: number): string[] {
|
||||
return Array.from({ length: count }, (_, i) => `/w/proj/src/file${i + 1}.ts`)
|
||||
}
|
||||
|
||||
it('does NOT render a show-more toggle with ≤5 files', () => {
|
||||
renderCard(makeFiles(5))
|
||||
expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(5)
|
||||
expect(screen.queryByText('chat.turnChangesShowMore')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('chat.turnChangesShowLess')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('with 8 files shows only 5 rows + a "show more" toggle (remaining = 3)', () => {
|
||||
renderCard(makeFiles(8))
|
||||
// only the first 5 diff-toggle rows are rendered
|
||||
expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(5)
|
||||
// the show-more toggle is present (identity-mock key). The real key carries the
|
||||
// remaining count via '{count}'; with the placeholder-bearing real string this
|
||||
// renders as "再显示 3 个文件" (8 - COLLAPSED_COUNT(5) = 3).
|
||||
expect(screen.getByText('chat.turnChangesShowMore')).toBeInTheDocument()
|
||||
// …and it is the only toggle (no "show less" while collapsed)
|
||||
expect(screen.queryByText('chat.turnChangesShowLess')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('clicking "show more" reveals all 8 rows and shows "show less"; clicking again re-collapses', () => {
|
||||
renderCard(makeFiles(8))
|
||||
const showMore = screen.getByText('chat.turnChangesShowMore')
|
||||
|
||||
fireEvent.click(showMore)
|
||||
expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(8)
|
||||
const showLess = screen.getByText('chat.turnChangesShowLess')
|
||||
expect(showLess).toBeInTheDocument()
|
||||
expect(screen.queryByText('chat.turnChangesShowMore')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(showLess)
|
||||
expect(screen.getAllByRole('button', { name: /turnChangesShowDiffAria/ })).toHaveLength(5)
|
||||
expect(screen.getByText('chat.turnChangesShowMore')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { sessionsApi, type SessionTurnCheckpoint } from '../../api/sessions'
|
||||
import { useTranslation, type TranslationKey } from '../../i18n'
|
||||
import { WorkspaceDiffSurface } from '../workspace/WorkspaceCodeSurface'
|
||||
@ -34,6 +34,8 @@ type ChangedFileEntry = {
|
||||
displayPath: string
|
||||
}
|
||||
|
||||
const COLLAPSED_COUNT = 5
|
||||
|
||||
export function CurrentTurnChangeCard({
|
||||
sessionId,
|
||||
targetUserMessageId,
|
||||
@ -48,6 +50,7 @@ export function CurrentTurnChangeCard({
|
||||
const [expandedPath, setExpandedPath] = useState<string | null>(null)
|
||||
const [diffByPath, setDiffByPath] = useState<Record<string, DiffPreviewState>>({})
|
||||
const [openWith, setOpenWith] = useState<{ items: OpenWithItem[]; anchor: DOMRect } | null>(null)
|
||||
const [showAllFiles, setShowAllFiles] = useState(false)
|
||||
|
||||
const files = useMemo<ChangedFileEntry[]>(
|
||||
() => checkpoint.code.filesChanged.map((filePath) => ({
|
||||
@ -57,6 +60,11 @@ export function CurrentTurnChangeCard({
|
||||
[checkpoint.code.filesChanged, workDir],
|
||||
)
|
||||
|
||||
const canCollapse = files.length > COLLAPSED_COUNT
|
||||
const visibleFiles = canCollapse && !showAllFiles
|
||||
? files.slice(0, COLLAPSED_COUNT)
|
||||
: files
|
||||
|
||||
const toggleDiff = useCallback((fileEntry: ChangedFileEntry) => {
|
||||
const nextExpandedPath = expandedPath === fileEntry.apiPath ? null : fileEntry.apiPath
|
||||
setExpandedPath(nextExpandedPath)
|
||||
@ -171,7 +179,7 @@ export function CurrentTurnChangeCard({
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-[var(--color-border)]">
|
||||
{files.map((fileEntry) => {
|
||||
{visibleFiles.map((fileEntry) => {
|
||||
const isExpanded = expandedPath === fileEntry.apiPath
|
||||
const diffState = diffByPath[fileEntry.apiPath]
|
||||
const fileName = fileEntry.displayPath.split('/').pop() || fileEntry.displayPath
|
||||
@ -238,6 +246,26 @@ export function CurrentTurnChangeCard({
|
||||
})}
|
||||
</div>
|
||||
|
||||
{canCollapse && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAllFiles((current) => !current)}
|
||||
className="flex w-full items-center justify-center gap-1 border-t border-[var(--color-border)] px-4 py-2 text-xs text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-brand)]/35"
|
||||
>
|
||||
{showAllFiles ? (
|
||||
<>
|
||||
{t('chat.turnChangesShowLess')}
|
||||
<ChevronUp size={14} strokeWidth={1.9} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('chat.turnChangesShowMore', { count: String(files.length - COLLAPSED_COUNT) })}
|
||||
<ChevronDown size={14} strokeWidth={1.9} />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="border-t border-[var(--color-error)]/20 bg-[var(--color-error-container)]/18 px-4 py-3 text-xs text-[var(--color-error)]">
|
||||
{error}
|
||||
|
||||
@ -1213,6 +1213,8 @@ export const en = {
|
||||
'chat.turnChangesHideDiffAria': 'Hide diff for {path}',
|
||||
'chat.turnChangesDiffLoading': 'Loading diff...',
|
||||
'chat.turnChangesDiffUnavailable': 'Diff unavailable.',
|
||||
'chat.turnChangesShowMore': 'Show {count} more files',
|
||||
'chat.turnChangesShowLess': 'Show less',
|
||||
|
||||
// ─── Streaming Indicator ──────────────────────────────────────
|
||||
'streaming.thinking': 'Thinking',
|
||||
|
||||
@ -1215,6 +1215,8 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'chat.turnChangesHideDiffAria': '收起 {path} 的 diff',
|
||||
'chat.turnChangesDiffLoading': '正在加载 diff...',
|
||||
'chat.turnChangesDiffUnavailable': 'Diff 不可用。',
|
||||
'chat.turnChangesShowMore': '再显示 {count} 个文件',
|
||||
'chat.turnChangesShowLess': '收起',
|
||||
|
||||
// ─── Streaming Indicator ──────────────────────────────────────
|
||||
'streaming.thinking': '思考中',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user