start-suite / static /script.js
ayush-thakur02's picture
Upload 8 files
30fe542 verified
// 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 = `
<span class="material-icons bookmark-icon">${bookmark.icon || 'bookmark'}</span>
<span class="bookmark-name">${bookmark.name}</span>
<span class="bookmark-url">${new URL(bookmark.url).hostname}</span>
`;
// 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 = `
<span class="material-icons">add</span>
`;
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 ? `<span class="close-note" onclick="event.stopPropagation(); window.notePad.removeNote(${note.id});">Γ—</span>` : ''}
`;
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 = `
<textarea class="notepad-textarea" placeholder="Start typing your notes here...">${note.content}</textarea>
<div class="notepad-footer">
<span class="save-indicator">
<span class="material-icons" style="font-size: 1rem; vertical-align: middle;">check_circle</span>
Saved
</span>
</div>
`;
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 = `
<div class="vault-empty">
<div class="material-icons">lock</div>
<p>${searchTerm ? 'No passwords found matching your search.' : 'Your vault is empty. Add your first password!'}</p>
</div>
`;
return;
}
vaultList.innerHTML = filteredPasswords.map((password, originalIndex) => {
const index = vaultPasswords.indexOf(password);
return `
<div class="vault-entry" onclick="openEditPasswordModal(${index})">
<div class="vault-entry-header">
<div class="vault-entry-title">${password.title}</div>
<div class="vault-entry-actions" onclick="event.stopPropagation();">
<button class="vault-action-btn" onclick="copyToClipboard('${password.password}', 'Password')" title="Copy Password">
<span class="material-icons">content_copy</span>
</button>
<button class="vault-action-btn" onclick="openEditPasswordModal(${index})" title="Edit">
<span class="material-icons">edit</span>
</button>
</div>
</div>
<div class="vault-entry-info">
${password.username ? `
<span class="vault-entry-label">Username:</span>
<span class="vault-entry-value">${password.username}</span>
` : ''}
${password.website ? `
<span class="vault-entry-label">Website:</span>
<span class="vault-entry-value">${password.website}</span>
` : ''}
${password.notes ? `
<span class="vault-entry-label">Notes:</span>
<span class="vault-entry-value">${password.notes}</span>
` : ''}
</div>
</div>
`;
}).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);
});
}
});