...
This commit is contained in:
		
							
								
								
									
										273
									
								
								static/js/editor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								static/js/editor.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,273 @@ | ||||
| /** | ||||
|  * Editor Module | ||||
|  * Handles CodeMirror editor and markdown preview | ||||
|  */ | ||||
|  | ||||
| class MarkdownEditor { | ||||
|     constructor(editorId, previewId, filenameInputId) { | ||||
|         this.editorElement = document.getElementById(editorId); | ||||
|         this.previewElement = document.getElementById(previewId); | ||||
|         this.filenameInput = document.getElementById(filenameInputId); | ||||
|         this.currentFile = null; | ||||
|         this.webdavClient = null; | ||||
|          | ||||
|         this.initCodeMirror(); | ||||
|         this.initMarkdown(); | ||||
|         this.initMermaid(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initialize CodeMirror | ||||
|      */ | ||||
|     initCodeMirror() { | ||||
|         this.editor = CodeMirror(this.editorElement, { | ||||
|             mode: 'markdown', | ||||
|             theme: 'monokai', | ||||
|             lineNumbers: true, | ||||
|             lineWrapping: true, | ||||
|             autofocus: true, | ||||
|             extraKeys: { | ||||
|                 'Ctrl-S': () => this.save(), | ||||
|                 'Cmd-S': () => this.save() | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Update preview on change | ||||
|         this.editor.on('change', () => { | ||||
|             this.updatePreview(); | ||||
|         }); | ||||
|  | ||||
|         // Sync scroll | ||||
|         this.editor.on('scroll', () => { | ||||
|             this.syncScroll(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initialize markdown parser | ||||
|      */ | ||||
|     initMarkdown() { | ||||
|         this.marked = window.marked; | ||||
|         this.marked.setOptions({ | ||||
|             breaks: true, | ||||
|             gfm: true, | ||||
|             highlight: (code, lang) => { | ||||
|                 if (lang && window.Prism.languages[lang]) { | ||||
|                     return window.Prism.highlight(code, window.Prism.languages[lang], lang); | ||||
|                 } | ||||
|                 return code; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initialize Mermaid | ||||
|      */ | ||||
|     initMermaid() { | ||||
|         if (window.mermaid) { | ||||
|             window.mermaid.initialize({ | ||||
|                 startOnLoad: false, | ||||
|                 theme: document.body.classList.contains('dark-mode') ? 'dark' : 'default' | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set WebDAV client | ||||
|      */ | ||||
|     setWebDAVClient(client) { | ||||
|         this.webdavClient = client; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Load file | ||||
|      */ | ||||
|     async loadFile(path) { | ||||
|         try { | ||||
|             const content = await this.webdavClient.get(path); | ||||
|             this.currentFile = path; | ||||
|             this.filenameInput.value = path; | ||||
|             this.editor.setValue(content); | ||||
|             this.updatePreview(); | ||||
|              | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification(`Loaded ${path}`, 'info'); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.error('Failed to load file:', error); | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('Failed to load file', 'danger'); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Save file | ||||
|      */ | ||||
|     async save() { | ||||
|         const path = this.filenameInput.value.trim(); | ||||
|         if (!path) { | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('Please enter a filename', 'warning'); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const content = this.editor.getValue(); | ||||
|  | ||||
|         try { | ||||
|             await this.webdavClient.put(path, content); | ||||
|             this.currentFile = path; | ||||
|              | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('✅ Saved', 'success'); | ||||
|             } | ||||
|  | ||||
|             // Trigger file tree reload | ||||
|             if (window.fileTree) { | ||||
|                 await window.fileTree.load(); | ||||
|                 window.fileTree.selectNode(path); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.error('Failed to save file:', error); | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('Failed to save file', 'danger'); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Create new file | ||||
|      */ | ||||
|     newFile() { | ||||
|         this.currentFile = null; | ||||
|         this.filenameInput.value = ''; | ||||
|         this.filenameInput.focus(); | ||||
|         this.editor.setValue(''); | ||||
|         this.updatePreview(); | ||||
|  | ||||
|         if (window.showNotification) { | ||||
|             window.showNotification('Enter filename and start typing', 'info'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Delete current file | ||||
|      */ | ||||
|     async deleteFile() { | ||||
|         if (!this.currentFile) { | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('No file selected', 'warning'); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!confirm(`Delete ${this.currentFile}?`)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             await this.webdavClient.delete(this.currentFile); | ||||
|              | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification(`Deleted ${this.currentFile}`, 'success'); | ||||
|             } | ||||
|  | ||||
|             this.newFile(); | ||||
|  | ||||
|             // Trigger file tree reload | ||||
|             if (window.fileTree) { | ||||
|                 await window.fileTree.load(); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.error('Failed to delete file:', error); | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('Failed to delete file', 'danger'); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update preview | ||||
|      */ | ||||
|     updatePreview() { | ||||
|         const markdown = this.editor.getValue(); | ||||
|         let html = this.marked.parse(markdown); | ||||
|  | ||||
|         // Process mermaid diagrams | ||||
|         html = html.replace(/<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/g, (match, code) => { | ||||
|             const id = 'mermaid-' + Math.random().toString(36).substr(2, 9); | ||||
|             return `<div class="mermaid" id="${id}">${code}</div>`; | ||||
|         }); | ||||
|  | ||||
|         this.previewElement.innerHTML = html; | ||||
|  | ||||
|         // Render mermaid diagrams | ||||
|         if (window.mermaid) { | ||||
|             window.mermaid.init(undefined, this.previewElement.querySelectorAll('.mermaid')); | ||||
|         } | ||||
|  | ||||
|         // Highlight code blocks | ||||
|         if (window.Prism) { | ||||
|             window.Prism.highlightAllUnder(this.previewElement); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sync scroll between editor and preview | ||||
|      */ | ||||
|     syncScroll() { | ||||
|         const scrollInfo = this.editor.getScrollInfo(); | ||||
|         const scrollPercent = scrollInfo.top / (scrollInfo.height - scrollInfo.clientHeight); | ||||
|          | ||||
|         const previewHeight = this.previewElement.scrollHeight - this.previewElement.clientHeight; | ||||
|         this.previewElement.scrollTop = previewHeight * scrollPercent; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handle image upload | ||||
|      */ | ||||
|     async uploadImage(file) { | ||||
|         try { | ||||
|             const filename = await this.webdavClient.uploadImage(file); | ||||
|             const imageUrl = `/fs/${this.webdavClient.currentCollection}/images/${filename}`; | ||||
|             const markdown = ``; | ||||
|              | ||||
|             // Insert at cursor | ||||
|             this.editor.replaceSelection(markdown); | ||||
|              | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('Image uploaded', 'success'); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.error('Failed to upload image:', error); | ||||
|             if (window.showNotification) { | ||||
|                 window.showNotification('Failed to upload image', 'danger'); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get editor content | ||||
|      */ | ||||
|     getValue() { | ||||
|         return this.editor.getValue(); | ||||
|     } | ||||
|      | ||||
|     insertAtCursor(text) { | ||||
|         const doc = this.editor.getDoc(); | ||||
|         const cursor = doc.getCursor(); | ||||
|         doc.replaceRange(text, cursor); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set editor content | ||||
|      */ | ||||
|     setValue(content) { | ||||
|         this.editor.setValue(content); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Export for use in other modules | ||||
| window.MarkdownEditor = MarkdownEditor; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user