Finder-launched terminals now inherit a login PATH so tools like ollama resolve, destructive SFTP/editor actions require confirmation, and terminal search plus window dragging work again. SSH forwards and RDP sessions keep listeners and survive reconnect without FastPath crashes. Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
2.8 KiB
TypeScript
109 lines
2.8 KiB
TypeScript
/**
|
|
* TabContextMenu - 标签右键菜单
|
|
*/
|
|
|
|
import { useRef, useEffect } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
interface MenuItem {
|
|
label: string
|
|
action: () => void
|
|
danger?: boolean
|
|
separator?: boolean
|
|
}
|
|
|
|
interface TabContextMenuProps {
|
|
isOpen: boolean
|
|
position: { x: number; y: number }
|
|
onClose: () => void
|
|
onCloseTab: () => void
|
|
onCloseOthers: () => void
|
|
onCloseRight: () => void
|
|
onCloseLeft: () => void
|
|
onCloseAll: () => void
|
|
onClone?: () => void
|
|
onOpenSftp?: () => void
|
|
}
|
|
|
|
export function TabContextMenu({
|
|
isOpen,
|
|
position,
|
|
onClose,
|
|
onCloseTab,
|
|
onCloseOthers,
|
|
onCloseRight,
|
|
onCloseLeft,
|
|
onCloseAll,
|
|
onClone,
|
|
onOpenSftp,
|
|
}: TabContextMenuProps) {
|
|
const { t } = useTranslation('common')
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
const onCloseRef = useRef(onClose)
|
|
onCloseRef.current = onClose
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return
|
|
const handlePointerDown = (e: PointerEvent) => {
|
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
onCloseRef.current()
|
|
}
|
|
}
|
|
const handleKeydown = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onCloseRef.current()
|
|
}
|
|
document.addEventListener('pointerdown', handlePointerDown, true)
|
|
document.addEventListener('keydown', handleKeydown)
|
|
return () => {
|
|
document.removeEventListener('pointerdown', handlePointerDown, true)
|
|
document.removeEventListener('keydown', handleKeydown)
|
|
}
|
|
}, [isOpen])
|
|
|
|
if (!isOpen) return null
|
|
|
|
const items: MenuItem[] = [
|
|
{ label: t('action.close'), action: onCloseTab },
|
|
{ label: t('tab.menu.closeOthers'), action: onCloseOthers },
|
|
{ label: t('tab.menu.closeRight'), action: onCloseRight },
|
|
{ label: t('tab.menu.closeLeft'), action: onCloseLeft },
|
|
{ label: t('tab.menu.closeAll'), action: onCloseAll, danger: true },
|
|
]
|
|
|
|
if (onClone) {
|
|
items.unshift({ label: t('tab.menu.clone'), action: onClone })
|
|
}
|
|
if (onOpenSftp) {
|
|
items.unshift({ label: t('tab.menu.openSftp'), action: onOpenSftp })
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={menuRef}
|
|
style={{ left: position.x, top: position.y }}
|
|
className="fixed z-50 bg-dark-800 border border-dark-700 rounded-lg shadow-xl py-1 min-w-[160px]"
|
|
onPointerDown={(e) => e.stopPropagation()}
|
|
>
|
|
{items.map((item, index) => (
|
|
<button
|
|
key={index}
|
|
type="button"
|
|
onClick={() => {
|
|
item.action()
|
|
onClose()
|
|
}}
|
|
className={`
|
|
w-full px-4 py-2 text-left text-sm transition-colors
|
|
${item.danger
|
|
? 'text-red-400 hover:bg-red-500/20'
|
|
: 'text-content-secondary hover:bg-dark-700'
|
|
}
|
|
`}
|
|
>
|
|
{item.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|