diff --git a/collections/documents/welcome2.md b/collections/documents/welcome2.md deleted file mode 100644 index fed0c5f..0000000 --- a/collections/documents/welcome2.md +++ /dev/null @@ -1,58 +0,0 @@ -# Welcome to Markdown Editor - -This is a **WebDAV-based** markdown editor with modular architecture. - -```mermaid -%%{init: {'theme':'dark'}}%% -graph TD - - %% User side - H1[Human A] --> PA1[Personal Agent A] - H2[Human B] --> PA2[Personal Agent B] - - %% Local mail nodes - PA1 --> M1[MyMail Node A] - PA2 --> M2[MyMail Node B] - - %% Proxy coordination layer - M1 --> Proxy1A[Proxy Agent L1] - Proxy1A --> Proxy2A[Proxy Agent L2] - Proxy2A --> Proxy2B[Proxy Agent L2] - Proxy2B --> Proxy1B[Proxy Agent L1] - Proxy1B --> M2 - - %% Blockchain anchoring - M1 --> Chain[Dynamic Blockchain] - M2 --> Chain -``` - -## Features - -- ✅ Standards-compliant WebDAV backend -- ✅ Multiple document collections -- ✅ Modular JavaScript/CSS -- ✅ Live preview -- ✅ Syntax highlighting -- ✅ Mermaid diagrams -- ✅ Dark mode - -## WebDAV Methods - -This editor uses standard WebDAV methods: - -- `PROPFIND` - List files -- `GET` - Read files -- `PUT` - Create/update files -- `DELETE` - Delete files -- `COPY` - Copy files -- `MOVE` - Move/rename files -- `MKCOL` - Create directories - -## Try It Out - -1. Create a new file -2. Edit markdown -3. See live preview -4. Save with WebDAV PUT - -Enjoy! diff --git a/collections/documents/images/welcome2.md b/collections/documents/welcome4.md similarity index 100% rename from collections/documents/images/welcome2.md rename to collections/documents/welcome4.md diff --git a/collections/notes/ttt/fdff/test_sub.md b/collections/notes/ttt/fdff/test_sub.md deleted file mode 100644 index 9fbcf0d..0000000 --- a/collections/notes/ttt/fdff/test_sub.md +++ /dev/null @@ -1,9 +0,0 @@ - -# test - -- 1 -- 2 - - - - diff --git a/server_webdav.py b/server_webdav.py index b631b58..e5a75db 100755 --- a/server_webdav.py +++ b/server_webdav.py @@ -83,6 +83,11 @@ class MarkdownEditorApp: if path.startswith('/static/'): return self.handle_static(environ, start_response) + # Health check + if path == '/health' and method == 'GET': + start_response('200 OK', [('Content-Type', 'text/plain')]) + return [b'OK'] + # API for collections if path == '/fs/' and method == 'GET': return self.handle_collections_list(environ, start_response) diff --git a/static/css/modal.css b/static/css/modal.css new file mode 100644 index 0000000..d7a27bf --- /dev/null +++ b/static/css/modal.css @@ -0,0 +1,3 @@ +.modal-header .btn-close { + filter: var(--bs-btn-close-white-filter); +} \ No newline at end of file diff --git a/static/js/app.js b/static/js/app.js index 1a52ef8..c608778 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -102,6 +102,12 @@ document.addEventListener('DOMContentLoaded', async () => { fileTree.selectNode(path); } }); + + window.eventBus.on('file-deleted', async () => { + if (fileTree) { + await fileTree.load(); + } + }); }); // Listen for column resize events to refresh editor @@ -131,117 +137,12 @@ function setupContextMenuHandlers() { hideContextMenu(); - await handleContextAction(action, targetPath, isDir); + await window.fileTreeActions.execute(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; - } -} +// All context actions are now handled by FileTreeActions, so this function is no longer needed. +// async function handleContextAction(action, targetPath, isDir) { ... } function updatePasteVisibility() { const pasteItem = document.getElementById('pasteMenuItem'); diff --git a/static/js/confirmation.js b/static/js/confirmation.js new file mode 100644 index 0000000..6582ac6 --- /dev/null +++ b/static/js/confirmation.js @@ -0,0 +1,68 @@ +/** + * Confirmation Modal Manager + * Handles showing and hiding a Bootstrap modal for confirmations and prompts. + */ +class Confirmation { + constructor(modalId) { + this.modalElement = document.getElementById(modalId); + this.modal = new bootstrap.Modal(this.modalElement); + this.messageElement = this.modalElement.querySelector('#confirmationMessage'); + this.inputElement = this.modalElement.querySelector('#confirmationInput'); + this.confirmButton = this.modalElement.querySelector('#confirmButton'); + this.titleElement = this.modalElement.querySelector('.modal-title'); + this.currentResolver = null; + } + + _show(message, title, showInput = false, defaultValue = '') { + return new Promise((resolve) => { + this.currentResolver = resolve; + this.titleElement.textContent = title; + this.messageElement.textContent = message; + + if (showInput) { + this.inputElement.style.display = 'block'; + this.inputElement.value = defaultValue; + this.inputElement.focus(); + } else { + this.inputElement.style.display = 'none'; + } + + this.confirmButton.onclick = () => this._handleConfirm(showInput); + this.modalElement.addEventListener('hidden.bs.modal', () => this._handleCancel(), { once: true }); + + this.modal.show(); + }); + } + + _handleConfirm(isPrompt) { + if (this.currentResolver) { + const value = isPrompt ? this.inputElement.value : true; + this.currentResolver(value); + this._cleanup(); + } + } + + _handleCancel() { + if (this.currentResolver) { + this.currentResolver(null); // Resolve with null for cancellation + this._cleanup(); + } + } + + _cleanup() { + this.confirmButton.onclick = null; + this.modal.hide(); + this.currentResolver = null; + } + + confirm(message, title = 'Confirmation') { + return this._show(message, title, false); + } + + prompt(message, defaultValue = '', title = 'Prompt') { + return this._show(message, title, true, defaultValue); + } +} + +// Make it globally available +window.ConfirmationManager = new Confirmation('confirmationModal'); diff --git a/static/js/editor.js b/static/js/editor.js index 4059078..da22348 100644 --- a/static/js/editor.js +++ b/static/js/editor.js @@ -164,32 +164,19 @@ class MarkdownEditor { */ async deleteFile() { if (!this.currentFile) { - if (window.showNotification) { - window.showNotification('No file selected', 'warning'); - } + window.showNotification('No file selected', 'warning'); return; } - if (!confirm(`Delete ${this.currentFile}?`)) { - return; - } - - try { - await this.webdavClient.delete(this.currentFile); - - if (window.showNotification) { + const confirmed = await window.ConfirmationManager.confirm(`Are you sure you want to delete ${this.currentFile}?`, 'Delete File'); + if (confirmed) { + try { + await this.webdavClient.delete(this.currentFile); 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) { + this.newFile(); + window.eventBus.dispatch('file-deleted'); + } catch (error) { + console.error('Failed to delete file:', error); window.showNotification('Failed to delete file', 'danger'); } } diff --git a/static/js/file-tree-actions.js b/static/js/file-tree-actions.js index 2ba0422..efa605a 100644 --- a/static/js/file-tree-actions.js +++ b/static/js/file-tree-actions.js @@ -152,6 +152,7 @@ class FileTreeActions { const cleanup = () => { dialog.remove(); + document.querySelector('.modal-backdrop').remove(); document.body.classList.remove('modal-open'); }; @@ -221,7 +222,7 @@ class FileTreeActions {