Files
NetMesh/components/SnippetsHistoryItem.tsx
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

98 lines
3.4 KiB
TypeScript

import React, { useState } from 'react';
import { Check, Copy } from 'lucide-react';
import { useI18n } from '../application/i18n/I18nProvider';
import type { ShellHistoryEntry } from '../types';
import { Button } from './ui/button';
import { Input } from './ui/input';
// History Item Component
interface HistoryItemProps {
entry: ShellHistoryEntry;
onSaveAsSnippet: (entry: ShellHistoryEntry, label: string) => void;
onCopy: () => void;
isCopied: boolean;
}
export const HistoryItem: React.FC<HistoryItemProps> = ({ entry, onSaveAsSnippet, onCopy, isCopied }) => {
const { t } = useI18n();
const [isEditing, setIsEditing] = useState(false);
const [label, setLabel] = useState('');
const handleSave = () => {
if (label.trim()) {
onSaveAsSnippet(entry, label);
setIsEditing(false);
setLabel('');
}
};
const formatTime = (timestamp: number) => {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return t('snippets.history.time.justNow');
if (diffMins < 60) return t('snippets.history.time.minutesAgo', { count: diffMins });
if (diffHours < 24) return t('snippets.history.time.hoursAgo', { count: diffHours });
if (diffDays < 7) return t('snippets.history.time.daysAgo', { count: diffDays });
return date.toLocaleDateString();
};
return (
<div className="group rounded-lg bg-background/60 border border-border/50 p-3">
<div className="flex items-start gap-2">
<div className="flex-1 min-w-0">
<div className="font-mono text-sm truncate">{entry.command}</div>
<div className="flex items-center gap-2 mt-1 text-[11px] text-muted-foreground">
<span>{entry.hostLabel}</span>
<span>{t('snippets.history.separator')}</span>
<span>{formatTime(entry.timestamp)}</span>
</div>
</div>
{!isEditing && (
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="ghost"
size="sm"
className="h-7 px-2"
onClick={onCopy}
>
{isCopied ? <Check size={14} /> : <Copy size={14} />}
</Button>
<Button
variant="default"
size="sm"
className="h-7 px-3"
onClick={() => setIsEditing(true)}
>
{t('common.save')}
</Button>
</div>
)}
</div>
{isEditing && (
<div className="mt-3 space-y-2">
<Input
placeholder={t('snippets.history.labelPlaceholder')}
value={label}
onChange={(e) => setLabel(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSave()}
autoFocus
/>
<div className="flex justify-end gap-2">
<Button variant="ghost" size="sm" onClick={() => { setIsEditing(false); setLabel(''); }}>
{t('common.cancel')}
</Button>
<Button size="sm" onClick={handleSave} disabled={!label.trim()}>
{t('snippets.history.saveAsSnippet')}
</Button>
</div>
</div>
)}
</div>
);
};