mozkovna - create project
This commit is contained in:
parent
1e1969f6dd
commit
4ab0c8a043
306
browserEdition/browser.html
Normal file
306
browserEdition/browser.html
Normal file
@ -0,0 +1,306 @@
|
||||
<!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>
|
335
local.html
Normal file
335
local.html
Normal file
@ -0,0 +1,335 @@
|
||||
<!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 exportButton = document.getElementById('exportButton');
|
||||
const importButton = document.getElementById('importButton');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const saveStatus = document.getElementById('saveStatus');
|
||||
|
||||
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;
|
||||
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
|
||||
function exportNotes() {
|
||||
const blob = new Blob([JSON.stringify(localNotesCache, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'notes.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function importNotes(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const importedNotes = JSON.parse(e.target.result);
|
||||
localNotesCache = importedNotes;
|
||||
localStorage.setItem('notes', JSON.stringify(localNotesCache));
|
||||
loadNotes();
|
||||
} catch (error) {
|
||||
console.error("Chyba při importu poznámek:", error);
|
||||
alert("Nepodařilo se importovat poznámky: " + error.message);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
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());
|
||||
newNoteInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
addNote();
|
||||
}
|
||||
});
|
||||
exportButton.addEventListener('click', exportNotes);
|
||||
importButton.addEventListener('click', () => fileInput.click());
|
||||
fileInput.addEventListener('change', importNotes);
|
||||
|
||||
// Initialize
|
||||
loadNotes();
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
#notesContainer::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
#notesContainer::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#notesContainer::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#notesContainer::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
.group:hover .opacity-0 {
|
||||
opacity: 1;
|
||||
}
|
||||
[contenteditable]:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
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>
|
||||
</header>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="mt-6 flex justify-between">
|
||||
<button id="exportButton"
|
||||
class="bg-green-600 hover:bg-green-700 text-white font-semibold py-2 px-4 rounded-lg shadow-md hover:shadow-lg transition-all duration-150 ease-in-out">
|
||||
Exportovat
|
||||
</button>
|
||||
<input type="file" id="fileInput" accept=".json" class="hidden">
|
||||
<button id="importButton"
|
||||
class="bg-yellow-600 hover:bg-yellow-700 text-white font-semibold py-2 px-4 rounded-lg shadow-md hover:shadow-lg transition-all duration-150 ease-in-out">
|
||||
Importovat
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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>
|
Loading…
x
Reference in New Issue
Block a user