Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Online Dictionary App</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <link href="https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.css" rel="stylesheet" type="text/css" /> | |
| <script src="https://kit.fontawesome.com/1f7e5e05ce.js" crossorigin="anonymous"></script> | |
| </head> | |
| <body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="text-center mb-10"> | |
| <h1 class="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-600 mb-2"> | |
| <i class="fas fa-book mr-3"></i>Smart Dictionary | |
| </h1> | |
| <p class="text-gray-600">Create, manage and share your personal dictionary with ease</p> | |
| </header> | |
| <!-- Main Tabs --> | |
| <div class="tabs tabs-lifted w-full max-w-6xl mx-auto mb-6"> | |
| <a class="tab tab-lg" id="input-tab">📝 Input</a> | |
| <a class="tab tab-lg" id="search-tab">🔍 Search</a> | |
| <a class="tab tab-lg" id="records-tab">📊 Records</a> | |
| <a class="tab tab-lg" id="import-tab">📂 Import/Export</a> | |
| </div> | |
| <!-- Input Tab Content --> | |
| <div id="input-content" class="tab-content bg-white rounded-xl shadow-xl p-8 mb-8 transition-all duration-300"> | |
| <h2 class="text-2xl font-semibold mb-6 text-gray-800 flex items-center"> | |
| <i class="fas fa-plus-circle mr-3 text-green-500"></i>Add New Dictionary Entry | |
| </h2> | |
| <form id="dictionary-form" class="space-y-6"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Word</span> | |
| <span class="label-text-alt text-red-500">*</span> | |
| </label> | |
| <input type="text" id="word" placeholder="Enter word" class="input input-bordered w-full" required /> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Part of Speech</span> | |
| </label> | |
| <select id="part-of-speech" class="select select-bordered w-full"> | |
| <option value="">Select...</option> | |
| <option value="noun">Noun</option> | |
| <option value="verb">Verb</option> | |
| <option value="adjective">Adjective</option> | |
| <option value="adverb">Adverb</option> | |
| <option value="pronoun">Pronoun</option> | |
| <option value="preposition">Preposition</option> | |
| <option value="conjunction">Conjunction</option> | |
| <option value="interjection">Interjection</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Definition</span> | |
| <span class="label-text-alt text-red-500">*</span> | |
| </label> | |
| <textarea id="definition" placeholder="Enter definition" class="textarea textarea-bordered h-24" required></textarea> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Example</span> | |
| </label> | |
| <textarea id="example" placeholder="Enter example usage" class="textarea textarea-bordered h-24"></textarea> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Synonyms</span> | |
| </label> | |
| <input type="text" id="synonyms" placeholder="Comma separated synonyms" class="input input-bordered w-full" /> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Category/Tags</span> | |
| </label> | |
| <input type="text" id="tags" placeholder="Comma separated tags (e.g. tech, medical, slang)" class="input input-bordered w-full" /> | |
| </div> | |
| <div class="flex flex-wrap gap-4 pt-4"> | |
| <button type="submit" class="btn btn-primary btn-lg flex-1 md:flex-none"> | |
| <i class="fas fa-save mr-2"></i>Save Entry | |
| </button> | |
| <button type="button" id="clear-form" class="btn btn-outline btn-lg flex-1 md:flex-none"> | |
| <i class="fas fa-times mr-2"></i>Clear | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| <!-- Search Tab Content --> | |
| <div id="search-content" class="tab-content bg-white rounded-xl shadow-xl p-8 mb-8 hidden transition-all duration-300"> | |
| <h2 class="text-2xl font-semibold mb-6 text-gray-800 flex items-center"> | |
| <i class="fas fa-search mr-3 text-blue-500"></i>Search Dictionary | |
| </h2> | |
| <div class="form-control mb-6"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Search Term</span> | |
| </label> | |
| <div class="relative"> | |
| <input type="text" id="search-input" placeholder="Search for words, definitions, tags..." class="input input-bordered w-full pr-12" /> | |
| <button id="search-btn" class="btn btn-square btn-ghost absolute right-1 top-1"> | |
| <i class="fas fa-search"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="form-control mb-6"> | |
| <label class="label cursor-pointer"> | |
| <span class="label-text">Search in definitions as well</span> | |
| <input type="checkbox" id="search-in-definitions" class="checkbox checkbox-primary" checked /> | |
| </label> | |
| </div> | |
| <div id="search-results" class="mt-6 space-y-4"></div> | |
| </div> | |
| <!-- Records Tab Content --> | |
| <div id="records-content" class="tab-content bg-white rounded-xl shadow-xl p-8 mb-8 hidden transition-all duration-300"> | |
| <h2 class="text-2xl font-semibold mb-6 text-gray-800 flex items-center"> | |
| <i class="fas fa-database mr-3 text-purple-500"></i>All Dictionary Entries | |
| <span id="record-count" class="badge badge-primary ml-3"></span> | |
| </h2> | |
| <div class="overflow-x-auto"> | |
| <table id="records-table" class="table table-zebra w-full"> | |
| <thead> | |
| <tr> | |
| <th>Word</th> | |
| <th>Part of Speech</th> | |
| <th>Definition</th> | |
| <th>Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Import/Export Tab Content --> | |
| <div id="import-content" class="tab-content bg-white rounded-xl shadow-xl p-8 hidden transition-all duration-300"> | |
| <h2 class="text-2xl font-semibold mb-6 text-gray-800 flex items-center"> | |
| <i class="fas fa-file-import mr-3 text-orange-500"></i>Import/Export Data | |
| </h2> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| <!-- Import Section --> | |
| <div class="p-6 border-2 border-dashed border-blue-300 rounded-xl bg-blue-50"> | |
| <h3 class="text-xl font-semibold mb-4 text-blue-700 flex items-center"> | |
| <i class="fas fa-upload mr-3"></i>Import Data | |
| </h3> | |
| <p class="text-gray-700 mb-4">Import your dictionary data from Excel (XLS/XLSX) file.</p> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text">Upload Excel File</span> | |
| </label> | |
| <input type="file" id="import-file" accept=".xlsx, .xls" class="file-input file-input-bordered w-full" /> | |
| </div> | |
| <button id="import-btn" class="btn btn-primary mt-4 w-full"> | |
| <i class="fas fa-download mr-2"></i>Import Data | |
| </button> | |
| <div class="mt-4 text-sm text-gray-600"> | |
| <p><strong>File format requirements:</strong></p> | |
| <ul class="list-disc list-inside mt-1"> | |
| <li>Must be in XLS or XLSX format</li> | |
| <li>First row should contain headers: Word, PartOfSpeech, Definition, Example, Synonyms, Tags</li> | |
| <li>All fields except "Example", "Synonyms", and "Tags" are required</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <!-- Export Section --> | |
| <div class="p-6 border-2 border-dashed border-green-300 rounded-xl bg-green-50"> | |
| <h3 class="text-xl font-semibold mb-4 text-green-700 flex items-center"> | |
| <i class="fas fa-download mr-3"></i>Export Data | |
| </h3> | |
| <p class="text-gray-700 mb-4">Export your dictionary data to Excel for backup or sharing.</p> | |
| <button id="export-btn" class="btn btn-success w-full text-lg py-4"> | |
| <i class="fas fa-file-excel mr-2"></i>Export to Excel | |
| </button> | |
| <div class="mt-4 text-sm text-gray-600"> | |
| <p><strong>Export options:</strong></p> | |
| <ul class="list-disc list-inside mt-1"> | |
| <li>Exports all dictionary entries</li> | |
| <li>Creates an XLSX file compatible with Excel and Google Sheets</li> | |
| <li>Includes all data fields</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 p-4 bg-yellow-50 border-l-4 border-yellow-400 rounded"> | |
| <p class="text-sm text-yellow-800 flex items-start"> | |
| <i class="fas fa-exclamation-triangle mr-2 mt-1"></i> | |
| <strong>Warning:</strong> Importing a new file will overwrite any existing entries with the same words. Please backup your data before importing. | |
| </p> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="toast toast-top toast-end hidden"> | |
| <div id="toast-content" class="alert alert-success"> | |
| <span id="toast-message"></span> | |
| </div> | |
| </div> | |
| <!-- Edit Modal --> | |
| <input type="checkbox" id="edit-modal" class="modal-toggle" /> | |
| <div class="modal modal-bottom sm:modal-middle"> | |
| <div class="modal-box"> | |
| <h3 id="edit-modal-title" class="font-bold text-lg mb-4">Edit Entry</h3> | |
| <form id="edit-form" class="space-y-4"> | |
| <input type="hidden" id="edit-id" /> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Word</span> | |
| </label> | |
| <input type="text" id="edit-word" class="input input-bordered w-full" required /> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Part of Speech</span> | |
| </label> | |
| <select id="edit-part-of-speech" class="select select-bordered w-full"> | |
| <option value="">Select...</option> | |
| <option value="noun">Noun</option> | |
| <option value="verb">Verb</option> | |
| <option value="adjective">Adjective</option> | |
| <option value="adverb">Adverb</option> | |
| <option value="pronoun">Pronoun</option> | |
| <option value="preposition">Preposition</option> | |
| <option value="conjunction">Conjunction</option> | |
| <option value="interjection">Interjection</option> | |
| </select> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Definition</span> | |
| </label> | |
| <textarea id="edit-definition" class="textarea textarea-bordered h-24" required></textarea> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Example</span> | |
| </label> | |
| <textarea id="edit-example" class="textarea textarea-bordered h-24"></textarea> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Synonyms</span> | |
| </label> | |
| <input type="text" id="edit-synonyms" class="input input-bordered w-full" /> | |
| </div> | |
| <div class="form-control"> | |
| <label class="label"> | |
| <span class="label-text font-medium">Tags</span> | |
| </label> | |
| <input type="text" id="edit-tags" class="input input-bordered w-full" /> | |
| </div> | |
| <div class="modal-action"> | |
| <button type="submit" class="btn btn-primary">Save Changes</button> | |
| <label for="edit-modal" class="btn">Cancel</label> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Delete Confirmation Modal --> | |
| <input type="checkbox" id="delete-modal" class="modal-toggle" /> | |
| <div class="modal"> | |
| <div class="modal-box"> | |
| <h3 id="delete-modal-title" class="font-bold text-lg">Delete Confirmation</h3> | |
| <p id="delete-modal-body" class="py-4">Are you sure you want to delete this entry? This action cannot be undone.</p> | |
| <div class="modal-action"> | |
| <button id="confirm-delete" class="btn btn-error">Yes, Delete</button> | |
| <label for="delete-modal" class="btn">Cancel</label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Dictionary App Main Logic | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize local storage if not exists | |
| if (!localStorage.getItem('dictionaryEntries')) { | |
| localStorage.setItem('dictionaryEntries', JSON.stringify([])); | |
| } | |
| // Tab functionality | |
| const tabs = document.querySelectorAll('.tab'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| // Remove active class from all tabs and contents | |
| tabs.forEach(t => t.classList.remove('tab-active')); | |
| tabContents.forEach(content => content.classList.add('hidden')); | |
| // Add active class to clicked tab and corresponding content | |
| tab.classList.add('tab-active'); | |
| const tabId = tab.id.replace('-tab', '-content'); | |
| document.getElementById(tabId).classList.remove('hidden'); | |
| // Special handling for records tab - refresh table when shown | |
| if (tabId === 'records-content') { | |
| displayAllRecords(); | |
| } | |
| }); | |
| }); | |
| // Show input tab by default | |
| document.getElementById('input-tab').click(); | |
| // Dictionary data functions | |
| function getEntries() { | |
| return JSON.parse(localStorage.getItem('dictionaryEntries')) || []; | |
| } | |
| function saveEntries(entries) { | |
| localStorage.setItem('dictionaryEntries', JSON.stringify(entries)); | |
| } | |
| function addEntry(entry) { | |
| const entries = getEntries(); | |
| // Check if word already exists and update if it does | |
| const existingIndex = entries.findIndex(e => e.word.toLowerCase() === entry.word.toLowerCase()); | |
| if (existingIndex > -1) { | |
| entries[existingIndex] = { ...entries[existingIndex], ...entry }; | |
| } else { | |
| entry.id = Date.now(); // Simple ID generation | |
| entries.push(entry); | |
| } | |
| saveEntries(entries); | |
| } | |
| function updateEntry(id, updatedEntry) { | |
| const entries = getEntries(); | |
| const index = entries.findIndex(e => e.id == id); | |
| if (index > -1) { | |
| entries[index] = { ...entries[index], ...updatedEntry }; | |
| saveEntries(entries); | |
| } | |
| } | |
| function deleteEntry(id) { | |
| let entries = getEntries(); | |
| entries = entries.filter(e => e.id != id); | |
| saveEntries(entries); | |
| } | |
| function searchEntries(term, searchInDefinitions = true) { | |
| const entries = getEntries(); | |
| const searchTerm = term.toLowerCase(); | |
| return entries.filter(entry => { | |
| if (entry.word.toLowerCase().includes(searchTerm)) { | |
| return true; | |
| } | |
| if (searchInDefinitions && entry.definition.toLowerCase().includes(searchTerm)) { | |
| return true; | |
| } | |
| if (entry.synonyms && entry.synonyms.toLowerCase().includes(searchTerm)) { | |
| return true; | |
| } | |
| if (entry.tags && entry.tags.toLowerCase().includes(searchTerm)) { | |
| return true; | |
| } | |
| return false; | |
| }); | |
| } | |
| // Form submission | |
| const dictionaryForm = document.getElementById('dictionary-form'); | |
| dictionaryForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const entry = { | |
| word: document.getElementById('word').value.trim(), | |
| partOfSpeech: document.getElementById('part-of-speech').value, | |
| definition: document.getElementById('definition').value.trim(), | |
| example: document.getElementById('example').value.trim(), | |
| synonyms: document.getElementById('synonyms').value.trim(), | |
| tags: document.getElementById('tags').value.trim() | |
| }; | |
| // Validation | |
| if (!entry.word) { | |
| showNotification('Word is required', 'error'); | |
| return; | |
| } | |
| if (!entry.definition) { | |
| showNotification('Definition is required', 'error'); | |
| return; | |
| } | |
| addEntry(entry); | |
| showNotification('Entry added successfully!', 'success'); | |
| // Reset form | |
| dictionaryForm.reset(); | |
| }); | |
| // Clear form | |
| document.getElementById('clear-form').addEventListener('click', function() { | |
| dictionaryForm.reset(); | |
| }); | |
| // Search functionality | |
| const searchBtn = document.getElementById('search-btn'); | |
| const searchInput = document.getElementById('search-input'); | |
| const searchInDefinitions = document.getElementById('search-in-definitions'); | |
| const searchResults = document.getElementById('search-results'); | |
| function performSearch() { | |
| const term = searchInput.value.trim(); | |
| if (!term) { | |
| searchResults.innerHTML = '<div class="alert alert-info">Enter a search term above.</div>'; | |
| return; | |
| } | |
| const results = searchEntries(term, searchInDefinitions.checked); | |
| if (results.length === 0) { | |
| searchResults.innerHTML = '<div class="alert alert-warning">No entries found matching your search.</div>'; | |
| return; | |
| } | |
| searchResults.innerHTML = ` | |
| <div class="alert alert-info mb-4"> | |
| Found ${results.length} result${results.length !== 1 ? 's' : ''} for "${term}" | |
| </div> | |
| ${results.map(renderEntryCard).join('')} | |
| `; | |
| // Add event listeners to edit and delete buttons | |
| document.querySelectorAll('.edit-btn').forEach(btn => { | |
| btn.addEventListener('click', handleEditClick); | |
| }); | |
| document.querySelectorAll('.delete-btn').forEach(btn => { | |
| btn.addEventListener('click', handleDeleteClick); | |
| }); | |
| } | |
| searchBtn.addEventListener('click', performSearch); | |
| searchInput.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| performSearch(); | |
| } | |
| }); | |
| searchInDefinitions.addEventListener('change', performSearch); | |
| // Display all records | |
| function displayAllRecords() { | |
| const entries = getEntries(); | |
| const tableBody = document.querySelector('#records-table tbody'); | |
| const recordCount = document.getElementById('record-count'); | |
| if (entries.length === 0) { | |
| tableBody.innerHTML = ` | |
| <tr> | |
| <td colspan="4" class="text-center py-8 text-gray-500"> | |
| <i class="fas fa-inbox text-4xl mb-2 opacity-50"></i> | |
| <div>No entries yet. Start by adding your first word!</div> | |
| </td> | |
| </tr> | |
| `; | |
| recordCount.textContent = '0 entries'; | |
| return; | |
| } | |
| tableBody.innerHTML = entries.map(entry => { | |
| return ` | |
| <tr> | |
| <td class="font-medium">${entry.word}</td> | |
| <td>${entry.partOfSpeech || '-'}</td> | |
| <td>${truncateText(entry.definition, 80)}</td> | |
| <td> | |
| <div class="join join-vertical md:join-horizontal"> | |
| <button class="btn btn-xs btn-outline join-item edit-btn" data-id="${entry.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="btn btn-xs btn-outline join-item delete-btn" data-id="${entry.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </td> | |
| </tr> | |
| `; | |
| }).join(''); | |
| recordCount.textContent = `${entries.length} entries`; | |
| // Add event listeners to edit and delete buttons | |
| document.querySelectorAll('.edit-btn').forEach(btn => { | |
| btn.addEventListener('click', handleEditClick); | |
| }); | |
| document.querySelectorAll('.delete-btn').forEach(btn => { | |
| btn.addEventListener('click', handleDeleteClick); | |
| }); | |
| } | |
| // Edit entry | |
| let currentEditId = null; | |
| function handleEditClick(e) { | |
| currentEditId = e.target.closest('.edit-btn').dataset.id; | |
| const entries = getEntries(); | |
| const entry = entries.find(e => e.id == currentEditId); | |
| // Fill the form | |
| document.getElementById('edit-id').value = entry.id; | |
| document.getElementById('edit-word').value = entry.word; | |
| document.getElementById('edit-part-of-speech').value = entry.partOfSpeech || ''; | |
| document.getElementById('edit-definition').value = entry.definition; | |
| document.getElementById('edit-example').value = entry.example || ''; | |
| document.getElementById('edit-synonyms').value = entry.synonyms || ''; | |
| document.getElementById('edit-tags').value = entry.tags || ''; | |
| // Show modal | |
| document.getElementById('edit-modal').checked = true; | |
| } | |
| document.getElementById('edit-form').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const updatedEntry = { | |
| word: document.getElementById('edit-word').value.trim(), | |
| partOfSpeech: document.getElementById('edit-part-of-speech').value, | |
| definition: document.getElementById('edit-definition').value.trim(), | |
| example: document.getElementById('edit-example').value.trim(), | |
| synonyms: document.getElementById('edit-synonyms').value.trim(), | |
| tags: document.getElementById('edit-tags').value.trim() | |
| }; | |
| // Validation | |
| if (!updatedEntry.word) { | |
| showNotification('Word is required', 'error'); | |
| return; | |
| } | |
| if (!updatedEntry.definition) { | |
| showNotification('Definition is required', 'error'); | |
| return; | |
| } | |
| updateEntry(currentEditId, updatedEntry); | |
| showNotification('Entry updated successfully!', 'success'); | |
| // Close modal | |
| document.getElementById('edit-modal').checked = false; | |
| // Refresh views if needed | |
| if (!document.getElementById('search-content').classList.contains('hidden')) { | |
| performSearch(); | |
| } | |
| if (!document.getElementById('records-content').classList.contains('hidden')) { | |
| displayAllRecords(); | |
| } | |
| }); | |
| // Delete entry | |
| let currentDeleteId = null; | |
| function handleDeleteClick(e) { | |
| currentDeleteId = e.target.closest('.delete-btn').dataset.id; | |
| // Update modal content | |
| const entries = getEntries(); | |
| const entry = entries.find(e => e.id == currentDeleteId); | |
| document.getElementById('delete-modal-body').textContent = | |
| `Are you sure you want to delete "${entry.word}"? This action cannot be undone.`; | |
| // Show modal | |
| document.getElementById('delete-modal').checked = true; | |
| } | |
| document.getElementById('confirm-delete').addEventListener('click', function() { | |
| deleteEntry(currentDeleteId); | |
| showNotification('Entry deleted successfully!', 'success'); | |
| // Close modal | |
| document.getElementById('delete-modal').checked = false; | |
| // Refresh views if needed | |
| if (!document.getElementById('search-content').classList.contains('hidden')) { | |
| performSearch(); | |
| } | |
| if (!document.getElementById('records-content').classList.contains('hidden')) { | |
| displayAllRecords(); | |
| } | |
| }); | |
| // Import/Export functionality | |
| document.getElementById('import-btn').addEventListener('click', function() { | |
| const fileInput = document.getElementById('import-file'); | |
| const file = fileInput.files[0]; | |
| if (!file) { | |
| showNotification('Please select a file to import', 'error'); | |
| return; | |
| } | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| try { | |
| const data = new Uint8Array(e.target.result); | |
| const workbook = XLSX.read(data, { type: 'array' }); | |
| const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; | |
| const jsonData = XLSX.utils.sheet_to_json(firstSheet); | |
| // Validate data structure | |
| const requiredFields = ['Word', 'Definition']; | |
| const missingFields = requiredFields.filter(field => !jsonData[0] || !jsonData[0].hasOwnProperty(field)); | |
| if (missingFields.length > 0) { | |
| showNotification(`Required column(s) missing: ${missingFields.join(', ')}`, 'error'); | |
| return; | |
| } | |
| // Process data | |
| const entries = getEntries(); | |
| let importedCount = 0; | |
| jsonData.forEach(row => { | |
| // Skip empty rows | |
| if (!row.Word || !row.Definition) return; | |
| const entry = { | |
| word: row.Word.trim(), | |
| partOfSpeech: row.PartOfSpeech || '', | |
| definition: row.Definition.trim(), | |
| example: row.Example || '', | |
| synonyms: row.Synonyms || '', | |
| tags: row.Tags || '', | |
| id: Date.now() + Math.floor(Math.random() * 1000) // Generate unique ID | |
| }; | |
| // Check if word already exists and update if needed | |
| const existingIndex = entries.findIndex(e => e.word.toLowerCase() === entry.word.toLowerCase()); | |
| if (existingIndex > -1) { | |
| entries[existingIndex] = { ...entries[existingIndex], ...entry }; | |
| } else { | |
| entries.push(entry); | |
| } | |
| importedCount++; | |
| }); | |
| saveEntries(entries); | |
| showNotification(`Successfully imported ${importedCount} entries!`, 'success'); | |
| // Clear file input | |
| fileInput.value = ''; | |
| // Refresh views | |
| if (!document.getElementById('records-content').classList.contains('hidden')) { | |
| displayAllRecords(); | |
| } | |
| } catch (error) { | |
| console.error(error); | |
| showNotification('Error processing file. Please check the format and try again.', 'error'); | |
| } | |
| }; | |
| reader.readAsArrayBuffer(file); | |
| }); | |
| document.getElementById('export-btn').addEventListener('click', function() { | |
| const entries = getEntries(); | |
| if (entries.length === 0) { | |
| showNotification('No data to export', 'warning'); | |
| return; | |
| } | |
| // Transform data for export | |
| const exportData = entries.map(entry => ({ | |
| 'Word': entry.word, | |
| 'PartOfSpeech': entry.partOfSpeech || '', | |
| 'Definition': entry.definition, | |
| 'Example': entry.example || '', | |
| 'Synonyms': entry.synonyms || '', | |
| 'Tags': entry.tags || '' | |
| })); | |
| // Create worksheet | |
| const worksheet = XLSX.utils.json_to_sheet(exportData); | |
| // Create workbook | |
| const workbook = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(workbook, worksheet, 'Dictionary'); | |
| // Generate file | |
| XLSX.writeFile(workbook, `dictionary_export_${new Date().toISOString().split('T')[0]}.xlsx`); | |
| showNotification('Data exported successfully!', 'success'); | |
| }); | |
| // Helper functions | |
| function renderEntryCard(entry) { | |
| return ` | |
| <div class="card bg-base-100 shadow-md border border-gray-100 mb-4"> | |
| <div class="card-body p-6"> | |
| <h3 class="card-title text-xl"> | |
| ${entry.word} | |
| ${entry.partOfSpeech ? `<span class="badge badge-outline text-sm">${entry.partOfSpeech}</span>` : ''} | |
| </h3> | |
| <div class="mt-2"> | |
| <p><strong>Definition:</strong> ${entry.definition}</p> | |
| ${entry.example ? `<p class="mt-2"><strong>Example:</strong> ${entry.example}</p>` : ''} | |
| ${entry.synonyms ? `<p class="mt-2"><strong>Synonyms:</strong> ${entry.synonyms}</p>` : ''} | |
| ${entry.tags ? `<p class="mt-2"><strong>Tags:</strong> ${entry.tags.split(',').map(tag => `<span class="badge badge-ghost">${tag.trim()}</span>`).join(' ')}</p>` : ''} | |
| </div> | |
| <div class="card-actions justify-end mt-4"> | |
| <button class="btn btn-outline btn-sm edit-btn" data-id="${entry.id}"> | |
| <i class="fas fa-edit mr-1"></i>Edit | |
| </button> | |
| <button class="btn btn-outline btn-error btn-sm delete-btn" data-id="${entry.id}"> | |
| <i class="fas fa-trash mr-1"></i>Delete | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| function truncateText(text, maxLength) { | |
| if (text.length <= maxLength) { | |
| return text; | |
| } | |
| return text.substring(0, maxLength) + '...'; | |
| } | |
| function showNotification(message, type = 'success') { | |
| const toast = document.getElementById('toast'); | |
| const toastContent = document.getElementById('toast-content'); | |
| const toastMessage = document.getElementById('toast-message'); | |
| // Set message and style | |
| toastMessage.textContent = message; | |
| // Remove previous classes | |
| toastContent.className = 'alert'; | |
| // Add appropriate class based on type | |
| if (type === 'error') { | |
| toastContent.classList.add('alert-error'); | |
| } else if (type === 'warning') { | |
| toastContent.classList.add('alert-warning'); | |
| } else { | |
| toastContent.classList.add('alert-success'); | |
| } | |
| // Show toast | |
| toast.classList.remove('hidden'); | |
| // Hide after 3 seconds | |
| setTimeout(() => { | |
| toast.classList.add('hidden'); | |
| }, 3000); | |
| } | |
| // Initialize the app | |
| displayAllRecords(); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=alterzick/smart-dictionary-v1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |