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;
 | |
| 
 |