diff --git a/projects/notes/index.html b/projects/notes/index.html index 77ad7b9..274e4cc 100644 --- a/projects/notes/index.html +++ b/projects/notes/index.html @@ -1,105 +1,125 @@
- - -No notes found.
`; - return; - } + if (tagFilterValue) { + filteredNotes = filteredNotes.filter(n => n.tag && n.tag.toLowerCase() === tagFilterValue.toLowerCase()); + } + if (sortBy === 'title') { + filteredNotes.sort((a, b) => a.title.localeCompare(b.title)); + } else { filteredNotes.sort((a, b) => b.pinned - a.pinned || b.created - a.created); + } + + if (filteredNotes.length === 0) { + grid.innerHTML = `No notes found.
`; + return; + } - for (const n of filteredNotes) { - const card = document.createElement('article'); - card.className = 'card'; - const date = new Date(n.created).toLocaleString(); - card.innerHTML = ` -${escapeHtml(n.content)}
- - `; - - card.querySelector('.del').addEventListener('click', () => { - if (confirm('Are you sure you want to delete this note?')) { - notes = notes.filter(x => x.id !== n.id); - saveNotes(); - render(search.value, tagFilter.value); - populateTags(); - } - }); - - card.querySelector('.pin').addEventListener('click', () => { - n.pinned = !n.pinned; - saveNotes(); - render(search.value, tagFilter.value); - }); - - grid.appendChild(card); - } + for (const n of filteredNotes) { + const card = document.createElement('article'); + card.className = 'card'; + const date = new Date(n.created).toLocaleString(); + card.innerHTML = ` +${escapeHtml(n.content.slice(0, 100))}...
+ + `; + + card.querySelector('.del').addEventListener('click', () => { + if (confirm('Are you sure you want to delete this note?')) { + notes = notes.filter(x => x.id !== n.id); + saveNotes(); + render(search.value, tagFilter.value, sortNotes.value); + populateTags(); + } + }); + + card.querySelector('.pin').addEventListener('click', () => { + n.pinned = !n.pinned; + saveNotes(); + render(search.value, tagFilter.value, sortNotes.value); + }); + + grid.appendChild(card); + } } function populateTags() { - const tags = [...new Set(notes.filter(n => n.tag).map(n => n.tag))]; - tagFilter.innerHTML = ``; - tags.forEach(t => { - const opt = document.createElement('option'); - opt.value = t; - opt.textContent = t; - tagFilter.appendChild(opt); - }); + const tags = [...new Set(notes.filter(n => n.tag).map(n => n.tag))]; + tagFilter.innerHTML = ``; + tags.forEach(t => { + const opt = document.createElement('option'); + opt.value = t; + opt.textContent = t; + tagFilter.appendChild(opt); + }); } -// Utility function to prevent XSS function escapeHtml(unsafe) { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); } -// Enhanced search with debouncing +// Search debounce let searchTimeout; search.addEventListener('input', () => { - clearTimeout(searchTimeout); - searchTimeout = setTimeout(() => { - render(search.value, tagFilter.value); - }, 300); + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + render(search.value, tagFilter.value, sortNotes.value); + }, 300); }); tagFilter.addEventListener('change', () => { - render(search.value, tagFilter.value); + render(search.value, tagFilter.value, sortNotes.value); +}); + +sortNotes.addEventListener('change', () => { + render(search.value, tagFilter.value, sortNotes.value); }); +// Theme toggle themeToggle.addEventListener('click', () => { - document.body.classList.toggle('light'); - currentTheme = document.body.classList.contains('light') ? 'light' : 'dark'; - localStorage.setItem('theme', currentTheme); - updateThemeIcon(); + document.body.classList.toggle('light'); + currentTheme = document.body.classList.contains('light') ? 'light' : 'dark'; + localStorage.setItem('theme', currentTheme); + updateThemeIcon(); }); function updateThemeIcon() { - themeToggle.textContent = document.body.classList.contains('light') ? '🌞' : '🌙'; + themeToggle.textContent = document.body.classList.contains('light') ? '🌞' : '🌙'; } -// Focus management - set focus to title input on page load +// Focus on title input window.addEventListener('load', () => { - title.focus(); + title.focus(); }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { - // Ctrl + / to focus search - if (e.ctrlKey && e.key === '/') { - e.preventDefault(); - search.focus(); - } - - // Escape to clear search - if (e.key === 'Escape' && document.activeElement === search) { - search.value = ''; - render('', tagFilter.value); - } - - // Ctrl + K to clear form (when not in input field) - if (e.ctrlKey && e.key === 'k' && !['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) { - e.preventDefault(); - resetForm(); - title.focus(); - } + if (e.ctrlKey && e.key === '/') { + e.preventDefault(); + search.focus(); + } + + if (e.key === 'Escape' && document.activeElement === search) { + search.value = ''; + render('', tagFilter.value, sortNotes.value); + } + + if (e.ctrlKey && e.key === 'k' && !['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) { + e.preventDefault(); + resetForm(); + title.focus(); + } }); -// Initialize the app +// Initialize render(); populateTags(); diff --git a/projects/notes/styles.css b/projects/notes/styles.css index 896bf7a..70cb48f 100644 --- a/projects/notes/styles.css +++ b/projects/notes/styles.css @@ -333,7 +333,7 @@ h1 { .form-actions { display: flex; - gap: 1rem; + gap: 0.3rem; margin-top: 1rem; } @@ -342,7 +342,7 @@ h1 { color: #1a0b2e; border: none; border-radius: 0.8rem; - padding: 1rem 2rem; + padding: 0rem 2rem; font-weight: 700; font-size: 1rem; cursor: pointer; @@ -573,4 +573,140 @@ body.light .btn-secondary { ::-webkit-scrollbar-thumb:hover { background: linear-gradient(135deg, #ff9800, #ffb74d); +} + +/* Autosave indicator */ +#autosaveStatus { + font-size: 0.85rem; + color: #b39ddb; + margin-top: -0.5rem; + text-align: right; + transition: color 0.3s ease; +} + +/* Sort bar */ +.sort-bar { + display: flex; + align-items: center; + gap: 0.8rem; + margin-bottom: 1.5rem; +} + +.sort-bar label { + color: #e0b3ff; + font-weight: 600; +} + +#sortNotes { + background: rgba(26, 11, 46, 0.8); + color: #f8fafc; + border: 1px solid rgba(255, 183, 77, 0.3); + border-radius: 0.8rem; + padding: 0.8rem 1rem; + font-size: 1rem; + backdrop-filter: blur(10px); + cursor: pointer; +} + +/* Undo/Redo buttons */ +#undo-btn, +#redo-btn { + background: rgba(255, 255, 255, 0.1); + color: #e0b3ff; + border: 1px solid rgba(255, 183, 77, 0.3); + border-radius: 0.8rem; + padding: 1rem 2rem; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + flex: 1; + backdrop-filter: blur(10px); +} + +#undo-btn:hover, +#redo-btn:hover { + background: rgba(255, 183, 77, 0.2); + border-color: #ffb74d; + transform: translateY(-3px); + box-shadow: 0 4px 15px rgba(255, 183, 77, 0.2); +} + +/* Note preview and timestamp */ +.note-preview { + font-size: 0.95rem; + color: #e0e0ff; + line-height: 1.6; + margin-bottom: 0.5rem; +} + +.note-timestamp { + font-size: 0.8rem; + color: #b39ddb; +} + +/* Light theme additions */ +body.light #autosaveStatus { + color: #8e24aa; +} + +body.light #tagFilter { + background: rgba(243, 229, 245, 0.8); + color: #4a148c; + border-color: rgba(255, 152, 0, 0.3); +} +body.light .sort-bar label { + color: #7b1fa2; +} + + +body.light #sortNotes { + background: rgba(243, 229, 245, 0.8); + color: #4a148c; + border-color: rgba(255, 152, 0, 0.3); +} + + +body.light #undo-btn, +body.light #redo-btn { + background: rgba(255, 152, 0, 0.1); + color: #7b1fa2; + border-color: rgba(255, 152, 0, 0.3); +} + +body.light #undo-btn:hover, +body.light #redo-btn:hover { + background: rgba(255, 152, 0, 0.2); + box-shadow: 0 4px 15px rgba(255, 152, 0, 0.2); +} + +body.light .note-preview { + color: #6a1b9a; +} + +body.light .note-timestamp { + color: #8e24aa; +} + +/* Responsive sort bar */ +@media (max-width: 900px) { + .sort-bar { + flex-direction: column; + align-items: stretch; + } + + #sortNotes { + width: 100%; + } +} + +#tagFilter{ + background: rgba(26, 11, 46, 0.8); + color: #f8fafc; + border: 1px solid rgba(255, 183, 77, 0.3); + border-radius: 0.8rem; + padding: 0.8rem 1rem; + font-size: 1rem; + backdrop-filter: blur(10px); + cursor: pointer; } \ No newline at end of file