276 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * 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);
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        'new-file': 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');
 | 
						|
                await this.editor.loadFile(fullPath);
 | 
						|
            }
 | 
						|
        },
 | 
						|
 | 
						|
        'new-folder': 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 cleanup = () => {
 | 
						|
                dialog.remove();
 | 
						|
                const backdrop = document.querySelector('.modal-backdrop');
 | 
						|
                if (backdrop) backdrop.remove();
 | 
						|
                document.body.classList.remove('modal-open');
 | 
						|
            };
 | 
						|
 | 
						|
            confirmBtn.onclick = () => {
 | 
						|
                resolve(input.value.trim());
 | 
						|
                cleanup();
 | 
						|
            };
 | 
						|
 | 
						|
            dialog.addEventListener('hidden.bs.modal', () => {
 | 
						|
                resolve(null);
 | 
						|
                cleanup();
 | 
						|
            });
 | 
						|
 | 
						|
            input.onkeypress = (e) => {
 | 
						|
                if (e.key === 'Enter') confirmBtn.click();
 | 
						|
            };
 | 
						|
 | 
						|
            document.body.appendChild(dialog);
 | 
						|
            const modal = new bootstrap.Modal(dialog);
 | 
						|
            modal.show();
 | 
						|
            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 cleanup = () => {
 | 
						|
                dialog.remove();
 | 
						|
                const backdrop = document.querySelector('.modal-backdrop');
 | 
						|
                if (backdrop) backdrop.remove();
 | 
						|
                document.body.classList.remove('modal-open');
 | 
						|
            };
 | 
						|
 | 
						|
            confirmBtn.onclick = () => {
 | 
						|
                resolve(true);
 | 
						|
                cleanup();
 | 
						|
            };
 | 
						|
 | 
						|
            dialog.addEventListener('hidden.bs.modal', () => {
 | 
						|
                resolve(false);
 | 
						|
                cleanup();
 | 
						|
            });
 | 
						|
 | 
						|
            document.body.appendChild(dialog);
 | 
						|
            const modal = new bootstrap.Modal(dialog);
 | 
						|
            modal.show();
 | 
						|
            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 = `
 | 
						|
            <div class="modal-dialog modal-dialog-centered">
 | 
						|
                <div class="modal-content">
 | 
						|
                    <div class="modal-header">
 | 
						|
                        <h5 class="modal-title">${title}</h5>
 | 
						|
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
 | 
						|
                    </div>
 | 
						|
                    <div class="modal-body">
 | 
						|
                        <input type="text" class="form-control" value="${placeholder}" autofocus>
 | 
						|
                    </div>
 | 
						|
                    <div class="modal-footer">
 | 
						|
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
						|
                        <button type="button" class="btn btn-primary">OK</button>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
        `;
 | 
						|
        
 | 
						|
        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 = `
 | 
						|
            <div class="modal-dialog modal-dialog-centered">
 | 
						|
                <div class="modal-content">
 | 
						|
                    <div class="modal-header border-danger">
 | 
						|
                        <h5 class="modal-title text-danger">${title}</h5>
 | 
						|
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
 | 
						|
                    </div>
 | 
						|
                    <div class="modal-body">
 | 
						|
                        <p>${message}</p>
 | 
						|
                        <p class="text-danger small">This action cannot be undone.</p>
 | 
						|
                    </div>
 | 
						|
                    <div class="modal-footer">
 | 
						|
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
						|
                        <button type="button" class="btn btn-danger">Delete</button>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
        `;
 | 
						|
        
 | 
						|
        document.body.appendChild(backdrop);
 | 
						|
        return dialog;
 | 
						|
    }
 | 
						|
 | 
						|
    updatePasteMenuItem() {
 | 
						|
        const pasteItem = document.getElementById('pasteMenuItem');
 | 
						|
        if (pasteItem) {
 | 
						|
            pasteItem.style.display = this.clipboard ? 'flex' : 'none';
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |