- 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
		
			
				
	
	
		
			674 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			674 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Main Application
 | |
|  * Coordinates all modules and handles user interactions
 | |
|  */
 | |
| 
 | |
| // Global state
 | |
| let webdavClient;
 | |
| let fileTree;
 | |
| let editor;
 | |
| let darkMode;
 | |
| let collectionSelector;
 | |
| let clipboard = null;
 | |
| let currentFilePath = null;
 | |
| 
 | |
| // Event bus is now loaded from event-bus.js module
 | |
| // No need to define it here - it's available as window.eventBus
 | |
| 
 | |
| /**
 | |
|  * Auto-load page in view mode
 | |
|  * Tries to load the last viewed page, falls back to first file if none saved
 | |
|  */
 | |
| async function autoLoadPageInViewMode() {
 | |
|     if (!editor || !fileTree) return;
 | |
| 
 | |
|     try {
 | |
|         // Try to get last viewed page
 | |
|         let pageToLoad = editor.getLastViewedPage();
 | |
| 
 | |
|         // If no last viewed page, get the first markdown file
 | |
|         if (!pageToLoad) {
 | |
|             pageToLoad = fileTree.getFirstMarkdownFile();
 | |
|         }
 | |
| 
 | |
|         // If we found a page to load, load it
 | |
|         if (pageToLoad) {
 | |
|             await editor.loadFile(pageToLoad);
 | |
|             // Highlight the file in the tree and expand parent directories
 | |
|             fileTree.selectAndExpandPath(pageToLoad);
 | |
|         } else {
 | |
|             // No files found, show empty state message
 | |
|             editor.previewElement.innerHTML = `
 | |
|                 <div class="text-muted text-center mt-5">
 | |
|                     <p>No content available</p>
 | |
|                 </div>
 | |
|             `;
 | |
|         }
 | |
|     } catch (error) {
 | |
|         console.error('Failed to auto-load page in view mode:', error);
 | |
|         editor.previewElement.innerHTML = `
 | |
|             <div class="alert alert-danger">
 | |
|                 <p>Failed to load content</p>
 | |
|             </div>
 | |
|         `;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Show directory preview with list of files
 | |
|  * @param {string} dirPath - The directory path
 | |
|  */
 | |
| async function showDirectoryPreview(dirPath) {
 | |
|     if (!editor || !fileTree || !webdavClient) return;
 | |
| 
 | |
|     try {
 | |
|         const dirName = dirPath.split('/').pop() || dirPath;
 | |
|         const files = fileTree.getDirectoryFiles(dirPath);
 | |
| 
 | |
|         // Start building the preview HTML
 | |
|         let html = `<div class="directory-preview">`;
 | |
|         html += `<h2>${dirName}</h2>`;
 | |
| 
 | |
|         if (files.length === 0) {
 | |
|             html += `<p>This directory is empty</p>`;
 | |
|         } else {
 | |
|             html += `<div class="directory-files">`;
 | |
| 
 | |
|             // Create cards for each file
 | |
|             for (const file of files) {
 | |
|                 const fileName = file.name;
 | |
|                 let fileDescription = '';
 | |
| 
 | |
|                 // Try to get file description from markdown files
 | |
|                 if (file.name.endsWith('.md')) {
 | |
|                     try {
 | |
|                         const content = await webdavClient.get(file.path);
 | |
|                         // Extract first heading or first line as description
 | |
|                         const lines = content.split('\n');
 | |
|                         for (const line of lines) {
 | |
|                             if (line.trim().startsWith('#')) {
 | |
|                                 fileDescription = line.replace(/^#+\s*/, '').trim();
 | |
|                                 break;
 | |
|                             } else if (line.trim() && !line.startsWith('---')) {
 | |
|                                 fileDescription = line.trim().substring(0, 100);
 | |
|                                 break;
 | |
|                             }
 | |
|                         }
 | |
|                     } catch (error) {
 | |
|                         console.error('Failed to read file description:', error);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 html += `
 | |
|                     <div class="file-card" data-path="${file.path}">
 | |
|                         <div class="file-card-header">
 | |
|                             <i class="bi bi-file-earmark-text"></i>
 | |
|                             <span class="file-card-name">${fileName}</span>
 | |
|                         </div>
 | |
|                         ${fileDescription ? `<div class="file-card-description">${fileDescription}</div>` : ''}
 | |
|                     </div>
 | |
|                 `;
 | |
|             }
 | |
| 
 | |
|             html += `</div>`;
 | |
|         }
 | |
| 
 | |
|         html += `</div>`;
 | |
| 
 | |
|         // Set the preview content
 | |
|         editor.previewElement.innerHTML = html;
 | |
| 
 | |
|         // Add click handlers to file cards
 | |
|         editor.previewElement.querySelectorAll('.file-card').forEach(card => {
 | |
|             card.addEventListener('click', async () => {
 | |
|                 const filePath = card.dataset.path;
 | |
|                 await editor.loadFile(filePath);
 | |
|                 fileTree.selectAndExpandPath(filePath);
 | |
|             });
 | |
|         });
 | |
|     } catch (error) {
 | |
|         console.error('Failed to show directory preview:', error);
 | |
|         editor.previewElement.innerHTML = `
 | |
|             <div class="alert alert-danger">
 | |
|                 <p>Failed to load directory preview</p>
 | |
|             </div>
 | |
|         `;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Parse URL to extract collection and file path
 | |
|  * URL format: /<collection>/<file_path> or /<collection>/<dir>/<file>
 | |
|  * @returns {Object} {collection, filePath} or {collection, null} if only collection
 | |
|  */
 | |
| function parseURLPath() {
 | |
|     const pathname = window.location.pathname;
 | |
|     const parts = pathname.split('/').filter(p => p); // Remove empty parts
 | |
| 
 | |
|     if (parts.length === 0) {
 | |
|         return { collection: null, filePath: null };
 | |
|     }
 | |
| 
 | |
|     const collection = parts[0];
 | |
|     const filePath = parts.length > 1 ? parts.slice(1).join('/') : null;
 | |
| 
 | |
|     return { collection, filePath };
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Update URL based on current collection and file
 | |
|  * @param {string} collection - The collection name
 | |
|  * @param {string} filePath - The file path (optional)
 | |
|  * @param {boolean} isEditMode - Whether in edit mode
 | |
|  */
 | |
| function updateURL(collection, filePath, isEditMode) {
 | |
|     let url = `/${collection}`;
 | |
|     if (filePath) {
 | |
|         url += `/${filePath}`;
 | |
|     }
 | |
|     if (isEditMode) {
 | |
|         url += '?edit=true';
 | |
|     }
 | |
| 
 | |
|     // Use pushState to update URL without reloading
 | |
|     window.history.pushState({ collection, filePath }, '', url);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Load file from URL path
 | |
|  * Assumes the collection is already set and file tree is loaded
 | |
|  * @param {string} collection - The collection name (for validation)
 | |
|  * @param {string} filePath - The file path
 | |
|  */
 | |
| async function loadFileFromURL(collection, filePath) {
 | |
|     console.log('[loadFileFromURL] Called with:', { collection, filePath });
 | |
| 
 | |
|     if (!fileTree || !editor || !collectionSelector) {
 | |
|         console.error('[loadFileFromURL] Missing dependencies:', { fileTree: !!fileTree, editor: !!editor, collectionSelector: !!collectionSelector });
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|         // Verify we're on the right collection
 | |
|         const currentCollection = collectionSelector.getCurrentCollection();
 | |
|         if (currentCollection !== collection) {
 | |
|             console.error(`[loadFileFromURL] Collection mismatch: expected ${collection}, got ${currentCollection}`);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Load the file or directory
 | |
|         if (filePath) {
 | |
|             // Check if the path is a directory or a file
 | |
|             const node = fileTree.findNode(filePath);
 | |
|             console.log('[loadFileFromURL] Found node:', node);
 | |
| 
 | |
|             if (node && node.isDirectory) {
 | |
|                 // It's a directory, show directory preview
 | |
|                 console.log('[loadFileFromURL] Loading directory preview');
 | |
|                 await showDirectoryPreview(filePath);
 | |
|                 fileTree.selectAndExpandPath(filePath);
 | |
|             } else if (node) {
 | |
|                 // It's a file, check if it's binary
 | |
|                 console.log('[loadFileFromURL] Loading file');
 | |
| 
 | |
|                 // 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}`);
 | |
|             }
 | |
|         }
 | |
|     } catch (error) {
 | |
|         console.error('[loadFileFromURL] Failed to load file from URL:', error);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Handle browser back/forward navigation
 | |
|  */
 | |
| function setupPopStateListener() {
 | |
|     window.addEventListener('popstate', async (event) => {
 | |
|         const { collection, filePath } = parseURLPath();
 | |
|         if (collection) {
 | |
|             // Ensure the collection is set
 | |
|             const currentCollection = collectionSelector.getCurrentCollection();
 | |
|             if (currentCollection !== collection) {
 | |
|                 await collectionSelector.setCollection(collection);
 | |
|                 await fileTree.load();
 | |
|             }
 | |
| 
 | |
|             // Load the file/directory
 | |
|             await loadFileFromURL(collection, filePath);
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| // Initialize application
 | |
| document.addEventListener('DOMContentLoaded', async () => {
 | |
|     // Determine view mode from URL parameter
 | |
|     const urlParams = new URLSearchParams(window.location.search);
 | |
|     const isEditMode = urlParams.get('edit') === 'true';
 | |
| 
 | |
|     // Set view mode class on body
 | |
|     if (isEditMode) {
 | |
|         document.body.classList.add('edit-mode');
 | |
|         document.body.classList.remove('view-mode');
 | |
|     } else {
 | |
|         document.body.classList.add('view-mode');
 | |
|         document.body.classList.remove('edit-mode');
 | |
|     }
 | |
| 
 | |
|     // Initialize WebDAV client
 | |
|     webdavClient = new WebDAVClient('/fs/');
 | |
| 
 | |
|     // Initialize dark mode
 | |
|     darkMode = new DarkMode();
 | |
|     document.getElementById('darkModeBtn').addEventListener('click', () => {
 | |
|         darkMode.toggle();
 | |
|     });
 | |
| 
 | |
|     // Initialize collection selector (always needed)
 | |
|     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();
 | |
| 
 | |
|     // Initialize editor (always needed for preview)
 | |
|     // In view mode, editor is read-only
 | |
|     editor = new MarkdownEditor('editor', 'preview', 'filenameInput', !isEditMode);
 | |
|     editor.setWebDAVClient(webdavClient);
 | |
| 
 | |
|     // Initialize file tree (needed in both modes)
 | |
|     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
 | |
|             updateURL(currentCollection, item.path, isEditMode);
 | |
|         } catch (error) {
 | |
|             Logger.error('Failed to select file:', error);
 | |
|             if (window.showNotification) {
 | |
|                 window.showNotification('Failed to load file', 'error');
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     fileTree.onFolderSelect = async (item) => {
 | |
|         try {
 | |
|             // Show directory preview
 | |
|             await showDirectoryPreview(item.path);
 | |
|             // Highlight the directory in the tree and expand parent directories
 | |
|             fileTree.selectAndExpandPath(item.path);
 | |
|             // Update URL to reflect current directory
 | |
|             const currentCollection = collectionSelector.getCurrentCollection();
 | |
|             updateURL(currentCollection, item.path, isEditMode);
 | |
|         } catch (error) {
 | |
|             Logger.error('Failed to select folder:', error);
 | |
|             if (window.showNotification) {
 | |
|                 window.showNotification('Failed to load folder', 'error');
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     collectionSelector.onChange = async (collection) => {
 | |
|         try {
 | |
|             await fileTree.load();
 | |
|             // In view mode, auto-load last viewed page when collection changes
 | |
|             if (!isEditMode) {
 | |
|                 await autoLoadPageInViewMode();
 | |
|             }
 | |
|         } catch (error) {
 | |
|             Logger.error('Failed to change collection:', error);
 | |
|             if (window.showNotification) {
 | |
|                 window.showNotification('Failed to change collection', 'error');
 | |
|             }
 | |
|         }
 | |
|     };
 | |
|     await fileTree.load();
 | |
| 
 | |
|     // Parse URL to load file if specified
 | |
|     const { collection: urlCollection, filePath: urlFilePath } = parseURLPath();
 | |
|     console.log('[URL PARSE]', { urlCollection, urlFilePath });
 | |
| 
 | |
|     if (urlCollection) {
 | |
|         // First ensure the collection is set
 | |
|         const currentCollection = collectionSelector.getCurrentCollection();
 | |
|         if (currentCollection !== urlCollection) {
 | |
|             console.log('[URL LOAD] Switching collection from', currentCollection, 'to', urlCollection);
 | |
|             await collectionSelector.setCollection(urlCollection);
 | |
|             await fileTree.load();
 | |
|         }
 | |
| 
 | |
|         // 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) {
 | |
|         // No URL collection specified, in view mode: auto-load last viewed page
 | |
|         await autoLoadPageInViewMode();
 | |
|     }
 | |
| 
 | |
|     // Initialize file tree and editor-specific features only in edit mode
 | |
|     if (isEditMode) {
 | |
|         // Add test content to verify preview works
 | |
|         setTimeout(() => {
 | |
|             if (!editor.editor.getValue()) {
 | |
|                 editor.editor.setValue('# Welcome to Markdown Editor\n\nStart typing to see preview...\n');
 | |
|                 editor.updatePreview();
 | |
|             }
 | |
|         }, 200);
 | |
| 
 | |
|         // Setup editor drop handler
 | |
|         const editorDropHandler = new EditorDropHandler(
 | |
|             document.querySelector('.editor-container'),
 | |
|             async (file) => {
 | |
|                 try {
 | |
|                     await handleEditorFileDrop(file);
 | |
|                 } catch (error) {
 | |
|                     Logger.error('Failed to handle file drop:', error);
 | |
|                 }
 | |
|             }
 | |
|         );
 | |
| 
 | |
|         // Setup button handlers
 | |
|         document.getElementById('newBtn').addEventListener('click', () => {
 | |
|             editor.newFile();
 | |
|         });
 | |
| 
 | |
|         document.getElementById('saveBtn').addEventListener('click', async () => {
 | |
|             try {
 | |
|                 await editor.save();
 | |
|             } catch (error) {
 | |
|                 Logger.error('Failed to save file:', error);
 | |
|                 if (window.showNotification) {
 | |
|                     window.showNotification('Failed to save file', 'error');
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         document.getElementById('deleteBtn').addEventListener('click', async () => {
 | |
|             try {
 | |
|                 await editor.deleteFile();
 | |
|             } catch (error) {
 | |
|                 Logger.error('Failed to delete file:', error);
 | |
|                 if (window.showNotification) {
 | |
|                     window.showNotification('Failed to delete file', 'error');
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         // Setup context menu handlers
 | |
|         setupContextMenuHandlers();
 | |
| 
 | |
|         // 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();
 | |
|     }
 | |
| 
 | |
|     // Initialize mermaid (always needed)
 | |
|     mermaid.initialize({ startOnLoad: true, theme: darkMode.isDark ? 'dark' : 'default' });
 | |
|     // Listen for file-saved event to reload file tree
 | |
|     window.eventBus.on('file-saved', async (path) => {
 | |
|         try {
 | |
|             if (fileTree) {
 | |
|                 await fileTree.load();
 | |
|                 fileTree.selectNode(path);
 | |
|             }
 | |
|         } catch (error) {
 | |
|             Logger.error('Failed to reload file tree after save:', error);
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     window.eventBus.on('file-deleted', async () => {
 | |
|         try {
 | |
|             if (fileTree) {
 | |
|                 await fileTree.load();
 | |
|             }
 | |
|         } catch (error) {
 | |
|             Logger.error('Failed to reload file tree after delete:', error);
 | |
|         }
 | |
|     });
 | |
| });
 | |
| 
 | |
| // Listen for column resize events to refresh editor
 | |
| window.addEventListener('column-resize', () => {
 | |
|     if (editor && editor.editor) {
 | |
|         editor.editor.refresh();
 | |
|     }
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * File Operations
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Context Menu Handlers
 | |
|  */
 | |
| function setupContextMenuHandlers() {
 | |
|     const menu = document.getElementById('contextMenu');
 | |
| 
 | |
|     menu.addEventListener('click', async (e) => {
 | |
|         const item = e.target.closest('.context-menu-item');
 | |
|         if (!item) return;
 | |
| 
 | |
|         const action = item.dataset.action;
 | |
|         const targetPath = menu.dataset.targetPath;
 | |
|         const isDir = menu.dataset.targetIsDir === 'true';
 | |
| 
 | |
|         hideContextMenu();
 | |
| 
 | |
|         await window.fileTreeActions.execute(action, targetPath, isDir);
 | |
|     });
 | |
| }
 | |
| 
 | |
| // 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');
 | |
|     if (pasteItem) {
 | |
|         pasteItem.style.display = clipboard ? 'block' : 'none';
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Editor File Drop Handler
 | |
|  */
 | |
| async function handleEditorFileDrop(file) {
 | |
|     try {
 | |
|         // Get current file's directory
 | |
|         let targetDir = '';
 | |
|         if (currentFilePath) {
 | |
|             const parts = currentFilePath.split('/');
 | |
|             parts.pop(); // Remove filename
 | |
|             targetDir = parts.join('/');
 | |
|         }
 | |
| 
 | |
|         // Upload 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}](${uploadedPath})`;
 | |
| 
 | |
|         editor.insertAtCursor(link);
 | |
|         showNotification(`Uploaded and inserted link`, 'success');
 | |
|     } catch (error) {
 | |
|         console.error('Failed to handle file drop:', error);
 | |
|         showNotification('Failed to upload file', 'error');
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Make showContextMenu global
 | |
| window.showContextMenu = showContextMenu;
 | |
| 
 |