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:
183
static/js/app.js
183
static/js/app.js
@@ -208,10 +208,17 @@ async function loadFileFromURL(collection, filePath) {
|
||||
await showDirectoryPreview(filePath);
|
||||
fileTree.selectAndExpandPath(filePath);
|
||||
} else if (node) {
|
||||
// It's a file, load it
|
||||
// It's a file, check if it's binary
|
||||
console.log('[loadFileFromURL] Loading file');
|
||||
await editor.loadFile(filePath);
|
||||
fileTree.selectAndExpandPath(filePath);
|
||||
|
||||
// Use the fileTree.onFileSelect callback to handle both text and binary files
|
||||
if (fileTree.onFileSelect) {
|
||||
fileTree.onFileSelect({ path: filePath, isDirectory: false });
|
||||
} else {
|
||||
// Fallback to direct loading
|
||||
await editor.loadFile(filePath);
|
||||
fileTree.selectAndExpandPath(filePath);
|
||||
}
|
||||
} else {
|
||||
console.error(`[loadFileFromURL] Path not found in file tree: ${filePath}`);
|
||||
}
|
||||
@@ -269,6 +276,37 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
collectionSelector = new CollectionSelector('collectionSelect', webdavClient);
|
||||
await collectionSelector.load();
|
||||
|
||||
// Setup New Collection button
|
||||
document.getElementById('newCollectionBtn').addEventListener('click', async () => {
|
||||
try {
|
||||
const collectionName = await window.ModalManager.prompt(
|
||||
'Enter new collection name (lowercase, underscore only):',
|
||||
'new_collection'
|
||||
);
|
||||
|
||||
if (!collectionName) return;
|
||||
|
||||
// Validate collection name
|
||||
const validation = ValidationUtils.validateFileName(collectionName, true);
|
||||
if (!validation.valid) {
|
||||
window.showNotification(validation.message, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the collection
|
||||
await webdavClient.createCollection(validation.sanitized);
|
||||
|
||||
// Reload collections and switch to the new one
|
||||
await collectionSelector.load();
|
||||
await collectionSelector.setCollection(validation.sanitized);
|
||||
|
||||
window.showNotification(`Collection "${validation.sanitized}" created`, 'success');
|
||||
} catch (error) {
|
||||
Logger.error('Failed to create collection:', error);
|
||||
window.showNotification('Failed to create collection', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
// Setup URL routing
|
||||
setupPopStateListener();
|
||||
|
||||
@@ -281,11 +319,102 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
fileTree = new FileTree('fileTree', webdavClient);
|
||||
fileTree.onFileSelect = async (item) => {
|
||||
try {
|
||||
const currentCollection = collectionSelector.getCurrentCollection();
|
||||
|
||||
// Check if the file is a binary/non-editable file
|
||||
if (PathUtils.isBinaryFile(item.path)) {
|
||||
const fileType = PathUtils.getFileType(item.path);
|
||||
const fileName = PathUtils.getFileName(item.path);
|
||||
|
||||
Logger.info(`Previewing binary file: ${item.path}`);
|
||||
|
||||
// Set flag to prevent auto-update of preview
|
||||
editor.isShowingCustomPreview = true;
|
||||
|
||||
// In edit mode, show a warning notification
|
||||
if (isEditMode) {
|
||||
if (window.showNotification) {
|
||||
window.showNotification(
|
||||
`"${fileName}" is read-only. Showing preview only.`,
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
|
||||
// Hide the editor pane temporarily
|
||||
const editorPane = document.getElementById('editorPane');
|
||||
const resizer1 = document.getElementById('resizer1');
|
||||
if (editorPane) editorPane.style.display = 'none';
|
||||
if (resizer1) resizer1.style.display = 'none';
|
||||
}
|
||||
|
||||
// Clear the editor (but don't trigger preview update due to flag)
|
||||
if (editor.editor) {
|
||||
editor.editor.setValue('');
|
||||
}
|
||||
editor.filenameInput.value = item.path;
|
||||
editor.currentFile = item.path;
|
||||
|
||||
// Build the file URL using the WebDAV client's method
|
||||
const fileUrl = webdavClient.getFullUrl(item.path);
|
||||
Logger.debug(`Binary file URL: ${fileUrl}`);
|
||||
|
||||
// Generate preview HTML based on file type
|
||||
let previewHtml = '';
|
||||
|
||||
if (fileType === 'Image') {
|
||||
// Preview images
|
||||
previewHtml = `
|
||||
<div style="padding: 20px; text-align: center;">
|
||||
<h3>${fileName}</h3>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 20px;">Image Preview (Read-only)</p>
|
||||
<img src="${fileUrl}" alt="${fileName}" style="max-width: 100%; height: auto; border: 1px solid var(--border-color); border-radius: 4px;">
|
||||
</div>
|
||||
`;
|
||||
} else if (fileType === 'PDF') {
|
||||
// Preview PDFs
|
||||
previewHtml = `
|
||||
<div style="padding: 20px;">
|
||||
<h3>${fileName}</h3>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 20px;">PDF Preview (Read-only)</p>
|
||||
<iframe src="${fileUrl}" style="width: 100%; height: 80vh; border: 1px solid var(--border-color); border-radius: 4px;"></iframe>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// For other binary files, show download link
|
||||
previewHtml = `
|
||||
<div style="padding: 20px;">
|
||||
<h3>${fileName}</h3>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 20px;">${fileType} File (Read-only)</p>
|
||||
<p>This file cannot be previewed in the browser.</p>
|
||||
<a href="${fileUrl}" download="${fileName}" class="btn btn-primary">Download ${fileName}</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Display in preview pane
|
||||
editor.previewElement.innerHTML = previewHtml;
|
||||
|
||||
// Highlight the file in the tree
|
||||
fileTree.selectAndExpandPath(item.path);
|
||||
|
||||
// Update URL to reflect current file
|
||||
updateURL(currentCollection, item.path, isEditMode);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// For text files, restore the editor pane if it was hidden
|
||||
if (isEditMode) {
|
||||
const editorPane = document.getElementById('editorPane');
|
||||
const resizer1 = document.getElementById('resizer1');
|
||||
if (editorPane) editorPane.style.display = '';
|
||||
if (resizer1) resizer1.style.display = '';
|
||||
}
|
||||
|
||||
await editor.loadFile(item.path);
|
||||
// Highlight the file in the tree and expand parent directories
|
||||
fileTree.selectAndExpandPath(item.path);
|
||||
// Update URL to reflect current file
|
||||
const currentCollection = collectionSelector.getCurrentCollection();
|
||||
updateURL(currentCollection, item.path, isEditMode);
|
||||
} catch (error) {
|
||||
Logger.error('Failed to select file:', error);
|
||||
@@ -332,9 +461,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
const { collection: urlCollection, filePath: urlFilePath } = parseURLPath();
|
||||
console.log('[URL PARSE]', { urlCollection, urlFilePath });
|
||||
|
||||
if (urlCollection && urlFilePath) {
|
||||
console.log('[URL LOAD] Loading from URL:', urlCollection, urlFilePath);
|
||||
|
||||
if (urlCollection) {
|
||||
// First ensure the collection is set
|
||||
const currentCollection = collectionSelector.getCurrentCollection();
|
||||
if (currentCollection !== urlCollection) {
|
||||
@@ -343,11 +470,17 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
await fileTree.load();
|
||||
}
|
||||
|
||||
// Now load the file from URL
|
||||
console.log('[URL LOAD] Calling loadFileFromURL');
|
||||
await loadFileFromURL(urlCollection, urlFilePath);
|
||||
// If there's a file path in the URL, load it
|
||||
if (urlFilePath) {
|
||||
console.log('[URL LOAD] Loading file from URL:', urlCollection, urlFilePath);
|
||||
await loadFileFromURL(urlCollection, urlFilePath);
|
||||
} else if (!isEditMode) {
|
||||
// Collection-only URL in view mode: auto-load last viewed page
|
||||
console.log('[URL LOAD] Collection-only URL, auto-loading page');
|
||||
await autoLoadPageInViewMode();
|
||||
}
|
||||
} else if (!isEditMode) {
|
||||
// In view mode, auto-load last viewed page if no URL file specified
|
||||
// No URL collection specified, in view mode: auto-load last viewed page
|
||||
await autoLoadPageInViewMode();
|
||||
}
|
||||
|
||||
@@ -405,11 +538,34 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
// Initialize file tree actions manager
|
||||
window.fileTreeActions = new FileTreeActions(webdavClient, fileTree, editor);
|
||||
|
||||
// Setup Exit Edit Mode button
|
||||
document.getElementById('exitEditModeBtn').addEventListener('click', () => {
|
||||
// Switch to view mode by removing edit=true from URL
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('edit');
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
|
||||
// Hide Edit Mode button in edit mode
|
||||
document.getElementById('editModeBtn').style.display = 'none';
|
||||
} else {
|
||||
// In view mode, hide editor buttons
|
||||
document.getElementById('newBtn').style.display = 'none';
|
||||
document.getElementById('saveBtn').style.display = 'none';
|
||||
document.getElementById('deleteBtn').style.display = 'none';
|
||||
document.getElementById('exitEditModeBtn').style.display = 'none';
|
||||
|
||||
// Show Edit Mode button in view mode
|
||||
document.getElementById('editModeBtn').style.display = 'block';
|
||||
|
||||
// Setup Edit Mode button
|
||||
document.getElementById('editModeBtn').addEventListener('click', () => {
|
||||
// Switch to edit mode by adding edit=true to URL
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('edit', 'true');
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
|
||||
// Auto-load last viewed page or first file
|
||||
await autoLoadPageInViewMode();
|
||||
@@ -498,10 +654,11 @@ async function handleEditorFileDrop(file) {
|
||||
const uploadedPath = await fileTree.uploadFile(targetDir, file);
|
||||
|
||||
// Insert markdown link at cursor
|
||||
// Use relative path (without collection name) so the image renderer can resolve it correctly
|
||||
const isImage = file.type.startsWith('image/');
|
||||
const link = isImage
|
||||
? ``
|
||||
: `[${file.name}](/${webdavClient.currentCollection}/${uploadedPath})`;
|
||||
? ``
|
||||
: `[${file.name}](${uploadedPath})`;
|
||||
|
||||
editor.insertAtCursor(link);
|
||||
showNotification(`Uploaded and inserted link`, 'success');
|
||||
|
||||
Reference in New Issue
Block a user