feat: terminal auto-copy on select and right-click paste
This commit is contained in:
parent
97a1b3d7b0
commit
f6511010bd
@ -177,7 +177,7 @@ function HostManager({ hosts, initialEditHost, onClose, onConnect, onUpdate }) {
|
|||||||
let message = `导入完成:新增 ${result.imported} 个`;
|
let message = `导入完成:新增 ${result.imported} 个`;
|
||||||
if (result.updated > 0) message += `,更新 ${result.updated} 个`;
|
if (result.updated > 0) message += `,更新 ${result.updated} 个`;
|
||||||
if (result.skipped > 0) message += `,跳过 ${result.skipped} 个`;
|
if (result.skipped > 0) message += `,跳过 ${result.skipped} 个`;
|
||||||
|
|
||||||
setImportExportResult({
|
setImportExportResult({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message
|
message
|
||||||
@ -289,20 +289,19 @@ function HostManager({ hosts, initialEditHost, onClose, onConnect, onUpdate }) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 导入导出结果提示 */}
|
{/* 导入导出结果提示 */}
|
||||||
{importExportResult && (
|
{importExportResult && (
|
||||||
<div className={`mx-6 mt-4 p-3 rounded-lg border ${
|
<div className={`mx-6 mt-4 p-3 rounded-lg border ${importExportResult.type === 'success'
|
||||||
importExportResult.type === 'success'
|
? 'bg-shell-success/10 border-shell-success/30 text-shell-success'
|
||||||
? 'bg-shell-success/10 border-shell-success/30 text-shell-success'
|
: 'bg-shell-error/10 border-shell-error/30 text-shell-error'
|
||||||
: 'bg-shell-error/10 border-shell-error/30 text-shell-error'
|
}`}>
|
||||||
}`}>
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{importExportResult.type === 'success' ? <FiCheck size={16} /> : <FiX size={16} />}
|
{importExportResult.type === 'success' ? <FiCheck size={16} /> : <FiX size={16} />}
|
||||||
<span className="text-sm">{importExportResult.message}</span>
|
<span className="text-sm">{importExportResult.message}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setImportExportResult(null)}
|
onClick={() => setImportExportResult(null)}
|
||||||
className="p-1 hover:opacity-70 transition-opacity"
|
className="p-1 hover:opacity-70 transition-opacity"
|
||||||
>
|
>
|
||||||
@ -342,8 +341,8 @@ function HostManager({ hosts, initialEditHost, onClose, onConnect, onUpdate }) {
|
|||||||
setTestResult(null);
|
setTestResult(null);
|
||||||
}}
|
}}
|
||||||
className={`group p-3 rounded-lg border transition-all cursor-pointer
|
className={`group p-3 rounded-lg border transition-all cursor-pointer
|
||||||
${isSelected
|
${isSelected
|
||||||
? 'bg-shell-accent/10 border-shell-accent/50'
|
? 'bg-shell-accent/10 border-shell-accent/50'
|
||||||
: 'bg-shell-card/50 border-shell-border hover:border-shell-accent/30 hover:bg-shell-card'
|
: 'bg-shell-card/50 border-shell-border hover:border-shell-accent/30 hover:bg-shell-card'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -547,11 +546,10 @@ function HostManager({ hosts, initialEditHost, onClose, onConnect, onUpdate }) {
|
|||||||
key={color}
|
key={color}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setFormData({ ...formData, color })}
|
onClick={() => setFormData({ ...formData, color })}
|
||||||
className={`w-8 h-8 rounded-lg transition-all ${
|
className={`w-8 h-8 rounded-lg transition-all ${formData.color === color
|
||||||
formData.color === color
|
? 'ring-2 ring-offset-2 ring-offset-shell-surface ring-white/50 scale-110'
|
||||||
? 'ring-2 ring-offset-2 ring-offset-shell-surface ring-white/50 scale-110'
|
: 'hover:scale-105'
|
||||||
: 'hover:scale-105'
|
}`}
|
||||||
}`}
|
|
||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@ -577,11 +575,10 @@ function HostManager({ hosts, initialEditHost, onClose, onConnect, onUpdate }) {
|
|||||||
{/* 测试结果 */}
|
{/* 测试结果 */}
|
||||||
{testResult && (
|
{testResult && (
|
||||||
<div
|
<div
|
||||||
className={`p-4 rounded-lg border ${
|
className={`p-4 rounded-lg border ${testResult.success
|
||||||
testResult.success
|
? 'bg-shell-success/10 border-shell-success/30 text-shell-success'
|
||||||
? 'bg-shell-success/10 border-shell-success/30 text-shell-success'
|
: 'bg-shell-error/10 border-shell-error/30 text-shell-error'
|
||||||
: 'bg-shell-error/10 border-shell-error/30 text-shell-error'
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{testResult.success ? <FiCheck size={18} /> : <FiX size={18} />}
|
{testResult.success ? <FiCheck size={18} /> : <FiX size={18} />}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
|
|||||||
const initTimerRef = useRef(null);
|
const initTimerRef = useRef(null);
|
||||||
const hasConnectedRef = useRef(false);
|
const hasConnectedRef = useRef(false);
|
||||||
const resizeObserverRef = useRef(null);
|
const resizeObserverRef = useRef(null);
|
||||||
|
const contextMenuHandlerRef = useRef(null);
|
||||||
|
|
||||||
const onConnectionChangeRef = useRef(onConnectionChange);
|
const onConnectionChangeRef = useRef(onConnectionChange);
|
||||||
onConnectionChangeRef.current = onConnectionChange;
|
onConnectionChangeRef.current = onConnectionChange;
|
||||||
@ -203,6 +204,30 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
|
|||||||
window.electronAPI.ssh.write(connectionIdRef.current, data);
|
window.electronAPI.ssh.write(connectionIdRef.current, data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 选中自动复制到剪贴板
|
||||||
|
term.onSelectionChange(() => {
|
||||||
|
const selection = term.getSelection();
|
||||||
|
if (selection && selection.length > 0) {
|
||||||
|
navigator.clipboard.writeText(selection).catch(err => {
|
||||||
|
console.error('复制到剪贴板失败:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 右键粘贴
|
||||||
|
contextMenuHandlerRef.current = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const text = await navigator.clipboard.readText();
|
||||||
|
if (text && connectionIdRef.current && window.electronAPI) {
|
||||||
|
window.electronAPI.ssh.write(connectionIdRef.current, text);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('从剪贴板粘贴失败:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
container.addEventListener('contextmenu', contextMenuHandlerRef.current);
|
||||||
|
|
||||||
resizeObserverRef.current = new ResizeObserver(() => {
|
resizeObserverRef.current = new ResizeObserver(() => {
|
||||||
fitTerminal();
|
fitTerminal();
|
||||||
@ -248,6 +273,12 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
|
|||||||
resizeObserverRef.current = null;
|
resizeObserverRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清理右键菜单事件监听器
|
||||||
|
if (contextMenuHandlerRef.current && terminalRef.current) {
|
||||||
|
terminalRef.current.removeEventListener('contextmenu', contextMenuHandlerRef.current);
|
||||||
|
contextMenuHandlerRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (cleanupListenersRef.current) {
|
if (cleanupListenersRef.current) {
|
||||||
cleanupListenersRef.current();
|
cleanupListenersRef.current();
|
||||||
cleanupListenersRef.current = null;
|
cleanupListenersRef.current = null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user