feat: Enhance WebDAV file management and UI
- Add functionality to create new collections via API - Implement copy and move operations between collections - Improve image rendering in markdown preview with relative path resolution - Add support for previewing binary files (images, PDFs) - Refactor modal styling to use flat buttons and improve accessibility
This commit is contained in:
		| @@ -14,6 +14,7 @@ class MarkdownEditor { | ||||
|         this.lastViewedStorageKey = 'lastViewedPage'; // localStorage key for tracking last viewed page | ||||
|         this.readOnly = readOnly; // Whether editor is in read-only mode | ||||
|         this.editor = null; // Will be initialized later | ||||
|         this.isShowingCustomPreview = false; // Flag to prevent auto-update when showing binary files | ||||
|  | ||||
|         // Only initialize CodeMirror if not in read-only mode (view mode) | ||||
|         if (!readOnly) { | ||||
| @@ -87,9 +88,88 @@ class MarkdownEditor { | ||||
|     initMarkdown() { | ||||
|         if (window.marked) { | ||||
|             this.marked = window.marked; | ||||
|  | ||||
|             // Create custom renderer for images | ||||
|             const renderer = new marked.Renderer(); | ||||
|  | ||||
|             renderer.image = (token) => { | ||||
|                 // Handle both old API (string params) and new API (token object) | ||||
|                 let href, title, text; | ||||
|  | ||||
|                 if (typeof token === 'object' && token !== null) { | ||||
|                     // New API: token is an object | ||||
|                     href = token.href || ''; | ||||
|                     title = token.title || ''; | ||||
|                     text = token.text || ''; | ||||
|                 } else { | ||||
|                     // Old API: separate parameters (href, title, text) | ||||
|                     href = arguments[0] || ''; | ||||
|                     title = arguments[1] || ''; | ||||
|                     text = arguments[2] || ''; | ||||
|                 } | ||||
|  | ||||
|                 // Ensure all are strings | ||||
|                 href = String(href || ''); | ||||
|                 title = String(title || ''); | ||||
|                 text = String(text || ''); | ||||
|  | ||||
|                 Logger.debug(`Image renderer called with href="${href}", title="${title}", text="${text}"`); | ||||
|  | ||||
|                 // Check if href contains binary data (starts with non-printable characters) | ||||
|                 if (href && href.length > 100 && /^[\x00-\x1F\x7F-\xFF]/.test(href)) { | ||||
|                     Logger.error('Image href contains binary data - this should not happen!'); | ||||
|                     Logger.error('First 50 chars:', href.substring(0, 50)); | ||||
|                     // Return a placeholder image | ||||
|                     return `<div class="alert alert-warning">⚠️ Invalid image data detected. Please re-upload the image.</div>`; | ||||
|                 } | ||||
|  | ||||
|                 // Fix relative image paths to use WebDAV base URL | ||||
|                 if (href && !href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('data:')) { | ||||
|                     // Get the directory of the current file | ||||
|                     const currentDir = this.currentFile ? PathUtils.getParentPath(this.currentFile) : ''; | ||||
|  | ||||
|                     // Resolve relative path | ||||
|                     let imagePath = href; | ||||
|                     if (href.startsWith('./')) { | ||||
|                         // Relative to current directory | ||||
|                         imagePath = PathUtils.joinPaths(currentDir, href.substring(2)); | ||||
|                     } else if (href.startsWith('../')) { | ||||
|                         // Relative to parent directory | ||||
|                         imagePath = PathUtils.joinPaths(currentDir, href); | ||||
|                     } else if (!href.startsWith('/')) { | ||||
|                         // Relative to current directory (no ./) | ||||
|                         imagePath = PathUtils.joinPaths(currentDir, href); | ||||
|                     } else { | ||||
|                         // Absolute path from collection root | ||||
|                         imagePath = href.substring(1); // Remove leading / | ||||
|                     } | ||||
|  | ||||
|                     // Build WebDAV URL - ensure no double slashes | ||||
|                     if (this.webdavClient && this.webdavClient.currentCollection) { | ||||
|                         // Remove trailing slash from baseUrl if present | ||||
|                         const baseUrl = this.webdavClient.baseUrl.endsWith('/') | ||||
|                             ? this.webdavClient.baseUrl.slice(0, -1) | ||||
|                             : this.webdavClient.baseUrl; | ||||
|  | ||||
|                         // Ensure imagePath doesn't start with / | ||||
|                         const cleanImagePath = imagePath.startsWith('/') ? imagePath.substring(1) : imagePath; | ||||
|  | ||||
|                         href = `${baseUrl}/${this.webdavClient.currentCollection}/${cleanImagePath}`; | ||||
|  | ||||
|                         Logger.debug(`Resolved image URL: ${href}`); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // Generate HTML directly | ||||
|                 const titleAttr = title ? ` title="${title}"` : ''; | ||||
|                 const altAttr = text ? ` alt="${text}"` : ''; | ||||
|                 return `<img src="${href}"${altAttr}${titleAttr}>`; | ||||
|             }; | ||||
|  | ||||
|             this.marked.setOptions({ | ||||
|                 breaks: true, | ||||
|                 gfm: true, | ||||
|                 renderer: renderer, | ||||
|                 highlight: (code, lang) => { | ||||
|                     if (lang && window.Prism.languages[lang]) { | ||||
|                         return window.Prism.highlight(code, window.Prism.languages[lang], lang); | ||||
| @@ -131,6 +211,9 @@ class MarkdownEditor { | ||||
|      */ | ||||
|     async loadFile(path) { | ||||
|         try { | ||||
|             // Reset custom preview flag when loading text files | ||||
|             this.isShowingCustomPreview = false; | ||||
|  | ||||
|             const content = await this.webdavClient.get(path); | ||||
|             this.currentFile = path; | ||||
|  | ||||
| @@ -337,6 +420,12 @@ class MarkdownEditor { | ||||
|      * Calls renderPreview with content from editor | ||||
|      */ | ||||
|     async updatePreview() { | ||||
|         // Skip auto-update if showing custom preview (e.g., binary files) | ||||
|         if (this.isShowingCustomPreview) { | ||||
|             Logger.debug('Skipping auto-update: showing custom preview'); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (this.editor) { | ||||
|             await this.renderPreview(); | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user