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:
Mahmoud-Emad
2025-10-26 17:29:45 +03:00
parent 0ed6bcf1f2
commit f319f29d4c
20 changed files with 1679 additions and 113 deletions

View File

@@ -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();
}