init
This commit is contained in:
247
web/static/app.js
Normal file
247
web/static/app.js
Normal file
@@ -0,0 +1,247 @@
|
||||
// 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');
|
||||
});
|
||||
|
||||
xhr.open('POST', '/upload');
|
||||
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 = '/create';
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
window.location.href = '/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
|
||||
};
|
||||
BIN
web/static/favicon.ico
Normal file
BIN
web/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
78
web/static/styles.css
Normal file
78
web/static/styles.css
Normal file
@@ -0,0 +1,78 @@
|
||||
/* Additional custom styles for Gobsidian */
|
||||
|
||||
/* Dark scrollbar for Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #475569 #1e293b;
|
||||
}
|
||||
|
||||
/* Focus states */
|
||||
.focus-ring:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Loading spinner */
|
||||
.spinner {
|
||||
border: 2px solid #374151;
|
||||
border-top: 2px solid #3b82f6;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
#sidebar {
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
z-index: 40;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#sidebar.mobile-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.sidebar-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 30;
|
||||
}
|
||||
}
|
||||
|
||||
/* File upload drag styles */
|
||||
.drag-over {
|
||||
border-color: #3b82f6 !important;
|
||||
background-color: rgba(59, 130, 246, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Syntax highlighting overrides for dark theme */
|
||||
.hljs {
|
||||
background: #111827 !important;
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
#sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user