update
This commit is contained in:
@@ -12,7 +12,7 @@ import { useFileEditorStore, type FileInfo } from '../stores/fileEditor.store';
|
||||
import { useSessionStore } from '../stores/session.store';
|
||||
import { useSettingsStore } from '../stores/settings.store'; // +++ 实例化 Settings Store +++
|
||||
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store'; // +++ 实例化焦点切换 Store +++
|
||||
import { useFileManagerContextMenu } from '../composables/file-manager/useFileManagerContextMenu'; // +++ 导入上下文菜单 Composable +++
|
||||
import { useFileManagerContextMenu, type ClipboardState } from '../composables/file-manager/useFileManagerContextMenu'; // +++ 导入上下文菜单 Composable 和 ClipboardState +++
|
||||
import { useFileManagerSelection } from '../composables/file-manager/useFileManagerSelection'; // +++ 导入选择 Composable +++
|
||||
import { useFileManagerDragAndDrop } from '../composables/file-manager/useFileManagerDragAndDrop'; // +++ 导入拖放 Composable +++
|
||||
import { useFileManagerKeyboardNavigation } from '../composables/file-manager/useFileManagerKeyboardNavigation'; // +++ 导入键盘导航 Composable +++
|
||||
@@ -129,6 +129,11 @@ const editablePath = ref('');
|
||||
const fileListContainerRef = ref<HTMLDivElement | null>(null); // 文件列表容器引用 (保留,传递给 Composable)
|
||||
// const scrollIntervalId = ref<number | null>(null); // 已移至 useFileManagerDragAndDrop
|
||||
|
||||
// +++ 新增:剪贴板状态 +++
|
||||
const clipboardState = ref<ClipboardState>({ hasContent: false });
|
||||
const clipboardSourcePaths = ref<string[]>([]); // 存储源完整路径
|
||||
const clipboardSourceBaseDir = ref<string>(''); // 存储源目录
|
||||
|
||||
const rowSizeMultiplier = ref(1.0); // 新增:行大小(字体)乘数, 默认值会被 store 覆盖
|
||||
// --- 键盘导航状态 (移至 useFileManagerKeyboardNavigation) ---
|
||||
// const selectedIndex = ref<number>(-1);
|
||||
@@ -370,6 +375,62 @@ const handleNewFileContextMenuClick = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// +++ 新增:复制、剪切、粘贴处理函数 +++
|
||||
const handleCopy = () => {
|
||||
if (!currentSftpManager.value || selectedItems.value.size === 0) return;
|
||||
const manager = currentSftpManager.value;
|
||||
clipboardSourcePaths.value = Array.from(selectedItems.value)
|
||||
.map(filename => manager.joinPath(manager.currentPath.value, filename));
|
||||
clipboardState.value = { hasContent: true, operation: 'copy' };
|
||||
clipboardSourceBaseDir.value = manager.currentPath.value; // 记录源目录
|
||||
console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Copied to clipboard:`, clipboardSourcePaths.value);
|
||||
// 可选:添加 UI 通知
|
||||
};
|
||||
|
||||
const handleCut = () => {
|
||||
if (!currentSftpManager.value || selectedItems.value.size === 0) return;
|
||||
const manager = currentSftpManager.value;
|
||||
clipboardSourcePaths.value = Array.from(selectedItems.value)
|
||||
.map(filename => manager.joinPath(manager.currentPath.value, filename));
|
||||
clipboardState.value = { hasContent: true, operation: 'cut' };
|
||||
clipboardSourceBaseDir.value = manager.currentPath.value; // 记录源目录
|
||||
console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Cut to clipboard:`, clipboardSourcePaths.value);
|
||||
// 可选:添加 UI 通知
|
||||
};
|
||||
|
||||
const handlePaste = () => {
|
||||
if (!currentSftpManager.value || !clipboardState.value.hasContent || clipboardSourcePaths.value.length === 0) return;
|
||||
const manager = currentSftpManager.value;
|
||||
const destinationDir = manager.currentPath.value;
|
||||
const operation = clipboardState.value.operation;
|
||||
const sources = clipboardSourcePaths.value;
|
||||
const sourceBaseDir = clipboardSourceBaseDir.value; // 获取源目录
|
||||
|
||||
console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Pasting items. Operation: ${operation}, Sources: ${sources.join(', ')}, Destination: ${destinationDir}`);
|
||||
|
||||
if (operation === 'copy') {
|
||||
// 调用 SFTP 管理器的 copyItems 方法 (稍后添加)
|
||||
manager.copyItems(sources, destinationDir);
|
||||
} else if (operation === 'cut') {
|
||||
// 调用 SFTP 管理器的 moveItems 方法 (稍后添加)
|
||||
// 检查是否在同一目录下剪切粘贴(无效操作)
|
||||
if (sourceBaseDir === destinationDir) {
|
||||
console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot cut and paste in the same directory.`);
|
||||
// 可选:显示警告通知
|
||||
return;
|
||||
}
|
||||
manager.moveItems(sources, destinationDir);
|
||||
// 剪切后清空剪贴板
|
||||
clipboardState.value = { hasContent: false };
|
||||
clipboardSourcePaths.value = [];
|
||||
clipboardSourceBaseDir.value = '';
|
||||
}
|
||||
// 粘贴后不清空复制的剪贴板,允许重复粘贴
|
||||
// 清空选择可能不是最佳体验,用户可能想继续操作粘贴后的文件
|
||||
// clearSelection();
|
||||
};
|
||||
|
||||
|
||||
// --- 文件上传触发器 (定义在此处,供 Composable 使用) ---
|
||||
const triggerFileUpload = () => { fileInputRef.value?.click(); };
|
||||
|
||||
@@ -422,6 +483,7 @@ const {
|
||||
currentPath: computed(() => currentSftpManager.value?.currentPath.value ?? '/'),
|
||||
isConnected: props.wsDeps.isConnected,
|
||||
isSftpReady: props.wsDeps.isSftpReady,
|
||||
clipboardState: readonly(clipboardState), // +++ 传递剪贴板状态 (只读) +++
|
||||
t,
|
||||
// --- 传递回调函数 ---
|
||||
// 修改:确保在调用前检查 currentSftpManager.value
|
||||
@@ -437,6 +499,9 @@ const {
|
||||
onChangePermissions: handleChangePermissionsContextMenuClick,
|
||||
onNewFolder: handleNewFolderContextMenuClick,
|
||||
onNewFile: handleNewFileContextMenuClick,
|
||||
onCopy: handleCopy, // +++ 传递复制回调 +++
|
||||
onCut: handleCut, // +++ 传递剪切回调 +++
|
||||
onPaste: handlePaste, // +++ 传递粘贴回调 +++
|
||||
});
|
||||
|
||||
// --- 目录加载与导航 ---
|
||||
@@ -1095,6 +1160,7 @@ defineExpose({ focusSearchInput, startPathEdit });
|
||||
@click="fileListContainerRef?.focus()"
|
||||
@keydown="handleKeydown"
|
||||
@wheel="handleWheel"
|
||||
@contextmenu.prevent="showContextMenu($event)"
|
||||
tabindex="0"
|
||||
:style="{ '--row-size-multiplier': rowSizeMultiplier }"
|
||||
>
|
||||
@@ -1179,7 +1245,7 @@ defineExpose({ focusSearchInput, startPathEdit });
|
||||
</tbody>
|
||||
|
||||
<!-- File List State -->
|
||||
<tbody v-else @contextmenu.prevent="showContextMenu($event)">
|
||||
<tbody v-else> <!-- Remove context menu handler from tbody -->
|
||||
<!-- '..' Entry -->
|
||||
<tr v-if="currentSftpManager?.currentPath.value !== '/'"
|
||||
class="transition-colors duration-150 cursor-pointer select-none"
|
||||
|
||||
@@ -10,6 +10,13 @@ export interface ContextMenuItem {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// 定义剪贴板状态类型
|
||||
export interface ClipboardState {
|
||||
hasContent: boolean;
|
||||
operation?: 'copy' | 'cut';
|
||||
// 可以添加 sourcePaths: string[] 等更多信息,但对于禁用/启用粘贴,hasContent 就够了
|
||||
}
|
||||
|
||||
// 定义 Composable 的输入参数类型
|
||||
export interface UseFileManagerContextMenuOptions {
|
||||
selectedItems: Ref<Set<string>>;
|
||||
@@ -18,6 +25,7 @@ export interface UseFileManagerContextMenuOptions {
|
||||
currentPath: Ref<string>;
|
||||
isConnected: Ref<boolean>;
|
||||
isSftpReady: Ref<boolean>;
|
||||
clipboardState: Ref<Readonly<ClipboardState>>; // +++ 新增:剪贴板状态 +++
|
||||
t: ReturnType<typeof useI18n>['t']; // 使用 useI18n 获取 t 的类型
|
||||
// --- 回调函数 ---
|
||||
onRefresh: () => void;
|
||||
@@ -28,6 +36,9 @@ export interface UseFileManagerContextMenuOptions {
|
||||
onChangePermissions: (item: FileListItem) => void;
|
||||
onNewFolder: () => void;
|
||||
onNewFile: () => void;
|
||||
onCopy: () => void; // +++ 新增:复制回调 +++
|
||||
onCut: () => void; // +++ 新增:剪切回调 +++
|
||||
onPaste: () => void; // +++ 新增:粘贴回调 +++
|
||||
}
|
||||
|
||||
export function useFileManagerContextMenu(options: UseFileManagerContextMenuOptions) {
|
||||
@@ -38,6 +49,7 @@ export function useFileManagerContextMenu(options: UseFileManagerContextMenuOpti
|
||||
currentPath,
|
||||
isConnected,
|
||||
isSftpReady,
|
||||
clipboardState, // +++ 解构剪贴板状态 +++
|
||||
t,
|
||||
onRefresh,
|
||||
onUpload,
|
||||
@@ -47,6 +59,9 @@ export function useFileManagerContextMenu(options: UseFileManagerContextMenuOpti
|
||||
onChangePermissions,
|
||||
onNewFolder,
|
||||
onNewFile,
|
||||
onCopy, // +++ 解构复制回调 +++
|
||||
onCut, // +++ 解构剪切回调 +++
|
||||
onPaste, // +++ 解构粘贴回调 +++
|
||||
} = options;
|
||||
|
||||
const contextMenuVisible = ref(false);
|
||||
@@ -77,24 +92,37 @@ export function useFileManagerContextMenu(options: UseFileManagerContextMenuOpti
|
||||
const selectionSize = selectedItems.value.size;
|
||||
const clickedItemIsSelected = targetItem && selectedItems.value.has(targetItem.filename);
|
||||
const canPerformActions = isConnected.value && isSftpReady.value;
|
||||
const hasClipboardContent = clipboardState.value.hasContent; // +++ 获取剪贴板状态 +++
|
||||
|
||||
// Build context menu items (使用传入的回调)
|
||||
if (selectionSize > 1 && clickedItemIsSelected) {
|
||||
// Multi-selection menu
|
||||
menu = [
|
||||
// +++ 添加复制/剪切 +++
|
||||
{ label: t('fileManager.actions.copy'), action: onCopy, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.cut'), action: onCut, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.deleteMultiple', { count: selectionSize }), action: onDelete, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.refresh'), action: onRefresh, disabled: !canPerformActions },
|
||||
];
|
||||
} else if (targetItem && targetItem.filename !== '..') {
|
||||
// Single item (not '..') menu
|
||||
menu = [
|
||||
// +++ 添加复制/剪切 +++
|
||||
{ label: t('fileManager.actions.copy'), action: onCopy, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.cut'), action: onCut, disabled: !canPerformActions },
|
||||
// --- 分隔符 (视觉上,实际由 CSS 处理) ---
|
||||
// { label: '---', action: () => {}, disabled: true },
|
||||
{ label: t('fileManager.actions.newFolder'), action: onNewFolder, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.newFile'), action: onNewFile, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.upload'), action: onUpload, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.refresh'), action: onRefresh, disabled: !canPerformActions },
|
||||
];
|
||||
if (targetItem.attrs.isFile) {
|
||||
menu.splice(1, 0, { label: t('fileManager.actions.download', { name: targetItem.filename }), action: () => onDownload(targetItem), disabled: !canPerformActions });
|
||||
menu.splice(3, 0, { label: t('fileManager.actions.download', { name: targetItem.filename }), action: () => onDownload(targetItem), disabled: !canPerformActions }); // 调整插入位置
|
||||
}
|
||||
// +++ 如果目标是文件夹,添加粘贴 +++
|
||||
if (targetItem.attrs.isDirectory) {
|
||||
menu.splice(3, 0, { label: t('fileManager.actions.paste'), action: onPaste, disabled: !canPerformActions || !hasClipboardContent }); // 调整插入位置
|
||||
}
|
||||
menu.push({ label: t('fileManager.actions.delete'), action: onDelete, disabled: !canPerformActions });
|
||||
menu.push({ label: t('fileManager.actions.rename'), action: () => onRename(targetItem), disabled: !canPerformActions });
|
||||
@@ -106,10 +134,16 @@ export function useFileManagerContextMenu(options: UseFileManagerContextMenuOpti
|
||||
{ label: t('fileManager.actions.newFolder'), action: onNewFolder, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.newFile'), action: onNewFile, disabled: !canPerformActions },
|
||||
{ label: t('fileManager.actions.upload'), action: onUpload, disabled: !canPerformActions },
|
||||
// +++ 添加粘贴 +++
|
||||
{ label: t('fileManager.actions.paste'), action: onPaste, disabled: !canPerformActions || !hasClipboardContent },
|
||||
{ label: t('fileManager.actions.refresh'), action: onRefresh, disabled: !canPerformActions },
|
||||
];
|
||||
} else { // Clicked on '..'
|
||||
menu = [{ label: t('fileManager.actions.refresh'), action: onRefresh, disabled: !canPerformActions }];
|
||||
menu = [
|
||||
// +++ 添加粘贴 (可以粘贴到上级目录) +++
|
||||
{ label: t('fileManager.actions.paste'), action: onPaste, disabled: !canPerformActions || !hasClipboardContent },
|
||||
{ label: t('fileManager.actions.refresh'), action: onRefresh, disabled: !canPerformActions }
|
||||
];
|
||||
}
|
||||
|
||||
contextMenuItems.value = menu;
|
||||
|
||||
@@ -406,6 +406,48 @@ export function createSftpActionsManager(
|
||||
});
|
||||
};
|
||||
|
||||
// +++ 新增:复制项目 +++
|
||||
const copyItems = (sourcePaths: string[], destinationDir: string) => {
|
||||
if (!isSftpReady.value) {
|
||||
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 });
|
||||
console.warn(`[SFTP ${instanceSessionId}] 尝试复制项目但 SFTP 未就绪。`);
|
||||
return;
|
||||
}
|
||||
if (sourcePaths.length === 0) return;
|
||||
const requestId = generateRequestId();
|
||||
sendMessage({
|
||||
type: 'sftp:copy',
|
||||
requestId: requestId,
|
||||
payload: { sources: sourcePaths, destination: destinationDir }
|
||||
});
|
||||
console.log(`[SFTP ${instanceSessionId}] 发送 sftp:copy 请求 (ID: ${requestId}) Sources: ${sourcePaths.join(', ')}, Dest: ${destinationDir}`);
|
||||
// 可选:显示一个“正在复制...”的通知
|
||||
};
|
||||
|
||||
// +++ 新增:移动项目 +++
|
||||
const moveItems = (sourcePaths: string[], destinationDir: string) => {
|
||||
if (!isSftpReady.value) {
|
||||
uiNotificationsStore.showError(t('fileManager.errors.sftpNotReady'), { timeout: 5000 });
|
||||
console.warn(`[SFTP ${instanceSessionId}] 尝试移动项目但 SFTP 未就绪。`);
|
||||
return;
|
||||
}
|
||||
if (sourcePaths.length === 0) return;
|
||||
// 可以在这里再次检查源目录和目标目录是否相同,虽然 FileManager.vue 也检查了
|
||||
// const sourceDir = sourcePaths[0].substring(0, sourcePaths[0].lastIndexOf('/')) || '/';
|
||||
// if (sourceDir === destinationDir) {
|
||||
// uiNotificationsStore.showWarning(t('fileManager.warnings.moveSameDirectory'), { timeout: 3000 });
|
||||
// return;
|
||||
// }
|
||||
const requestId = generateRequestId();
|
||||
sendMessage({
|
||||
type: 'sftp:move', // 使用 'sftp:move' 类型
|
||||
requestId: requestId,
|
||||
payload: { sources: sourcePaths, destination: destinationDir }
|
||||
});
|
||||
console.log(`[SFTP ${instanceSessionId}] 发送 sftp:move 请求 (ID: ${requestId}) Sources: ${sourcePaths.join(', ')}, Dest: ${destinationDir}`);
|
||||
// 可选:显示一个“正在移动...”的通知
|
||||
};
|
||||
|
||||
|
||||
// --- Message Handlers ---
|
||||
|
||||
@@ -716,6 +758,86 @@ export function createSftpActionsManager(
|
||||
}
|
||||
};
|
||||
|
||||
// +++ 新增:处理复制成功 +++
|
||||
const onCopySuccess = (payload: MessagePayload, message: WebSocketMessage) => {
|
||||
// 后端应发送 { destination: string, items: FileListItem[] | null }
|
||||
const copyPayload = payload as { destination: string, items: FileListItem[] | null };
|
||||
const destinationDir = copyPayload.destination;
|
||||
const newItems = copyPayload.items;
|
||||
|
||||
console.log(`[SFTP ${instanceSessionId}] 复制成功到: ${destinationDir}`);
|
||||
uiNotificationsStore.showSuccess(t('fileManager.notifications.copySuccess'), { timeout: 3000 }); // 添加成功通知
|
||||
|
||||
// 更新文件树
|
||||
const destNode = findNodeByPath(fileTree, destinationDir);
|
||||
if (destNode && newItems) {
|
||||
// 如果目标节点已加载,直接添加新项目
|
||||
if (destNode.childrenLoaded && destNode.children) {
|
||||
newItems.forEach(item => addOrUpdateNodeInTree(destinationDir, item));
|
||||
} else {
|
||||
// 如果目标节点未加载,标记为需要刷新
|
||||
destNode.childrenLoaded = false;
|
||||
console.log(`[SFTP ${instanceSessionId}] 复制成功,但目标目录 ${destinationDir} 未加载,标记为需要刷新`);
|
||||
// 如果复制发生在当前目录,触发刷新
|
||||
if (destinationDir === currentPathRef.value) {
|
||||
loadDirectory(currentPathRef.value);
|
||||
}
|
||||
}
|
||||
} else if (destNode && !newItems) {
|
||||
// 成功但没有收到项目详情,标记目标目录需要刷新
|
||||
destNode.childrenLoaded = false;
|
||||
console.warn(`[SFTP ${instanceSessionId}] Copy success to ${destinationDir} but no item details received. Marking parent for reload.`);
|
||||
if (destinationDir === currentPathRef.value) {
|
||||
loadDirectory(currentPathRef.value);
|
||||
}
|
||||
} else {
|
||||
console.warn(`[SFTP ${instanceSessionId}] Copy success, but destination node ${destinationDir} not found in tree.`);
|
||||
// 可能需要刷新根目录或采取其他措施
|
||||
}
|
||||
};
|
||||
|
||||
// +++ 新增:处理移动成功 +++
|
||||
const onMoveSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
|
||||
// 后端应发送 { sources: string[], destination: string, items: FileListItem[] | null }
|
||||
const movePayload = payload as { sources: string[], destination: string, items: FileListItem[] | null };
|
||||
const sourcePaths = movePayload.sources;
|
||||
const destinationDir = movePayload.destination;
|
||||
const newItems = movePayload.items;
|
||||
|
||||
console.log(`[SFTP ${instanceSessionId}] 移动成功到: ${destinationDir}`);
|
||||
uiNotificationsStore.showSuccess(t('fileManager.notifications.moveSuccess'), { timeout: 3000 }); // 添加成功通知
|
||||
|
||||
// 1. 从旧位置移除
|
||||
sourcePaths.forEach(oldPath => {
|
||||
const oldParentPath = oldPath.substring(0, oldPath.lastIndexOf('/')) || '/';
|
||||
const oldFilename = oldPath.substring(oldPath.lastIndexOf('/') + 1);
|
||||
removeNodeFromTree(oldParentPath, oldFilename);
|
||||
});
|
||||
|
||||
// 2. 添加到新位置
|
||||
const destNode = findNodeByPath(fileTree, destinationDir);
|
||||
if (destNode && newItems) {
|
||||
if (destNode.childrenLoaded && destNode.children) {
|
||||
newItems.forEach(item => addOrUpdateNodeInTree(destinationDir, item));
|
||||
} else {
|
||||
destNode.childrenLoaded = false; // 标记需要刷新
|
||||
console.log(`[SFTP ${instanceSessionId}] 移动成功,但目标目录 ${destinationDir} 未加载,标记为需要刷新`);
|
||||
if (destinationDir === currentPathRef.value) {
|
||||
loadDirectory(currentPathRef.value);
|
||||
}
|
||||
}
|
||||
} else if (destNode && !newItems) {
|
||||
destNode.childrenLoaded = false;
|
||||
console.warn(`[SFTP ${instanceSessionId}] Move success to ${destinationDir} but no item details received. Marking parent for reload.`);
|
||||
if (destinationDir === currentPathRef.value) {
|
||||
loadDirectory(currentPathRef.value);
|
||||
}
|
||||
} else {
|
||||
console.warn(`[SFTP ${instanceSessionId}] Move success, but destination node ${destinationDir} not found in tree.`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// *** 新增:处理上传成功 ***
|
||||
const onUploadSuccess = (payload: MessagePayload, message: WebSocketMessage) => {
|
||||
const newItem = payload as FileListItem | null; // 后端应发送 FileListItem 或 null
|
||||
@@ -750,6 +872,8 @@ export function createSftpActionsManager(
|
||||
'sftp:rename:error': t('fileManager.errors.renameFailed'),
|
||||
'sftp:chmod:error': t('fileManager.errors.chmodFailed'),
|
||||
'sftp:writefile:error': t('fileManager.errors.saveFailed'),
|
||||
'sftp:copy:error': t('fileManager.errors.copyFailed'), // +++
|
||||
'sftp:move:error': t('fileManager.errors.moveFailed'), // +++
|
||||
};
|
||||
const prefix = actionTypeMap[message.type] || t('fileManager.errors.generic');
|
||||
// error.value = `${prefix}: ${errorPayload}`; // 使用通知
|
||||
@@ -773,6 +897,11 @@ export function createSftpActionsManager(
|
||||
unregisterCallbacks.push(onMessage('sftp:rename:error', onActionError));
|
||||
unregisterCallbacks.push(onMessage('sftp:chmod:error', onActionError));
|
||||
unregisterCallbacks.push(onMessage('sftp:writefile:error', onActionError));
|
||||
// +++ 新增:监听复制/移动错误 +++
|
||||
unregisterCallbacks.push(onMessage('sftp:copy:success', onCopySuccess));
|
||||
unregisterCallbacks.push(onMessage('sftp:copy:error', onActionError));
|
||||
unregisterCallbacks.push(onMessage('sftp:move:success', onMoveSuccess));
|
||||
unregisterCallbacks.push(onMessage('sftp:move:error', onActionError));
|
||||
|
||||
// 移除 onUnmounted 块
|
||||
|
||||
@@ -808,6 +937,8 @@ export function createSftpActionsManager(
|
||||
changePermissions,
|
||||
readFile,
|
||||
writeFile,
|
||||
copyItems, // +++ 暴露 copyItems +++
|
||||
moveItems, // +++ 暴露 moveItems +++
|
||||
joinPath, // 暴露辅助函数
|
||||
// clearSftpError, // 移除 clearSftpError
|
||||
|
||||
|
||||
@@ -254,7 +254,10 @@
|
||||
"save": "Save",
|
||||
"closeTab": "Close Tab",
|
||||
"closeEditor": "Close Editor",
|
||||
"cdToTerminal": "Change terminal directory to current path"
|
||||
"cdToTerminal": "Change terminal directory to current path",
|
||||
"copy": "Copy",
|
||||
"cut": "Cut",
|
||||
"paste": "Paste"
|
||||
},
|
||||
"headers": {
|
||||
"type": "Type",
|
||||
@@ -280,7 +283,22 @@
|
||||
"saveFailed": "Failed to save file",
|
||||
"saveTimeout": "Save timed out",
|
||||
"fileExists": "File \"{name}\" already exists.",
|
||||
"loadDirectoryFailed": "Failed to load directory"
|
||||
"loadDirectoryFailed": "Failed to load directory",
|
||||
"copyFailed": "Copy failed",
|
||||
"moveFailed": "Move failed",
|
||||
"sftpNotReady": "SFTP session not ready",
|
||||
"sftpManagerNotFound": "SFTP manager not found",
|
||||
"noActiveSession": "No active session found",
|
||||
"terminalManagerNotFound": "Terminal manager not found",
|
||||
"sendCommandFailed": "Failed to send command"
|
||||
},
|
||||
"notifications": {
|
||||
"copySuccess": "Copy successful",
|
||||
"moveSuccess": "Move successful",
|
||||
"cdCommandSent": "CD command sent to terminal"
|
||||
},
|
||||
"warnings": {
|
||||
"moveSameDirectory": "Cannot cut and paste in the same directory."
|
||||
},
|
||||
"prompts": {
|
||||
"enterFolderName": "Enter the name for the new folder:",
|
||||
|
||||
@@ -254,7 +254,10 @@
|
||||
"save": "保存",
|
||||
"closeTab": "关闭标签页",
|
||||
"closeEditor": "关闭编辑器",
|
||||
"cdToTerminal": "将终端目录切换到当前路径"
|
||||
"cdToTerminal": "将终端目录切换到当前路径",
|
||||
"copy": "复制",
|
||||
"cut": "剪切",
|
||||
"paste": "粘贴"
|
||||
},
|
||||
"headers": {
|
||||
"type": "类型",
|
||||
@@ -280,7 +283,22 @@
|
||||
"saveFailed": "保存文件失败",
|
||||
"saveTimeout": "保存超时",
|
||||
"fileExists": "文件 \"{name}\" 已存在。",
|
||||
"loadDirectoryFailed": "加载目录失败"
|
||||
"loadDirectoryFailed": "加载目录失败",
|
||||
"copyFailed": "复制失败",
|
||||
"moveFailed": "移动失败",
|
||||
"sftpNotReady": "SFTP 会话未就绪",
|
||||
"sftpManagerNotFound": "SFTP 管理器未找到",
|
||||
"noActiveSession": "未找到活动会话",
|
||||
"terminalManagerNotFound": "未找到终端管理器",
|
||||
"sendCommandFailed": "发送命令失败"
|
||||
},
|
||||
"notifications": {
|
||||
"copySuccess": "复制成功",
|
||||
"moveSuccess": "移动成功",
|
||||
"cdCommandSent": "CD 命令已发送到终端"
|
||||
},
|
||||
"warnings": {
|
||||
"moveSameDirectory": "不能在同一目录下剪切和粘贴。"
|
||||
},
|
||||
"prompts": {
|
||||
"enterFolderName": "请输入新文件夹的名称:",
|
||||
|
||||
Reference in New Issue
Block a user