255 lines
9.0 KiB
JavaScript
255 lines
9.0 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) {
|
|
// Derive folder path accounting for BASE prefix
|
|
const base = (window.BASE || '').replace(/\/$/, '');
|
|
let path = window.location.pathname || '';
|
|
if (base && path.startsWith(base)) path = path.slice(base.length);
|
|
const folderPath = path.startsWith('/folder/') ? path.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', window.prefix('/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 = window.prefix('/editor/create');
|
|
}
|
|
break;
|
|
case 's':
|
|
if (e.ctrlKey || e.metaKey) {
|
|
e.preventDefault();
|
|
window.location.href = window.prefix('/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
|
|
};
|