/** * 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; } 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); } }, newFile: async function(path, isDir) { if (!isDir) return; const filename = await this.showInputDialog('Enter filename:', 'new-file.md'); if (filename) { const fullPath = `${path}/${filename}`.replace(/\/+/g, '/'); await this.webdavClient.put(fullPath, '# New File\n\n'); await this.fileTree.load(); showNotification(`Created ${filename}`, 'success'); } }, newFolder: async function(path, isDir) { if (!isDir) return; const foldername = await this.showInputDialog('Enter folder name:', 'new-folder'); if (foldername) { const fullPath = `${path}/${foldername}`.replace(/\/+/g, '/'); await this.webdavClient.mkcol(fullPath); await this.fileTree.load(); showNotification(`Created folder ${foldername}`, 'success'); } }, rename: async function(path, isDir) { const oldName = path.split('/').pop(); const newName = await this.showInputDialog('Rename to:', oldName); if (newName && newName !== oldName) { const parentPath = path.substring(0, path.lastIndexOf('/')); const newPath = parentPath ? `${parentPath}/${newName}` : newName; await this.webdavClient.move(path, newPath); await this.fileTree.load(); showNotification('Renamed', 'success'); } }, copy: async function(path, isDir) { this.clipboard = { path, operation: 'copy', isDirectory: isDir }; showNotification(`Copied: ${path.split('/').pop()}`, 'info'); this.updatePasteMenuItem(); }, cut: async function(path, isDir) { this.clipboard = { path, operation: 'cut', isDirectory: isDir }; showNotification(`Cut: ${path.split('/').pop()}`, 'warning'); 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); showNotification('Copied', 'success'); } else { await this.webdavClient.move(this.clipboard.path, destPath); this.clipboard = null; this.updatePasteMenuItem(); showNotification('Moved', 'success'); } await this.fileTree.load(); }, delete: async function(path, isDir) { const name = path.split('/').pop(); const type = isDir ? 'folder' : 'file'; if (!await this.showConfirmDialog(`Delete this ${type}?`, `${name}`)) { return; } await this.webdavClient.delete(path); await this.fileTree.load(); showNotification(`Deleted ${name}`, 'success'); }, download: async function(path, isDir) { showNotification('Downloading...', 'info'); // Implementation here }, 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(); } }; // Modern dialog implementations async showInputDialog(title, placeholder = '') { return new Promise((resolve) => { const dialog = this.createInputDialog(title, placeholder); const input = dialog.querySelector('input'); const confirmBtn = dialog.querySelector('.btn-primary'); const cancelBtn = dialog.querySelector('.btn-secondary'); const cleanup = () => { dialog.remove(); document.body.classList.remove('modal-open'); }; confirmBtn.onclick = () => { resolve(input.value.trim()); cleanup(); }; cancelBtn.onclick = () => { resolve(null); cleanup(); }; input.onkeypress = (e) => { if (e.key === 'Enter') confirmBtn.click(); if (e.key === 'Escape') cancelBtn.click(); }; document.body.appendChild(dialog); document.body.classList.add('modal-open'); input.focus(); input.select(); }); } async showConfirmDialog(title, message = '') { return new Promise((resolve) => { const dialog = this.createConfirmDialog(title, message); const confirmBtn = dialog.querySelector('.btn-danger'); const cancelBtn = dialog.querySelector('.btn-secondary'); const cleanup = () => { dialog.remove(); document.body.classList.remove('modal-open'); }; confirmBtn.onclick = () => { resolve(true); cleanup(); }; cancelBtn.onclick = () => { resolve(false); cleanup(); }; document.body.appendChild(dialog); document.body.classList.add('modal-open'); confirmBtn.focus(); }); } createInputDialog(title, placeholder) { const backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade show'; const dialog = document.createElement('div'); dialog.className = 'modal fade show d-block'; dialog.setAttribute('tabindex', '-1'); dialog.style.display = 'block'; dialog.innerHTML = ` `; document.body.appendChild(backdrop); return dialog; } createConfirmDialog(title, message) { const backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade show'; const dialog = document.createElement('div'); dialog.className = 'modal fade show d-block'; dialog.setAttribute('tabindex', '-1'); dialog.style.display = 'block'; dialog.innerHTML = ` `; document.body.appendChild(backdrop); return dialog; } updatePasteMenuItem() { const pasteItem = document.getElementById('pasteMenuItem'); if (pasteItem) { pasteItem.style.display = this.clipboard ? 'flex' : 'none'; } } }