/** * UI Utilities Module * Toast notifications, context menu, dark mode, file upload dialog */ /** * Show toast notification */ function showNotification(message, type = 'info') { const container = document.getElementById('toastContainer') || createToastContainer(); const toast = document.createElement('div'); const bgClass = type === 'error' ? 'danger' : type === 'success' ? 'success' : type === 'warning' ? 'warning' : 'primary'; toast.className = `toast align-items-center text-white bg-${bgClass} border-0`; toast.setAttribute('role', 'alert'); toast.innerHTML = `
${message}
`; container.appendChild(toast); const bsToast = new bootstrap.Toast(toast, { delay: 3000 }); bsToast.show(); toast.addEventListener('hidden.bs.toast', () => { toast.remove(); }); } function createToastContainer() { const container = document.createElement('div'); container.id = 'toastContainer'; container.className = 'toast-container position-fixed top-0 end-0 p-3'; container.style.zIndex = '9999'; document.body.appendChild(container); return container; } /** * Enhanced Context Menu */ function showContextMenu(x, y, target) { const menu = document.getElementById('contextMenu'); if (!menu) return; // Store target menu.dataset.targetPath = target.path; menu.dataset.targetIsDir = target.isDir; // Show/hide menu items based on target type const newFileItem = menu.querySelector('[data-action="new-file"]'); const newFolderItem = menu.querySelector('[data-action="new-folder"]'); const uploadItem = menu.querySelector('[data-action="upload"]'); const downloadItem = menu.querySelector('[data-action="download"]'); if (target.isDir) { // Folder context menu if (newFileItem) newFileItem.style.display = 'block'; if (newFolderItem) newFolderItem.style.display = 'block'; if (uploadItem) uploadItem.style.display = 'block'; if (downloadItem) downloadItem.style.display = 'block'; } else { // File context menu if (newFileItem) newFileItem.style.display = 'none'; if (newFolderItem) newFolderItem.style.display = 'none'; if (uploadItem) uploadItem.style.display = 'none'; if (downloadItem) downloadItem.style.display = 'block'; } // Position menu menu.style.display = 'block'; menu.style.left = x + 'px'; menu.style.top = y + 'px'; // Adjust if off-screen const rect = menu.getBoundingClientRect(); if (rect.right > window.innerWidth) { menu.style.left = (window.innerWidth - rect.width - 10) + 'px'; } if (rect.bottom > window.innerHeight) { menu.style.top = (window.innerHeight - rect.height - 10) + 'px'; } } function hideContextMenu() { const menu = document.getElementById('contextMenu'); if (menu) { menu.style.display = 'none'; } } // Hide context menu on click outside document.addEventListener('click', (e) => { if (!e.target.closest('#contextMenu')) { hideContextMenu(); } }); /** * File Upload Dialog */ function showFileUploadDialog(targetPath, onUpload) { const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.addEventListener('change', async (e) => { const files = Array.from(e.target.files); if (files.length === 0) return; for (const file of files) { try { await onUpload(targetPath, file); } catch (error) { console.error('Upload failed:', error); } } }); input.click(); } /** * Dark Mode Manager */ class DarkMode { constructor() { this.isDark = localStorage.getItem('darkMode') === 'true'; this.apply(); } toggle() { this.isDark = !this.isDark; localStorage.setItem('darkMode', this.isDark); this.apply(); } apply() { if (this.isDark) { document.body.classList.add('dark-mode'); const btn = document.getElementById('darkModeBtn'); if (btn) btn.textContent = '☀️'; // Update mermaid theme if (window.mermaid) { mermaid.initialize({ theme: 'dark' }); } } else { document.body.classList.remove('dark-mode'); const btn = document.getElementById('darkModeBtn'); if (btn) btn.textContent = '🌙'; // Update mermaid theme if (window.mermaid) { mermaid.initialize({ theme: 'default' }); } } } } /** * Collection Selector */ class CollectionSelector { constructor(selectId, webdavClient) { this.select = document.getElementById(selectId); this.webdavClient = webdavClient; this.onChange = null; } async load() { try { const collections = await this.webdavClient.getCollections(); this.select.innerHTML = ''; collections.forEach(collection => { const option = document.createElement('option'); option.value = collection; option.textContent = collection; this.select.appendChild(option); }); // Select first collection if (collections.length > 0) { this.select.value = collections[0]; this.webdavClient.setCollection(collections[0]); if (this.onChange) { this.onChange(collections[0]); } } // Add change listener this.select.addEventListener('change', () => { const collection = this.select.value; this.webdavClient.setCollection(collection); if (this.onChange) { this.onChange(collection); } }); } catch (error) { console.error('Failed to load collections:', error); showNotification('Failed to load collections', 'error'); } } } /** * Editor Drop Handler * Handles file drops into the editor */ class EditorDropHandler { constructor(editorElement, onFileDrop) { this.editorElement = editorElement; this.onFileDrop = onFileDrop; this.setupHandlers(); } setupHandlers() { this.editorElement.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); this.editorElement.classList.add('drag-over'); }); this.editorElement.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); this.editorElement.classList.remove('drag-over'); }); this.editorElement.addEventListener('drop', async (e) => { e.preventDefault(); e.stopPropagation(); this.editorElement.classList.remove('drag-over'); const files = Array.from(e.dataTransfer.files); if (files.length === 0) return; for (const file of files) { try { if (this.onFileDrop) { await this.onFileDrop(file); } } catch (error) { console.error('Drop failed:', error); showNotification(`Failed to upload ${file.name}`, 'error'); } } }); } }