276 lines
9.9 KiB
JavaScript
276 lines
9.9 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);
|
|
}
|
|
},
|
|
|
|
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 = `
|
|
<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"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="text" class="form-control" placeholder="${placeholder}" autofocus>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary">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"></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">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';
|
|
}
|
|
}
|
|
} |