// API Base URL const API_BASE = '/claude/api'; // State let currentFile = null; let isEditing = false; let fileTree = []; // DOM Elements const loginScreen = document.getElementById('login-screen'); const mainApp = document.getElementById('main-app'); const loginForm = document.getElementById('login-form'); const loginError = document.getElementById('login-error'); const logoutBtn = document.getElementById('logout-btn'); const searchInput = document.getElementById('search-input'); const fileTreeEl = document.getElementById('file-tree'); const recentFilesEl = document.getElementById('recent-files'); const contentView = document.getElementById('content-view'); const contentEditor = document.getElementById('content-editor'); const breadcrumb = document.getElementById('breadcrumb'); const editBtn = document.getElementById('edit-btn'); const saveBtn = document.getElementById('save-btn'); const cancelBtn = document.getElementById('cancel-btn'); // Initialize document.addEventListener('DOMContentLoaded', () => { checkAuth(); setupEventListeners(); }); function setupEventListeners() { // Login form const loginForm = document.getElementById('login-form'); if (loginForm) { loginForm.addEventListener('submit', handleLogin); } // Logout const logoutBtn = document.getElementById('logout-btn'); if (logoutBtn) { logoutBtn.addEventListener('click', handleLogout); } // Search const searchInput = document.getElementById('search-input'); if (searchInput) { let searchTimeout; searchInput.addEventListener('input', (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => handleSearch(e.target.value), 300); }); } // Edit/Save/Cancel buttons const editBtn = document.getElementById('edit-btn'); const saveBtn = document.getElementById('save-btn'); const cancelBtn = document.getElementById('cancel-btn'); if (editBtn) { editBtn.addEventListener('click', startEditing); } if (saveBtn) { saveBtn.addEventListener('click', saveFile); } if (cancelBtn) { cancelBtn.addEventListener('click', stopEditing); } } // Auth functions async function checkAuth() { try { const res = await fetch(`${API_BASE}/auth/status`); const data = await res.json(); if (data.authenticated) { // Redirect to landing page if authenticated window.location.href = '/claude/'; } else { showLogin(); } } catch (error) { console.error('Auth check failed:', error); showLogin(); } } async function handleLogin(e) { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; try { const res = await fetch(`${API_BASE}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await res.json(); if (data.success) { // Redirect to landing page after successful login window.location.href = '/claude/'; } else { loginError.textContent = data.error || 'Login failed'; } } catch (error) { loginError.textContent = 'Network error. Please try again.'; } } async function handleLogout() { try { await fetch(`${API_BASE}/logout`, { method: 'POST' }); showLogin(); } catch (error) { console.error('Logout failed:', error); } } function showLogin() { loginScreen.style.display = 'flex'; mainApp.style.display = 'none'; } function showApp() { loginScreen.style.display = 'none'; mainApp.style.display = 'flex'; loadFileTree(); loadRecentFiles(); } // File functions async function loadFileTree() { try { const res = await fetch(`${API_BASE}/files`); const data = await res.json(); fileTree = data.tree; renderFileTree(fileTree, fileTreeEl); } catch (error) { console.error('Failed to load file tree:', error); } } function renderFileTree(tree, container, level = 0) { container.innerHTML = ''; tree.forEach(item => { const div = document.createElement('div'); div.className = 'tree-item ' + item.type; div.style.paddingLeft = (level * 1 + 0.75) + 'rem'; const icon = document.createElement('span'); icon.className = 'tree-icon'; icon.textContent = item.type === 'folder' ? '📁' : '📄'; div.appendChild(icon); const name = document.createElement('span'); name.textContent = item.name; div.appendChild(name); if (item.type === 'folder' && item.children) { const children = document.createElement('div'); children.className = 'tree-children'; children.style.display = 'none'; div.addEventListener('click', () => { children.style.display = children.style.display === 'none' ? 'block' : 'none'; icon.textContent = children.style.display === 'none' ? '📁' : '📂'; }); renderFileTree(item.children, children, level + 1); div.appendChild(children); } else if (item.type === 'file') { div.addEventListener('click', () => loadFile(item.path)); } container.appendChild(div); }); } async function loadRecentFiles() { try { const res = await fetch(`${API_BASE}/recent?limit=10`); const data = await res.json(); recentFilesEl.innerHTML = ''; data.files.forEach(file => { const li = document.createElement('li'); li.textContent = file.name; li.title = file.path; li.addEventListener('click', () => loadFile(file.path)); recentFilesEl.appendChild(li); }); } catch (error) { console.error('Failed to load recent files:', error); } } async function loadFile(filePath) { try { const res = await fetch(`${API_BASE}/file/${encodeURIComponent(filePath)}`); const data = await res.json(); if (data.error) { console.error('Error loading file:', data.error); return; } currentFile = data; breadcrumb.textContent = data.path; contentView.innerHTML = data.html; contentEditor.value = data.content; editBtn.style.display = 'inline-block'; stopEditing(); } catch (error) { console.error('Failed to load file:', error); } } async function handleSearch(query) { if (!query.trim()) { return; } try { const res = await fetch(`${API_BASE}/search?q=${encodeURIComponent(query)}`); const data = await res.json(); // Show search results in file tree fileTreeEl.innerHTML = ''; if (data.results.length === 0) { fileTreeEl.innerHTML = '
No results found
'; return; } data.results.forEach(result => { const div = document.createElement('div'); div.className = 'tree-item file'; const name = document.createElement('div'); name.textContent = result.name; name.style.fontWeight = '500'; div.appendChild(name); const preview = document.createElement('div'); preview.textContent = result.preview; preview.style.fontSize = '0.8rem'; preview.style.color = 'var(--text-secondary)'; preview.style.marginTop = '0.25rem'; div.appendChild(preview); div.addEventListener('click', () => loadFile(result.path)); fileTreeEl.appendChild(div); }); } catch (error) { console.error('Search failed:', error); } } // Edit functions function startEditing() { if (!currentFile) return; isEditing = true; contentView.style.display = 'none'; contentEditor.style.display = 'block'; editBtn.style.display = 'none'; saveBtn.style.display = 'inline-block'; cancelBtn.style.display = 'inline-block'; contentEditor.focus(); } function stopEditing() { isEditing = false; contentView.style.display = 'block'; contentEditor.style.display = 'none'; editBtn.style.display = 'inline-block'; saveBtn.style.display = 'none'; cancelBtn.style.display = 'none'; } async function saveFile() { if (!currentFile || !isEditing) return; const content = contentEditor.value; try { const res = await fetch(`${API_BASE}/file/${encodeURIComponent(currentFile.path)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); const data = await res.json(); if (data.success) { // Reload the file to show updated content await loadFile(currentFile.path); } else { alert('Failed to save: ' + (data.error || 'Unknown error')); } } catch (error) { console.error('Failed to save file:', error); alert('Failed to save file. Please try again.'); } } // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl/Cmd + S to save if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); if (isEditing) { saveFile(); } } // Escape to cancel editing if (e.key === 'Escape' && isEditing) { stopEditing(); } // Ctrl/Cmd + E to edit if ((e.ctrlKey || e.metaKey) && e.key === 'e' && !isEditing && currentFile) { e.preventDefault(); startEditing(); } });