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