Files
markdown_editor/static/js/file-tree-actions.js
Mahmoud-Emad 0ed6bcf1f2 refactor: Modularize UI components and utilities
- Extract UI components into separate JS files
- Centralize configuration values in config.js
- Introduce a dedicated logger module
- Improve file tree drag-and-drop and undo functionality
- Refactor modal handling to a single manager
- Add URL routing support for SPA navigation
- Implement view mode for read-only access
2025-10-26 15:42:15 +03:00

254 lines
8.5 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;
}
/**
* 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';
}
}
}