...
This commit is contained in:
		
							
								
								
									
										302
									
								
								static/js/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								static/js/app.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,302 @@ | ||||
| /** | ||||
|  * 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 collection selector | ||||
|     collectionSelector = new CollectionSelector('collectionSelect', webdavClient); | ||||
|     collectionSelector.onChange = async (collection) => { | ||||
|         await fileTree.load(); | ||||
|     }; | ||||
|     await collectionSelector.load(); | ||||
|      | ||||
|     // Initialize file tree | ||||
|     fileTree = new FileTree('fileTree', webdavClient); | ||||
|     fileTree.onFileSelect = async (item) => { | ||||
|         await loadFile(item.path); | ||||
|     }; | ||||
|     await fileTree.load(); | ||||
|      | ||||
|     // Initialize editor | ||||
|     editor = new MarkdownEditor('editor', 'preview'); | ||||
|      | ||||
|     // 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' }); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * 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})`; | ||||
|          | ||||
|         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; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user