Temporary Archive

Name Title Created Modified Size Description Contributor Tags Alt Text
Rename
Download
Delete
Upload Files
Upload Folder
New Directory
New Text File
`; popup.document.write(popupContent); popup.document.close(); popup.addEventListener('unload', () => { openPopups.delete(popup); }); } else { // Open as modal (new behavior) const modal = document.createElement('div'); modal.className = 'media-modal'; modal.innerHTML = `
${displayTitle}
${viewerHtml}
`; // Add a special class for text editors and 3D models if (isType('text/') || metadata.mimeType === 'text/plain' || isType('model/')) { modal.classList.add('header-draggable-only'); } document.body.appendChild(modal); openModals.add(modal); // Set up the control buttons const controls = modal.querySelectorAll('.media-modal-control'); controls[0].addEventListener('click', () => { modal.remove(); openModals.delete(modal); openMediaViewer(filePath, metadata, true); // Open as popup }); controls[1].addEventListener('click', () => { modal.remove(); openModals.delete(modal); }); // Make the modal draggable makeDraggable(modal, modal); } } // Fix the draggable function function makeDraggable(element, handle) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; let firstDrag = true; // Check if this is a text editor or 3D model modal const isTextEditor = element.querySelector('#text-content-container') !== null; const is3DModel = element.querySelector('model-viewer') !== null; // For text editors and 3D models, only make the header draggable if (isTextEditor || is3DModel) { const header = element.querySelector('.media-modal-header'); if (header) { header.onmousedown = dragMouseDown; } // For 3D models, add special handling to ensure model-viewer controls work if (is3DModel) { const modelViewer = element.querySelector('model-viewer'); if (modelViewer) { // Make sure the model-viewer has pointer-events enabled modelViewer.style.pointerEvents = 'auto'; // Add class to indicate this modal has special handling element.classList.add('model-viewer-modal'); } } } else { // For other media types, make the entire modal draggable element.onmousedown = dragMouseDown; } function dragMouseDown(e) { // Don't start drag on interactive elements if (e.target.closest('button, a, input, textarea, .media-modal-control')) { return; } // Don't interfere with resize handle const rect = element.getBoundingClientRect(); const isResizeArea = e.clientX > rect.right - 20 && e.clientY > rect.bottom - 20; if (isResizeArea) return; e.preventDefault(); // Get the mouse cursor position at startup pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // Call a function whenever the cursor moves document.onmousemove = elementDrag; } function elementDrag(e) { e.preventDefault(); // Calculate the new cursor position pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // Set the element's new position element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { // Stop moving when mouse button is released document.onmouseup = null; document.onmousemove = null; } } // Update the window unload handler to close all popups and modals window.addEventListener('unload', () => { for (const popup of openPopups) { popup.close(); } for (const modal of openModals) { // Clean up any observers for (const modal of openModals) { if (modal._observer) { modal._observer.disconnect(); } modal.remove(); } } }); // Add after other initialization code const viewToggle = document.getElementById('viewToggle'); const columnMenu = document.getElementById('columnMenu'); // Initialize checkboxes and column visibility (columnVisibility already initialized above) Object.entries(columnVisibility).forEach(([column, isVisible]) => { const checkbox = document.getElementById(`col-${column}`); if (checkbox) { checkbox.checked = isVisible; updateColumnVisibility(column, isVisible); } }); // Toggle menu viewToggle.addEventListener('click', (e) => { e.stopPropagation(); columnMenu.classList.toggle('show'); }); // Toggle view visibility (collapse/expand) const viewToggleVisibility = document.getElementById('viewToggleVisibility'); const viewContainerWrapper = document.querySelector('.view-container-wrapper'); const eyeIcon = document.getElementById('eyeIcon'); let isCollapsed = false; // Initialize expanded state if (viewContainerWrapper) { viewContainerWrapper.classList.add('expanded'); } if (viewToggleVisibility && viewContainerWrapper) { viewToggleVisibility.addEventListener('click', (e) => { e.stopPropagation(); isCollapsed = !isCollapsed; if (isCollapsed) { viewContainerWrapper.classList.add('collapsed'); viewContainerWrapper.classList.remove('expanded'); // Update eye icon to show crossed-out version eyeIcon.innerHTML = ` `; } else { viewContainerWrapper.classList.remove('collapsed'); viewContainerWrapper.classList.add('expanded'); // Restore normal eye icon eyeIcon.innerHTML = ` `; } }); } // Close menu when clicking outside document.addEventListener('click', (e) => { if (!columnMenu.contains(e.target)) { columnMenu.classList.remove('show'); } }); // Handle checkbox changes document.querySelectorAll('.column-option input').forEach(checkbox => { checkbox.addEventListener('change', (e) => { const column = e.target.id.replace('col-', ''); const isVisible = e.target.checked; columnVisibility[column] = isVisible; saveColumnVisibility(columnVisibility); // Use utility function updateColumnVisibility(column, isVisible); }); }); function updateColumnVisibility(column, isVisible) { // Update via ViewManager if available if (viewManager) { viewManager.updateColumnVisibility(column, isVisible); } else { // Fallback to direct DOM manipulation const th = document.querySelector(`.file-table th[data-column="${column}"]`); if (th) { th.classList.toggle('hidden-column', !isVisible); } document.querySelectorAll(`.file-table td[data-column="${column}"]`).forEach(cell => { cell.classList.toggle('hidden-column', !isVisible); }); } } const chatButton = document.getElementById('chatButton'); let chatModal = null; function openChatModal() { // Check if chat modal is already open if (chatModal && document.body.contains(chatModal)) { // Bring to front let currentMaxZ = 1000; document.querySelectorAll('.media-modal').forEach(m => { const z = parseInt(window.getComputedStyle(m).zIndex) || 1000; if (z >= currentMaxZ) currentMaxZ = z + 1; }); chatModal.style.zIndex = currentMaxZ; return; } // Save scroll position and lock body scroll (mobile fix) const appContent = document.querySelector('.app-content'); const savedScrollTop = appContent ? appContent.scrollTop : 0; const savedScrollLeft = appContent ? appContent.scrollLeft : 0; // Lock body scroll on mobile if (window.innerWidth <= 768) { document.body.style.position = 'fixed'; document.body.style.top = `-${savedScrollTop}px`; document.body.style.left = `-${savedScrollLeft}px`; document.body.style.width = '100%'; document.body.style.height = '100%'; document.body.style.overflow = 'hidden'; } // Get username and socket from current window let currentUser = localStorage.getItem('userName') || 'Anonymous'; const socket = chatSocket; // Create modal chatModal = document.createElement('div'); chatModal.className = 'media-modal'; chatModal.style.width = '800px'; chatModal.style.height = '600px'; chatModal._savedScrollTop = savedScrollTop; chatModal._savedScrollLeft = savedScrollLeft; // Set z-index let maxZIndex = 1000; document.querySelectorAll('.media-modal').forEach(m => { const z = parseInt(window.getComputedStyle(m).zIndex) || 1000; if (z >= maxZIndex) maxZIndex = z + 1; }); chatModal.style.zIndex = maxZIndex; // Bring to front on click chatModal.addEventListener('mousedown', () => { let currentMaxZ = 1000; document.querySelectorAll('.media-modal').forEach(m => { const z = parseInt(window.getComputedStyle(m).zIndex) || 1000; if (z >= currentMaxZ) currentMaxZ = z + 1; }); chatModal.style.zIndex = currentMaxZ; }); chatModal.innerHTML = `
Chat
b
    System
    Welcome to the chat!
    `; document.body.appendChild(chatModal); openModals.add(chatModal); // Set up blend mode button if (mediaViewer) { mediaViewer.setupBlendModeButton(chatModal); } // Set up close button const closeBtn = chatModal.querySelector('.chat-close-btn'); if (closeBtn) { closeBtn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); // Unlock body scroll and restore position (mobile fix) if (window.innerWidth <= 768) { document.body.style.position = ''; document.body.style.top = ''; document.body.style.left = ''; document.body.style.width = ''; document.body.style.height = ''; document.body.style.overflow = ''; const appContent = document.querySelector('.app-content'); if (appContent && chatModal._savedScrollTop !== undefined) { appContent.scrollTop = chatModal._savedScrollTop; appContent.scrollLeft = chatModal._savedScrollLeft || 0; } } chatModal.remove(); openModals.delete(chatModal); chatModal = null; }); } // Make draggable (but not on mobile) if (window.innerWidth > 768 && mediaViewer) { mediaViewer.makeDraggable(chatModal); } // Get DOM elements const usersList = chatModal.querySelector('#chatUsersList'); const messagesContainer = chatModal.querySelector('#chatMessages'); const messageInput = chatModal.querySelector('.chat-message-input'); // Initialize chat interface const timeElement = chatModal.querySelector('.chat-message-time'); if (timeElement) { timeElement.textContent = new Date().toLocaleTimeString(); } // Helper functions function updateUsersList(users) { if (!usersList) return; usersList.innerHTML = ''; users.forEach(user => { const userItem = document.createElement('li'); userItem.className = 'chat-user-item'; userItem.setAttribute('data-user-id', user.id); userItem.textContent = user.id === socket.id ? `You (${user.name})` : user.name; usersList.appendChild(userItem); }); } function addMessage(message) { if (!messagesContainer) return; const messageDiv = document.createElement('div'); messageDiv.className = 'chat-message'; const header = document.createElement('div'); header.className = 'chat-message-header'; const sender = document.createElement('span'); sender.className = 'chat-message-sender'; sender.textContent = message.userId === socket.id ? `You (${message.sender})` : message.sender; const time = document.createElement('span'); time.className = 'chat-message-time'; time.textContent = new Date(message.time).toLocaleTimeString(); const content = document.createElement('div'); content.className = 'chat-message-content'; content.textContent = message.content; header.appendChild(sender); header.appendChild(time); messageDiv.appendChild(header); messageDiv.appendChild(content); messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function addSystemMessage(text) { const message = { sender: 'System', content: text, time: new Date().toISOString(), userId: 'system' }; addMessage(message); } function saveName(newName) { newName = newName.trim() || 'Anonymous'; currentUser = newName; // Update storage localStorage.setItem('userName', newName); if (window.currentUser !== undefined) { window.currentUser = newName; } // Update the display const nameDisplay = document.getElementById('userName'); if (nameDisplay) { nameDisplay.textContent = newName; } // Notify server socket.emit('nameChanged', { name: newName }); // Trigger storage event for other windows window.dispatchEvent(new StorageEvent('storage', { key: 'userName', newValue: newName })); } // Initialize users list if (activeUsers) { const users = Array.from(activeUsers.values()); updateUsersList(users); } // Request chat history socket.emit('getChatHistory'); // Add welcome message addSystemMessage(`Welcome to the chat, ${currentUser}!`); // Handle active users updates const activeUsersHandler = (users) => { updateUsersList(users); }; socket.on('activeUsers', activeUsersHandler); // Handle user left event socket.on('userLeft', (userId) => { // The server will broadcast updated users list }); // Handle messages const chatMessageHandler = (message) => { addMessage(message); }; socket.on('chatMessage', chatMessageHandler); // Prevent iOS scroll/zoom on focus if (messageInput) { messageInput.addEventListener('focus', (e) => { e.preventDefault(); // Scroll the input into view within the modal without affecting page scroll if (window.innerWidth <= 768) { setTimeout(() => { messageInput.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' }); }, 300); } }, { passive: false }); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); const message = messageInput.value.trim(); if (message) { socket.emit('chatMessage', message); messageInput.value = ''; } } }); } // Handle user list clicks for name editing if (usersList) { usersList.addEventListener('click', (e) => { const userItem = e.target.closest('.chat-user-item'); if (userItem && userItem.getAttribute('data-user-id') === socket.id) { const input = document.createElement('input'); input.value = currentUser; input.style.width = '150px'; userItem.textContent = ''; userItem.appendChild(input); input.focus(); input.addEventListener('blur', () => saveName(input.value)); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); saveName(input.value); } }); } }); } // Handle chat history const chatHistoryHandler = (messages) => { // Clear existing messages except the welcome message while (messagesContainer.children.length > 1) { messagesContainer.removeChild(messagesContainer.lastChild); } // Add each message to the chat messages.forEach(message => { addMessage(message); }); }; socket.on('chatHistory', chatHistoryHandler); // Clean up event listeners when modal is closed const originalRemove = chatModal.remove.bind(chatModal); chatModal.remove = function() { socket.off('activeUsers', activeUsersHandler); socket.off('chatMessage', chatMessageHandler); socket.off('chatHistory', chatHistoryHandler); originalRemove(); }; } chatButton.addEventListener('click', () => { openChatModal(); }); // File input handler const fileInput = document.getElementById('fileInput'); const folderInput = document.getElementById('folderInput'); // Handler for file input (individual files) if (fileInput) { fileInput.addEventListener('change', async (e) => { const files = e.target.files; if (!files.length) return; if (fileUploader) { await fileUploader.handleFileInput(files); } else { // Fallback const formData = new FormData(); formData.append('contributor', currentUser); formData.append('currentDirectory', currentDirectory); // Add each file to formData for (let file of files) { const fileInfo = { lastModified: file.lastModified, created: file.lastModifiedDate || new Date(file.lastModified) }; formData.append(file.name, file); formData.append(`${file.name}_info`, JSON.stringify(fileInfo)); } try { const uploadFn = window.fileAPI?.uploadFiles || (async (formData) => { const response = await fetch('/upload', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); }); const result = await uploadFn(formData); if (result.success) { loadDirectory(currentDirectory); } } catch (error) { console.error('Upload failed:', error); alert(`Upload failed: ${error.message}`); } } // Reset input fileInput.value = ''; }); } // Handler for folder input (directory upload) if (folderInput) { folderInput.addEventListener('change', async (e) => { const files = e.target.files; if (!files.length) return; if (fileUploader) { await fileUploader.handleFileInput(files); } else { // Fallback const formData = new FormData(); formData.append('contributor', currentUser); formData.append('currentDirectory', currentDirectory); // When webkitdirectory is used, files have webkitRelativePath that preserves directory structure for (let file of files) { const fileInfo = { lastModified: file.lastModified, created: file.lastModifiedDate || new Date(file.lastModified) }; // Use webkitRelativePath for directory uploads const relativePath = file.webkitRelativePath || file.name; formData.append(relativePath, file); formData.append(`${relativePath}_info`, JSON.stringify(fileInfo)); } try { const uploadFn = window.fileAPI?.uploadFiles || (async (formData) => { const response = await fetch('/upload', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); }); const result = await uploadFn(formData); if (result.success) { loadDirectory(currentDirectory); } else { throw new Error(result.message); } } catch (error) { console.error('Upload failed:', error); alert(`Upload failed: ${error.message}`); } } // Reset input folderInput.value = ''; }); } // Helper to check if user can edit archive name function canEditArchiveName(userName) { if (!userName) return false; const lowerName = userName.toLowerCase(); return lowerName === 'whisper' || lowerName === 'hal'; } // Function to update archive title display function updateArchiveTitle(archiveName) { const titleElement = document.querySelector('.archive-title'); if (titleElement) { titleElement.textContent = archiveName; // Update page title too document.title = archiveName; } } // Function to make archive title editable let archiveTitleEditHandler = null; function makeArchiveTitleEditable() { const titleElement = document.querySelector('.archive-title'); if (!titleElement) return; // Remove existing handler if any if (archiveTitleEditHandler) { titleElement.removeEventListener('click', archiveTitleEditHandler); archiveTitleEditHandler = null; } // Only make editable if user is whisper or hal if (!canEditArchiveName(currentUser)) { titleElement.style.cursor = ''; titleElement.removeAttribute('title'); return; } // Add cursor pointer style titleElement.style.cursor = 'text'; titleElement.setAttribute('title', 'Click to edit archive name'); archiveTitleEditHandler = async (e) => { e.stopPropagation(); const currentName = titleElement.textContent; const input = document.createElement('input'); input.type = 'text'; input.value = currentName; input.style.cssText = ` font-size: inherit; font-weight: inherit; font-family: inherit; border: 1px solid #333; background: #fff; padding: 2px 4px; margin: 0; width: ${Math.max(currentName.length * 0.6, 200)}px; `; // Replace title with input titleElement.style.display = 'none'; titleElement.parentNode.insertBefore(input, titleElement); input.focus(); input.select(); const saveEdit = async () => { const newName = input.value.trim(); if (newName && newName !== currentName) { try { await window.configAPI.updateArchiveName(newName, currentUser); updateArchiveTitle(newName); } catch (error) { console.error('Failed to update archive name:', error); alert(error.message || 'Failed to update archive name'); updateArchiveTitle(currentName); // Revert on error } } else { updateArchiveTitle(currentName); // Revert if empty or unchanged } input.remove(); titleElement.style.display = ''; }; input.addEventListener('blur', saveEdit); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); input.blur(); } else if (e.key === 'Escape') { e.preventDefault(); updateArchiveTitle(currentName); input.remove(); titleElement.style.display = ''; } }); }; titleElement.addEventListener('click', archiveTitleEditHandler); } // Fetch and display archive name on load async function loadArchiveName() { try { const config = await window.configAPI.getConfig(); if (config && config.archiveName) { updateArchiveTitle(config.archiveName); } } catch (error) { console.error('Failed to load archive name:', error); } } // Initialize user data when the main page loads document.addEventListener('DOMContentLoaded', async () => { const userName = (window.getUserName && window.getUserName()) || localStorage.getItem('userName') || 'Anonymous'; console.log('Main page loaded, initializing user:', userName); // Initialize the user on the server socket.emit('initUser', { name: userName }); // Store the socket for the chat window to use window.chatSocket = socket; // Load archive name await loadArchiveName(); // Make title editable if user is whisper or hal // Wait a bit for modules to load setTimeout(() => { makeArchiveTitleEditable(); }, 100); }); // Listen for real-time archive name updates socket.on('archiveNameUpdated', (data) => { if (data && data.archiveName) { updateArchiveTitle(data.archiveName); } }); // When opening the chat window, pass the existing socket function openChat() { const chatWindow = window.open('/chat.html', 'chat', 'width=800,height=600'); // The chat window will use this socket instead of creating a new one } // Initialize ContextMenu component let contextMenuInstance = null; let contextMenuInitialized = false; const contextMenuElement = document.getElementById('contextMenu'); function initializeContextMenu() { if (contextMenuInitialized) return; // Prevent double initialization if (!contextMenuElement) { console.warn('Context menu element not found'); return; } if (!window.ContextMenu) { if (window.modulesLoaded) { console.warn('ContextMenu component not available even after modules loaded'); } else { window.addEventListener('modulesReady', initializeContextMenu, { once: true }); } return; } contextMenuInitialized = true; try { contextMenuInstance = new window.ContextMenu(contextMenuElement); console.log('ContextMenu initialized successfully'); } catch (error) { console.error('Failed to initialize ContextMenu:', error); } } initializeContextMenu(); // Wrapper function for backward compatibility function showContextMenu(e, filePath, metadata) { // Update share menu items visibility const shareWithMetadataItem = document.getElementById('shareWithMetadataItem'); const shareFileItem = document.getElementById('shareFileItem'); const shareDirectoryItem = document.getElementById('shareDirectoryItem'); if (metadata.isDirectory) { // Directory: show directory share option if (shareWithMetadataItem) shareWithMetadataItem.style.display = 'none'; if (shareFileItem) shareFileItem.style.display = 'none'; if (shareDirectoryItem) shareDirectoryItem.style.display = 'block'; } else { // File: show file share options if (shareWithMetadataItem) shareWithMetadataItem.style.display = 'block'; if (shareFileItem) shareFileItem.style.display = 'block'; if (shareDirectoryItem) shareDirectoryItem.style.display = 'none'; } if (contextMenuInstance) { contextMenuInstance.show(e, filePath, metadata); } else { // Fallback to old implementation e.preventDefault(); const menu = document.getElementById('contextMenu'); if (menu) { menu.style.left = `${e.pageX}px`; menu.style.top = `${e.pageY}px`; menu.style.display = 'block'; window._contextMenuTarget = { path: filePath, isDirectory: metadata.isDirectory, metadata: metadata }; } } } function hideContextMenu() { if (contextMenuInstance) { contextMenuInstance.hide(); } else { // Fallback const menu = document.getElementById('contextMenu'); if (menu) { menu.style.display = 'none'; } window._contextMenuTarget = null; } } // Add event listener for rename option if (contextMenuElement) { document.getElementById('renameItem')?.addEventListener('click', async () => { const target = contextMenuInstance?.getTarget() || window._contextMenuTarget; if (!target) return; const { path: filePath, isDirectory } = target; const currentName = path.basename(filePath); // Extract extension from original filename (if it's a file, not a directory) let originalExtension = ''; let nameWithoutExtension = currentName; if (!isDirectory) { originalExtension = path.extname(currentName); nameWithoutExtension = path.nameWithoutExt(currentName); } // Show prompt with name without extension, but note that extension will be preserved const promptText = isDirectory ? `Enter new name:` : `Enter new name (extension ${originalExtension} will be preserved):`; const newNameInput = prompt(promptText, nameWithoutExtension); if (newNameInput && newNameInput.trim()) { // For files, ensure extension is preserved let finalNewName = newNameInput.trim(); if (!isDirectory && originalExtension) { // If user didn't include extension, add it if (!finalNewName.endsWith(originalExtension)) { finalNewName = finalNewName + originalExtension; } } // Only rename if the name actually changed if (finalNewName !== currentName) { try { const renameFn = window.fileAPI?.renameItem || (async (oldPath, newName) => { const response = await fetch('/rename-item', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: oldPath, newName }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || `HTTP error! status: ${response.status}`); } return await response.json(); }); const result = await renameFn(filePath, finalNewName); if (result.success) { // Reload the current directory to reflect changes loadDirectory(currentDirectory); } else { alert(`Failed to rename: ${result.message || 'Unknown error'}`); } } catch (error) { console.error('Error renaming item:', error); alert(`Error: ${error.message}`); } } } hideContextMenu(); }); } // Add event listener for the delete option if (contextMenuElement) { document.getElementById('deleteItem')?.addEventListener('click', async () => { const target = contextMenuInstance?.getTarget() || window._contextMenuTarget; if (!target) return; const { path: filePath, isDirectory } = target; const fileName = path.basename(filePath); // Confirm deletion const confirmMessage = isDirectory ? `Are you sure you want to delete the folder "${fileName}" and ALL its contents? This cannot be undone.` : `Are you sure you want to delete "${fileName}"? This cannot be undone.`; if (confirm(confirmMessage)) { try { const deleteFn = window.fileAPI?.deleteItem || (async (path, isDir) => { const response = await fetch('/delete-item', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path, isDirectory: isDir }) }); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return await response.json(); }); const result = await deleteFn(filePath, isDirectory); if (result.success) { // Reload the current directory to reflect changes loadDirectory(currentDirectory); } else { alert(`Failed to delete: ${result.message || 'Unknown error'}`); } } catch (error) { console.error('Error deleting item:', error); alert(`Error: ${error.message}`); } } // Reset the target if (contextMenuInstance) { contextMenuInstance.clearTarget(); } else { window._contextMenuTarget = null; } }); } // Share menu handlers document.getElementById('shareWithMetadataItem')?.addEventListener('click', async () => { const target = contextMenuInstance?.getTarget() || window._contextMenuTarget; if (!target) return; const { path: filePath, metadata } = target; const shareLink = ViewActions.getFileShareLink(filePath, true); const success = await ViewActions.copyToClipboard(shareLink); if (success) { alert('Link copied to clipboard!'); } else { alert('Failed to copy link. Link: ' + shareLink); } hideContextMenu(); }); document.getElementById('shareFileItem')?.addEventListener('click', async () => { const target = contextMenuInstance?.getTarget() || window._contextMenuTarget; if (!target) return; const { path: filePath } = target; const shareLink = ViewActions.getFileShareLink(filePath, false); const success = await ViewActions.copyToClipboard(shareLink); if (success) { alert('Link copied to clipboard!'); } else { alert('Failed to copy link. Link: ' + shareLink); } hideContextMenu(); }); document.getElementById('shareDirectoryItem')?.addEventListener('click', async () => { const target = contextMenuInstance?.getTarget() || window._contextMenuTarget; if (!target) return; const { path: dirPath } = target; const shareLink = ViewActions.getDirectoryShareLink(dirPath); const success = await ViewActions.copyToClipboard(shareLink); if (success) { alert('Link copied to clipboard!'); } else { alert('Failed to copy link. Link: ' + shareLink); } hideContextMenu(); }); // Download item handler document.getElementById('downloadItem')?.addEventListener('click', async () => { const target = contextMenuInstance?.getTarget() || window._contextMenuTarget; if (!target) return; const { path: filePath, isDirectory } = target; if (isDirectory) { // Download folder as zip const downloadFn = window.fileAPI?.downloadFolder || (async (path) => { window.location.href = `/download-folder?path=${encodeURIComponent(path)}`; }); await downloadFn(filePath); } else { // Download file const downloadFn = window.fileAPI?.downloadFile || (async (path) => { window.location.href = `/download-file?path=${encodeURIComponent(path)}`; }); await downloadFn(filePath); } hideContextMenu(); }); // New item button handler const newItemBtn = document.getElementById('newItemBtn'); const newItemMenu = document.getElementById('newItemMenu'); if (newItemBtn && newItemMenu) { newItemBtn.addEventListener('click', (e) => { e.stopPropagation(); const rect = newItemBtn.getBoundingClientRect(); newItemMenu.style.left = `${rect.left}px`; newItemMenu.style.top = `${rect.bottom + 5}px`; newItemMenu.style.display = 'block'; }); // Close new item menu when clicking elsewhere document.addEventListener('click', (e) => { if (!newItemMenu.contains(e.target) && e.target !== newItemBtn) { newItemMenu.style.display = 'none'; } }); // New Directory handler document.getElementById('newDirectory')?.addEventListener('click', async () => { newItemMenu.style.display = 'none'; const name = prompt('Enter directory name:'); if (name && name.trim()) { try { const createDirFn = window.fileAPI?.createDirectory || (async (dirPath, name, contributor) => { const response = await fetch('/create-directory', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: dirPath, name, contributor }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || `HTTP error! status: ${response.status}`); } return await response.json(); }); const result = await createDirFn(currentDirectory, name.trim(), currentUser); if (result.success) { loadDirectory(currentDirectory); } else { alert(`Failed to create directory: ${result.message || 'Unknown error'}`); } } catch (error) { console.error('Error creating directory:', error); alert(`Error: ${error.message}`); } } }); // New Text File handler document.getElementById('newTextFile')?.addEventListener('click', async () => { newItemMenu.style.display = 'none'; const name = prompt('Enter file name (e.g., readme.txt):'); if (name && name.trim()) { try { const createFileFn = window.fileAPI?.createTextFile || (async (dirPath, name, content, contributor) => { const response = await fetch('/create-text-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: dirPath, name, content: content || '', contributor }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || `HTTP error! status: ${response.status}`); } return await response.json(); }); const result = await createFileFn(currentDirectory, name.trim(), '', currentUser); if (result.success) { loadDirectory(currentDirectory); // Open the file for editing setTimeout(() => { openMediaViewer(result.path, { mimeType: 'text/plain' }, true); }, 500); } else { alert(`Failed to create file: ${result.message || 'Unknown error'}`); } } catch (error) { console.error('Error creating text file:', error); alert(`Error: ${error.message}`); } } }); // Upload Files handler (individual files) document.getElementById('uploadFiles')?.addEventListener('click', () => { newItemMenu.style.display = 'none'; const fileInput = document.getElementById('fileInput'); if (fileInput) { fileInput.click(); } }); // Upload Folder handler (directory upload) document.getElementById('uploadFolder')?.addEventListener('click', () => { newItemMenu.style.display = 'none'; const folderInput = document.getElementById('folderInput'); if (folderInput) { folderInput.click(); } }); // New Link handler document.getElementById('newLink')?.addEventListener('click', async () => { newItemMenu.style.display = 'none'; const name = prompt('Enter link name:'); if (name && name.trim()) { const url = prompt('Enter URL:'); if (url && url.trim()) { try { // Create a link by uploading a placeholder file with link metadata const formData = new FormData(); formData.append('contributor', currentUser); formData.append('currentDirectory', currentDirectory); // Create a small placeholder file const blob = new Blob([''], { type: 'text/plain' }); formData.append(name.trim(), blob, name.trim()); formData.append(`${name.trim()}_info`, JSON.stringify({ lastModified: Date.now(), created: Date.now() })); formData.append(`${name.trim()}_link`, url.trim()); const uploadFn = window.fileAPI?.uploadFiles || (async (formData) => { const response = await fetch('/upload', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); }); const result = await uploadFn(formData); if (result.success) { loadDirectory(currentDirectory); } else { alert(`Failed to create link: ${result.message || 'Unknown error'}`); } } catch (error) { console.error('Error creating link:', error); alert(`Error: ${error.message}`); } } } }); } // Close context menu when window is resized window.addEventListener('resize', hideContextMenu); // Prevent the browser's context menu on the entire document document.addEventListener('contextmenu', (e) => { // Only prevent default if we're not in an input or textarea if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') { e.preventDefault(); } }); // Add this function after updateMetadata async function saveTextFile(filePath, content) { try { const saveTextFn = window.fileAPI?.saveTextFile || (async (path, content) => { const response = await fetch('/save-text-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path, content }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || 'Failed to save file'); } return await response.json(); }); return await saveTextFn(filePath, content); } catch (error) { console.error('Error saving text file:', error); throw error; } } // Add event listener for the download option document.getElementById('downloadItem').addEventListener('click', async () => { if (!contextMenuTarget) return; const { path: filePath, isDirectory } = contextMenuTarget; try { if (isDirectory) { // For directories, request a zip file const downloadFn = window.fileAPI?.downloadFolder || ((path) => { window.location.href = `/download-folder?path=${encodeURIComponent(path)}`; }); downloadFn(filePath); } else { // For individual files, download directly const downloadFn = window.fileAPI?.downloadFile || ((path) => { window.location.href = `/download-file?path=${encodeURIComponent(path)}`; }); downloadFn(filePath); } } catch (error) { console.error('Error downloading item:', error); alert(`Error: ${error.message}`); } // Reset the target contextMenuTarget = null; }); // Add this after the chat button event handler const exportButton = document.getElementById('exportButton'); exportButton.addEventListener('click', () => { if (confirm('Download the entire archive? This may take some time for large archives.')) { const exportFn = window.fileAPI?.exportArchive || (() => { window.location.href = '/export-archive'; }); exportFn(); } });