import { useState, useEffect } from 'react'; import { LoginView } from './components/LoginView'; import { NotesList } from './components/NotesList'; import { NoteEditor } from './components/NoteEditor'; import { CategoriesSidebar } from './components/CategoriesSidebar'; import { NextcloudAPI } from './api/nextcloud'; import { Note } from './types'; function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [api, setApi] = useState(null); const [notes, setNotes] = useState([]); const [selectedNoteId, setSelectedNoteId] = useState(null); const [searchText, setSearchText] = useState(''); const [showFavoritesOnly, setShowFavoritesOnly] = useState(false); const [selectedCategory, setSelectedCategory] = useState(''); const [manualCategories, setManualCategories] = useState([]); const [isCategoriesCollapsed, setIsCategoriesCollapsed] = useState(false); const [isFocusMode, setIsFocusMode] = useState(false); const [username, setUsername] = useState(''); const [theme, setTheme] = useState<'light' | 'dark' | 'system'>('system'); const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>('light'); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [editorFont, setEditorFont] = useState('Source Code Pro'); const [editorFontSize, setEditorFontSize] = useState(14); const [previewFont, setPreviewFont] = useState('Merriweather'); const [previewFontSize, setPreviewFontSize] = useState(16); useEffect(() => { const savedServer = localStorage.getItem('serverURL'); const savedUsername = localStorage.getItem('username'); const savedPassword = localStorage.getItem('password'); const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | 'system' | null; const savedEditorFont = localStorage.getItem('editorFont'); const savedPreviewFont = localStorage.getItem('previewFont'); if (savedTheme) { setTheme(savedTheme); } if (savedEditorFont) { setEditorFont(savedEditorFont); } if (savedPreviewFont) { setPreviewFont(savedPreviewFont); } const savedEditorFontSize = localStorage.getItem('editorFontSize'); const savedPreviewFontSize = localStorage.getItem('previewFontSize'); if (savedEditorFontSize) { setEditorFontSize(parseInt(savedEditorFontSize, 10)); } if (savedPreviewFontSize) { setPreviewFontSize(parseInt(savedPreviewFontSize, 10)); } if (savedServer && savedUsername && savedPassword) { const apiInstance = new NextcloudAPI({ serverURL: savedServer, username: savedUsername, password: savedPassword, }); setApi(apiInstance); setUsername(savedUsername); setIsLoggedIn(true); } }, []); useEffect(() => { const updateEffectiveTheme = () => { if (theme === 'system') { const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; setEffectiveTheme(isDark ? 'dark' : 'light'); } else { setEffectiveTheme(theme); } }; updateEffectiveTheme(); if (theme === 'system') { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handler = () => updateEffectiveTheme(); mediaQuery.addEventListener('change', handler); return () => mediaQuery.removeEventListener('change', handler); } }, [theme]); useEffect(() => { document.documentElement.classList.toggle('dark', effectiveTheme === 'dark'); }, [effectiveTheme]); useEffect(() => { if (api && isLoggedIn) { syncNotes(); const interval = setInterval(syncNotes, 300000); return () => clearInterval(interval); } }, [api, isLoggedIn]); const syncNotes = async () => { if (!api) return; try { const fetched = await api.fetchNotes(); setNotes(fetched.sort((a, b) => b.modified - a.modified)); if (!selectedNoteId && fetched.length > 0) { setSelectedNoteId(fetched[0].id); } } catch (error) { console.error('Sync failed:', error); } }; const handleLogin = (serverURL: string, username: string, password: string) => { localStorage.setItem('serverURL', serverURL); localStorage.setItem('username', username); localStorage.setItem('password', password); const apiInstance = new NextcloudAPI({ serverURL, username, password }); setApi(apiInstance); setUsername(username); setIsLoggedIn(true); }; const handleLogout = () => { localStorage.removeItem('serverURL'); localStorage.removeItem('username'); localStorage.removeItem('password'); setApi(null); setUsername(''); setNotes([]); setSelectedNoteId(null); setIsLoggedIn(false); }; const handleThemeChange = (newTheme: 'light' | 'dark' | 'system') => { setTheme(newTheme); localStorage.setItem('theme', newTheme); }; const handleEditorFontChange = (font: string) => { setEditorFont(font); localStorage.setItem('editorFont', font); }; const handlePreviewFontChange = (font: string) => { setPreviewFont(font); localStorage.setItem('previewFont', font); }; const handleEditorFontSizeChange = (size: number) => { setEditorFontSize(size); localStorage.setItem('editorFontSize', size.toString()); }; const handlePreviewFontSizeChange = (size: number) => { setPreviewFontSize(size); localStorage.setItem('previewFontSize', size.toString()); }; const handleCreateNote = async () => { if (!api) return; try { const timestamp = new Date().toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false, }).replace(/[/:]/g, '-').replace(', ', ' '); const note = await api.createNote(`New Note ${timestamp}`, '', selectedCategory); setNotes([note, ...notes]); setSelectedNoteId(note.id); } catch (error) { console.error('Create note failed:', error); } }; const handleCreateCategory = (name: string) => { if (!manualCategories.includes(name)) { setManualCategories([...manualCategories, name]); } }; const handleUpdateNote = async (updatedNote: Note) => { if (!api) return; try { console.log('Sending to API - content length:', updatedNote.content.length); console.log('Sending to API - last 50 chars:', updatedNote.content.slice(-50)); const result = await api.updateNote(updatedNote); console.log('Received from API - content length:', result.content.length); console.log('Received from API - last 50 chars:', result.content.slice(-50)); // Update notes array with server response now that we have manual save setNotes(notes.map(n => n.id === result.id ? result : n)); } catch (error) { console.error('Update note failed:', error); } }; const handleDeleteNote = async (note: Note) => { if (!api) return; try { await api.deleteNote(note.id); setNotes(notes.filter(n => n.id !== note.id)); if (selectedNoteId === note.id) { setSelectedNoteId(notes[0]?.id || null); } } catch (error) { console.error('Delete note failed:', error); } }; const categoriesFromNotes = Array.from(new Set(notes.map(n => n.category).filter(c => c))); const categories = Array.from(new Set([...categoriesFromNotes, ...manualCategories])).sort(); const filteredNotes = notes.filter(note => { if (selectedCategory && note.category !== selectedCategory) return false; if (showFavoritesOnly && !note.favorite) return false; if (searchText) { const search = searchText.toLowerCase(); return note.title.toLowerCase().includes(search) || note.content.toLowerCase().includes(search); } return true; }); const selectedNote = notes.find(n => n.id === selectedNoteId) || null; if (!isLoggedIn) { return ; } return (
{!isFocusMode && ( <> setIsCategoriesCollapsed(!isCategoriesCollapsed)} username={username} onLogout={handleLogout} theme={theme} onThemeChange={handleThemeChange} editorFont={editorFont} onEditorFontChange={handleEditorFontChange} editorFontSize={editorFontSize} onEditorFontSizeChange={handleEditorFontSizeChange} previewFont={previewFont} onPreviewFontChange={handlePreviewFontChange} previewFontSize={previewFontSize} onPreviewFontSizeChange={handlePreviewFontSizeChange} /> setShowFavoritesOnly(!showFavoritesOnly)} hasUnsavedChanges={hasUnsavedChanges} /> )} setIsFocusMode(!isFocusMode)} editorFont={editorFont} editorFontSize={editorFontSize} previewFont={previewFont} previewFontSize={previewFontSize} api={api} />
); } export default App;