All notes
Connecting...
would execute // JavaScript - this is called an XSS (Cross-Site Scripting) // attack and is one of the most common web vulnerabilities. // // Enterprise principle: always escape user content before // rendering it into the DOM. Never trust input. // ============================================================ function escapeHtml(str) { if (!str) return ''; return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // ============================================================ // TOAST NOTIFICATIONS // Brief messages that appear and automatically disappear. // Used to give feedback after API operations succeed or fail. // ============================================================ function showToast(message, type = 'success') { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); // Automatically remove the toast after 3 seconds setTimeout(() => { toast.style.opacity = '0'; toast.style.transition = 'opacity 0.3s'; setTimeout(() => toast.remove(), 300); }, 3000); } // ============================================================ // MODAL MANAGEMENT // Functions to open and close modals. // ============================================================ function openCreateModal() { state.editingId = null; document.getElementById('modal-title').textContent = 'New note'; document.getElementById('modal-save-btn').textContent = 'Save note'; document.getElementById('note-title-input').value = ''; document.getElementById('note-content-input').value = ''; document.getElementById('note-modal').classList.add('visible'); document.getElementById('note-title-input').focus(); } function openEditModal(event, id) { // Stop the click from bubbling up to the note item event.stopPropagation(); const note = state.notes.find(n => n.id === id); if (!note) return; state.editingId = id; document.getElementById('modal-title').textContent = 'Edit note'; document.getElementById('modal-save-btn').textContent = 'Update note'; document.getElementById('note-title-input').value = note.title; document.getElementById('note-content-input').value = note.content || ''; document.getElementById('note-modal').classList.add('visible'); document.getElementById('note-title-input').focus(); } function openDeleteModal(event, id, title) { event.stopPropagation(); state.deletingId = id; document.getElementById('delete-note-title').textContent = `"${title}"`; document.getElementById('delete-modal').classList.add('visible'); } function closeModal(id) { document.getElementById(id).classList.remove('visible'); } // Close modal when clicking the overlay background document.querySelectorAll('.overlay').forEach(overlay => { overlay.addEventListener('click', e => { if (e.target === overlay) closeModal(overlay.id); }); }); // Close modal with Escape key document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal('note-modal'); closeModal('delete-modal'); } }); // ============================================================ // CRUD OPERATIONS // Functions that call the API and update state + UI. // Each follows the same pattern: // 1. Validate input // 2. Call API // 3. Update state // 4. Re-render UI // 5. Show feedback toast // ============================================================ async function loadNotes() { setStatus('loading', 'Loading notes...'); renderSkeletons(); try { const notes = await fetchNotes(); state.notes = notes; state.filtered = notes; renderNotes(notes); setStatus('connected', 'Connected to notes-api.catalinpetre.com'); updateCount(); } catch (err) { setStatus('error', 'Failed to connect to API'); document.getElementById('notes-list').innerHTML = `

Could not load notes

${err.message}

`; } } async function saveNote() { const title = document.getElementById('note-title-input').value.trim(); const content = document.getElementById('note-content-input').value.trim(); // Validate - title is required if (!title) { document.getElementById('note-title-input').focus(); showToast('Please enter a title', 'error'); return; } const btn = document.getElementById('modal-save-btn'); btn.disabled = true; btn.textContent = 'Saving...'; try { if (state.editingId) { // UPDATE existing note const updated = await updateNote(state.editingId, title, content); // Replace the note in our local state array state.notes = state.notes.map(n => n.id === state.editingId ? updated : n); showToast('Note updated successfully'); } else { // CREATE new note const created = await createNote(title, content); // Add new note to the beginning of our local state array state.notes = [created, ...state.notes]; showToast('Note created successfully'); } state.filtered = state.notes; renderNotes(state.filtered); updateCount(); closeModal('note-modal'); } catch (err) { showToast(err.message, 'error'); } finally { // Always re-enable the button whether success or failure btn.disabled = false; btn.textContent = state.editingId ? 'Update note' : 'Save note'; } } async function confirmDelete() { if (!state.deletingId) return; try { await deleteNote(state.deletingId); // Remove note from local state state.notes = state.notes.filter(n => n.id !== state.deletingId); state.filtered = state.notes; renderNotes(state.filtered); updateCount(); closeModal('delete-modal'); showToast('Note deleted'); } catch (err) { showToast(err.message, 'error'); } } // ============================================================ // SEARCH / FILTER // Client-side filtering - we filter the already-loaded notes // in memory rather than making a new API call for each keypress. // This gives instant results with no network latency. // ============================================================ function filterNotes(query) { const q = query.toLowerCase().trim(); if (!q) { state.filtered = state.notes; } else { state.filtered = state.notes.filter(note => note.title.toLowerCase().includes(q) || (note.content && note.content.toLowerCase().includes(q)) ); } renderNotes(state.filtered); } function showAllNotes() { document.getElementById('search-input').value = ''; state.filtered = state.notes; renderNotes(state.filtered); } // ============================================================ // KEYBOARD SHORTCUT // Ctrl+N or Cmd+N opens the new note modal. // Small details like keyboard shortcuts make an app feel // professional and polished. // ============================================================ document.addEventListener('keydown', e => { if ((e.ctrlKey || e.metaKey) && e.key === 'n') { e.preventDefault(); openCreateModal(); } }); // ============================================================ // INITIALISATION // Load notes when the page first loads. // This is the entry point of the application. // ============================================================ loadNotes();