Files
gobsidian/web/static/app.js
2025-08-25 21:19:15 +01:00

253 lines
8.8 KiB
JavaScript

// Additional JavaScript functionality for Gobsidian
// Mobile responsiveness
function initMobileSupport() {
const sidebar = document.getElementById('sidebar');
const sidebarToggle = document.getElementById('sidebar-toggle');
// Check if we're on mobile
function isMobile() {
return window.innerWidth < 768;
}
// Handle sidebar toggle on mobile
function handleMobileToggle() {
if (isMobile()) {
sidebar.classList.toggle('mobile-open');
// Add/remove overlay
let overlay = document.querySelector('.sidebar-overlay');
if (sidebar.classList.contains('mobile-open')) {
if (!overlay) {
overlay = document.createElement('div');
overlay.className = 'sidebar-overlay';
document.body.appendChild(overlay);
overlay.addEventListener('click', () => {
sidebar.classList.remove('mobile-open');
overlay.remove();
});
}
} else if (overlay) {
overlay.remove();
}
}
}
// Override sidebar toggle for mobile
if (isMobile()) {
sidebarToggle.removeEventListener('click', toggleSidebar);
sidebarToggle.addEventListener('click', handleMobileToggle);
}
// Handle window resize
window.addEventListener('resize', () => {
if (!isMobile()) {
sidebar.classList.remove('mobile-open');
const overlay = document.querySelector('.sidebar-overlay');
if (overlay) overlay.remove();
}
});
}
// Enhanced file upload with progress
function initEnhancedUpload() {
const uploadElements = {
area: document.getElementById('upload-area'),
input: document.getElementById('file-input'),
progress: document.getElementById('upload-progress'),
progressBar: document.getElementById('progress-bar'),
status: document.getElementById('upload-status')
};
if (!uploadElements.area) return;
// Enhanced upload function with progress tracking
function uploadFilesWithProgress(files) {
const folderPath = window.location.pathname.includes('/folder/')
? window.location.pathname.replace('/folder/', '')
: '';
uploadElements.progress.classList.remove('hidden');
uploadElements.progressBar.style.width = '0%';
uploadElements.status.textContent = 'Starting upload...';
const formData = new FormData();
formData.append('path', folderPath);
Array.from(files).forEach(file => {
formData.append('file', file);
});
const xhr = new XMLHttpRequest();
// Track upload progress
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
uploadElements.progressBar.style.width = percentComplete + '%';
uploadElements.status.textContent = `Uploading... ${Math.round(percentComplete)}%`;
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response.success) {
uploadElements.progressBar.style.width = '100%';
uploadElements.status.textContent = 'Upload complete!';
showNotification('Files uploaded successfully', 'success');
setTimeout(() => window.location.reload(), 1000);
} else {
throw new Error(response.error || 'Upload failed');
}
} catch (e) {
throw new Error('Invalid server response');
}
} else {
throw new Error(`HTTP ${xhr.status}: ${xhr.statusText}`);
}
});
xhr.addEventListener('error', () => {
uploadElements.status.textContent = 'Upload failed: Network error';
showNotification('Upload failed: Network error', 'error');
});
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/);
const csrf = m && m[1] ? decodeURIComponent(m[1]) : '';
xhr.open('POST', '/editor/upload');
if (csrf) {
try { xhr.setRequestHeader('X-CSRF-Token', csrf); } catch (_) {}
}
xhr.send(formData);
}
// Replace the existing upload function if it exists
if (window.uploadFiles) {
window.uploadFiles = uploadFilesWithProgress;
}
}
// Search functionality
function initSearch() {
// Create search input if it doesn't exist
const sidebar = document.getElementById('sidebar');
if (!sidebar) return;
const searchContainer = document.createElement('div');
searchContainer.className = 'p-4 border-b border-gray-700';
searchContainer.innerHTML = `
<div class="relative">
<input type="text" id="search-input" placeholder="Search notes..."
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 pl-10 text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
</div>
`;
// Insert after the header
const header = sidebar.querySelector('.p-4.border-b');
if (header) {
header.after(searchContainer);
}
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', debounce((e) => {
const query = e.target.value.toLowerCase().trim();
const treeNodes = sidebar.querySelectorAll('.tree-node a');
treeNodes.forEach(node => {
const text = node.textContent.toLowerCase();
const match = !query || text.includes(query);
node.closest('.tree-node').style.display = match ? 'block' : 'none';
// Show parent folders if child matches
if (match && query) {
let parent = node.closest('.tree-children');
while (parent) {
parent.style.display = 'block';
parent.classList.remove('hidden');
const toggle = parent.previousElementSibling;
if (toggle && toggle.classList.contains('tree-toggle')) {
toggle.querySelector('.tree-chevron').classList.add('rotate-90');
}
parent = parent.parentElement.closest('.tree-children');
}
}
});
}, 300));
}
// Debounce utility
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Keyboard shortcuts
function initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Global shortcuts (when not in input/textarea)
if (!e.target.matches('input, textarea')) {
switch(e.key) {
case '/':
e.preventDefault();
const searchInput = document.getElementById('search-input');
if (searchInput) {
searchInput.focus();
}
break;
case 'n':
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
window.location.href = '/editor/create';
}
break;
case 's':
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
window.location.href = '/editor/settings';
}
break;
}
}
// Escape key to close modals
if (e.key === 'Escape') {
const modals = document.querySelectorAll('.modal-overlay');
modals.forEach(modal => {
if (!modal.classList.contains('hidden')) {
modal.classList.add('hidden');
}
});
}
});
}
// Initialize all enhancements
document.addEventListener('DOMContentLoaded', () => {
initMobileSupport();
initEnhancedUpload();
initSearch();
initKeyboardShortcuts();
});
// Export for use in other scripts
window.GobsidianUtils = {
initMobileSupport,
initEnhancedUpload,
initSearch,
initKeyboardShortcuts,
debounce
};