Loading entries...
-Error loading entries. Please try again.
-No entries found matching your filter.
-Loading entries...
-Error loading entries. Please try again.
-No entries found matching your filter.
-No examples added yet
- - `; - examplesContainer.appendChild(noExamples); - } else { - reindexExamples(senseIndex); - } - } - } - }); - - // Audio preview modal - const audioPreviewModal = new bootstrap.Modal(document.getElementById('audioPreviewModal')); - - // Save audio button - document.getElementById('save-audio-btn').addEventListener('click', function() { - const audioPlayer = document.getElementById('audio-preview-player'); - const audioSrc = audioPlayer.src; - const currentPronunciationIndex = audioPlayer.dataset.pronunciationIndex; - - // Save the audio file path to the input - const audioFileInput = document.querySelector(`input[name="pronunciations[${currentPronunciationIndex}].audio_file"]`); - audioFileInput.value = audioSrc.split('/').pop(); - - // Close the modal - audioPreviewModal.hide(); - }); -}); - -/** - * Validate the form before submission - * - * @param {boolean} showValidationModal - Whether to show the validation modal - * @returns {boolean} Whether the form is valid - */ -function validateForm(showValidationModal = false) { - const errors = []; - - // Basic validation - const lexicalUnit = document.getElementById('lexical-unit').value.trim(); - if (!lexicalUnit) { - errors.push('Lexical Unit is required'); - } - - const partOfSpeech = document.getElementById('part-of-speech').value; - if (!partOfSpeech) { - errors.push('Part of Speech is required'); - } - - // Sense validation - const senses = document.querySelectorAll('.sense-item'); - if (senses.length === 0) { - errors.push('At least one sense is required'); - } else { - senses.forEach((sense, index) => { - const definition = sense.querySelector(`textarea[name="senses[${index}].definition"]`).value.trim(); - if (!definition) { - errors.push(`Sense ${index + 1}: Definition is required`); - } - - // Validate examples if present - const examples = sense.querySelectorAll('.example-item'); - examples.forEach((example, exIndex) => { - const exampleText = example.querySelector(`textarea[name="senses[${index}].examples[${exIndex}].text"]`).value.trim(); - if (!exampleText) { - errors.push(`Sense ${index + 1}, Example ${exIndex + 1}: Example text is required`); - } - }); - }); - } - - // Show validation errors - if (errors.length > 0) { - if (showValidationModal) { - const errorsList = document.getElementById('validation-errors-list'); - errorsList.innerHTML = ''; - - errors.forEach(error => { - const li = document.createElement('li'); - li.className = 'text-danger'; - li.textContent = error; - errorsList.appendChild(li); - }); - - const validationModal = new bootstrap.Modal(document.getElementById('validationModal')); - validationModal.show(); - } - - return false; - } - - return true; -} - -/** - * Submit the form via AJAX - */ -function submitForm() { - const form = document.getElementById('entry-form'); - const formData = new FormData(form); - - // Convert form data to JSON - const jsonData = {}; - - for (const [key, value] of formData.entries()) { - // Handle arrays and nested objects - if (key.includes('[') && key.includes(']')) { - const parts = key.split('['); - const mainKey = parts[0]; - const subKey = parts[1].replace(']', ''); - - if (parts.length === 2) { - // Simple array or object property - if (subKey === '') { - // Array - if (!jsonData[mainKey]) { - jsonData[mainKey] = []; - } - jsonData[mainKey].push(value); - } else { - // Object property - if (!jsonData[mainKey]) { - jsonData[mainKey] = {}; - } - jsonData[mainKey][subKey] = value; - } - } else if (parts.length === 3) { - // Nested array in object - const nestedKey = parts[2].replace(']', ''); - - if (!jsonData[mainKey]) { - jsonData[mainKey] = {}; - } - - if (!jsonData[mainKey][subKey]) { - jsonData[mainKey][subKey] = []; - } - - if (nestedKey === '') { - jsonData[mainKey][subKey].push(value); - } else { - if (!jsonData[mainKey][subKey][nestedKey]) { - jsonData[mainKey][subKey][nestedKey] = value; - } - } - } else if (parts.length === 4) { - // Deeply nested object - const arrayIndex = parseInt(parts[1].replace(']', '')); - const objectKey = parts[2].replace(']', ''); - const nestedKey = parts[3].replace(']', ''); - - if (!jsonData[mainKey]) { - jsonData[mainKey] = []; - } - - if (!jsonData[mainKey][arrayIndex]) { - jsonData[mainKey][arrayIndex] = {}; - } - - if (!jsonData[mainKey][arrayIndex][objectKey]) { - jsonData[mainKey][arrayIndex][objectKey] = {}; - } - - jsonData[mainKey][arrayIndex][objectKey][nestedKey] = value; - } - } else { - // Simple key-value - jsonData[key] = value; - } - } - - // Show loading state - const saveBtn = document.getElementById('save-btn'); - const originalText = saveBtn.innerHTML; - saveBtn.innerHTML = ' Saving...'; - saveBtn.disabled = true; - - // Send request - fetch(form.action, { - method: form.method || 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(jsonData) - }) - .then(response => { - if (!response.ok) { - throw new Error('Error saving entry'); - } - return response.json(); - }) - .then(data => { - // Redirect to the entry view page - window.location.href = `/entries/${data.id}`; - }) - .catch(error => { - console.error('Error:', error); - - // Reset button - saveBtn.innerHTML = originalText; - saveBtn.disabled = false; - - // Show error message - alert('Error saving entry. Please try again.'); - }); -} - -/** - * Add a new pronunciation field - */ -function addPronunciation() { - const container = document.getElementById('pronunciation-container'); - const pronunciationItems = container.querySelectorAll('.pronunciation-item'); - const newIndex = pronunciationItems.length; - - // Get the template and replace the index placeholder - let template = document.getElementById('pronunciation-template').innerHTML; - template = template.replace(/INDEX/g, newIndex); - - // Create a temporary element to hold the template - const temp = document.createElement('div'); - temp.innerHTML = template; - - // Append the new pronunciation item - container.appendChild(temp.firstElementChild); -} - -/** - * Add a new sense - */ -function addSense() { - const container = document.getElementById('senses-container'); - const senseItems = container.querySelectorAll('.sense-item'); - const newIndex = senseItems.length; - const newNumber = newIndex + 1; - - // Get the template and replace the index placeholder - let template = document.getElementById('sense-template').innerHTML; - template = template.replace(/INDEX/g, newIndex); - template = template.replace(/NUMBER/g, newNumber); - - // Create a temporary element to hold the template - const temp = document.createElement('div'); - temp.innerHTML = template; - - // Append the new sense item - container.appendChild(temp.firstElementChild); - - // Initialize Select2 for the new sense - $(`.sense-item[data-sense-index="${newIndex}"] .select2-tags`).select2({ - theme: 'bootstrap-5', - tags: true, - tokenSeparators: [',', ' '], - placeholder: 'Enter or select values...' - }); -} - -/** - * Add a new example to a sense - * - * @param {number} senseIndex - Index of the sense to add the example to - */ -function addExample(senseIndex) { - const senseItem = document.querySelector(`.sense-item[data-sense-index="${senseIndex}"]`); - const examplesContainer = senseItem.querySelector('.examples-container'); - const exampleItems = examplesContainer.querySelectorAll('.example-item'); - const newIndex = exampleItems.length; - const newNumber = newIndex + 1; - - // Get the template and replace the placeholders - let template = document.getElementById('example-template').innerHTML; - template = template.replace(/SENSE_INDEX/g, senseIndex); - template = template.replace(/EXAMPLE_INDEX/g, newIndex); - template = template.replace(/NUMBER/g, newNumber); - - // Create a temporary element to hold the template - const temp = document.createElement('div'); - temp.innerHTML = template; - - // Append the new example item - examplesContainer.appendChild(temp.firstElementChild); -} - -/** - * Reindex senses after removal - */ -function reindexSenses() { - const senseItems = document.querySelectorAll('.sense-item'); - - senseItems.forEach((sense, index) => { - // Update sense number in header - sense.querySelector('h6').textContent = `Sense ${index + 1}`; - - // Update sense index attribute - sense.dataset.senseIndex = index; - - // Update remove button data attribute - const removeBtn = sense.querySelector('.remove-sense-btn'); - if (removeBtn) { - removeBtn.dataset.index = index; - } - - // Update add example button data attribute - const addExampleBtn = sense.querySelector('.add-example-btn'); - addExampleBtn.dataset.senseIndex = index; - - // Update field names - sense.querySelectorAll('[name^="senses["]').forEach(field => { - const name = field.getAttribute('name'); - const newName = name.replace(/senses\[\d+\]/, `senses[${index}]`); - field.setAttribute('name', newName); - }); - - // Update example buttons - sense.querySelectorAll('.remove-example-btn').forEach(btn => { - btn.dataset.senseIndex = index; - }); - - // Reindex examples - reindexExamples(index); - }); -} - -/** - * Reindex examples within a sense after removal - * - * @param {number} senseIndex - Index of the sense containing the examples - */ -function reindexExamples(senseIndex) { - const senseItem = document.querySelector(`.sense-item[data-sense-index="${senseIndex}"]`); - const exampleItems = senseItem.querySelectorAll('.example-item'); - - exampleItems.forEach((example, index) => { - // Update example number - example.querySelector('small').textContent = `Example ${index + 1}`; - - // Update remove button data attributes - const removeBtn = example.querySelector('.remove-example-btn'); - removeBtn.dataset.senseIndex = senseIndex; - removeBtn.dataset.exampleIndex = index; - - // Update field names - example.querySelectorAll('[name^="senses["]').forEach(field => { - const name = field.getAttribute('name'); - const newName = name.replace(/examples\[\d+\]/, `examples[${index}]`); - field.setAttribute('name', newName); - }); - }); -} - -/** - * Generate audio for a pronunciation - * - * @param {string} word - The word to generate audio for - * @param {string} ipa - The IPA pronunciation - * @param {number} index - The index of the pronunciation item - */ -function generateAudio(word, ipa, index) { - // Show loading state - const btn = document.querySelector(`.generate-audio-btn[data-index="${index}"]`); - const originalText = btn.innerHTML; - btn.innerHTML = ' Generating...'; - btn.disabled = true; - - // Make API request to generate audio - fetch('/api/pronunciations/generate', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - word: word, - ipa: ipa - }) - }) - .then(response => { - if (!response.ok) { - throw new Error('Error generating audio'); - } - return response.json(); - }) - .then(data => { - // Reset button - btn.innerHTML = originalText; - btn.disabled = false; - - // Show audio preview - const audioPlayer = document.getElementById('audio-preview-player'); - audioPlayer.src = data.audio_url; - audioPlayer.dataset.pronunciationIndex = index; - - const audioPreviewModal = new bootstrap.Modal(document.getElementById('audioPreviewModal')); - audioPreviewModal.show(); - }) - .catch(error => { - console.error('Error:', error); - - // Reset button - btn.innerHTML = originalText; - btn.disabled = false; - - // Show error message - alert('Error generating audio. Please try again.'); - }); -} diff --git a/.history/app/static/js/entry-form_20250623221914.js b/.history/app/static/js/entry-form_20250623221914.js deleted file mode 100644 index 8f4a8d4f..00000000 --- a/.history/app/static/js/entry-form_20250623221914.js +++ /dev/null @@ -1,540 +0,0 @@ -/** - * Dictionary Writing System - Entry Form JavaScript - * - * This file contains the functionality for the entry edit/add form. - */ - -document.addEventListener('DOMContentLoaded', function() { - // Initialize Select2 for tag inputs - $('.select2-tags').select2({ - theme: 'bootstrap-5', - tags: true, - tokenSeparators: [',', ' '], - placeholder: 'Enter or select values...' - }); - - // Handle form submission - const entryForm = document.getElementById('entry-form'); - entryForm.addEventListener('submit', function(e) { - e.preventDefault(); - - // Perform client-side validation - if (!validateForm()) { - return; - } - - // Submit the form - submitForm(); - }); - - // Validate button handler - document.getElementById('validate-btn').addEventListener('click', function() { - validateForm(true); - }); - - // Cancel button handler - document.getElementById('cancel-btn').addEventListener('click', function() { - if (confirm('Are you sure you want to cancel? Any unsaved changes will be lost.')) { - window.location.href = '/entries'; - } - }); - - // Add pronunciation button handler - document.getElementById('add-pronunciation-btn').addEventListener('click', function() { - addPronunciation(); - }); - - // Add sense button handler - document.getElementById('add-sense-btn').addEventListener('click', function() { - addSense(); - }); - - // Add first sense button handler (for when no senses exist) - const addFirstSenseBtn = document.getElementById('add-first-sense-btn'); - if (addFirstSenseBtn) { - addFirstSenseBtn.addEventListener('click', function() { - document.getElementById('no-senses-message').style.display = 'none'; - addSense(); - }); - } - - // Handle pronunciation section events (delegated) - document.getElementById('pronunciation-container').addEventListener('click', function(e) { - // Remove pronunciation button - if (e.target.closest('.remove-pronunciation-btn')) { - const btn = e.target.closest('.remove-pronunciation-btn'); - const pronunciationItem = btn.closest('.pronunciation-item'); - - if (confirm('Are you sure you want to remove this pronunciation?')) { - pronunciationItem.remove(); - } - } - - // Generate audio button - if (e.target.closest('.generate-audio-btn')) { - const btn = e.target.closest('.generate-audio-btn'); - const index = btn.dataset.index; - const pronunciationItem = btn.closest('.pronunciation-item'); - const ipaInput = pronunciationItem.querySelector(`input[name="pronunciations[${index}].value"]`); - const lexicalUnit = document.getElementById('lexical-unit').value; - - generateAudio(lexicalUnit, ipaInput.value, index); - } - }); - - // Handle senses container events (delegated) - document.getElementById('senses-container').addEventListener('click', function(e) { - // Remove sense button - if (e.target.closest('.remove-sense-btn')) { - const btn = e.target.closest('.remove-sense-btn'); - const senseItem = btn.closest('.sense-item'); - const senseIndex = senseItem.dataset.senseIndex; - - if (confirm('Are you sure you want to remove this sense and all its examples?')) { - senseItem.remove(); - reindexSenses(); - } - } - - // Add example button - if (e.target.closest('.add-example-btn')) { - const btn = e.target.closest('.add-example-btn'); - const senseIndex = btn.dataset.senseIndex; - const senseItem = document.querySelector(`.sense-item[data-sense-index="${senseIndex}"]`); - const examplesContainer = senseItem.querySelector('.examples-container'); - - // Remove "no examples" message if present - const noExamples = examplesContainer.querySelector('.no-examples'); - if (noExamples) { - noExamples.remove(); - } - - addExample(senseIndex); - } - - // Remove example button - if (e.target.closest('.remove-example-btn')) { - const btn = e.target.closest('.remove-example-btn'); - const exampleItem = btn.closest('.example-item'); - const senseIndex = btn.dataset.senseIndex; - const senseItem = document.querySelector(`.sense-item[data-sense-index="${senseIndex}"]`); - const examplesContainer = senseItem.querySelector('.examples-container'); - - if (confirm('Are you sure you want to remove this example?')) { - exampleItem.remove(); - - // Reindex examples - const examples = examplesContainer.querySelectorAll('.example-item'); - if (examples.length === 0) { - // Show "no examples" message - const noExamples = document.createElement('div'); - noExamples.className = 'no-examples text-center text-muted py-3 border rounded'; - noExamples.innerHTML = ` -No examples added yet
- - `; - examplesContainer.appendChild(noExamples); - } else { - reindexExamples(senseIndex); - } - } - } - }); - - // Audio preview modal - const audioPreviewModal = new bootstrap.Modal(document.getElementById('audioPreviewModal')); - - // Save audio button - document.getElementById('save-audio-btn').addEventListener('click', function() { - const audioPlayer = document.getElementById('audio-preview-player'); - const audioSrc = audioPlayer.src; - const currentPronunciationIndex = audioPlayer.dataset.pronunciationIndex; - - // Save the audio file path to the input - const audioFileInput = document.querySelector(`input[name="pronunciations[${currentPronunciationIndex}].audio_file"]`); - audioFileInput.value = audioSrc.split('/').pop(); - - // Close the modal - audioPreviewModal.hide(); - }); -}); - -/** - * Validate the form before submission - * - * @param {boolean} showValidationModal - Whether to show the validation modal - * @returns {boolean} Whether the form is valid - */ -function validateForm(showValidationModal = false) { - const errors = []; - - // Basic validation - const lexicalUnit = document.getElementById('lexical-unit').value.trim(); - if (!lexicalUnit) { - errors.push('Lexical Unit is required'); - } - - const partOfSpeech = document.getElementById('part-of-speech').value; - if (!partOfSpeech) { - errors.push('Part of Speech is required'); - } - - // Sense validation - const senses = document.querySelectorAll('.sense-item'); - if (senses.length === 0) { - errors.push('At least one sense is required'); - } else { - senses.forEach((sense, index) => { - const definition = sense.querySelector(`textarea[name="senses[${index}].definition"]`).value.trim(); - if (!definition) { - errors.push(`Sense ${index + 1}: Definition is required`); - } - - // Validate examples if present - const examples = sense.querySelectorAll('.example-item'); - examples.forEach((example, exIndex) => { - const exampleText = example.querySelector(`textarea[name="senses[${index}].examples[${exIndex}].text"]`).value.trim(); - if (!exampleText) { - errors.push(`Sense ${index + 1}, Example ${exIndex + 1}: Example text is required`); - } - }); - }); - } - - // Show validation errors - if (errors.length > 0) { - if (showValidationModal) { - const errorsList = document.getElementById('validation-errors-list'); - errorsList.innerHTML = ''; - - errors.forEach(error => { - const li = document.createElement('li'); - li.className = 'text-danger'; - li.textContent = error; - errorsList.appendChild(li); - }); - - const validationModal = new bootstrap.Modal(document.getElementById('validationModal')); - validationModal.show(); - } - - return false; - } - - return true; -} - -/** - * Submit the form via AJAX - */ -function submitForm() { - const form = document.getElementById('entry-form'); - const formData = new FormData(form); - - // Convert form data to JSON - const jsonData = {}; - - for (const [key, value] of formData.entries()) { - // Handle arrays and nested objects - if (key.includes('[') && key.includes(']')) { - const parts = key.split('['); - const mainKey = parts[0]; - const subKey = parts[1].replace(']', ''); - - if (parts.length === 2) { - // Simple array or object property - if (subKey === '') { - // Array - if (!jsonData[mainKey]) { - jsonData[mainKey] = []; - } - jsonData[mainKey].push(value); - } else { - // Object property - if (!jsonData[mainKey]) { - jsonData[mainKey] = {}; - } - jsonData[mainKey][subKey] = value; - } - } else if (parts.length === 3) { - // Nested array in object - const nestedKey = parts[2].replace(']', ''); - - if (!jsonData[mainKey]) { - jsonData[mainKey] = {}; - } - - if (!jsonData[mainKey][subKey]) { - jsonData[mainKey][subKey] = []; - } - - if (nestedKey === '') { - jsonData[mainKey][subKey].push(value); - } else { - if (!jsonData[mainKey][subKey][nestedKey]) { - jsonData[mainKey][subKey][nestedKey] = value; - } - } - } else if (parts.length === 4) { - // Deeply nested object - const arrayIndex = parseInt(parts[1].replace(']', '')); - const objectKey = parts[2].replace(']', ''); - const nestedKey = parts[3].replace(']', ''); - - if (!jsonData[mainKey]) { - jsonData[mainKey] = []; - } - - if (!jsonData[mainKey][arrayIndex]) { - jsonData[mainKey][arrayIndex] = {}; - } - - if (!jsonData[mainKey][arrayIndex][objectKey]) { - jsonData[mainKey][arrayIndex][objectKey] = {}; - } - - jsonData[mainKey][arrayIndex][objectKey][nestedKey] = value; - } - } else { - // Simple key-value - jsonData[key] = value; - } - } - - // Show loading state - const saveBtn = document.getElementById('save-btn'); - const originalText = saveBtn.innerHTML; - saveBtn.innerHTML = ' Saving...'; - saveBtn.disabled = true; - - // Send request - fetch(form.action, { - method: form.method || 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(jsonData) - }) - .then(response => { - if (!response.ok) { - throw new Error('Error saving entry'); - } - return response.json(); - }) - .then(data => { - // Redirect to the entry view page - window.location.href = `/entries/${data.id}`; - }) - .catch(error => { - console.error('Error:', error); - - // Reset button - saveBtn.innerHTML = originalText; - saveBtn.disabled = false; - - // Show error message - alert('Error saving entry. Please try again.'); - }); -} - -/** - * Add a new pronunciation field - */ -function addPronunciation() { - const container = document.getElementById('pronunciation-container'); - const pronunciationItems = container.querySelectorAll('.pronunciation-item'); - const newIndex = pronunciationItems.length; - - // Get the template and replace the index placeholder - let template = document.getElementById('pronunciation-template').innerHTML; - template = template.replace(/INDEX/g, newIndex); - - // Create a temporary element to hold the template - const temp = document.createElement('div'); - temp.innerHTML = template; - - // Append the new pronunciation item - container.appendChild(temp.firstElementChild); -} - -/** - * Add a new sense - */ -function addSense() { - const container = document.getElementById('senses-container'); - const senseItems = container.querySelectorAll('.sense-item'); - const newIndex = senseItems.length; - const newNumber = newIndex + 1; - - // Get the template and replace the index placeholder - let template = document.getElementById('sense-template').innerHTML; - template = template.replace(/INDEX/g, newIndex); - template = template.replace(/NUMBER/g, newNumber); - - // Create a temporary element to hold the template - const temp = document.createElement('div'); - temp.innerHTML = template; - - // Append the new sense item - container.appendChild(temp.firstElementChild); - - // Initialize Select2 for the new sense - $(`.sense-item[data-sense-index="${newIndex}"] .select2-tags`).select2({ - theme: 'bootstrap-5', - tags: true, - tokenSeparators: [',', ' '], - placeholder: 'Enter or select values...' - }); -} - -/** - * Add a new example to a sense - * - * @param {number} senseIndex - Index of the sense to add the example to - */ -function addExample(senseIndex) { - const senseItem = document.querySelector(`.sense-item[data-sense-index="${senseIndex}"]`); - const examplesContainer = senseItem.querySelector('.examples-container'); - const exampleItems = examplesContainer.querySelectorAll('.example-item'); - const newIndex = exampleItems.length; - const newNumber = newIndex + 1; - - // Get the template and replace the placeholders - let template = document.getElementById('example-template').innerHTML; - template = template.replace(/SENSE_INDEX/g, senseIndex); - template = template.replace(/EXAMPLE_INDEX/g, newIndex); - template = template.replace(/NUMBER/g, newNumber); - - // Create a temporary element to hold the template - const temp = document.createElement('div'); - temp.innerHTML = template; - - // Append the new example item - examplesContainer.appendChild(temp.firstElementChild); -} - -/** - * Reindex senses after removal - */ -function reindexSenses() { - const senseItems = document.querySelectorAll('.sense-item'); - - senseItems.forEach((sense, index) => { - // Update sense number in header - sense.querySelector('h6').textContent = `Sense ${index + 1}`; - - // Update sense index attribute - sense.dataset.senseIndex = index; - - // Update remove button data attribute - const removeBtn = sense.querySelector('.remove-sense-btn'); - if (removeBtn) { - removeBtn.dataset.index = index; - } - - // Update add example button data attribute - const addExampleBtn = sense.querySelector('.add-example-btn'); - addExampleBtn.dataset.senseIndex = index; - - // Update field names - sense.querySelectorAll('[name^="senses["]').forEach(field => { - const name = field.getAttribute('name'); - const newName = name.replace(/senses\[\d+\]/, `senses[${index}]`); - field.setAttribute('name', newName); - }); - - // Update example buttons - sense.querySelectorAll('.remove-example-btn').forEach(btn => { - btn.dataset.senseIndex = index; - }); - - // Reindex examples - reindexExamples(index); - }); -} - -/** - * Reindex examples within a sense after removal - * - * @param {number} senseIndex - Index of the sense containing the examples - */ -function reindexExamples(senseIndex) { - const senseItem = document.querySelector(`.sense-item[data-sense-index="${senseIndex}"]`); - const exampleItems = senseItem.querySelectorAll('.example-item'); - - exampleItems.forEach((example, index) => { - // Update example number - example.querySelector('small').textContent = `Example ${index + 1}`; - - // Update remove button data attributes - const removeBtn = example.querySelector('.remove-example-btn'); - removeBtn.dataset.senseIndex = senseIndex; - removeBtn.dataset.exampleIndex = index; - - // Update field names - example.querySelectorAll('[name^="senses["]').forEach(field => { - const name = field.getAttribute('name'); - const newName = name.replace(/examples\[\d+\]/, `examples[${index}]`); - field.setAttribute('name', newName); - }); - }); -} - -/** - * Generate audio for a pronunciation - * - * @param {string} word - The word to generate audio for - * @param {string} ipa - The IPA pronunciation - * @param {number} index - The index of the pronunciation item - */ -function generateAudio(word, ipa, index) { - // Show loading state - const btn = document.querySelector(`.generate-audio-btn[data-index="${index}"]`); - const originalText = btn.innerHTML; - btn.innerHTML = ' Generating...'; - btn.disabled = true; - - // Make API request to generate audio - fetch('/api/pronunciations/generate', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - word: word, - ipa: ipa - }) - }) - .then(response => { - if (!response.ok) { - throw new Error('Error generating audio'); - } - return response.json(); - }) - .then(data => { - // Reset button - btn.innerHTML = originalText; - btn.disabled = false; - - // Show audio preview - const audioPlayer = document.getElementById('audio-preview-player'); - audioPlayer.src = data.audio_url; - audioPlayer.dataset.pronunciationIndex = index; - - const audioPreviewModal = new bootstrap.Modal(document.getElementById('audioPreviewModal')); - audioPreviewModal.show(); - }) - .catch(error => { - console.error('Error:', error); - - // Reset button - btn.innerHTML = originalText; - btn.disabled = false; - - // Show error message - alert('Error generating audio. Please try again.'); - }); -} diff --git a/.history/app/static/js/entry-view_20250623214730.js b/.history/app/static/js/entry-view_20250623214730.js deleted file mode 100644 index 80eb9d74..00000000 --- a/.history/app/static/js/entry-view_20250623214730.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Dictionary Writing System - Entry View JavaScript - * - * This file contains the functionality for the entry view page. - */ - -document.addEventListener('DOMContentLoaded', function() { - // Initialize audio player modal - const audioPlayerModal = new bootstrap.Modal(document.getElementById('audioPlayerModal')); - const audioPlayer = document.getElementById('audio-player'); - - // Handle play audio buttons - document.querySelectorAll('.play-audio-btn').forEach(btn => { - btn.addEventListener('click', function() { - const audioFile = this.dataset.audioFile; - if (audioFile) { - // Set the audio source - audioPlayer.src = `/audio/${audioFile}`; - - // Show the modal - audioPlayerModal.show(); - - // Play the audio - audioPlayer.play(); - } - }); - }); - - // When modal is hidden, pause the audio - document.getElementById('audioPlayerModal').addEventListener('hidden.bs.modal', function() { - audioPlayer.pause(); - }); -}); diff --git a/.history/app/static/js/entry-view_20250623221914.js b/.history/app/static/js/entry-view_20250623221914.js deleted file mode 100644 index 80eb9d74..00000000 --- a/.history/app/static/js/entry-view_20250623221914.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Dictionary Writing System - Entry View JavaScript - * - * This file contains the functionality for the entry view page. - */ - -document.addEventListener('DOMContentLoaded', function() { - // Initialize audio player modal - const audioPlayerModal = new bootstrap.Modal(document.getElementById('audioPlayerModal')); - const audioPlayer = document.getElementById('audio-player'); - - // Handle play audio buttons - document.querySelectorAll('.play-audio-btn').forEach(btn => { - btn.addEventListener('click', function() { - const audioFile = this.dataset.audioFile; - if (audioFile) { - // Set the audio source - audioPlayer.src = `/audio/${audioFile}`; - - // Show the modal - audioPlayerModal.show(); - - // Play the audio - audioPlayer.play(); - } - }); - }); - - // When modal is hidden, pause the audio - document.getElementById('audioPlayerModal').addEventListener('hidden.bs.modal', function() { - audioPlayer.pause(); - }); -}); diff --git a/.history/app/static/js/search_20250623214617.js b/.history/app/static/js/search_20250623214617.js deleted file mode 100644 index e060f200..00000000 --- a/.history/app/static/js/search_20250623214617.js +++ /dev/null @@ -1,444 +0,0 @@ -/** - * Dictionary Writing System - Search JavaScript - * - * This file contains the functionality for the search page. - */ - -document.addEventListener('DOMContentLoaded', function() { - // Set up search form submission - const searchForm = document.getElementById('search-form'); - searchForm.addEventListener('submit', function(e) { - e.preventDefault(); - performSearch(1); - }); - - // View mode buttons - document.getElementById('btn-view-all').addEventListener('click', function() { - setResultsViewMode('all'); - }); - - document.getElementById('btn-view-entries').addEventListener('click', function() { - setResultsViewMode('entries'); - }); - - document.getElementById('btn-view-senses').addEventListener('click', function() { - setResultsViewMode('senses'); - }); - - document.getElementById('btn-view-examples').addEventListener('click', function() { - setResultsViewMode('examples'); - }); - - // Load recent searches - loadRecentSearches(); - - // Handle recent search clicks - document.getElementById('recent-searches').addEventListener('click', function(e) { - const removeBtn = e.target.closest('.recent-search-remove'); - if (removeBtn) { - e.preventDefault(); - const searchItem = removeBtn.closest('li'); - const searchQuery = searchItem.querySelector('.recent-search-link').textContent; - removeRecentSearch(searchQuery); - searchItem.remove(); - return; - } - - const searchLink = e.target.closest('.recent-search-link'); - if (searchLink) { - e.preventDefault(); - const searchQuery = searchLink.textContent; - document.getElementById('search-query').value = searchQuery; - performSearch(1); - } - }); - - // Check for URL parameters to perform a search - const urlParams = new URLSearchParams(window.location.search); - const queryParam = urlParams.get('q'); - if (queryParam) { - document.getElementById('search-query').value = queryParam; - performSearch(1); - } -}); - -/** - * Perform a search using the form values - * - * @param {number} page - Page number for pagination - */ -function performSearch(page = 1) { - // Show loading state - document.getElementById('search-initial').style.display = 'none'; - document.getElementById('search-no-results').style.display = 'none'; - document.getElementById('search-results').style.display = 'none'; - document.getElementById('search-loading').style.display = 'block'; - document.getElementById('results-pagination').style.display = 'none'; - - // Get form values - const query = document.getElementById('search-query').value.trim(); - if (!query) { - document.getElementById('search-loading').style.display = 'none'; - document.getElementById('search-initial').style.display = 'block'; - return; - } - - // Get selected fields - const fieldsCheckboxes = document.querySelectorAll('input[name="fields[]"]:checked'); - const fields = Array.from(fieldsCheckboxes).map(cb => cb.value).join(','); - - // Get part of speech filter - const posFilter = document.getElementById('pos-filter').value; - - // Get search options - const exactMatch = document.getElementById('check-exact-match').checked ? 1 : 0; - const caseSensitive = document.getElementById('check-case-sensitive').checked ? 1 : 0; - - // Calculate offset for pagination - const limit = 20; - const offset = (page - 1) * limit; - - // Build API URL - let url = `/api/search/?q=${encodeURIComponent(query)}&limit=${limit}&offset=${offset}`; - - if (fields) { - url += `&fields=${fields}`; - } - - if (posFilter) { - url += `&pos=${posFilter}`; - } - - if (exactMatch) { - url += `&exact_match=${exactMatch}`; - } - - if (caseSensitive) { - url += `&case_sensitive=${caseSensitive}`; - } - - // Fetch search results from API - fetch(url) - .then(response => { - if (!response.ok) { - throw new Error('Error performing search'); - } - return response.json(); - }) - .then(data => { - document.getElementById('search-loading').style.display = 'none'; - - if (data.results.length === 0) { - document.getElementById('search-no-results').style.display = 'block'; - document.getElementById('results-pagination').style.display = 'none'; - return; - } - - // Display results - displaySearchResults(data.results); - document.getElementById('search-results').style.display = 'block'; - - // Update pagination - updatePagination(data.total_count, limit, page); - document.getElementById('results-count').textContent = `${data.total_count} results found`; - document.getElementById('results-pagination').style.display = 'block'; - - // Update search results header - document.getElementById('search-results-header').textContent = - `Search Results for "${query}" (${data.total_count} results)`; - - // Add to recent searches - addRecentSearch(query); - }) - .catch(error => { - console.error('Error:', error); - document.getElementById('search-loading').style.display = 'none'; - document.getElementById('search-no-results').style.display = 'block'; - document.getElementById('search-no-results').querySelector('p.lead').textContent = 'Error performing search'; - }); -} - -/** - * Display search results - * - * @param {Array} results - Array of search result objects - */ -function displaySearchResults(results) { - const resultsContainer = document.getElementById('search-results'); - resultsContainer.innerHTML = ''; - - const entryTemplate = document.getElementById('entry-result-template'); - const senseTemplate = document.getElementById('sense-result-template'); - const exampleTemplate = document.getElementById('example-result-template'); - - results.forEach(result => { - // Clone the entry template - const clone = document.importNode(entryTemplate.content, true); - - // Set entry data - const entryLink = clone.querySelector('.result-entry-link'); - entryLink.textContent = result.lexical_unit; - entryLink.href = `/entries/${result.id}`; - - if (result.grammatical_info && result.grammatical_info.part_of_speech) { - clone.querySelector('.result-pos').textContent = result.grammatical_info.part_of_speech; - } else { - clone.querySelector('.result-pos').remove(); - } - - clone.querySelector('.result-entry-id').textContent = `ID: ${result.id}`; - - if (result.citation_form) { - clone.querySelector('.result-entry-citation').textContent = result.citation_form; - } else { - clone.querySelector('.result-entry-citation').remove(); - } - - // Set edit and view links - clone.querySelector('.result-edit-link').href = `/entries/${result.id}/edit`; - clone.querySelector('.result-view-link').href = `/entries/${result.id}`; - - // Add senses - const sensesContainer = clone.querySelector('.result-senses'); - - if (result.senses && result.senses.length > 0) { - result.senses.forEach((sense, index) => { - const senseClone = document.importNode(senseTemplate.content, true); - - senseClone.querySelector('.sense-number').textContent = index + 1; - senseClone.querySelector('.sense-definition').textContent = sense.definition; - - const examplesContainer = senseClone.querySelector('.sense-examples'); - - if (sense.examples && sense.examples.length > 0) { - sense.examples.forEach(example => { - const exampleClone = document.importNode(exampleTemplate.content, true); - exampleClone.querySelector('.example-text').textContent = example.text; - examplesContainer.appendChild(exampleClone); - }); - } else { - examplesContainer.remove(); - } - - sensesContainer.appendChild(senseClone); - }); - } else { - const noSenses = document.createElement('div'); - noSenses.className = 'text-muted small'; - noSenses.textContent = 'No senses available'; - sensesContainer.appendChild(noSenses); - } - - // Add to results container - resultsContainer.appendChild(clone); - }); - - // Initialize current view mode - setResultsViewMode('all'); -} - -/** - * Set the view mode for search results - * - * @param {string} mode - View mode ('all', 'entries', 'senses', 'examples') - */ -function setResultsViewMode(mode) { - // Update active button - document.querySelectorAll('#btn-view-all, #btn-view-entries, #btn-view-senses, #btn-view-examples') - .forEach(btn => btn.classList.remove('active')); - - document.getElementById(`btn-view-${mode}`).classList.add('active'); - - // Get all result elements - const results = document.querySelectorAll('.search-result'); - - results.forEach(result => { - // Show the result by default - result.style.display = 'block'; - - // Show or hide senses based on mode - const senses = result.querySelectorAll('.sense-item'); - senses.forEach(sense => { - sense.style.display = (mode === 'all' || mode === 'senses') ? 'block' : 'none'; - }); - - // Show or hide examples based on mode - const examples = result.querySelectorAll('.example-item'); - examples.forEach(example => { - example.style.display = (mode === 'all' || mode === 'examples') ? 'block' : 'none'; - }); - - // Handle 'entries' mode specially - hide senses and examples - if (mode === 'entries') { - const sensesContainer = result.querySelector('.result-senses'); - sensesContainer.style.display = 'none'; - } else { - const sensesContainer = result.querySelector('.result-senses'); - sensesContainer.style.display = 'block'; - } - }); -} - -/** - * Update pagination controls - * - * @param {number} totalCount - Total number of search results - * @param {number} limit - Results per page - * @param {number} currentPage - Current page number - */ -function updatePagination(totalCount, limit, currentPage) { - const pagination = document.getElementById('search-pagination'); - pagination.innerHTML = ''; - - const totalPages = Math.ceil(totalCount / limit); - if (totalPages <= 1) { - return; - } - - // Previous button - const prevLi = document.createElement('li'); - prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`; - - const prevLink = document.createElement('a'); - prevLink.className = 'page-link'; - prevLink.href = '#'; - prevLink.setAttribute('aria-label', 'Previous'); - prevLink.innerHTML = ''; - - if (currentPage > 1) { - prevLink.addEventListener('click', (e) => { - e.preventDefault(); - performSearch(currentPage - 1); - }); - } - - prevLi.appendChild(prevLink); - pagination.appendChild(prevLi); - - // Page numbers - let startPage = Math.max(1, currentPage - 2); - let endPage = Math.min(totalPages, startPage + 4); - - if (endPage - startPage < 4) { - startPage = Math.max(1, endPage - 4); - } - - for (let i = startPage; i <= endPage; i++) { - const pageLi = document.createElement('li'); - pageLi.className = `page-item ${i === currentPage ? 'active' : ''}`; - - const pageLink = document.createElement('a'); - pageLink.className = 'page-link'; - pageLink.href = '#'; - pageLink.textContent = i; - - if (i !== currentPage) { - pageLink.addEventListener('click', (e) => { - e.preventDefault(); - performSearch(i); - }); - } - - pageLi.appendChild(pageLink); - pagination.appendChild(pageLi); - } - - // Next button - const nextLi = document.createElement('li'); - nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`; - - const nextLink = document.createElement('a'); - nextLink.className = 'page-link'; - nextLink.href = '#'; - nextLink.setAttribute('aria-label', 'Next'); - nextLink.innerHTML = ''; - - if (currentPage < totalPages) { - nextLink.addEventListener('click', (e) => { - e.preventDefault(); - performSearch(currentPage + 1); - }); - } - - nextLi.appendChild(nextLink); - pagination.appendChild(nextLi); -} - -/** - * Save a recent search to localStorage - * - * @param {string} query - Search query - */ -function addRecentSearch(query) { - // Get existing recent searches - let recentSearches = JSON.parse(localStorage.getItem('recentSearches')) || []; - - // Don't add duplicates - if (recentSearches.includes(query)) { - // Move to the top of the list - recentSearches = recentSearches.filter(item => item !== query); - } - - // Add to the beginning of the array - recentSearches.unshift(query); - - // Limit to 10 recent searches - recentSearches = recentSearches.slice(0, 10); - - // Save back to localStorage - localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); - - // Update the UI - loadRecentSearches(); -} - -/** - * Remove a recent search from localStorage - * - * @param {string} query - Search query to remove - */ -function removeRecentSearch(query) { - // Get existing recent searches - let recentSearches = JSON.parse(localStorage.getItem('recentSearches')) || []; - - // Remove the query - recentSearches = recentSearches.filter(item => item !== query); - - // Save back to localStorage - localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); -} - -/** - * Load recent searches from localStorage and display them - */ -function loadRecentSearches() { - const recentSearches = JSON.parse(localStorage.getItem('recentSearches')) || []; - const container = document.getElementById('recent-searches'); - - // Clear container - container.innerHTML = ''; - - if (recentSearches.length === 0) { - const emptyItem = document.createElement('li'); - emptyItem.className = 'list-group-item text-center text-muted'; - emptyItem.textContent = 'No recent searches'; - container.appendChild(emptyItem); - return; - } - - // Create a list item for each recent search - const template = document.getElementById('recent-search-template'); - - recentSearches.forEach(query => { - const clone = document.importNode(template.content, true); - - const link = clone.querySelector('.recent-search-link'); - link.textContent = query; - link.title = `Search for "${query}"`; - - const removeBtn = clone.querySelector('.recent-search-remove'); - removeBtn.title = `Remove "${query}" from recent searches`; - - container.appendChild(clone); - }); -} diff --git a/.history/app/static/js/search_20250623221914.js b/.history/app/static/js/search_20250623221914.js deleted file mode 100644 index e060f200..00000000 --- a/.history/app/static/js/search_20250623221914.js +++ /dev/null @@ -1,444 +0,0 @@ -/** - * Dictionary Writing System - Search JavaScript - * - * This file contains the functionality for the search page. - */ - -document.addEventListener('DOMContentLoaded', function() { - // Set up search form submission - const searchForm = document.getElementById('search-form'); - searchForm.addEventListener('submit', function(e) { - e.preventDefault(); - performSearch(1); - }); - - // View mode buttons - document.getElementById('btn-view-all').addEventListener('click', function() { - setResultsViewMode('all'); - }); - - document.getElementById('btn-view-entries').addEventListener('click', function() { - setResultsViewMode('entries'); - }); - - document.getElementById('btn-view-senses').addEventListener('click', function() { - setResultsViewMode('senses'); - }); - - document.getElementById('btn-view-examples').addEventListener('click', function() { - setResultsViewMode('examples'); - }); - - // Load recent searches - loadRecentSearches(); - - // Handle recent search clicks - document.getElementById('recent-searches').addEventListener('click', function(e) { - const removeBtn = e.target.closest('.recent-search-remove'); - if (removeBtn) { - e.preventDefault(); - const searchItem = removeBtn.closest('li'); - const searchQuery = searchItem.querySelector('.recent-search-link').textContent; - removeRecentSearch(searchQuery); - searchItem.remove(); - return; - } - - const searchLink = e.target.closest('.recent-search-link'); - if (searchLink) { - e.preventDefault(); - const searchQuery = searchLink.textContent; - document.getElementById('search-query').value = searchQuery; - performSearch(1); - } - }); - - // Check for URL parameters to perform a search - const urlParams = new URLSearchParams(window.location.search); - const queryParam = urlParams.get('q'); - if (queryParam) { - document.getElementById('search-query').value = queryParam; - performSearch(1); - } -}); - -/** - * Perform a search using the form values - * - * @param {number} page - Page number for pagination - */ -function performSearch(page = 1) { - // Show loading state - document.getElementById('search-initial').style.display = 'none'; - document.getElementById('search-no-results').style.display = 'none'; - document.getElementById('search-results').style.display = 'none'; - document.getElementById('search-loading').style.display = 'block'; - document.getElementById('results-pagination').style.display = 'none'; - - // Get form values - const query = document.getElementById('search-query').value.trim(); - if (!query) { - document.getElementById('search-loading').style.display = 'none'; - document.getElementById('search-initial').style.display = 'block'; - return; - } - - // Get selected fields - const fieldsCheckboxes = document.querySelectorAll('input[name="fields[]"]:checked'); - const fields = Array.from(fieldsCheckboxes).map(cb => cb.value).join(','); - - // Get part of speech filter - const posFilter = document.getElementById('pos-filter').value; - - // Get search options - const exactMatch = document.getElementById('check-exact-match').checked ? 1 : 0; - const caseSensitive = document.getElementById('check-case-sensitive').checked ? 1 : 0; - - // Calculate offset for pagination - const limit = 20; - const offset = (page - 1) * limit; - - // Build API URL - let url = `/api/search/?q=${encodeURIComponent(query)}&limit=${limit}&offset=${offset}`; - - if (fields) { - url += `&fields=${fields}`; - } - - if (posFilter) { - url += `&pos=${posFilter}`; - } - - if (exactMatch) { - url += `&exact_match=${exactMatch}`; - } - - if (caseSensitive) { - url += `&case_sensitive=${caseSensitive}`; - } - - // Fetch search results from API - fetch(url) - .then(response => { - if (!response.ok) { - throw new Error('Error performing search'); - } - return response.json(); - }) - .then(data => { - document.getElementById('search-loading').style.display = 'none'; - - if (data.results.length === 0) { - document.getElementById('search-no-results').style.display = 'block'; - document.getElementById('results-pagination').style.display = 'none'; - return; - } - - // Display results - displaySearchResults(data.results); - document.getElementById('search-results').style.display = 'block'; - - // Update pagination - updatePagination(data.total_count, limit, page); - document.getElementById('results-count').textContent = `${data.total_count} results found`; - document.getElementById('results-pagination').style.display = 'block'; - - // Update search results header - document.getElementById('search-results-header').textContent = - `Search Results for "${query}" (${data.total_count} results)`; - - // Add to recent searches - addRecentSearch(query); - }) - .catch(error => { - console.error('Error:', error); - document.getElementById('search-loading').style.display = 'none'; - document.getElementById('search-no-results').style.display = 'block'; - document.getElementById('search-no-results').querySelector('p.lead').textContent = 'Error performing search'; - }); -} - -/** - * Display search results - * - * @param {Array} results - Array of search result objects - */ -function displaySearchResults(results) { - const resultsContainer = document.getElementById('search-results'); - resultsContainer.innerHTML = ''; - - const entryTemplate = document.getElementById('entry-result-template'); - const senseTemplate = document.getElementById('sense-result-template'); - const exampleTemplate = document.getElementById('example-result-template'); - - results.forEach(result => { - // Clone the entry template - const clone = document.importNode(entryTemplate.content, true); - - // Set entry data - const entryLink = clone.querySelector('.result-entry-link'); - entryLink.textContent = result.lexical_unit; - entryLink.href = `/entries/${result.id}`; - - if (result.grammatical_info && result.grammatical_info.part_of_speech) { - clone.querySelector('.result-pos').textContent = result.grammatical_info.part_of_speech; - } else { - clone.querySelector('.result-pos').remove(); - } - - clone.querySelector('.result-entry-id').textContent = `ID: ${result.id}`; - - if (result.citation_form) { - clone.querySelector('.result-entry-citation').textContent = result.citation_form; - } else { - clone.querySelector('.result-entry-citation').remove(); - } - - // Set edit and view links - clone.querySelector('.result-edit-link').href = `/entries/${result.id}/edit`; - clone.querySelector('.result-view-link').href = `/entries/${result.id}`; - - // Add senses - const sensesContainer = clone.querySelector('.result-senses'); - - if (result.senses && result.senses.length > 0) { - result.senses.forEach((sense, index) => { - const senseClone = document.importNode(senseTemplate.content, true); - - senseClone.querySelector('.sense-number').textContent = index + 1; - senseClone.querySelector('.sense-definition').textContent = sense.definition; - - const examplesContainer = senseClone.querySelector('.sense-examples'); - - if (sense.examples && sense.examples.length > 0) { - sense.examples.forEach(example => { - const exampleClone = document.importNode(exampleTemplate.content, true); - exampleClone.querySelector('.example-text').textContent = example.text; - examplesContainer.appendChild(exampleClone); - }); - } else { - examplesContainer.remove(); - } - - sensesContainer.appendChild(senseClone); - }); - } else { - const noSenses = document.createElement('div'); - noSenses.className = 'text-muted small'; - noSenses.textContent = 'No senses available'; - sensesContainer.appendChild(noSenses); - } - - // Add to results container - resultsContainer.appendChild(clone); - }); - - // Initialize current view mode - setResultsViewMode('all'); -} - -/** - * Set the view mode for search results - * - * @param {string} mode - View mode ('all', 'entries', 'senses', 'examples') - */ -function setResultsViewMode(mode) { - // Update active button - document.querySelectorAll('#btn-view-all, #btn-view-entries, #btn-view-senses, #btn-view-examples') - .forEach(btn => btn.classList.remove('active')); - - document.getElementById(`btn-view-${mode}`).classList.add('active'); - - // Get all result elements - const results = document.querySelectorAll('.search-result'); - - results.forEach(result => { - // Show the result by default - result.style.display = 'block'; - - // Show or hide senses based on mode - const senses = result.querySelectorAll('.sense-item'); - senses.forEach(sense => { - sense.style.display = (mode === 'all' || mode === 'senses') ? 'block' : 'none'; - }); - - // Show or hide examples based on mode - const examples = result.querySelectorAll('.example-item'); - examples.forEach(example => { - example.style.display = (mode === 'all' || mode === 'examples') ? 'block' : 'none'; - }); - - // Handle 'entries' mode specially - hide senses and examples - if (mode === 'entries') { - const sensesContainer = result.querySelector('.result-senses'); - sensesContainer.style.display = 'none'; - } else { - const sensesContainer = result.querySelector('.result-senses'); - sensesContainer.style.display = 'block'; - } - }); -} - -/** - * Update pagination controls - * - * @param {number} totalCount - Total number of search results - * @param {number} limit - Results per page - * @param {number} currentPage - Current page number - */ -function updatePagination(totalCount, limit, currentPage) { - const pagination = document.getElementById('search-pagination'); - pagination.innerHTML = ''; - - const totalPages = Math.ceil(totalCount / limit); - if (totalPages <= 1) { - return; - } - - // Previous button - const prevLi = document.createElement('li'); - prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`; - - const prevLink = document.createElement('a'); - prevLink.className = 'page-link'; - prevLink.href = '#'; - prevLink.setAttribute('aria-label', 'Previous'); - prevLink.innerHTML = ''; - - if (currentPage > 1) { - prevLink.addEventListener('click', (e) => { - e.preventDefault(); - performSearch(currentPage - 1); - }); - } - - prevLi.appendChild(prevLink); - pagination.appendChild(prevLi); - - // Page numbers - let startPage = Math.max(1, currentPage - 2); - let endPage = Math.min(totalPages, startPage + 4); - - if (endPage - startPage < 4) { - startPage = Math.max(1, endPage - 4); - } - - for (let i = startPage; i <= endPage; i++) { - const pageLi = document.createElement('li'); - pageLi.className = `page-item ${i === currentPage ? 'active' : ''}`; - - const pageLink = document.createElement('a'); - pageLink.className = 'page-link'; - pageLink.href = '#'; - pageLink.textContent = i; - - if (i !== currentPage) { - pageLink.addEventListener('click', (e) => { - e.preventDefault(); - performSearch(i); - }); - } - - pageLi.appendChild(pageLink); - pagination.appendChild(pageLi); - } - - // Next button - const nextLi = document.createElement('li'); - nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`; - - const nextLink = document.createElement('a'); - nextLink.className = 'page-link'; - nextLink.href = '#'; - nextLink.setAttribute('aria-label', 'Next'); - nextLink.innerHTML = ''; - - if (currentPage < totalPages) { - nextLink.addEventListener('click', (e) => { - e.preventDefault(); - performSearch(currentPage + 1); - }); - } - - nextLi.appendChild(nextLink); - pagination.appendChild(nextLi); -} - -/** - * Save a recent search to localStorage - * - * @param {string} query - Search query - */ -function addRecentSearch(query) { - // Get existing recent searches - let recentSearches = JSON.parse(localStorage.getItem('recentSearches')) || []; - - // Don't add duplicates - if (recentSearches.includes(query)) { - // Move to the top of the list - recentSearches = recentSearches.filter(item => item !== query); - } - - // Add to the beginning of the array - recentSearches.unshift(query); - - // Limit to 10 recent searches - recentSearches = recentSearches.slice(0, 10); - - // Save back to localStorage - localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); - - // Update the UI - loadRecentSearches(); -} - -/** - * Remove a recent search from localStorage - * - * @param {string} query - Search query to remove - */ -function removeRecentSearch(query) { - // Get existing recent searches - let recentSearches = JSON.parse(localStorage.getItem('recentSearches')) || []; - - // Remove the query - recentSearches = recentSearches.filter(item => item !== query); - - // Save back to localStorage - localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); -} - -/** - * Load recent searches from localStorage and display them - */ -function loadRecentSearches() { - const recentSearches = JSON.parse(localStorage.getItem('recentSearches')) || []; - const container = document.getElementById('recent-searches'); - - // Clear container - container.innerHTML = ''; - - if (recentSearches.length === 0) { - const emptyItem = document.createElement('li'); - emptyItem.className = 'list-group-item text-center text-muted'; - emptyItem.textContent = 'No recent searches'; - container.appendChild(emptyItem); - return; - } - - // Create a list item for each recent search - const template = document.getElementById('recent-search-template'); - - recentSearches.forEach(query => { - const clone = document.importNode(template.content, true); - - const link = clone.querySelector('.recent-search-link'); - link.textContent = query; - link.title = `Search for "${query}"`; - - const removeBtn = clone.querySelector('.recent-search-remove'); - removeBtn.title = `Remove "${query}" from recent searches`; - - container.appendChild(clone); - }); -} diff --git a/.history/app/templates/base_20250623214041.html b/.history/app/templates/base_20250623214041.html deleted file mode 100644 index 04c36224..00000000 --- a/.history/app/templates/base_20250623214041.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - -No examples added yet
- -No examples added yet
- -No examples added yet
- -No examples added yet
- -{{ entry.citation_form }}
- {% endif %} -{{ entry.etymology }}
-{{ entry.note }}
-{{ entry.citation_form }}
- {% endif %} -{{ entry.etymology }}
-{{ entry.note }}
-Export the dictionary to the standard LIFT (Lexicon Interchange Format) XML format.
-This format is compatible with:
-Export the dictionary to Kindle format for use on Amazon Kindle devices.
-Export the dictionary to SQLite format for use in mobile apps.
-Export the dictionary to the standard LIFT (Lexicon Interchange Format) XML format.
-This format is compatible with:
-Export the dictionary to Kindle format for use on Amazon Kindle devices.
- -Export the dictionary to SQLite format for use in mobile apps.
- -Export the dictionary to the standard LIFT (Lexicon Interchange Format) XML format.
-This format is compatible with:
-Export the dictionary to Kindle format for use on Amazon Kindle devices.
- -Export the dictionary to SQLite format for use in mobile apps.
- -