// Clock functionality function updateClock() { const now = new Date(); const timeElement = document.getElementById('time'); const dateElement = document.getElementById('date'); // Format time const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const seconds = now.getSeconds().toString().padStart(2, '0'); timeElement.textContent = `${hours}:${minutes}:${seconds}`; // Format date const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; const formattedDate = now.toLocaleDateString('en-US', options); // Add weather emoji const month = now.getMonth(); const hour = now.getHours(); let weatherEmoji = '☀️'; if (month >= 11 || month <= 2) weatherEmoji = '❄️'; else if (month >= 3 && month <= 5) weatherEmoji = '🌸'; else if (month >= 6 && month <= 8) weatherEmoji = '☀️'; else weatherEmoji = '🍂'; if (hour >= 19 || hour <= 6) weatherEmoji = '🌙'; dateElement.textContent = `${formattedDate} ${weatherEmoji}`; } // Authentication functions async function logout() { try { await fetch('/logout', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); window.location.href = '/'; } catch (error) { console.error('Logout failed:', error); window.location.href = '/'; } } // Search functionality function handleSearch() { const searchInput = document.getElementById('searchInput'); const query = searchInput.value.trim(); if (query) { // Track search usage fetch('/api/search', { method: 'POST', headers: { 'Content-Type': 'application/json', } }).catch(err => console.log('Search tracking failed:', err)); // Check if it's a URL const urlPattern = /^(https?:\/\/)?([\w\-])+\.{1}([a-zA-Z]{2,63})([\/\w\-\._~:?#[\]@!\$&'()*+,;=]*)?$/; if (urlPattern.test(query)) { // It's a URL, add https:// if not present const url = query.startsWith('http') ? query : `https://${query}`; window.open(url, '_blank'); } else { // It's a search query window.open(`https://www.google.com/search?q=${encodeURIComponent(query)}`, '_blank'); } searchInput.value = ''; } } // Default bookmarks const defaultBookmarks = [ { name: 'YouTube', url: 'https://youtube.com', icon: 'play_circle' }, { name: 'GitHub', url: 'https://github.com', icon: 'code' }, { name: 'Gmail', url: 'https://gmail.com', icon: 'mail' }, { name: 'Google Drive', url: 'https://drive.google.com', icon: 'cloud' }, { name: 'Netflix', url: 'https://netflix.com', icon: 'movie' }, { name: 'Reddit', url: 'https://reddit.com', icon: 'forum' }, { name: 'Twitter', url: 'https://twitter.com', icon: 'alternate_email' }, { name: 'LinkedIn', url: 'https://linkedin.com', icon: 'work' } ]; // Bookmarks management class BookmarksManager { constructor() { this.bookmarks = []; this.loadBookmarks(); } async loadBookmarks() { try { const response = await fetch('/api/bookmarks'); if (response.ok) { this.bookmarks = await response.json(); } else { // Fallback to default bookmarks if API fails this.bookmarks = defaultBookmarks; } } catch (error) { console.error('Failed to load bookmarks:', error); this.bookmarks = defaultBookmarks; } this.renderBookmarks(); } async saveBookmarks() { try { await fetch('/api/bookmarks', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ bookmarks: this.bookmarks }) }); } catch (error) { console.error('Failed to save bookmarks:', error); } } addBookmark(bookmark) { this.bookmarks.push(bookmark); this.saveBookmarks(); this.renderBookmarks(); } editBookmark(index, bookmark) { this.bookmarks[index] = bookmark; this.saveBookmarks(); this.renderBookmarks(); } removeBookmark(index) { this.bookmarks.splice(index, 1); this.saveBookmarks(); this.renderBookmarks(); } renderBookmarks() { const container = document.getElementById('bookmarksGrid'); container.innerHTML = ''; // Render existing bookmarks this.bookmarks.forEach((bookmark, index) => { const bookmarkElement = document.createElement('a'); bookmarkElement.className = 'bookmark'; bookmarkElement.href = bookmark.url; bookmarkElement.target = '_blank'; bookmarkElement.innerHTML = ` ${bookmark.icon || 'bookmark'} ${bookmark.name} ${new URL(bookmark.url).hostname} `; // Add context menu for editing bookmarkElement.addEventListener('contextmenu', (e) => { e.preventDefault(); this.openEditModal(index, bookmark); }); container.appendChild(bookmarkElement); }); // Add "Add Bookmark" button const addButton = document.createElement('div'); addButton.className = 'add-bookmark-btn'; addButton.innerHTML = ` add `; addButton.addEventListener('click', () => { this.openAddModal(); }); container.appendChild(addButton); } openEditModal(index, bookmark) { const modal = document.getElementById('bookmarkModal'); const modalTitle = document.getElementById('modalTitle'); const submitBtn = document.getElementById('submitBtn'); const deleteBtn = document.getElementById('deleteBtn'); const nameInput = document.getElementById('bookmarkName'); const urlInput = document.getElementById('bookmarkUrl'); const iconSelect = document.getElementById('bookmarkIcon'); // Set modal to edit mode modalTitle.textContent = 'Edit Bookmark'; submitBtn.textContent = 'Update'; deleteBtn.style.display = 'block'; // Fill form with current bookmark data nameInput.value = bookmark.name; urlInput.value = bookmark.url; iconSelect.value = bookmark.icon || ''; // Store the index for editing modal.dataset.editIndex = index; modal.dataset.mode = 'edit'; // Set up delete button deleteBtn.onclick = () => { if (confirm(`Remove "${bookmark.name}" from bookmarks?`)) { this.removeBookmark(index); modal.style.display = 'none'; } }; modal.style.display = 'block'; } openAddModal() { const modal = document.getElementById('bookmarkModal'); const modalTitle = document.getElementById('modalTitle'); const submitBtn = document.getElementById('submitBtn'); const deleteBtn = document.getElementById('deleteBtn'); const form = document.getElementById('bookmarkForm'); // Set modal to add mode modalTitle.textContent = 'Add Bookmark'; submitBtn.textContent = 'Add Bookmark'; deleteBtn.style.display = 'none'; // Clear form form.reset(); // Clear edit data delete modal.dataset.editIndex; modal.dataset.mode = 'add'; modal.style.display = 'block'; } } // Notepad functionality with multiple notes class NotePad { constructor() { this.notes = []; this.activeNoteId = 0; this.saveTimeouts = new Map(); this.loadNotes(); } async loadNotes() { try { const response = await fetch('/api/notes'); if (response.ok) { this.notes = await response.json(); if (this.notes.length === 0) { this.notes = [{ id: 0, title: 'Note 1', content: '' }]; } } else { this.notes = [{ id: 0, title: 'Note 1', content: '' }]; } } catch (error) { console.error('Failed to load notes:', error); this.notes = [{ id: 0, title: 'Note 1', content: '' }]; } this.renderNotes(); this.setupEventListeners(); } async saveNotes() { try { await fetch('/api/notes', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ notes: this.notes }) }); } catch (error) { console.error('Failed to save notes:', error); } } addNote() { const newId = Math.max(...this.notes.map(n => n.id), -1) + 1; const newNote = { id: newId, title: `Note ${newId + 1}`, content: '' }; this.notes.push(newNote); this.saveNotes(); this.renderNotes(); this.switchToNote(newId); } removeNote(noteId) { if (this.notes.length <= 1) { alert('Cannot delete the last note!'); return; } if (confirm('Are you sure you want to delete this note?')) { this.notes = this.notes.filter(note => note.id !== noteId); // If we deleted the active note, switch to the first available note if (this.activeNoteId === noteId) { this.activeNoteId = this.notes[0].id; } this.saveNotes(); this.renderNotes(); } } switchToNote(noteId) { // Save current note content first this.saveCurrentNote(); this.activeNoteId = noteId; this.renderNotes(); // Focus on the new note's textarea const activeTextarea = document.querySelector('.note-content.active .notepad-textarea'); if (activeTextarea) { activeTextarea.focus(); } } saveCurrentNote() { const activeTextarea = document.querySelector('.note-content.active .notepad-textarea'); if (activeTextarea) { const note = this.notes.find(n => n.id === this.activeNoteId); if (note) { note.content = activeTextarea.value; this.saveNotes(); } } } showSaveIndicator(noteId) { const saveIndicator = document.querySelector(`[data-note-id="${noteId}"] .save-indicator`); if (saveIndicator) { saveIndicator.classList.add('visible'); setTimeout(() => { saveIndicator.classList.remove('visible'); }, 1500); } } renderNotes() { const tabsContainer = document.querySelector('.notes-tabs'); const notesContainer = document.querySelector('.notepad-container'); // Clear existing tabs (except add button) const addButton = tabsContainer.querySelector('.add-note-btn'); tabsContainer.innerHTML = ''; // Render note tabs this.notes.forEach(note => { const tab = document.createElement('button'); tab.className = `note-tab ${note.id === this.activeNoteId ? 'active' : ''}`; tab.setAttribute('data-note-id', note.id); tab.innerHTML = ` ${note.title} ${this.notes.length > 1 ? `×` : ''} `; tab.addEventListener('click', () => this.switchToNote(note.id)); tabsContainer.appendChild(tab); }); // Re-add the add button tabsContainer.appendChild(addButton); // Clear and render note contents const existingContents = notesContainer.querySelectorAll('.note-content'); existingContents.forEach(content => content.remove()); this.notes.forEach(note => { const noteContent = document.createElement('div'); noteContent.className = `note-content ${note.id === this.activeNoteId ? 'active' : ''}`; noteContent.setAttribute('data-note-id', note.id); noteContent.innerHTML = ` `; notesContainer.appendChild(noteContent); }); this.setupNoteEventListeners(); } setupEventListeners() { // Global event listeners can be set up here } setupNoteEventListeners() { this.notes.forEach(note => { const textarea = document.querySelector(`[data-note-id="${note.id}"] .notepad-textarea`); if (textarea) { // Remove existing listeners to avoid duplicates textarea.removeEventListener('input', this.handleInput); textarea.removeEventListener('blur', this.handleBlur); // Add new listeners textarea.addEventListener('input', () => { // Update note content const noteData = this.notes.find(n => n.id === note.id); if (noteData) { noteData.content = textarea.value; } // Auto-save with debounce if (this.saveTimeouts.has(note.id)) { clearTimeout(this.saveTimeouts.get(note.id)); } const timeout = setTimeout(() => { this.saveNotes(); this.showSaveIndicator(note.id); }, 1000); this.saveTimeouts.set(note.id, timeout); }); textarea.addEventListener('blur', () => { if (this.saveTimeouts.has(note.id)) { clearTimeout(this.saveTimeouts.get(note.id)); } this.saveCurrentNote(); this.showSaveIndicator(note.id); }); } }); } } // Modal functionality class Modal { constructor() { this.modal = document.getElementById('bookmarkModal'); this.form = document.getElementById('bookmarkForm'); this.closeBtn = document.getElementById('closeModal'); this.cancelBtn = document.getElementById('cancelBtn'); this.setupEventListeners(); } setupEventListeners() { // Close modal events this.closeBtn.addEventListener('click', () => this.close()); this.cancelBtn.addEventListener('click', () => this.close()); // Close on outside click window.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } }); // Form submission this.form.addEventListener('submit', (e) => { e.preventDefault(); this.handleSubmit(); }); // Close on Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.modal.style.display === 'block') { this.close(); } }); } open() { this.modal.style.display = 'block'; document.getElementById('bookmarkName').focus(); } close() { this.modal.style.display = 'none'; this.form.reset(); // Clear edit data delete this.modal.dataset.editIndex; delete this.modal.dataset.mode; // Reset modal to add mode document.getElementById('modalTitle').textContent = 'Add Bookmark'; document.getElementById('submitBtn').textContent = 'Add Bookmark'; document.getElementById('deleteBtn').style.display = 'none'; } handleSubmit() { const name = document.getElementById('bookmarkName').value.trim(); const url = document.getElementById('bookmarkUrl').value.trim(); const icon = document.getElementById('bookmarkIcon').value.trim() || 'bookmark'; if (name && url) { // Ensure URL has protocol const finalUrl = url.startsWith('http') ? url : `https://${url}`; const bookmark = { name, url: finalUrl, icon }; // Check if we're in edit mode if (this.modal.dataset.mode === 'edit') { const editIndex = parseInt(this.modal.dataset.editIndex); bookmarksManager.editBookmark(editIndex, bookmark); } else { bookmarksManager.addBookmark(bookmark); } this.close(); } } } // Theme and settings removed - using white background only // Initialize everything when DOM is loaded document.addEventListener('DOMContentLoaded', () => { // Initialize clock updateClock(); setInterval(updateClock, 1000); // Initialize components window.bookmarksManager = new BookmarksManager(); window.notePad = new NotePad(); window.modal = new Modal(); // Setup search functionality const searchInput = document.getElementById('searchInput'); const searchIcon = document.getElementById('searchIcon'); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleSearch(); } }); searchIcon.addEventListener('click', handleSearch); // Focus search on load searchInput.focus(); // Display username (passed from Flask template) const usernameElement = document.getElementById('username'); if (usernameElement && typeof window.currentUser !== 'undefined') { usernameElement.textContent = window.currentUser; } }); // Service Worker for offline capability (future enhancement) if ('serviceWorker' in navigator) { window.addEventListener('load', () => { // Service worker registration could be added here console.log('Start page loaded successfully!'); }); } // Export for potential future use window.StartPageAPI = { bookmarksManager: () => window.bookmarksManager, notePad: () => window.notePad, addBookmark: (bookmark) => window.bookmarksManager.addBookmark(bookmark), exportData: () => ({ bookmarks: window.bookmarksManager.bookmarks, notes: window.notePad.notes }), importData: (data) => { if (data.bookmarks) { localStorage.setItem('startpage-bookmarks', JSON.stringify(data.bookmarks)); } if (data.notes) { localStorage.setItem('startpage-notes', JSON.stringify(data.notes)); } location.reload(); } }; // Rain Effect class RainEffect { constructor() { this.rainContainer = document.getElementById('rainContainer'); this.rainDrops = []; this.maxDrops = 150; this.init(); } init() { this.createRain(); this.animateRain(); } createRain() { for (let i = 0; i < this.maxDrops; i++) { this.createRainDrop(); } } createRainDrop() { const drop = document.createElement('div'); drop.className = 'rain-drop'; // Random horizontal position const x = Math.random() * window.innerWidth; // Random size variation const size = Math.random() * 0.8 + 0.2; drop.style.width = `${2 * size}px`; drop.style.height = `${20 * size}px`; // Random speed (duration) const duration = Math.random() * 2 + 1; // 1-3 seconds drop.style.animationDuration = `${duration}s`; // Random delay const delay = Math.random() * 2; drop.style.animationDelay = `${delay}s`; // Position the drop drop.style.left = `${x}px`; drop.style.top = '-20px'; this.rainContainer.appendChild(drop); this.rainDrops.push(drop); } animateRain() { // Clean up and recreate drops periodically setInterval(() => { this.rainDrops.forEach(drop => { const rect = drop.getBoundingClientRect(); if (rect.top > window.innerHeight) { // Reset the drop to the top with new random position drop.style.left = `${Math.random() * window.innerWidth}px`; drop.style.top = '-20px'; // Randomize properties again const size = Math.random() * 0.8 + 0.2; drop.style.width = `${2 * size}px`; drop.style.height = `${20 * size}px`; const duration = Math.random() * 2 + 1; drop.style.animationDuration = `${duration}s`; } }); }, 100); } } // Initialize rain effect when the page loads document.addEventListener('DOMContentLoaded', () => { new RainEffect(); }); // Account dropdown functionality function toggleAccountDropdown() { const dropdown = document.getElementById('accountDropdown'); dropdown.classList.toggle('active'); } // Close dropdown when clicking outside document.addEventListener('click', (e) => { const dropdown = document.getElementById('accountDropdown'); const accountBtn = document.querySelector('.account-btn'); if (!accountBtn.contains(e.target) && !dropdown.contains(e.target)) { dropdown.classList.remove('active'); } }); // Change Password Modal Functions function openChangePasswordModal() { const modal = document.getElementById('passwordModal'); modal.style.display = 'block'; document.getElementById('accountDropdown').classList.remove('active'); } function closePasswordModal() { const modal = document.getElementById('passwordModal'); modal.style.display = 'none'; document.getElementById('passwordForm').reset(); } // Handle password form submission document.addEventListener('DOMContentLoaded', () => { const passwordForm = document.getElementById('passwordForm'); if (passwordForm) { passwordForm.addEventListener('submit', async (e) => { e.preventDefault(); const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; if (newPassword !== confirmPassword) { alert('New passwords do not match!'); return; } if (newPassword.length < 6) { alert('Password must be at least 6 characters long!'); return; } try { const response = await fetch('/api/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ currentPassword, newPassword }) }); const data = await response.json(); if (response.ok) { alert('Password changed successfully!'); closePasswordModal(); } else { alert(data.error || 'Failed to change password'); } } catch (error) { console.error('Error changing password:', error); alert('Failed to change password. Please try again.'); } }); } }); // Developer Stats Modal Functions function openDeveloperPage() { const modal = document.getElementById('developerModal'); modal.style.display = 'block'; document.getElementById('accountDropdown').classList.remove('active'); loadDeveloperStats(); } function closeDeveloperModal() { const modal = document.getElementById('developerModal'); modal.style.display = 'none'; // Destroy chart to prevent memory leaks if (activityChart) { activityChart.destroy(); activityChart = null; } } async function loadDeveloperStats() { try { const response = await fetch('/api/developer-stats'); if (response.ok) { const data = await response.json(); // Update stats display document.getElementById('totalUsers').textContent = data.totalUsers || 0; document.getElementById('totalVisits').textContent = data.totalVisits || 0; document.getElementById('totalSearches').textContent = data.totalSearches || 0; // Create chart createActivityChart(data.monthlyData || []); } else { console.error('Failed to load developer stats'); } } catch (error) { console.error('Error loading developer stats:', error); } } let activityChart = null; // Global variable to store chart instance function createActivityChart(monthlyData) { const canvas = document.getElementById('activityChart'); const ctx = canvas.getContext('2d'); // Destroy existing chart if it exists if (activityChart) { activityChart.destroy(); } // Generate sample monthly data if none provided if (monthlyData.length === 0) { const currentDate = new Date(); monthlyData = []; for (let i = 11; i >= 0; i--) { const date = new Date(currentDate.getFullYear(), currentDate.getMonth() - i, 1); const monthName = date.toLocaleString('default', { month: 'short' }); monthlyData.push({ month: monthName, visits: Math.floor(Math.random() * 500) + 200, searches: Math.floor(Math.random() * 300) + 100 }); } } // Extract data for Chart.js const labels = monthlyData.map(data => data.month); const visitsData = monthlyData.map(data => data.visits); const searchesData = monthlyData.map(data => data.searches); // Create Chart.js line chart activityChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [ { label: 'Page Visits', data: visitsData, borderColor: '#4285f4', backgroundColor: 'rgba(66, 133, 244, 0.1)', borderWidth: 3, fill: true, tension: 0.4, pointBackgroundColor: '#4285f4', pointBorderColor: '#4285f4', pointHoverBackgroundColor: '#ffffff', pointHoverBorderColor: '#4285f4', pointRadius: 5, pointHoverRadius: 7 }, { label: 'Searches', data: searchesData, borderColor: '#34a853', backgroundColor: 'rgba(52, 168, 83, 0.1)', borderWidth: 3, fill: true, tension: 0.4, pointBackgroundColor: '#34a853', pointBorderColor: '#34a853', pointHoverBackgroundColor: '#ffffff', pointHoverBorderColor: '#34a853', pointRadius: 5, pointHoverRadius: 7 } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top', labels: { usePointStyle: true, padding: 20, font: { size: 12, family: 'Lexend, sans-serif' } } }, tooltip: { mode: 'index', intersect: false, backgroundColor: 'rgba(0, 0, 0, 0.8)', titleColor: '#ffffff', bodyColor: '#ffffff', borderColor: '#4285f4', borderWidth: 1, cornerRadius: 8, displayColors: true, titleFont: { size: 14, weight: 'bold' }, bodyFont: { size: 13 } } }, interaction: { mode: 'nearest', axis: 'x', intersect: false }, scales: { x: { display: true, title: { display: true, text: 'Month', font: { size: 14, weight: 'bold', family: 'Lexend, sans-serif' }, color: '#666' }, grid: { display: true, color: 'rgba(0, 0, 0, 0.1)' }, ticks: { font: { size: 12, family: 'Lexend, sans-serif' }, color: '#666' } }, y: { display: true, title: { display: true, text: 'Count', font: { size: 14, weight: 'bold', family: 'Lexend, sans-serif' }, color: '#666' }, grid: { display: true, color: 'rgba(0, 0, 0, 0.1)' }, ticks: { beginAtZero: true, font: { size: 12, family: 'Lexend, sans-serif' }, color: '#666' } } }, elements: { line: { borderJoinStyle: 'round' }, point: { borderWidth: 2, hoverBorderWidth: 3 } }, animation: { duration: 1500, easing: 'easeInOutQuart' } } }); } // Close modals on Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const passwordModal = document.getElementById('passwordModal'); const developerModal = document.getElementById('developerModal'); const vaultAuthModal = document.getElementById('vaultAuthModal'); const vaultModal = document.getElementById('vaultModal'); const passwordEntryModal = document.getElementById('passwordEntryModal'); if (passwordModal.style.display === 'block') { closePasswordModal(); } if (developerModal.style.display === 'block') { closeDeveloperModal(); } if (vaultAuthModal.style.display === 'block') { closeVaultAuthModal(); } if (vaultModal.style.display === 'block') { closeVaultModal(); } if (passwordEntryModal.style.display === 'block') { closePasswordEntryModal(); } } }); // Close modals when clicking outside window.addEventListener('click', (e) => { const passwordModal = document.getElementById('passwordModal'); const developerModal = document.getElementById('developerModal'); const vaultAuthModal = document.getElementById('vaultAuthModal'); const vaultModal = document.getElementById('vaultModal'); const passwordEntryModal = document.getElementById('passwordEntryModal'); if (e.target === passwordModal) { closePasswordModal(); } if (e.target === developerModal) { closeDeveloperModal(); } if (e.target === vaultAuthModal) { closeVaultAuthModal(); } if (e.target === vaultModal) { closeVaultModal(); } if (e.target === passwordEntryModal) { closePasswordEntryModal(); } }); // Vault functionality let vaultPasswords = []; let currentEditingPassword = null; function openVaultPage() { document.getElementById('accountDropdown').classList.remove('active'); document.getElementById('vaultAuthModal').style.display = 'block'; } function closeVaultAuthModal() { document.getElementById('vaultAuthModal').style.display = 'none'; document.getElementById('vaultAuthForm').reset(); } function openVaultModal() { document.getElementById('vaultModal').style.display = 'block'; loadVaultPasswords(); } function closeVaultModal() { document.getElementById('vaultModal').style.display = 'none'; vaultPasswords = []; renderVaultPasswords(); } function openAddPasswordModal() { currentEditingPassword = null; document.getElementById('passwordEntryTitle').textContent = 'Add Password'; document.getElementById('savePasswordBtn').textContent = 'Save Password'; document.getElementById('deletePasswordBtn').style.display = 'none'; document.getElementById('passwordEntryForm').reset(); document.getElementById('passwordEntryModal').style.display = 'block'; } function openEditPasswordModal(index) { currentEditingPassword = index; const password = vaultPasswords[index]; document.getElementById('passwordEntryTitle').textContent = 'Edit Password'; document.getElementById('savePasswordBtn').textContent = 'Update Password'; document.getElementById('deletePasswordBtn').style.display = 'block'; document.getElementById('entryTitle').value = password.title; document.getElementById('entryUsername').value = password.username || ''; document.getElementById('entryPassword').value = password.password; document.getElementById('entryWebsite').value = password.website || ''; document.getElementById('entryNotes').value = password.notes || ''; document.getElementById('passwordEntryModal').style.display = 'block'; } function closePasswordEntryModal() { document.getElementById('passwordEntryModal').style.display = 'none'; document.getElementById('passwordEntryForm').reset(); currentEditingPassword = null; } async function loadVaultPasswords() { try { const response = await fetch('/api/vault/passwords'); if (response.ok) { vaultPasswords = await response.json(); } else { vaultPasswords = []; } renderVaultPasswords(); } catch (error) { console.error('Failed to load vault passwords:', error); vaultPasswords = []; renderVaultPasswords(); } } async function saveVaultPassword(passwordData) { try { const response = await fetch('/api/vault/passwords', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(passwordData) }); if (response.ok) { loadVaultPasswords(); return true; } else { console.error('Failed to save password'); return false; } } catch (error) { console.error('Failed to save password:', error); return false; } } async function deleteVaultPassword(index) { try { const password = vaultPasswords[index]; const response = await fetch(`/api/vault/passwords/${password.id}`, { method: 'DELETE' }); if (response.ok) { loadVaultPasswords(); return true; } else { console.error('Failed to delete password'); return false; } } catch (error) { console.error('Failed to delete password:', error); return false; } } function renderVaultPasswords() { const vaultList = document.getElementById('vaultList'); const searchTerm = document.getElementById('vaultSearchInput').value.toLowerCase(); // Filter passwords based on search term const filteredPasswords = vaultPasswords.filter(password => password.title.toLowerCase().includes(searchTerm) || (password.username && password.username.toLowerCase().includes(searchTerm)) || (password.website && password.website.toLowerCase().includes(searchTerm)) || (password.notes && password.notes.toLowerCase().includes(searchTerm)) ); if (filteredPasswords.length === 0) { vaultList.innerHTML = `
lock

${searchTerm ? 'No passwords found matching your search.' : 'Your vault is empty. Add your first password!'}

`; return; } vaultList.innerHTML = filteredPasswords.map((password, originalIndex) => { const index = vaultPasswords.indexOf(password); return `
${password.title}
${password.username ? ` ${password.username} ` : ''} ${password.website ? ` ${password.website} ` : ''} ${password.notes ? ` ${password.notes} ` : ''}
`; }).join(''); } function copyToClipboard(text, type) { navigator.clipboard.writeText(text).then(() => { // Show temporary success message const message = document.createElement('div'); message.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #4caf50; color: white; padding: 12px 20px; border-radius: 8px; z-index: 10000; font-size: 14px; `; message.textContent = `${type} copied to clipboard!`; document.body.appendChild(message); setTimeout(() => { document.body.removeChild(message); }, 2000); }).catch(() => { alert('Failed to copy to clipboard'); }); } function togglePasswordVisibility(inputId) { const input = document.getElementById(inputId); const toggleBtn = input.parentNode.querySelector('.password-toggle-btn span'); if (input.type === 'password') { input.type = 'text'; toggleBtn.textContent = 'visibility_off'; } else { input.type = 'password'; toggleBtn.textContent = 'visibility'; } } function generatePassword() { const length = 16; const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'; let password = ''; for (let i = 0; i < length; i++) { password += charset.charAt(Math.floor(Math.random() * charset.length)); } document.getElementById('entryPassword').value = password; updatePasswordStrength(password); } function updatePasswordStrength(password) { const strengthIndicator = document.querySelector('.password-strength-bar'); if (!strengthIndicator) return; let strength = 0; if (password.length >= 8) strength++; if (/[a-z]/.test(password)) strength++; if (/[A-Z]/.test(password)) strength++; if (/[0-9]/.test(password)) strength++; if (/[^A-Za-z0-9]/.test(password)) strength++; const classes = ['strength-weak', 'strength-fair', 'strength-good', 'strength-strong']; strengthIndicator.className = 'password-strength-bar ' + (classes[Math.min(strength - 1, 3)] || ''); } // Initialize vault event listeners document.addEventListener('DOMContentLoaded', () => { // Vault authentication form const vaultAuthForm = document.getElementById('vaultAuthForm'); if (vaultAuthForm) { vaultAuthForm.addEventListener('submit', async (e) => { e.preventDefault(); const password = document.getElementById('vaultPassword').value; try { const response = await fetch('/api/vault/authenticate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ password }) }); if (response.ok) { closeVaultAuthModal(); openVaultModal(); } else { const data = await response.json(); alert(data.error || 'Invalid password'); } } catch (error) { console.error('Vault authentication failed:', error); alert('Authentication failed. Please try again.'); } }); } // Password entry form const passwordEntryForm = document.getElementById('passwordEntryForm'); if (passwordEntryForm) { passwordEntryForm.addEventListener('submit', async (e) => { e.preventDefault(); const passwordData = { title: document.getElementById('entryTitle').value, username: document.getElementById('entryUsername').value, password: document.getElementById('entryPassword').value, website: document.getElementById('entryWebsite').value, notes: document.getElementById('entryNotes').value }; if (currentEditingPassword !== null) { passwordData.id = vaultPasswords[currentEditingPassword].id; } const success = await saveVaultPassword(passwordData); if (success) { closePasswordEntryModal(); } else { alert('Failed to save password. Please try again.'); } }); } // Delete password button const deletePasswordBtn = document.getElementById('deletePasswordBtn'); if (deletePasswordBtn) { deletePasswordBtn.addEventListener('click', async () => { if (currentEditingPassword !== null) { if (confirm('Are you sure you want to delete this password?')) { const success = await deleteVaultPassword(currentEditingPassword); if (success) { closePasswordEntryModal(); } else { alert('Failed to delete password. Please try again.'); } } } }); } // Vault search functionality const vaultSearchInput = document.getElementById('vaultSearchInput'); if (vaultSearchInput) { vaultSearchInput.addEventListener('input', () => { renderVaultPasswords(); }); } // Password strength indicator const entryPasswordInput = document.getElementById('entryPassword'); if (entryPasswordInput) { entryPasswordInput.addEventListener('input', (e) => { updatePasswordStrength(e.target.value); }); } });