Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>LinkNoteMaster Pro - Notes</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <!-- Header --> | |
| <header class="bg-white shadow-sm border-b"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex justify-between items-center py-4"> | |
| <div class="flex items-center space-x-4"> | |
| <a href="index.html" class="text-gray-600 hover:text-gray-900"> | |
| <i data-feather="arrow-left"></i> | |
| </a> | |
| <h1 class="text-2xl font-bold text-gray-900">LinkNoteMaster Pro</h1> | |
| </div> | |
| <button id="newNoteBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors"> | |
| <i data-feather="plus"></i> | |
| <span>New Note</span> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
| <!-- Search and Filter --> | |
| <div class="mb-8"> | |
| <div class="flex flex-col sm:flex-row gap-4"> | |
| <div class="flex-1"> | |
| <div class="relative"> | |
| <input type="text" id="searchInput" placeholder="Search notes and links..." class="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"> | |
| <i data-feather="search" class="absolute left-3 top-3.5 text-gray-400"></i> | |
| </div> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <select id="filterSelect" class="border border-gray-300 rounded-lg px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent"> | |
| <option value="all">All Notes</option> | |
| <option value="text">Text Only</option> | |
| <option value="link">Links Only</option> | |
| <option value="favorite">Favorites</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Notes Grid --> | |
| <div id="notesContainer" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> | |
| <!-- Notes will be dynamically added here --> | |
| </div> | |
| <!-- Empty State --> | |
| <div id="emptyState" class="text-center py-16 hidden"> | |
| <i data-feather="file-text" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">No notes yet</h3> | |
| <p class="text-gray-500 mb-6">Create your first note to get started!</p> | |
| <button id="emptyNewNoteBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg transition-colors"> | |
| Create Your First Note | |
| </button> | |
| </div> | |
| </main> | |
| <!-- Add/Edit Note Modal --> | |
| <div id="noteModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden"> | |
| <div class="bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-hidden"> | |
| <div class="flex justify-between items-center p-6 border-b"> | |
| <h3 class="text-xl font-semibold" id="modalTitle">New Note</h3> | |
| <button id="closeModal" class="text-gray-400 hover:text-gray-600"> | |
| <i data-feather="x"></i> | |
| </button> | |
| </div> | |
| <div class="p-6"> | |
| <form id="noteForm"> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Title</label> | |
| <input type="text" id="noteTitle" class="w-full border border-gray-300 rounded-lg px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Enter note title" required> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Content</label> | |
| <textarea id="noteContent" rows="6" class="w-full border border-gray-300 rounded-lg px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Enter your note content..."></textarea> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Link (Optional)</label> | |
| <input type="url" id="noteLink" class="w-full border border-gray-300 rounded-lg px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="https://example.com"> | |
| </div> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center space-x-4"> | |
| <label class="flex items-center"> | |
| <input type="checkbox" id="noteFavorite" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"> | |
| <span class="ml-2 text-sm text-gray-700">Mark as favorite</span> | |
| </label> | |
| <label class="flex items-center"> | |
| <input type="checkbox" id="notePrivate" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"> | |
| <span class="ml-2 text-sm text-gray-700">Private note</span> | |
| </label> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button type="button" id="cancelNote" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors"> | |
| Cancel | |
| </button> | |
| <button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition-colors"> | |
| Save Note | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Notes data structure | |
| let notes = JSON.parse(localStorage.getItem('notes')) || []; | |
| let currentEditingId = null; | |
| // DOM Elements | |
| const notesContainer = document.getElementById('notesContainer'); | |
| const emptyState = document.getElementById('emptyState'); | |
| const noteModal = document.getElementById('noteModal'); | |
| const noteForm = document.getElementById('noteForm'); | |
| const searchInput = document.getElementById('searchInput'); | |
| const filterSelect = document.getElementById('filterSelect'); | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', function() { | |
| feather.replace(); | |
| renderNotes(); | |
| setupEventListeners(); | |
| }); | |
| function setupEventListeners() { | |
| // New note buttons | |
| document.getElementById('newNoteBtn').addEventListener('click', () => openModal()); | |
| document.getElementById('emptyNewNoteBtn').addEventListener('click', () => openModal()); | |
| // Modal controls | |
| document.getElementById('closeModal').addEventListener('click', () => closeModal()); | |
| document.getElementById('cancelNote').addEventListener('click', () => closeModal()); | |
| // Form submission | |
| noteForm.addEventListener('submit', handleNoteSubmit); | |
| // Search and filter | |
| searchInput.addEventListener('input', renderNotes); | |
| filterSelect.addEventListener('change', renderNotes); | |
| } | |
| function openModal(note = null) { | |
| currentEditingId = note ? note.id : null; | |
| document.getElementById('modalTitle').textContent = note ? 'Edit Note' : 'New Note'; | |
| document.getElementById('noteTitle').value = note ? note.title : ''; | |
| document.getElementById('noteContent').value = note ? note.content : ''; | |
| document.getElementById('noteLink').value = note ? note.link || '' : ''; | |
| document.getElementById('noteFavorite').checked = note ? note.favorite : false; | |
| document.getElementById('notePrivate').checked = note ? note.private : false; | |
| noteModal.classList.remove('hidden'); | |
| } | |
| function closeModal() { | |
| noteModal.classList.add('hidden'); | |
| currentEditingId = null; | |
| noteForm.reset(); | |
| } | |
| function handleNoteSubmit(e) { | |
| e.preventDefault(); | |
| const noteData = { | |
| id: currentEditingId || Date.now().toString(), | |
| title: document.getElementById('noteTitle').value, | |
| content: document.getElementById('noteContent').value, | |
| link: document.getElementById('noteLink').value, | |
| favorite: document.getElementById('noteFavorite').checked, | |
| private: document.getElementById('notePrivate').checked, | |
| createdAt: currentEditingId ? notes.find(n => n.id === currentEditingId).createdAt : new Date().toISOString(), | |
| updatedAt: new Date().toISOString() | |
| }; | |
| if (currentEditingId) { | |
| const index = notes.findIndex(n => n.id === currentEditingId); | |
| notes[index] = noteData; | |
| } else { | |
| notes.unshift(noteData); | |
| } | |
| saveNotes(); | |
| renderNotes(); | |
| closeModal(); | |
| } | |
| function saveNotes() { | |
| localStorage.setItem('notes', JSON.stringify(notes)); | |
| } | |
| function renderNotes() { | |
| const searchTerm = searchInput.value.toLowerCase(); | |
| const filter = filterSelect.value; | |
| let filteredNotes = notes.filter(note => { | |
| const matchesSearch = note.title.toLowerCase().includes(searchTerm) || | |
| note.content.toLowerCase().includes(searchTerm) || | |
| (note.link && note.link.toLowerCase().includes(searchTerm)); | |
| if (filter === 'text') return matchesSearch && !note.link; | |
| if (filter === 'link') return matchesSearch && note.link; | |
| if (filter === 'favorite') return matchesSearch && note.favorite; | |
| return matchesSearch; | |
| }); | |
| if (filteredNotes.length === 0) { | |
| notesContainer.classList.add('hidden'); | |
| emptyState.classList.remove('hidden'); | |
| return; | |
| } | |
| notesContainer.classList.remove('hidden'); | |
| emptyState.classList.add('hidden'); | |
| notesContainer.innerHTML = filteredNotes.map(note => ` | |
| <div class="bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md transition-shadow overflow-hidden"> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-start mb-3"> | |
| <h3 class="font-semibold text-gray-900 text-lg truncate">${note.title}</h3> | |
| <div class="flex space-x-1"> | |
| ${note.favorite ? '<i data-feather="star" class="w-4 h-4 text-yellow-500 fill-current"></i>' : ''} | |
| ${note.private ? '<i data-feather="lock" class="w-4 h-4 text-gray-400"></i>' : ''} | |
| </div> | |
| </div> | |
| <p class="text-gray-600 text-sm mb-4 line-clamp-3">${note.content}</p> | |
| ${note.link ? ` | |
| <div class="mb-4"> | |
| <a href="${note.link}" target="_blank" class="text-blue-600 hover:text-blue-800 text-sm truncate block"> | |
| <i data-feather="link" class="w-3 h-3 inline mr-1"></i> | |
| ${note.link} | |
| </a> | |
| </div> | |
| ` : ''} | |
| <div class="flex justify-between items-center text-xs text-gray-500"> | |
| <span>${new Date(note.updatedAt).toLocaleDateString()}</span> | |
| <div class="flex space-x-2"> | |
| <button onclick="editNote('${note.id}')" class="text-gray-400 hover:text-blue-600 transition-colors"> | |
| <i data-feather="edit-2" class="w-4 h-4"></i> | |
| </button> | |
| <button onclick="deleteNote('${note.id}')" class="text-gray-400 hover:text-red-600 transition-colors"> | |
| <i data-feather="trash-2" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| feather.replace(); | |
| } | |
| function editNote(id) { | |
| const note = notes.find(n => n.id === id); | |
| if (note) openModal(note); | |
| } | |
| function deleteNote(id) { | |
| if (confirm('Are you sure you want to delete this note?')) { | |
| notes = notes.filter(note => note.id !== id); | |
| saveNotes(); | |
| renderNotes(); | |
| } | |
| } | |
| // Make functions globally available for onclick handlers | |
| window.editNote = editNote; | |
| window.deleteNote = deleteNote; | |
| </script> | |
| </body> | |
| </html> |