// Second Brain Vault - Client-side JavaScript
// Debounce function for search
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Notification system
function showNotification(title, message) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.innerHTML = `
<div class="notification-content">
<strong>${title}</strong>
<p>${message}</p>
</div>
<button class="notification-close" onclick="this.parentElement.remove()">×</button>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 5000);
}
// Sidebar toggle
document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const openBtn = document.getElementById('sidebarOpen');
const closeBtn = document.getElementById('sidebarClose');
if (sidebar && openBtn) {
openBtn.addEventListener('click', () => {
sidebar.classList.add('mobile-open');
if (overlay) overlay.classList.add('active');
});
}
if (closeBtn) {
closeBtn.addEventListener('click', () => {
sidebar.classList.remove('mobile-open');
if (overlay) overlay.classList.remove('active');
});
}
if (overlay) {
overlay.addEventListener('click', () => {
sidebar.classList.remove('mobile-open');
overlay.classList.remove('active');
});
}
// Close sidebar on escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && sidebar && sidebar.classList.contains('mobile-open')) {
sidebar.classList.remove('mobile-open');
if (overlay) overlay.classList.remove('active');
}
});
// Search with debounce (inside DOMContentLoaded to ensure element exists)
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('input', debounce((e) => {
const query = e.target.value.trim();
if (query.length >= 2) {
window.location.href = `/search?q=${encodeURIComponent(query)}`;
}
}, 300));
}
// Animate page cards
const cards = document.querySelectorAll('.page-card');
cards.forEach((card, i) => {
card.style.animationDelay = `${i * 0.05}s`;
});
});
// Auto-refresh for wiki pages
if (document.querySelector('.wiki-page')) {
const PAGE_HASH = document.querySelector('[data-page-hash]')?.dataset.pageHash;
const SLUG = window.location.pathname.split('/').pop();
if (PAGE_HASH) {
async function checkForUpdates() {
try {
const response = await fetch(`/api/page/${SLUG}`);
const data = await response.json();
if (data.hash !== PAGE_HASH) {
showNotification('Page updated', 'This page has been updated. Reloading...');
setTimeout(() => location.reload(), 2000);
}
} catch (e) {
// Ignore errors
}
}
setInterval(checkForUpdates, 3000);
}
}
// Wikilink navigation with smooth scrolling
document.addEventListener('click', (e) => {
if (e.target.classList.contains('wikilink')) {
const link = e.target.closest('a');
if (link) {
// For now, just let the normal link behavior work
}
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Cmd/Ctrl + K to focus search
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.focus();
}
}
});
// Auto-save for editor (if on edit page)
if (document.getElementById('editor')) {
const editor = document.getElementById('editor');
let saveTimeout;
editor.addEventListener('input', () => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
const form = document.querySelector('.editor-form');
if (form) {
form.classList.add('dirty');
}
}, 1000);
});
}
// Service Worker registration for PWA
if ('serviceWorker' in navigator) {
// Could register a service worker here for offline support
// navigator.serviceWorker.register('/sw.js');
}