/** * 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})` : `[${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;