/** * File Tree Actions Manager * Centralized handling of all tree operations */ class FileTreeActions { constructor(webdavClient, fileTree, editor) { this.webdavClient = webdavClient; this.fileTree = fileTree; this.editor = editor; this.clipboard = null; } /** * Validate and sanitize filename/folder name * Returns { valid: boolean, sanitized: string, message: string } * Now uses ValidationUtils from utils.js */ validateFileName(name, isFolder = false) { return ValidationUtils.validateFileName(name, isFolder); } async execute(action, targetPath, isDirectory) { const handler = this.actions[action]; if (!handler) { console.error(`Unknown action: ${action}`); return; } try { await handler.call(this, targetPath, isDirectory); } catch (error) { console.error(`Action failed: ${action}`, error); showNotification(`Failed to ${action}`, 'error'); } } actions = { open: async function (path, isDir) { if (!isDir) { await this.editor.loadFile(path); } }, 'new-file': async function (path, isDir) { if (!isDir) return; const filename = await window.ModalManager.prompt( 'Enter filename (lowercase, underscore only):', 'new_file.md', 'New File' ); if (!filename) return; let finalFilename = filename; const validation = this.validateFileName(filename, false); if (!validation.valid) { showNotification(validation.message, 'warning'); // Ask if user wants to use sanitized version if (validation.sanitized) { const useSanitized = await window.ModalManager.confirm( `${filename} → ${validation.sanitized}`, 'Use sanitized name?', false ); if (useSanitized) { finalFilename = validation.sanitized; } else { return; } } else { return; } } const fullPath = `${path}/${finalFilename}`.replace(/\/+/g, '/'); await this.webdavClient.put(fullPath, '# New File\n\n'); // Clear undo history since new file was created if (this.fileTree.lastMoveOperation) { this.fileTree.lastMoveOperation = null; } await this.fileTree.load(); showNotification(`Created ${finalFilename}`, 'success'); await this.editor.loadFile(fullPath); }, 'new-folder': async function (path, isDir) { if (!isDir) return; const foldername = await window.ModalManager.prompt( 'Enter folder name (lowercase, underscore only):', 'new_folder', 'New Folder' ); if (!foldername) return; let finalFoldername = foldername; const validation = this.validateFileName(foldername, true); if (!validation.valid) { showNotification(validation.message, 'warning'); if (validation.sanitized) { const useSanitized = await window.ModalManager.confirm( `${foldername} → ${validation.sanitized}`, 'Use sanitized name?', false ); if (useSanitized) { finalFoldername = validation.sanitized; } else { return; } } else { return; } } const fullPath = `${path}/${finalFoldername}`.replace(/\/+/g, '/'); await this.webdavClient.mkcol(fullPath); // Clear undo history since new folder was created if (this.fileTree.lastMoveOperation) { this.fileTree.lastMoveOperation = null; } await this.fileTree.load(); showNotification(`Created folder ${finalFoldername}`, 'success'); }, rename: async function (path, isDir) { const oldName = path.split('/').pop(); const newName = await window.ModalManager.prompt( 'Rename to:', oldName, 'Rename' ); if (newName && newName !== oldName) { const parentPath = path.substring(0, path.lastIndexOf('/')); const newPath = parentPath ? `${parentPath}/${newName}` : newName; await this.webdavClient.move(path, newPath); // Clear undo history since manual rename occurred if (this.fileTree.lastMoveOperation) { this.fileTree.lastMoveOperation = null; } await this.fileTree.load(); showNotification('Renamed', 'success'); } }, copy: async function (path, isDir) { this.clipboard = { path, operation: 'copy', isDirectory: isDir }; // No notification for copy - it's a quick operation this.updatePasteMenuItem(); }, cut: async function (path, isDir) { this.clipboard = { path, operation: 'cut', isDirectory: isDir }; // No notification for cut - it's a quick operation this.updatePasteMenuItem(); }, paste: async function (targetPath, isDir) { if (!this.clipboard || !isDir) return; const itemName = this.clipboard.path.split('/').pop(); const destPath = `${targetPath}/${itemName}`.replace(/\/+/g, '/'); if (this.clipboard.operation === 'copy') { await this.webdavClient.copy(this.clipboard.path, destPath); // No notification for paste - file tree updates show the result } else { await this.webdavClient.move(this.clipboard.path, destPath); this.clipboard = null; this.updatePasteMenuItem(); // No notification for move - file tree updates show the result } await this.fileTree.load(); }, delete: async function (path, isDir) { const name = path.split('/').pop(); const type = isDir ? 'folder' : 'file'; const confirmed = await window.ModalManager.confirm( `Are you sure you want to delete ${name}?`, `Delete this ${type}?`, true ); if (!confirmed) return; await this.webdavClient.delete(path); // Clear undo history since manual delete occurred if (this.fileTree.lastMoveOperation) { this.fileTree.lastMoveOperation = null; } await this.fileTree.load(); showNotification(`Deleted ${name}`, 'success'); }, download: async function (path, isDir) { Logger.info(`Downloading ${isDir ? 'folder' : 'file'}: ${path}`); if (isDir) { await this.fileTree.downloadFolder(path); } else { await this.fileTree.downloadFile(path); } }, upload: async function (path, isDir) { if (!isDir) return; const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.onchange = async (e) => { const files = Array.from(e.target.files); for (const file of files) { const fullPath = `${path}/${file.name}`.replace(/\/+/g, '/'); const content = await file.arrayBuffer(); await this.webdavClient.putBinary(fullPath, content); showNotification(`Uploaded ${file.name}`, 'success'); } await this.fileTree.load(); }; input.click(); } }; // Old deprecated modal methods removed - all modals now use window.ModalManager updatePasteMenuItem() { const pasteItem = document.getElementById('pasteMenuItem'); if (pasteItem) { pasteItem.style.display = this.clipboard ? 'flex' : 'none'; } } }