Compare commits

...
Author SHA1 Message Date
netcatty-bot 3d65dae319 fix(#3562): automated AI fix 2026-09-29 11:02:35 +00:00
7 changed files with 57 additions and 0 deletions

No files matched your search

+3
View File
@@ -27,6 +27,9 @@ export const enScriptsMessages = {
'scripts.actions.runParallel': 'Run on all tabs (parallel)',
'scripts.actions.runSequential': 'Run on all tabs (sequential)',
'scripts.actions.runOnAllTabs': 'Run on all tabs',
'scripts.actions.copyCommand': 'Copy command',
'scripts.actions.copyCommand.copied': 'Command copied',
'scripts.actions.copyCommand.failed': 'Could not copy command',
'scripts.actions.skippedConnectingSessions': '{count} tab(s) still connecting and were skipped',
'scripts.actions.skippedSensitiveSessions': '{count} tab(s) skipped (password/sensitive input)',
'scripts.actions.noRunnableHosts': 'No connectable hosts match this script\'s targets',
+3
View File
@@ -27,6 +27,9 @@ export const esScriptsMessages = {
'scripts.actions.runParallel': 'Ejecutar en todas las pestañas (en paralelo)',
'scripts.actions.runSequential': 'Ejecutar en todas las pestañas (en secuencia)',
'scripts.actions.runOnAllTabs': 'Ejecutar en todas las pestañas',
'scripts.actions.copyCommand': 'Copiar comando',
'scripts.actions.copyCommand.copied': 'Comando copiado',
'scripts.actions.copyCommand.failed': 'No se pudo copiar el comando',
'scripts.actions.skippedConnectingSessions': '{count} pestaña(s) aún conectándose y fueron omitidas',
'scripts.actions.skippedSensitiveSessions': '{count} pestaña(s) omitidas (entrada de contraseña/datos sensibles)',
'scripts.actions.noRunnableHosts': 'Ningún host conectable coincide con los destinos de este script',
+3
View File
@@ -27,6 +27,9 @@ export const ruScriptsMessages = {
'scripts.actions.runParallel': 'Запустить на всех вкладках (параллельно)',
'scripts.actions.runSequential': 'Запустить на всех вкладках (последовательно)',
'scripts.actions.runOnAllTabs': 'Запустить на всех вкладках',
'scripts.actions.copyCommand': 'Копировать команду',
'scripts.actions.copyCommand.copied': 'Команда скопирована',
'scripts.actions.copyCommand.failed': 'Не удалось скопировать команду',
'scripts.actions.skippedConnectingSessions': 'Пропущено вкладок, которые ещё подключаются: {count}',
'scripts.actions.skippedSensitiveSessions': 'Пропущено вкладок с паролем/чувствительным вводом: {count}',
'scripts.actions.noRunnableHosts': 'Нет доступных для подключения хостов, соответствующих целям этого скрипта',
@@ -27,6 +27,9 @@ export const zhCNScriptsMessages = {
'scripts.actions.runParallel': '在所有标签页并行运行',
'scripts.actions.runSequential': '在所有标签页顺序运行',
'scripts.actions.runOnAllTabs': '在所有标签页运行',
'scripts.actions.copyCommand': '复制命令',
'scripts.actions.copyCommand.copied': '命令已复制',
'scripts.actions.copyCommand.failed': '复制命令失败',
'scripts.actions.skippedConnectingSessions': '有 {count} 个标签仍在连接中,已跳过',
'scripts.actions.skippedSensitiveSessions': '有 {count} 个标签处于密码/敏感输入,已跳过',
'scripts.actions.noRunnableHosts': '没有与脚本目标匹配的可连接主机',
@@ -27,6 +27,9 @@ export const zhTWScriptsMessages = {
'scripts.actions.runParallel': '在所有分頁並行執行',
'scripts.actions.runSequential': '在所有分頁依序執行',
'scripts.actions.runOnAllTabs': '在所有分頁執行',
'scripts.actions.copyCommand': '複製指令',
'scripts.actions.copyCommand.copied': '指令已複製',
'scripts.actions.copyCommand.failed': '複製指令失敗',
'scripts.actions.skippedConnectingSessions': '有 {count} 個分頁仍在連線中,已跳過',
'scripts.actions.skippedSensitiveSessions': '有 {count} 個分頁處於密碼/敏感輸入,已跳過',
'scripts.actions.noRunnableHosts': '沒有與指令碼目標相符的可連線主機',
+13
View File
@@ -53,6 +53,19 @@ test("scripts side panel offers run-on-all-tabs for every snippet, not only auto
assert.match(source, /openPackageDialog/);
});
test("scripts side panel snippet context menu offers copy command without running", () => {
// Copy must sit between the run actions and Edit, write snippet.command to
// the clipboard, and confirm with a toast — it never executes the command.
assert.match(source, /onCopyCommand=\{\(\) => handleCopySnippetCommand\(item\.snippet\)\}/);
assert.match(source, /onCopyCommand=\{\(\) => handleCopySnippetCommand\(item\.row\.snippet\)\}/);
assert.match(source, /scripts\.actions\.copyCommand'/);
const menu = source.match(/\{onRunParallel \? \([\s\S]*?<\/ContextMenu>/);
assert.ok(menu, "snippet row context menu should exist");
assert.match(menu[0], /onCopyCommand[\s\S]*?onClick=\{onEdit\}/);
assert.match(menu[0], /<Copy className="[\s\S]*?\{copyCommandLabel\}/);
assert.match(source, /handleCopySnippetCommand[\s\S]*?snippet\.command[\s\S]*?toast\.success/);
});
test("scripts side panel exposes create actions as inline toolbar icons", () => {
assert.match(source, /snippets\.action\.newSnippet/);
assert.match(source, /snippets\.action\.newPackage/);
+29
View File
@@ -10,6 +10,7 @@
import {
CheckSquare,
ChevronRight,
Copy,
Edit2,
Expand,
FolderPlus,
@@ -54,6 +55,7 @@ import { SnippetCommandTooltipContent } from './snippets/SnippetCommandTooltipCo
import { TERMINAL_SIDE_PANEL_INNER_HEADER_CLASS } from './terminalLayer/terminalSidePanelChrome';
import { isNonPrimaryPointer, primaryOnlyDragHandlers } from './ui/primaryOnlyDrag';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
import { toast } from './ui/toast';
const toolbarIconButtonClass =
'h-7 w-7 shrink-0 flex items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors disabled:opacity-40 disabled:pointer-events-none';
@@ -889,6 +891,20 @@ const ScriptsSidePanelInner: React.FC<ScriptsSidePanelProps> = ({
);
}, []);
const handleCopySnippetCommand = useCallback((snippet: Snippet) => {
if (!snippet.command) return;
navigator.clipboard.writeText(snippet.command).then(
() => {
toast.success(t('scripts.actions.copyCommand.copied'), {
title: snippet.label,
});
},
() => {
toast.error(t('scripts.actions.copyCommand.failed'));
},
);
}, [t]);
if (!isVisible) return null;
const hasAnyContent = snippets.length > 0 || packages.length > 0;
@@ -1129,6 +1145,8 @@ const ScriptsSidePanelInner: React.FC<ScriptsSidePanelProps> = ({
onClick={() => handleSnippetClick(item.snippet)}
onEdit={() => handleEditSnippet(item.snippet)}
onDelete={() => handleDeleteSnippet(item.snippet.id)}
onCopyCommand={() => handleCopySnippetCommand(item.snippet)}
copyCommandLabel={t('scripts.actions.copyCommand')}
onRunParallel={onRunScriptOnWorkspace
? () => onRunScriptOnWorkspace(item.snippet, 'parallel')
: undefined}
@@ -1182,6 +1200,8 @@ const ScriptsSidePanelInner: React.FC<ScriptsSidePanelProps> = ({
onClick={() => handleSnippetClick(item.row.snippet)}
onEdit={() => handleEditSnippet(item.row.snippet)}
onDelete={() => handleDeleteSnippet(item.row.snippet.id)}
onCopyCommand={() => handleCopySnippetCommand(item.row.snippet)}
copyCommandLabel={t('scripts.actions.copyCommand')}
onRunParallel={onRunScriptOnWorkspace
? () => onRunScriptOnWorkspace(item.row.snippet, 'parallel')
: undefined}
@@ -1505,6 +1525,8 @@ interface SnippetRowProps {
onDelete: () => void;
onRunParallel?: () => void;
onRunSequential?: () => void;
onCopyCommand?: () => void;
copyCommandLabel?: string;
runParallelLabel?: string;
runSequentialLabel?: string;
editLabel: string;
@@ -1527,6 +1549,8 @@ const SnippetRow = memo<SnippetRowProps>(({
onDelete,
onRunParallel,
onRunSequential,
onCopyCommand,
copyCommandLabel,
runParallelLabel,
runSequentialLabel,
editLabel,
@@ -1604,6 +1628,11 @@ const SnippetRow = memo<SnippetRowProps>(({
<Layers className="mr-2 h-4 w-4" /> {runSequentialLabel}
</ContextMenuItem>
) : null}
{onCopyCommand ? (
<ContextMenuItem onClick={onCopyCommand}>
<Copy className="mr-2 h-4 w-4" /> {copyCommandLabel}
</ContextMenuItem>
) : null}
<ContextMenuItem onClick={onEdit}>
<Edit2 className="mr-2 h-4 w-4" /> {editLabel}
</ContextMenuItem>