mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-29 18:11:40 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d176f55a3f | ||
|
|
48a0688923 | ||
|
|
a2076bf38c |
No files matched your search
@@ -727,6 +727,7 @@ export const enTerminalMessages: Messages = {
|
||||
|
||||
// Snippets
|
||||
'snippets.searchPlaceholder': 'Search scripts...',
|
||||
'snippets.action.new': 'New',
|
||||
'snippets.action.newSnippet': 'New Snippet',
|
||||
'snippets.action.newPackage': 'New Script Package',
|
||||
'snippets.action.import': 'Import',
|
||||
|
||||
@@ -689,6 +689,7 @@ export const esTerminalMessages: Messages = {
|
||||
|
||||
// Snippets
|
||||
'snippets.searchPlaceholder': 'Buscar scripts...',
|
||||
'snippets.action.new': 'Nuevo',
|
||||
'snippets.action.newSnippet': 'Nuevo snippet',
|
||||
'snippets.action.newPackage': 'Nuevo paquete de scripts',
|
||||
'snippets.action.import': 'Importar',
|
||||
|
||||
@@ -742,6 +742,7 @@ export const ruTerminalMessages: Messages = {
|
||||
|
||||
// Snippets
|
||||
'snippets.searchPlaceholder': 'Поиск сниппетов...',
|
||||
'snippets.action.new': 'Создать',
|
||||
'snippets.action.newSnippet': 'Новый сниппет',
|
||||
'snippets.action.newPackage': 'Новый пакет',
|
||||
'snippets.action.import': 'Импорт',
|
||||
|
||||
@@ -813,6 +813,7 @@ export const zhCNTerminalMessages: Messages = {
|
||||
|
||||
// Snippets
|
||||
'snippets.searchPlaceholder': '搜索脚本...',
|
||||
'snippets.action.new': '新建',
|
||||
'snippets.action.newSnippet': '新建代码片段',
|
||||
'snippets.action.newPackage': '新建脚本包',
|
||||
'snippets.action.import': '导入',
|
||||
|
||||
@@ -813,6 +813,7 @@ export const zhTWTerminalMessages: Messages = {
|
||||
|
||||
// Snippets
|
||||
'snippets.searchPlaceholder': '搜尋程式碼片段...',
|
||||
'snippets.action.new': '新增',
|
||||
'snippets.action.newSnippet': '新增程式碼片段',
|
||||
'snippets.action.newPackage': '新增程式碼包',
|
||||
'snippets.action.import': '匯入',
|
||||
|
||||
@@ -493,6 +493,7 @@ const SnippetsManager: React.FC<SnippetsManagerProps> = ({
|
||||
);
|
||||
const shellHistory = shellHistoryProp ?? (shellHistoryFromStore as ShellHistoryEntry[]);
|
||||
const [rightPanelMode, setRightPanelMode] = useState<RightPanelMode>('none');
|
||||
const [showNewMenu, setShowNewMenu] = useState(false);
|
||||
const [editingSnippet, setEditingSnippet] = useState<Partial<Snippet>>({
|
||||
label: '',
|
||||
command: '',
|
||||
@@ -1664,23 +1665,60 @@ const SnippetsManager: React.FC<SnippetsManagerProps> = ({
|
||||
/>
|
||||
<div className={snippetsHeaderActionsWrapClass}>
|
||||
<div className={cn(snippetsHeaderActionsClass, hasSnippetsSidePanel && 'pr-1')}>
|
||||
<Button onClick={() => handleEdit()} size="sm" className="h-10 px-3 shrink-0">
|
||||
<Plus size={14} className="mr-2" /> {t('snippets.action.newSnippet')}
|
||||
</Button>
|
||||
<Button onClick={() => handleEdit(undefined, true)} size="sm" variant="secondary" className={cn(vaultHeaderSecondaryButtonClass, 'shrink-0')}>
|
||||
<Play size={14} className="mr-2" /> {t('snippets.action.newScript')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setNewPackageName('');
|
||||
setIsPackageDialogOpen(true);
|
||||
}}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className={cn(vaultHeaderSecondaryButtonClass, 'shrink-0')}
|
||||
>
|
||||
<FolderPlus size={14} className="mr-1" /> {t('snippets.action.newPackage')}
|
||||
</Button>
|
||||
<Dropdown open={showNewMenu} onOpenChange={setShowNewMenu}>
|
||||
<DropdownTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className={cn(vaultHeaderSecondaryButtonClass, 'shrink-0')}
|
||||
aria-expanded={showNewMenu}
|
||||
aria-haspopup="menu"
|
||||
>
|
||||
<Plus size={14} />
|
||||
{t('snippets.action.new')}
|
||||
<ChevronDown size={14} className={cn('transition-transform', showNewMenu && 'rotate-180')} />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownContent className="w-max flex flex-col" align="start" sideOffset={8} keyboardNavigation>
|
||||
<Button
|
||||
variant="ghost"
|
||||
role="menuitem"
|
||||
className="justify-start gap-3 h-10 px-3 whitespace-nowrap focus-visible:bg-accent"
|
||||
onClick={() => {
|
||||
setShowNewMenu(false);
|
||||
handleEdit();
|
||||
}}
|
||||
>
|
||||
<Zap size={16} className="text-sky-500" />
|
||||
{t('snippets.action.newSnippet')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
role="menuitem"
|
||||
className="justify-start gap-3 h-10 px-3 whitespace-nowrap focus-visible:bg-accent"
|
||||
onClick={() => {
|
||||
setShowNewMenu(false);
|
||||
handleEdit(undefined, true);
|
||||
}}
|
||||
>
|
||||
<Play size={16} className="text-violet-500" />
|
||||
{t('snippets.action.newScript')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
role="menuitem"
|
||||
className="justify-start gap-3 h-10 px-3 whitespace-nowrap focus-visible:bg-accent"
|
||||
onClick={() => {
|
||||
setShowNewMenu(false);
|
||||
setNewPackageName('');
|
||||
setIsPackageDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<FolderPlus size={16} className="text-primary" />
|
||||
{t('snippets.action.newPackage')}
|
||||
</Button>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
|
||||
@@ -123,6 +123,8 @@ interface DropdownContentProps {
|
||||
alignToParent?: boolean;
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||
/** Opt in for action menus whose options need keyboard focus and navigation. */
|
||||
keyboardNavigation?: boolean;
|
||||
}
|
||||
|
||||
const DropdownContent: React.FC<DropdownContentProps> = ({
|
||||
@@ -134,9 +136,11 @@ const DropdownContent: React.FC<DropdownContentProps> = ({
|
||||
alignToParent = false,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
keyboardNavigation = false,
|
||||
}) => {
|
||||
const { open, setOpen, triggerRef } = useDropdown();
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const didFocusOnOpen = useRef(false);
|
||||
const [position, setPosition] = useState<{
|
||||
top: number;
|
||||
left: number;
|
||||
@@ -248,6 +252,17 @@ const DropdownContent: React.FC<DropdownContentProps> = ({
|
||||
}
|
||||
}, [open, calculatePosition]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!open) {
|
||||
didFocusOnOpen.current = false;
|
||||
return;
|
||||
}
|
||||
if (keyboardNavigation && position && !didFocusOnOpen.current) {
|
||||
contentRef.current?.querySelector<HTMLButtonElement>('button:not(:disabled)')?.focus();
|
||||
didFocusOnOpen.current = true;
|
||||
}
|
||||
}, [open, position, keyboardNavigation]);
|
||||
|
||||
// Close on click outside
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -293,6 +308,46 @@ const DropdownContent: React.FC<DropdownContentProps> = ({
|
||||
)}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
role={keyboardNavigation ? 'menu' : undefined}
|
||||
onClickCapture={keyboardNavigation ? (event) => {
|
||||
if (event.target instanceof Element && event.target.closest('button:not(:disabled)')) {
|
||||
triggerRef.current?.focus();
|
||||
}
|
||||
} : undefined}
|
||||
onKeyDown={keyboardNavigation ? (event) => {
|
||||
const items = Array.from(
|
||||
event.currentTarget.querySelectorAll<HTMLButtonElement>('button:not(:disabled)'),
|
||||
);
|
||||
const index = items.indexOf(document.activeElement as HTMLButtonElement);
|
||||
let nextIndex: number;
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
nextIndex = (index + 1) % items.length;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
nextIndex = (index - 1 + items.length) % items.length;
|
||||
break;
|
||||
case 'Home':
|
||||
nextIndex = 0;
|
||||
break;
|
||||
case 'End':
|
||||
nextIndex = items.length - 1;
|
||||
break;
|
||||
case 'Escape':
|
||||
case 'Tab':
|
||||
setOpen(false);
|
||||
triggerRef.current?.focus();
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
items[nextIndex]?.focus();
|
||||
} : undefined}
|
||||
style={{
|
||||
top: position?.top ?? -9999,
|
||||
left: position?.left ?? -9999,
|
||||
|
||||
Reference in new issue
Block a user