307 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html>
 | 
						|
<html lang="cs">
 | 
						|
<head>
 | 
						|
    <meta charset="UTF-8">
 | 
						|
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
						|
    <title>Mozkovna</title>
 | 
						|
    <script src="https://cdn.tailwindcss.com"></script>
 | 
						|
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
 | 
						|
    <script type="module">
 | 
						|
        // DOM Elements
 | 
						|
        const notesContainer = document.getElementById('notesContainer');
 | 
						|
        const newNoteInput = document.getElementById('newNoteInput');
 | 
						|
        const addNoteButton = document.getElementById('addNoteButton');
 | 
						|
        const loadingIndicator = document.getElementById('loadingIndicator');
 | 
						|
        const authStatus = document.getElementById('authStatus');
 | 
						|
        const userIdDisplay = document.getElementById('userIdDisplay');
 | 
						|
        const saveStatus = document.getElementById('saveStatus');
 | 
						|
 | 
						|
        // --- Notes Logic ---
 | 
						|
        let localNotesCache = [];
 | 
						|
 | 
						|
        function renderNotes(notes) {
 | 
						|
            notesContainer.innerHTML = ''; // Clear existing notes
 | 
						|
            const notesMap = new Map(notes.map(note => [note.createdAt, { ...note, children: [] }]));
 | 
						|
 | 
						|
            const rootNotes = [];
 | 
						|
            notesMap.forEach(note => {
 | 
						|
                if (note.parentId && notesMap.has(note.parentId)) {
 | 
						|
                    notesMap.get(note.parentId).children.push(note);
 | 
						|
                } else {
 | 
						|
                    rootNotes.push(note);
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            notesMap.forEach(note => {
 | 
						|
                note.children.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
 | 
						|
            });
 | 
						|
            rootNotes.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
 | 
						|
 | 
						|
            if (rootNotes.length === 0 && notes.length > 0 && !notes.some(n => !n.parentId)) {
 | 
						|
                notes.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))
 | 
						|
                    .forEach(note => notesContainer.appendChild(createNoteElement(note, 0, notesMap)));
 | 
						|
            } else if (rootNotes.length === 0) {
 | 
						|
                notesContainer.innerHTML = '<p class="text-gray-500 text-center py-4">Zatím žádné poznámky. Přidejte první!</p>';
 | 
						|
            } else {
 | 
						|
                rootNotes.forEach(note => notesContainer.appendChild(createNoteElement(note, 0, notesMap)));
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function createNoteElement(note, level, notesMap) {
 | 
						|
            const li = document.createElement('li');
 | 
						|
            li.className = `mb-2 p-3 rounded-lg shadow bg-white group relative transition-all duration-150 ease-in-out hover:shadow-md`;
 | 
						|
            li.style.marginLeft = `${level * 20}px`;
 | 
						|
            li.dataset.id = note.createdAt;
 | 
						|
 | 
						|
            const textSpan = document.createElement('span');
 | 
						|
            textSpan.textContent = note.text;
 | 
						|
            textSpan.className = 'flex-grow break-words cursor-text focus:outline-none focus:ring-2 focus:ring-blue-500 rounded px-1 py-0.5';
 | 
						|
            textSpan.contentEditable = true;
 | 
						|
 | 
						|
            let saveTimeout;
 | 
						|
            textSpan.addEventListener('input', () => {
 | 
						|
                clearTimeout(saveTimeout);
 | 
						|
                showSaveStatus('Ukládám...');
 | 
						|
                saveTimeout = setTimeout(() => {
 | 
						|
                    try {
 | 
						|
                        const updatedNote = { ...note, text: textSpan.textContent };
 | 
						|
                        localNotesCache = localNotesCache.map(n => n.createdAt === note.createdAt ? updatedNote : n);
 | 
						|
                        localStorage.setItem('notes', JSON.stringify(localNotesCache));
 | 
						|
                        showSaveStatus('Uloženo!', true);
 | 
						|
                    } catch (error) {
 | 
						|
                        console.error("Chyba při aktualizaci poznámky:", error);
 | 
						|
                        showSaveStatus('Chyba ukládání', false, true);
 | 
						|
                    }
 | 
						|
                }, 1000); // Debounce saving
 | 
						|
            });
 | 
						|
            textSpan.addEventListener('keydown', (e) => {
 | 
						|
                if (e.key === 'Enter' && !e.shiftKey) {
 | 
						|
                    e.preventDefault();
 | 
						|
                    textSpan.blur(); // Save on Enter
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            const controlsDiv = document.createElement('div');
 | 
						|
            controlsDiv.className = 'flex items-center space-x-2 mt-2 opacity-0 group-hover:opacity-100 transition-opacity duration-150 ease-in-out';
 | 
						|
 | 
						|
            const addChildButton = document.createElement('button');
 | 
						|
            addChildButton.innerHTML = `
 | 
						|
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-600 hover:text-green-800">
 | 
						|
                    <line x1="12" y1="5" x2="12" y2="19"></line>
 | 
						|
                    <line x1="5" y1="12" x2="19" y2="12"></line>
 | 
						|
                </svg>
 | 
						|
                <span class="sr-only">Přidat podúkol</span>`;
 | 
						|
            addChildButton.title = "Přidat podúkol";
 | 
						|
            addChildButton.className = 'p-1 rounded hover:bg-green-100';
 | 
						|
            addChildButton.onclick = () => addNote(note.createdAt);
 | 
						|
 | 
						|
            const deleteButton = document.createElement('button');
 | 
						|
            deleteButton.innerHTML = `
 | 
						|
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-red-500 hover:text-red-700">
 | 
						|
                    <polyline points="3 6 5 6 21 6"></polyline>
 | 
						|
                    <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
 | 
						|
                    <line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line>
 | 
						|
                </svg>
 | 
						|
                <span class="sr-only">Smazat</span>`;
 | 
						|
            deleteButton.title = "Smazat poznámku";
 | 
						|
            deleteButton.className = 'p-1 rounded hover:bg-red-100';
 | 
						|
            deleteButton.onclick = () => deleteNoteWithChildren(note.createdAt, notesMap);
 | 
						|
 | 
						|
            const indentButton = document.createElement('button');
 | 
						|
            indentButton.innerHTML = `
 | 
						|
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-blue-500 hover:text-blue-700">
 | 
						|
                    <polyline points="15 18 21 12 15 6"></polyline><polyline points="3 18 9 12 3 6"></polyline>
 | 
						|
                </svg>
 | 
						|
                <span class="sr-only">Odsadit</span>`;
 | 
						|
            indentButton.title = "Odsadit (učinit podúkolem předchozího sourozence)";
 | 
						|
            indentButton.className = 'p-1 rounded hover:bg-blue-100';
 | 
						|
            indentButton.onclick = () => indentNote(note.createdAt, notesMap);
 | 
						|
 | 
						|
            const outdentButton = document.createElement('button');
 | 
						|
            outdentButton.innerHTML = `
 | 
						|
                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-purple-500 hover:text-purple-700">
 | 
						|
                     <polyline points="9 18 3 12 9 6"></polyline><polyline points="21 18 15 12 21 6"></polyline>
 | 
						|
                </svg>
 | 
						|
                <span class="sr-only">Předsadit</span>`;
 | 
						|
            outdentButton.title = "Předsadit (stát se sourozencem rodiče)";
 | 
						|
            outdentButton.className = 'p-1 rounded hover:bg-purple-100';
 | 
						|
            outdentButton.onclick = () => outdentNote(note.createdAt, notesMap);
 | 
						|
            if (!note.parentId) outdentButton.disabled = true; // Cannot outdent root notes
 | 
						|
 | 
						|
            controlsDiv.appendChild(addChildButton);
 | 
						|
            controlsDiv.appendChild(indentButton);
 | 
						|
            controlsDiv.appendChild(outdentButton);
 | 
						|
            controlsDiv.appendChild(deleteButton);
 | 
						|
 | 
						|
            li.appendChild(textSpan);
 | 
						|
            li.appendChild(controlsDiv);
 | 
						|
 | 
						|
            if (note.children && note.children.length > 0) {
 | 
						|
                const childrenUl = document.createElement('ul');
 | 
						|
                childrenUl.className = 'mt-2';
 | 
						|
                note.children.forEach(childNote => childrenUl.appendChild(createNoteElement(childNote, level + 1, notesMap)));
 | 
						|
                li.appendChild(childrenUl);
 | 
						|
            }
 | 
						|
            return li;
 | 
						|
        }
 | 
						|
 | 
						|
        function addNote(parentId = null) {
 | 
						|
            const text = newNoteInput.value.trim();
 | 
						|
            if (!text) {
 | 
						|
                alert("Text poznámky nemůže být prázdný.");
 | 
						|
                newNoteInput.focus();
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            newNoteInput.value = ''; // Clear input
 | 
						|
 | 
						|
            const note = {
 | 
						|
                text: text,
 | 
						|
                parentId: parentId,
 | 
						|
                createdAt: new Date().toISOString()
 | 
						|
            };
 | 
						|
 | 
						|
            showSaveStatus('Přidávám...');
 | 
						|
            try {
 | 
						|
                localNotesCache.push(note);
 | 
						|
                localStorage.setItem('notes', JSON.stringify(localNotesCache));
 | 
						|
                showSaveStatus('Poznámka přidána!', true);
 | 
						|
                loadNotes();
 | 
						|
            } catch (error) {
 | 
						|
                console.error("Chyba při přidávání poznámky:", error);
 | 
						|
                showSaveStatus('Chyba přidávání', false, true);
 | 
						|
                alert("Nepodařilo se přidat poznámku: " + error.message);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function deleteNoteWithChildren(createdAt, notesMap) {
 | 
						|
            if (!confirm("Opravdu chcete smazat tuto poznámku a všechny její podpoznámky?")) return;
 | 
						|
 | 
						|
            showSaveStatus('Mažu...');
 | 
						|
            try {
 | 
						|
                localNotesCache = localNotesCache.filter(note => note.createdAt !== createdAt);
 | 
						|
                localStorage.setItem('notes', JSON.stringify(localNotesCache));
 | 
						|
                showSaveStatus('Smazáno!', true);
 | 
						|
                loadNotes();
 | 
						|
            } catch (error) {
 | 
						|
                console.error("Chyba při mazání poznámky:", error);
 | 
						|
                showSaveStatus('Chyba mazání', false, true);
 | 
						|
                alert("Nepodařilo se smazat poznámku: " + error.message);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function loadNotes() {
 | 
						|
            try {
 | 
						|
                localNotesCache = JSON.parse(localStorage.getItem('notes') || '[]');
 | 
						|
                renderNotes(localNotesCache);
 | 
						|
            } catch (error) {
 | 
						|
                console.error("Chyba při načítání poznámek:", error);
 | 
						|
                notesContainer.innerHTML = `<p class="text-red-500">Chyba načítání poznámek: ${error.message}</p>`;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        let statusTimeout;
 | 
						|
        function showSaveStatus(message, success = false, error = false) {
 | 
						|
            saveStatus.textContent = message;
 | 
						|
            saveStatus.classList.remove('hidden', 'text-green-600', 'text-red-600', 'text-gray-600');
 | 
						|
            if (success) {
 | 
						|
                saveStatus.classList.add('text-green-600');
 | 
						|
            } else if (error) {
 | 
						|
                saveStatus.classList.add('text-red-600');
 | 
						|
            } else {
 | 
						|
                saveStatus.classList.add('text-gray-600');
 | 
						|
            }
 | 
						|
 | 
						|
            clearTimeout(statusTimeout);
 | 
						|
            statusTimeout = setTimeout(() => {
 | 
						|
                saveStatus.classList.add('hidden');
 | 
						|
            }, 3000);
 | 
						|
        }
 | 
						|
 | 
						|
        // --- Event Listeners ---
 | 
						|
        addNoteButton.addEventListener('click', () => addNote()); // Add root note
 | 
						|
        newNoteInput.addEventListener('keypress', (e) => {
 | 
						|
            if (e.key === 'Enter') {
 | 
						|
                addNote(); // Add root note
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        // Initialize
 | 
						|
        loadNotes();
 | 
						|
    </script>
 | 
						|
    <style>
 | 
						|
        body {
 | 
						|
            font-family: 'Inter', sans-serif;
 | 
						|
            background-color: #f0f2f5; /* Světle šedé pozadí */
 | 
						|
        }
 | 
						|
        /* Custom scrollbar for notes container */
 | 
						|
        #notesContainer::-webkit-scrollbar {
 | 
						|
            width: 8px;
 | 
						|
        }
 | 
						|
        #notesContainer::-webkit-scrollbar-track {
 | 
						|
            background: #f1f1f1;
 | 
						|
            border-radius: 10px;
 | 
						|
        }
 | 
						|
        #notesContainer::-webkit-scrollbar-thumb {
 | 
						|
            background: #cbd5e1; /* Tailwind gray-300 */
 | 
						|
            border-radius: 10px;
 | 
						|
        }
 | 
						|
        #notesContainer::-webkit-scrollbar-thumb:hover {
 | 
						|
            background: #94a3b8; /* Tailwind gray-400 */
 | 
						|
        }
 | 
						|
        .group:hover .opacity-0 { /* Show controls on hover */
 | 
						|
            opacity: 1;
 | 
						|
        }
 | 
						|
        /* Styling for contenteditable focus */
 | 
						|
        [contenteditable]:focus {
 | 
						|
            outline: 2px solid #3b82f6; /* blue-500 */
 | 
						|
            border-radius: 4px;
 | 
						|
            box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
 | 
						|
        }
 | 
						|
    </style>
 | 
						|
</head>
 | 
						|
<body class="bg-slate-100 min-h-screen flex flex-col items-center pt-8 px-4">
 | 
						|
 | 
						|
    <div class="w-full max-w-3xl bg-white shadow-xl rounded-xl p-6 md:p-8">
 | 
						|
        <header class="mb-6 text-center">
 | 
						|
            <h1 class="text-4xl font-bold text-slate-800">Mozkovna</h1>
 | 
						|
            <p class="text-slate-600">Tvoje digitální nástěnka pro poznámky a nápady.</p>
 | 
						|
            <div id="authStatusContainer" class="mt-3 text-sm text-slate-500">
 | 
						|
                <span id="authStatus">Inicializace...</span>
 | 
						|
                <span id="userIdDisplay" class="font-mono bg-slate-200 px-2 py-1 rounded text-xs hidden"></span>
 | 
						|
            </div>
 | 
						|
        </header>
 | 
						|
 | 
						|
        <div id="loadingIndicator" class="text-center py-4">
 | 
						|
            <svg class="animate-spin h-8 w-8 text-blue-600 mx-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
 | 
						|
                <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
 | 
						|
                <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
 | 
						|
            </svg>
 | 
						|
            <p class="mt-2 text-slate-600">Načítám poznámky...</p>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div id="saveStatus" class="text-sm text-center mb-2 hidden transition-all duration-300"></div>
 | 
						|
 | 
						|
        <div class="mb-6 flex items-center gap-3">
 | 
						|
            <input type="text" id="newNoteInput" placeholder="Napsat novou hlavní poznámku..."
 | 
						|
                   class="flex-grow p-3 border border-slate-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow">
 | 
						|
            <button id="addNoteButton"
 | 
						|
                    class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-6 rounded-lg shadow-md hover:shadow-lg transition-all duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed">
 | 
						|
                Přidat
 | 
						|
            </button>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <main>
 | 
						|
            <ul id="notesContainer" class="space-y-2 max-h-[60vh] overflow-y-auto pr-2">
 | 
						|
                </ul>
 | 
						|
        </main>
 | 
						|
 | 
						|
        <footer class="mt-8 text-center text-xs text-slate-400">
 | 
						|
            <p>© <span id="currentYear"></span> Mozkovna App. Kankys 2025.</p>
 | 
						|
        </footer>
 | 
						|
    </div>
 | 
						|
    <script>
 | 
						|
        document.getElementById('currentYear').textContent = new Date().getFullYear();
 | 
						|
    </script>
 | 
						|
</body>
 | 
						|
</html>
 |