/** * 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; // 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 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'); // 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', () => { newFile(); }); document.getElementById('saveBtn').addEventListener('click', async () => { await saveFile(); }); document.getElementById('deleteBtn').addEventListener('click', async () => { await deleteCurrentFile(); }); // 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 column resize events to refresh editor window.addEventListener('column-resize', () => { if (editor && editor.editor) { editor.editor.refresh(); } }); /** * File Operations */ async function loadFile(path) { try { const content = await webdavClient.get(path); editor.setValue(content); document.getElementById('filenameInput').value = path; currentFilePath = path; showNotification('File loaded', 'success'); } catch (error) { console.error('Failed to load file:', error); showNotification('Failed to load file', 'error'); } } function newFile() { editor.setValue('# New File\n\nStart typing...\n'); document.getElementById('filenameInput').value = ''; document.getElementById('filenameInput').focus(); currentFilePath = null; showNotification('New file', 'info'); } async function saveFile() { const filename = document.getElementById('filenameInput').value.trim(); if (!filename) { showNotification('Please enter a filename', 'warning'); return; } try { const content = editor.getValue(); await webdavClient.put(filename, content); currentFilePath = filename; await fileTree.load(); showNotification('Saved', 'success'); } catch (error) { console.error('Failed to save file:', error); showNotification('Failed to save file', 'error'); } } async function deleteCurrentFile() { if (!currentFilePath) { showNotification('No file selected', 'warning'); return; } if (!confirm(`Delete ${currentFilePath}?`)) { return; } try { await webdavClient.delete(currentFilePath); await fileTree.load(); newFile(); showNotification('Deleted', 'success'); } catch (error) { console.error('Failed to delete file:', error); showNotification('Failed to delete file', 'error'); } } /** * 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 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;