286 lines
8.7 KiB
JavaScript
286 lines
8.7 KiB
JavaScript
/**
|
|
* Main Application
|
|
* Coordinates all modules and handles user interactions
|
|
*/
|
|
|
|
// Global state
|
|
let webdavClient;
|
|
let fileTree;
|
|
let editor;
|
|
let darkMode;
|
|
let collectionSelector;
|
|
let clipboard = null;
|
|
let currentFilePath = null;
|
|
|
|
// Simple event bus
|
|
const eventBus = {
|
|
listeners: {},
|
|
on(event, callback) {
|
|
if (!this.listeners[event]) {
|
|
this.listeners[event] = [];
|
|
}
|
|
this.listeners[event].push(callback);
|
|
},
|
|
dispatch(event, data) {
|
|
if (this.listeners[event]) {
|
|
this.listeners[event].forEach(callback => callback(data));
|
|
}
|
|
}
|
|
};
|
|
window.eventBus = eventBus;
|
|
|
|
// Initialize application
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Initialize WebDAV client
|
|
webdavClient = new WebDAVClient('/fs/');
|
|
|
|
// Initialize dark mode
|
|
darkMode = new DarkMode();
|
|
document.getElementById('darkModeBtn').addEventListener('click', () => {
|
|
darkMode.toggle();
|
|
});
|
|
|
|
// Initialize file tree
|
|
fileTree = new FileTree('fileTree', webdavClient);
|
|
fileTree.onFileSelect = async (item) => {
|
|
await editor.loadFile(item.path);
|
|
};
|
|
|
|
// Initialize collection selector
|
|
collectionSelector = new CollectionSelector('collectionSelect', webdavClient);
|
|
collectionSelector.onChange = async (collection) => {
|
|
await fileTree.load();
|
|
};
|
|
await collectionSelector.load();
|
|
await fileTree.load();
|
|
|
|
// Initialize editor
|
|
editor = new MarkdownEditor('editor', 'preview', 'filenameInput');
|
|
editor.setWebDAVClient(webdavClient);
|
|
|
|
// Add test content to verify preview works
|
|
setTimeout(() => {
|
|
if (!editor.editor.getValue()) {
|
|
editor.editor.setValue('# Welcome to Markdown Editor\n\nStart typing to see preview...\n');
|
|
editor.updatePreview();
|
|
}
|
|
}, 200);
|
|
|
|
// Setup editor drop handler
|
|
const editorDropHandler = new EditorDropHandler(
|
|
document.querySelector('.editor-container'),
|
|
async (file) => {
|
|
await handleEditorFileDrop(file);
|
|
}
|
|
);
|
|
|
|
// Setup button handlers
|
|
document.getElementById('newBtn').addEventListener('click', () => {
|
|
editor.newFile();
|
|
});
|
|
|
|
document.getElementById('saveBtn').addEventListener('click', async () => {
|
|
await editor.save();
|
|
});
|
|
|
|
document.getElementById('deleteBtn').addEventListener('click', async () => {
|
|
await editor.deleteFile();
|
|
});
|
|
|
|
// Setup context menu handlers
|
|
setupContextMenuHandlers();
|
|
|
|
// Initialize mermaid
|
|
mermaid.initialize({ startOnLoad: true, theme: darkMode.isDark ? 'dark' : 'default' });
|
|
|
|
// Initialize file tree actions manager
|
|
window.fileTreeActions = new FileTreeActions(webdavClient, fileTree, editor);
|
|
// Listen for file-saved event to reload file tree
|
|
window.eventBus.on('file-saved', async (path) => {
|
|
if (fileTree) {
|
|
await fileTree.load();
|
|
fileTree.selectNode(path);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Listen for column resize events to refresh editor
|
|
window.addEventListener('column-resize', () => {
|
|
if (editor && editor.editor) {
|
|
editor.editor.refresh();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* File Operations
|
|
*/
|
|
|
|
/**
|
|
* Context Menu Handlers
|
|
*/
|
|
function setupContextMenuHandlers() {
|
|
const menu = document.getElementById('contextMenu');
|
|
|
|
menu.addEventListener('click', async (e) => {
|
|
const item = e.target.closest('.context-menu-item');
|
|
if (!item) return;
|
|
|
|
const action = item.dataset.action;
|
|
const targetPath = menu.dataset.targetPath;
|
|
const isDir = menu.dataset.targetIsDir === 'true';
|
|
|
|
hideContextMenu();
|
|
|
|
await handleContextAction(action, targetPath, isDir);
|
|
});
|
|
}
|
|
|
|
async function handleContextAction(action, targetPath, isDir) {
|
|
switch (action) {
|
|
case 'open':
|
|
if (!isDir) {
|
|
await editor.loadFile(targetPath);
|
|
}
|
|
break;
|
|
|
|
case 'new-file':
|
|
if (isDir) {
|
|
const filename = prompt('Enter filename:');
|
|
if (filename) {
|
|
await fileTree.createFile(targetPath, filename);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'new-folder':
|
|
if (isDir) {
|
|
const foldername = prompt('Enter folder name:');
|
|
if (foldername) {
|
|
await fileTree.createFolder(targetPath, foldername);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'upload':
|
|
if (isDir) {
|
|
showFileUploadDialog(targetPath, async (path, file) => {
|
|
await fileTree.uploadFile(path, file);
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'download':
|
|
if (isDir) {
|
|
await fileTree.downloadFolder(targetPath);
|
|
} else {
|
|
await fileTree.downloadFile(targetPath);
|
|
}
|
|
break;
|
|
|
|
case 'rename':
|
|
const newName = prompt('Enter new name:', targetPath.split('/').pop());
|
|
if (newName) {
|
|
const parentPath = targetPath.split('/').slice(0, -1).join('/');
|
|
const newPath = parentPath ? `${parentPath}/${newName}` : newName;
|
|
try {
|
|
await webdavClient.move(targetPath, newPath);
|
|
await fileTree.load();
|
|
showNotification('Renamed', 'success');
|
|
} catch (error) {
|
|
console.error('Failed to rename:', error);
|
|
showNotification('Failed to rename', 'error');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'copy':
|
|
clipboard = { path: targetPath, operation: 'copy' };
|
|
showNotification('Copied to clipboard', 'info');
|
|
updatePasteVisibility();
|
|
break;
|
|
|
|
case 'cut':
|
|
clipboard = { path: targetPath, operation: 'cut' };
|
|
showNotification('Cut to clipboard', 'info');
|
|
updatePasteVisibility();
|
|
break;
|
|
|
|
case 'paste':
|
|
if (clipboard && isDir) {
|
|
const filename = clipboard.path.split('/').pop();
|
|
const destPath = `${targetPath}/${filename}`;
|
|
|
|
try {
|
|
if (clipboard.operation === 'copy') {
|
|
await webdavClient.copy(clipboard.path, destPath);
|
|
showNotification('Copied', 'success');
|
|
} else {
|
|
await webdavClient.move(clipboard.path, destPath);
|
|
showNotification('Moved', 'success');
|
|
clipboard = null;
|
|
updatePasteVisibility();
|
|
}
|
|
await fileTree.load();
|
|
} catch (error) {
|
|
console.error('Failed to paste:', error);
|
|
showNotification('Failed to paste', 'error');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'delete':
|
|
if (confirm(`Delete ${targetPath}?`)) {
|
|
try {
|
|
await webdavClient.delete(targetPath);
|
|
await fileTree.load();
|
|
showNotification('Deleted', 'success');
|
|
} catch (error) {
|
|
console.error('Failed to delete:', error);
|
|
showNotification('Failed to delete', 'error');
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function updatePasteVisibility() {
|
|
const pasteItem = document.getElementById('pasteMenuItem');
|
|
if (pasteItem) {
|
|
pasteItem.style.display = clipboard ? 'block' : 'none';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Editor File Drop Handler
|
|
*/
|
|
async function handleEditorFileDrop(file) {
|
|
try {
|
|
// Get current file's directory
|
|
let targetDir = '';
|
|
if (currentFilePath) {
|
|
const parts = currentFilePath.split('/');
|
|
parts.pop(); // Remove filename
|
|
targetDir = parts.join('/');
|
|
}
|
|
|
|
// Upload file
|
|
const uploadedPath = await fileTree.uploadFile(targetDir, file);
|
|
|
|
// Insert markdown link at cursor
|
|
const isImage = file.type.startsWith('image/');
|
|
const link = isImage
|
|
? ``
|
|
: `[${file.name}](/${webdavClient.currentCollection}/${uploadedPath})`;
|
|
|
|
editor.insertAtCursor(link);
|
|
showNotification(`Uploaded and inserted link`, 'success');
|
|
} catch (error) {
|
|
console.error('Failed to handle file drop:', error);
|
|
showNotification('Failed to upload file', 'error');
|
|
}
|
|
}
|
|
|
|
// Make showContextMenu global
|
|
window.showContextMenu = showContextMenu;
|
|
|