Spaces:
Sleeping
Sleeping
// Admin JavaScript for the backend management interface | |
// Global object to hold Quill instances | |
let quillInstances = {}; | |
document.addEventListener('DOMContentLoaded', function() { | |
// Initialize theme | |
initTheme(); | |
// Setup dashboard functionality | |
setupDashboardCards(); | |
// Setup admin forms | |
setupMatiereForm(); | |
setupSousCategorieForm(); | |
setupTexteForm(); // Basic setup (e.g., category dropdowns if needed) | |
// Setup content block editor specifically for edit_texte.html | |
if (document.getElementById('blocks-container')) { | |
setupContentBlockEditor(); | |
} | |
// Setup image management (gallery interaction, maybe general upload elsewhere) | |
setupImageGallery(); // Handles selecting from the modal | |
// Note: The specific AJAX upload logic is now mainly in edit_texte.html's script block | |
// but we keep setupImageUploader for potential preview logic or other upload forms. | |
setupImageUploader(); // Sets up preview for the upload form | |
// Setup theme toggle | |
setupThemeToggle(); | |
}); | |
// Initialize theme based on user preference | |
function initTheme() { | |
const userPreference = localStorage.getItem('theme') || 'light'; | |
document.documentElement.setAttribute('data-theme', userPreference); | |
updateThemeIcon(userPreference); | |
} | |
// Setup theme toggle functionality | |
function setupThemeToggle() { | |
const themeToggle = document.getElementById('theme-toggle'); | |
if (!themeToggle) return; | |
themeToggle.addEventListener('click', function() { | |
const currentTheme = document.documentElement.getAttribute('data-theme'); | |
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; | |
document.documentElement.setAttribute('data-theme', newTheme); | |
localStorage.setItem('theme', newTheme); | |
updateThemeIcon(newTheme); | |
saveThemePreference(newTheme); // Optional: Inform server | |
}); | |
} | |
// Update the theme toggle icon based on current theme | |
function updateThemeIcon(theme) { | |
const themeToggle = document.getElementById('theme-toggle'); | |
if (!themeToggle) return; | |
const icon = themeToggle.querySelector('i'); | |
if (!icon) return; | |
if (theme === 'dark') { | |
icon.classList.remove('fa-moon'); | |
icon.classList.add('fa-sun'); | |
themeToggle.setAttribute('title', 'Activer le mode clair'); | |
} else { | |
icon.classList.remove('fa-sun'); | |
icon.classList.add('fa-moon'); | |
themeToggle.setAttribute('title', 'Activer le mode sombre'); | |
} | |
} | |
// Save theme preference to server (Optional) | |
function saveThemePreference(theme) { | |
// Example using fetch API - adjust URL and method as needed | |
/* | |
fetch('/set_theme_preference', { // Replace with your actual endpoint | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
// Add CSRF token header if needed | |
// 'X-CSRFToken': getCsrfToken() | |
}, | |
body: JSON.stringify({ theme: theme }) | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
if(data.success) console.log('Theme preference saved on server.'); | |
else console.error('Failed to save theme preference on server.'); | |
}) | |
.catch(error => { | |
console.error('Error saving theme preference:', error); | |
}); | |
*/ | |
} | |
// Setup dashboard cards with hover effects | |
function setupDashboardCards() { | |
const dashboardCards = document.querySelectorAll('.admin-card'); | |
dashboardCards.forEach(card => { | |
card.addEventListener('mouseenter', function() { | |
this.style.transform = 'translateY(-5px)'; | |
this.style.boxShadow = 'var(--hover-shadow)'; | |
// Keep transition consistent (defined in CSS is better) | |
}); | |
card.addEventListener('mouseleave', function() { | |
this.style.transform = 'translateY(0)'; | |
this.style.boxShadow = 'var(--shadow)'; | |
}); | |
}); | |
} | |
// Setup matiere form functionality (if on matiere page) | |
function setupMatiereForm() { | |
const editButtons = document.querySelectorAll('.edit-matiere-btn'); | |
editButtons.forEach(button => { | |
button.addEventListener('click', function() { | |
const matiereId = this.getAttribute('data-id'); | |
const matiereName = this.getAttribute('data-name'); | |
const matiereColor = this.getAttribute('data-color'); | |
const editForm = document.getElementById('edit-matiere-form'); | |
const addSection = document.getElementById('add-matiere-section'); | |
const editSection = document.getElementById('edit-matiere-section'); | |
if (editForm && addSection && editSection) { | |
editForm.querySelector('input[name="matiere_id"]').value = matiereId; | |
editForm.querySelector('input[name="nom"]').value = matiereName; | |
const colorInput = editForm.querySelector('input[name="color_code"]'); | |
colorInput.value = matiereColor; | |
// Trigger input event to update preview if color picker exists | |
if(colorInput) colorInput.dispatchEvent(new Event('input')); | |
addSection.classList.add('d-none'); | |
editSection.classList.remove('d-none'); | |
editSection.scrollIntoView({ behavior: 'smooth' }); | |
} | |
}); | |
}); | |
const cancelEditButton = document.getElementById('cancel-edit-matiere'); | |
if (cancelEditButton) { | |
cancelEditButton.addEventListener('click', function(e) { | |
e.preventDefault(); | |
document.getElementById('add-matiere-section')?.classList.remove('d-none'); | |
document.getElementById('edit-matiere-section')?.classList.add('d-none'); | |
}); | |
} | |
// Color picker preview logic | |
const colorPickers = document.querySelectorAll('input[type="color"]'); | |
colorPickers.forEach(picker => { | |
const previewId = picker.id + '-preview'; // Assuming a convention like id="color" and preview span id="color-preview" | |
let preview = document.getElementById(previewId); | |
if (!preview) { // Create preview dynamically if not present | |
preview = document.createElement('span'); | |
preview.className = 'color-preview'; | |
preview.id = previewId; | |
preview.style.display = 'inline-block'; | |
preview.style.width = '24px'; | |
preview.style.height = '24px'; | |
preview.style.borderRadius = '4px'; | |
preview.style.marginLeft = '10px'; | |
preview.style.verticalAlign = 'middle'; | |
preview.style.border = '1px solid var(--border-color)'; | |
picker.parentNode.insertBefore(preview, picker.nextSibling); | |
} | |
picker.addEventListener('input', function() { | |
preview.style.backgroundColor = this.value; | |
}); | |
// Initialize preview color | |
preview.style.backgroundColor = picker.value; | |
}); | |
} | |
// Setup sous categorie form functionality (if on sous_categorie page) | |
function setupSousCategorieForm() { | |
const editButtons = document.querySelectorAll('.edit-sous-categorie-btn'); | |
editButtons.forEach(button => { | |
button.addEventListener('click', function() { | |
const sousCategorieId = this.getAttribute('data-id'); | |
const sousCategorieName = this.getAttribute('data-name'); | |
const matiereId = this.getAttribute('data-matiere-id'); | |
const editForm = document.getElementById('edit-sous-categorie-form'); | |
const addSection = document.getElementById('add-sous-categorie-section'); | |
const editSection = document.getElementById('edit-sous-categorie-section'); | |
if (editForm && addSection && editSection) { | |
editForm.querySelector('input[name="sous_categorie_id"]').value = sousCategorieId; | |
editForm.querySelector('input[name="nom"]').value = sousCategorieName; | |
editForm.querySelector('select[name="matiere_id"]').value = matiereId; | |
addSection.classList.add('d-none'); | |
editSection.classList.remove('d-none'); | |
editSection.scrollIntoView({ behavior: 'smooth' }); | |
} | |
}); | |
}); | |
const cancelEditButton = document.getElementById('cancel-edit-sous-categorie'); | |
if (cancelEditButton) { | |
cancelEditButton.addEventListener('click', function(e) { | |
e.preventDefault(); | |
document.getElementById('add-sous-categorie-section')?.classList.remove('d-none'); | |
document.getElementById('edit-sous-categorie-section')?.classList.add('d-none'); | |
}); | |
} | |
// Filter logic if present | |
const matiereFilterSelect = document.getElementById('matiere-filter'); | |
if (matiereFilterSelect) { | |
matiereFilterSelect.addEventListener('change', function() { | |
const selectedMatiereId = this.value; | |
const tableBody = document.querySelector('.table tbody'); // Adjust selector if needed | |
if (!tableBody) return; | |
const sousCategorieRows = tableBody.querySelectorAll('tr'); // Assuming each row represents a sous-categorie | |
sousCategorieRows.forEach(row => { | |
// Check if the row has a data attribute identifying the matiere | |
const rowMatiereId = row.getAttribute('data-matiere-id'); | |
if (rowMatiereId) { | |
if (selectedMatiereId === '' || rowMatiereId === selectedMatiereId) { | |
row.style.display = ''; // Show row | |
} else { | |
row.style.display = 'none'; // Hide row | |
} | |
} | |
}); | |
}); | |
// Trigger change on load to apply initial filter if a value is pre-selected | |
matiereFilterSelect.dispatchEvent(new Event('change')); | |
} | |
} | |
// Setup general texte form functionality (e.g., dynamic dropdowns if creating new text) | |
function setupTexteForm() { | |
// Example: If there's a matiere dropdown that populates sous-categories on a *creation* page | |
const matiereSelect = document.getElementById('matiere-select'); // Adjust ID if different | |
const sousCategorieSelect = document.getElementById('sous-categorie-select'); // Adjust ID | |
if (matiereSelect && sousCategorieSelect) { | |
matiereSelect.addEventListener('change', function() { | |
const matiereId = this.value; | |
// Clear current sous-categorie options (except the default placeholder) | |
sousCategorieSelect.innerHTML = '<option value="">Sélectionnez une sous-catégorie</option>'; | |
sousCategorieSelect.disabled = true; | |
if (matiereId) { | |
// Fetch sous-categories for the selected matiere | |
fetch(`/api/get_sous_categories/${matiereId}`) // Adjust API endpoint | |
.then(response => { | |
if (!response.ok) { | |
throw new Error('Network response was not ok'); | |
} | |
return response.json(); | |
}) | |
.then(data => { | |
if (data && data.length > 0) { | |
data.forEach(sc => { | |
const option = document.createElement('option'); | |
option.value = sc.id; | |
option.textContent = sc.nom; | |
sousCategorieSelect.appendChild(option); | |
}); | |
sousCategorieSelect.disabled = false; | |
} else { | |
// Handle case with no sous-categories | |
const option = document.createElement('option'); | |
option.textContent = 'Aucune sous-catégorie trouvée'; | |
option.disabled = true; | |
sousCategorieSelect.appendChild(option); | |
} | |
}) | |
.catch(error => { | |
console.error('Error loading sous-categories:', error); | |
// Optionally display an error message to the user | |
const option = document.createElement('option'); | |
option.textContent = 'Erreur de chargement'; | |
option.disabled = true; | |
sousCategorieSelect.appendChild(option); | |
}); | |
} | |
}); | |
// Trigger change on load if a matiere is pre-selected | |
if(matiereSelect.value) { | |
matiereSelect.dispatchEvent(new Event('change')); | |
} | |
} | |
} | |
// --- Content Block Editor Specific Functions --- | |
function setupContentBlockEditor() { | |
const blocksContainer = document.getElementById('blocks-container'); | |
const addBlockButton = document.getElementById('add-block-button'); | |
const saveBlocksButton = document.getElementById('save-blocks-button'); | |
if (!blocksContainer) return; // Only run if the container exists | |
// Initialize SortableJS for drag-and-drop | |
if (window.Sortable) { | |
new Sortable(blocksContainer, { | |
animation: 150, | |
handle: '.block-handle', // Class for the drag handle element | |
ghostClass: 'block-ghost', // Class applied to the ghost element | |
onEnd: function(evt) { | |
updateBlockOrder(); // Renumber blocks after drag | |
} | |
}); | |
} else { | |
console.warn('SortableJS not loaded. Drag and drop for blocks disabled.'); | |
} | |
// Add new block button listener | |
if (addBlockButton) { | |
addBlockButton.addEventListener('click', function() { | |
addContentBlock(); // Add an empty block | |
}); | |
} | |
// Save blocks button listener | |
if (saveBlocksButton) { | |
saveBlocksButton.addEventListener('click', function() { | |
saveContentBlocks(); // Gather data and submit the form | |
}); | |
} | |
// Setup controls and Quill for existing blocks on page load | |
setupExistingBlockControls(); | |
} | |
// Initialize Quill Editor on a given container | |
function initializeQuillEditor(container, hiddenInput, initialContent = '') { | |
if (!container || !hiddenInput) { | |
console.error("Quill initialization failed: Container or hidden input missing."); | |
return; | |
} | |
const blockId = container.id.replace('quill-editor-', ''); | |
// Prevent re-initialization | |
if (quillInstances[blockId]) { | |
console.warn(`Quill instance for block ${blockId} already exists.`); | |
return quillInstances[blockId]; // Return existing instance | |
} | |
try { | |
const quill = new Quill(container, { | |
theme: 'snow', // Use the 'snow' theme (matches the CSS) | |
modules: { | |
toolbar: [ | |
[{ 'header': [1, 2, 3, 4, false] }], | |
['bold', 'italic', 'underline', 'strike'], // toggled buttons | |
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme | |
[{ 'list': 'ordered'}, { 'list': 'bullet' }], | |
[{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript | |
[{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent | |
[{ 'direction': 'rtl' }], // text direction | |
[{ 'align': [] }], | |
['link'], // Add link button // Removed 'image' and 'video' as they are handled per block | |
['blockquote', 'code-block'], | |
['clean'] // remove formatting button | |
] | |
}, | |
placeholder: 'Saisissez le contenu du bloc ici...', | |
}); | |
// Set initial content if provided. Use dangerouslyPasteHTML for raw HTML. | |
if (initialContent && initialContent.trim() !== '<p><br></p>') { // Avoid pasting empty paragraph | |
quill.clipboard.dangerouslyPasteHTML(0, initialContent); | |
} | |
// Update hidden input whenever text changes | |
quill.on('text-change', (delta, oldDelta, source) => { | |
// Get HTML content from Quill. Handles empty case too. | |
let htmlContent = quill.root.innerHTML; | |
// Quill often leaves an empty paragraph tag (<p><br></p>) when cleared. Treat it as empty. | |
if (htmlContent === '<p><br></p>') { | |
htmlContent = ''; | |
} | |
hiddenInput.value = htmlContent; | |
}); | |
quillInstances[blockId] = quill; // Store the instance | |
console.log(`Quill initialized for block ${blockId}`); | |
return quill; | |
} catch (error) { | |
console.error(`Error initializing Quill for block ${blockId}:`, error); | |
container.innerHTML = `<div class="alert alert-danger">Erreur d'initialisation de l'éditeur pour ce bloc. Contenu brut :</div><textarea class="form-control" rows="3" disabled>${initialContent}</textarea>`; | |
// Still update the hidden input with the original content for safety | |
hiddenInput.value = initialContent; | |
return null; | |
} | |
} | |
// Setup controls for existing blocks on page load | |
function setupExistingBlockControls() { | |
const blockEditors = document.querySelectorAll('#blocks-container .block-editor'); | |
blockEditors.forEach((blockEditor, index) => { | |
const blockId = blockEditor.getAttribute('data-block-id'); | |
if (!blockId) { | |
console.error("Block editor found without data-block-id attribute.", blockEditor); | |
return; // Skip this block | |
} | |
// Initialize Quill for this block | |
const editorContainer = blockEditor.querySelector(`#quill-editor-${blockId}`); | |
const hiddenInput = blockEditor.querySelector(`#block-${blockId}-content`); | |
if (editorContainer && hiddenInput) { | |
initializeQuillEditor(editorContainer, hiddenInput, hiddenInput.value); // Pass initial content from hidden input | |
} else { | |
console.error(`Could not find editor container or hidden input for block ${blockId}`); | |
} | |
// Setup Delete Button | |
const deleteButton = blockEditor.querySelector('.delete-block-btn'); | |
if (deleteButton) { | |
deleteButton.addEventListener('click', function() { | |
if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ? Cette action est irréversible.')) { | |
// Clean up Quill instance before removing the element | |
if (quillInstances[blockId]) { | |
delete quillInstances[blockId]; // Remove reference | |
console.log(`Quill instance for block ${blockId} cleaned up.`); | |
} | |
blockEditor.remove(); | |
updateBlockOrder(); // Renumber remaining blocks | |
} | |
}); | |
} else { | |
console.warn(`Delete button not found for block ${blockId}`); | |
} | |
// Setup Image Selection Button | |
const selectButton = blockEditor.querySelector('.select-image-btn'); | |
if (selectButton) { | |
selectButton.addEventListener('click', function() { | |
const galleryModalElement = document.getElementById('image-gallery-modal'); | |
if (galleryModalElement) { | |
// Store the target block ID on the modal before showing | |
galleryModalElement.setAttribute('data-target-block', blockId); | |
// Use Bootstrap's JS to show the modal | |
const galleryModal = bootstrap.Modal.getOrCreateInstance(galleryModalElement); | |
galleryModal.show(); | |
} else { | |
alert("Erreur : La galerie d'images n'a pas pu être trouvée."); | |
} | |
}); | |
} else { | |
console.warn(`Select image button not found for block ${blockId}`); | |
} | |
// Setup Image Removal Button | |
const removeButton = blockEditor.querySelector('.remove-image-btn'); | |
if (removeButton) { | |
removeButton.addEventListener('click', function() { | |
const imageIdInput = blockEditor.querySelector('.block-image-id'); | |
const imagePreview = blockEditor.querySelector('.image-preview'); | |
if (imageIdInput) imageIdInput.value = ''; // Clear the hidden ID | |
if (imagePreview) { | |
imagePreview.src = ''; | |
imagePreview.style.display = 'none'; // Hide preview | |
imagePreview.alt = 'Preview'; | |
} | |
// Toggle button visibility | |
this.style.display = 'none'; // Hide remove button | |
const selectBtn = blockEditor.querySelector('.select-image-btn'); | |
if(selectBtn) selectBtn.style.display = 'inline-block'; // Show select button | |
// Hide position controls (handled by specific function in edit_texte.html) | |
const event = new CustomEvent('imageRemoved', { bubbles: true, detail: { blockEditor: blockEditor } }); | |
blockEditor.dispatchEvent(event); | |
}); | |
} else { | |
console.warn(`Remove image button not found for block ${blockId}`); | |
} | |
// Setup Image Position Select (if needed, though primary logic might be in HTML script) | |
const positionSelect = blockEditor.querySelector('.image-position-select'); | |
if (positionSelect) { | |
positionSelect.addEventListener('change', function() { | |
// Optional: Add JS logic here if needed, e.g., apply preview classes | |
console.log(`Block ${blockId} image position changed to: ${this.value}`); | |
}); | |
} | |
}); | |
updateBlockOrder(); // Ensure initial numbering is correct | |
} | |
// Add a new content block to the editor | |
function addContentBlock(data = null) { | |
const blocksContainer = document.getElementById('blocks-container'); | |
if (!blocksContainer) return; | |
const blockCount = blocksContainer.children.length; | |
const newBlockIndex = blockCount + 1; | |
const blockId = 'new-block-' + Date.now(); // Unique ID for new blocks | |
// Default values if data is not provided | |
const initialData = { | |
id: blockId, // Use the generated temporary ID | |
title: data?.title || '', | |
content: data?.content || '', // Default empty content for Quill | |
image: data?.image || null, // { id: ..., src: ..., alt: ... } | |
image_position: data?.image_position || 'left' | |
}; | |
// Create block HTML using template literals | |
const blockHtml = ` | |
<div class="block-editor mb-4" data-block-id="${initialData.id}"> | |
<div class="block-editor-header"> | |
<div class="d-flex align-items-center"> | |
<span class="block-handle"><i class="fas fa-grip-vertical"></i></span> | |
<h3 class="block-editor-title">Bloc #${newBlockIndex}</h3> | |
</div> | |
<div class="block-editor-actions"> | |
<button type="button" class="btn btn-danger btn-sm delete-block-btn" title="Supprimer ce bloc"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</div> | |
</div> | |
<div class="form-group mb-3"> | |
<label for="block-${initialData.id}-title">Titre du bloc (optionnel)</label> | |
<input type="text" class="form-control block-title" id="block-${initialData.id}-title" value="${initialData.title}"> | |
</div> | |
<div class="form-group mb-3"> | |
<label for="quill-editor-${initialData.id}">Contenu du bloc</label> | |
<!-- Quill editor container --> | |
<div id="quill-editor-${initialData.id}" class="block-content-editor"></div> | |
<!-- Hidden input to store Quill's HTML content --> | |
<input type="hidden" class="block-content-hidden" id="block-${initialData.id}-content" value="${initialData.content}"> | |
</div> | |
<div class="form-group"> | |
<label>Image (optionnel)</label> | |
<div class="d-flex align-items-center mb-2"> | |
<button type="button" class="btn btn-primary btn-sm select-image-btn" style="${initialData.image ? 'display:none;' : ''}"> | |
<i class="fas fa-image"></i> Sélectionner une image | |
</button> | |
<button type="button" class="btn btn-warning btn-sm remove-image-btn ml-2" style="${initialData.image ? '' : 'display:none;'}"> | |
<i class="fas fa-times"></i> Retirer l'image | |
</button> | |
</div> | |
<input type="hidden" class="block-image-id" value="${initialData.image?.id || ''}"> | |
<div class="block-image-container"> | |
<img src="${initialData.image?.src || ''}" alt="${initialData.image?.alt || 'Prévisualisation'}" class="image-preview" style="${initialData.image ? '' : 'display:none;'}"> | |
</div> | |
<div class="form-group mt-3 image-position-controls ${initialData.image ? '' : 'd-none'}"> | |
<label for="block-${initialData.id}-image-position">Position de l'image</label> | |
<select class="form-control image-position-select" id="block-${initialData.id}-image-position"> | |
<option value="left" ${initialData.image_position === 'left' ? 'selected' : ''}>Gauche</option> | |
<option value="right" ${initialData.image_position === 'right' ? 'selected' : ''}>Droite</option> | |
<option value="top" ${initialData.image_position === 'top' ? 'selected' : ''}>Haut</option> | |
</select> | |
</div> | |
</div> | |
</div> | |
`; | |
// Add the block HTML to the container | |
blocksContainer.insertAdjacentHTML('beforeend', blockHtml); | |
// Get the newly added block element | |
const newBlockElement = blocksContainer.lastElementChild; | |
// Initialize Quill for the new block | |
const editorContainer = newBlockElement.querySelector(`#quill-editor-${initialData.id}`); | |
const hiddenInput = newBlockElement.querySelector(`#block-${initialData.id}-content`); | |
if(editorContainer && hiddenInput) { | |
initializeQuillEditor(editorContainer, hiddenInput, initialData.content); | |
} else { | |
console.error(`Failed to find editor elements for new block ${initialData.id}`); | |
} | |
// Add event listeners for the new block's controls (delete, select image, remove image) | |
// Delegate event listeners might be more efficient, but direct binding is simpler here | |
// Delete button | |
const deleteButton = newBlockElement.querySelector('.delete-block-btn'); | |
if (deleteButton) { | |
deleteButton.addEventListener('click', function() { | |
if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ?')) { | |
// Clean up Quill instance | |
if (quillInstances[initialData.id]) { | |
delete quillInstances[initialData.id]; | |
console.log(`Quill instance for block ${initialData.id} cleaned up.`); | |
} | |
newBlockElement.remove(); | |
updateBlockOrder(); | |
} | |
}); | |
} | |
// Select image button | |
const selectButton = newBlockElement.querySelector('.select-image-btn'); | |
if (selectButton) { | |
selectButton.addEventListener('click', function() { | |
const galleryModalElement = document.getElementById('image-gallery-modal'); | |
if (galleryModalElement) { | |
galleryModalElement.setAttribute('data-target-block', initialData.id); | |
const galleryModal = bootstrap.Modal.getOrCreateInstance(galleryModalElement); | |
galleryModal.show(); | |
} else { | |
alert("Erreur : La galerie d'images n'a pas pu être trouvée."); | |
} | |
}); | |
} | |
// Remove image button | |
const removeButton = newBlockElement.querySelector('.remove-image-btn'); | |
if (removeButton) { | |
removeButton.addEventListener('click', function() { | |
const imageIdInput = newBlockElement.querySelector('.block-image-id'); | |
const imagePreview = newBlockElement.querySelector('.image-preview'); | |
if (imageIdInput) imageIdInput.value = ''; | |
if (imagePreview) { | |
imagePreview.src = ''; | |
imagePreview.style.display = 'none'; | |
imagePreview.alt = 'Preview'; | |
} | |
this.style.display = 'none'; // Hide remove button | |
const selectBtn = newBlockElement.querySelector('.select-image-btn'); | |
if (selectBtn) selectBtn.style.display = 'inline-block'; // Show select button | |
// Hide position controls | |
const event = new CustomEvent('imageRemoved', { bubbles: true, detail: { blockEditor: newBlockElement } }); | |
newBlockElement.dispatchEvent(event); | |
}); | |
} | |
// Scroll to the new block | |
newBlockElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
} | |
// Update block order numbers in the UI titles | |
function updateBlockOrder() { | |
const blocks = document.querySelectorAll('#blocks-container .block-editor'); | |
blocks.forEach((block, index) => { | |
const titleEl = block.querySelector('.block-editor-title'); | |
if (titleEl) { | |
titleEl.textContent = `Bloc #${index + 1}`; | |
} | |
}); | |
console.log("Block order updated."); | |
} | |
// Save content blocks by gathering data and submitting the form | |
function saveContentBlocks() { | |
const blocksContainer = document.getElementById('blocks-container'); | |
const blocksDataInput = document.getElementById('blocks-data'); | |
const blocksForm = document.getElementById('blocks-form'); | |
if (!blocksContainer || !blocksDataInput || !blocksForm) { | |
console.error("Cannot save blocks: Missing container, data input, or form element."); | |
alert("Erreur : Impossible de sauvegarder les blocs. Éléments de formulaire manquants."); | |
return; | |
} | |
const blockElements = blocksContainer.querySelectorAll('.block-editor'); | |
const blocksData = []; | |
console.log(`Gathering data for ${blockElements.length} blocks...`); | |
blockElements.forEach((blockElement, index) => { | |
const blockId = blockElement.getAttribute('data-block-id'); | |
const titleInput = blockElement.querySelector('.block-title'); | |
const hiddenContentInput = blockElement.querySelector('.block-content-hidden'); | |
const imageIdInput = blockElement.querySelector('.block-image-id'); | |
const imagePositionSelect = blockElement.querySelector('.image-position-select'); | |
let content = ''; | |
// Crucially, get the latest content from Quill instance if available | |
if (quillInstances[blockId]) { | |
content = quillInstances[blockId].root.innerHTML; | |
// Treat empty Quill editor as empty string | |
if (content === '<p><br></p>') { | |
content = ''; | |
} | |
// Ensure hidden input is also synced before submission (belt and suspenders) | |
if (hiddenContentInput) hiddenContentInput.value = content; | |
} else if (hiddenContentInput) { | |
// Fallback to hidden input if Quill instance is missing (error condition) | |
content = hiddenContentInput.value; | |
console.warn(`Quill instance missing for block ${blockId}. Using hidden input value.`); | |
} else { | |
console.error(`Cannot get content for block ${blockId}: No Quill instance and no hidden input.`); | |
} | |
const blockDataItem = { | |
// Include the original block ID if it's not a new one, | |
// otherwise, the backend should know how to handle blocks without a numeric ID | |
id: blockId.startsWith('new-block-') ? null : blockId, | |
temp_id: blockId.startsWith('new-block-') ? blockId : null, // Send temp ID for new blocks if needed | |
title: titleInput ? titleInput.value.trim() : '', | |
content: content, | |
image_id: imageIdInput ? (imageIdInput.value || null) : null, // Send null if empty | |
image_position: imagePositionSelect ? imagePositionSelect.value : 'left', | |
order: index // Set the order based on current position | |
}; | |
blocksData.push(blockDataItem); | |
}); | |
// Set the JSON data in the hidden input field | |
blocksDataInput.value = JSON.stringify(blocksData); | |
console.log("Blocks data prepared:", blocksDataInput.value); | |
// Submit the form | |
// Add loading indicator? | |
const saveButton = document.getElementById('save-blocks-button'); | |
if(saveButton) { | |
saveButton.disabled = true; | |
saveButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sauvegarde...'; | |
} | |
blocksForm.submit(); | |
} | |
// --- Image Management Functions --- | |
// Setup image uploader preview (for the form in edit_texte.html) | |
function setupImageUploader() { | |
const imageFileInput = document.getElementById('image-file'); // ID from edit_texte.html form | |
const imagePreview = document.getElementById('upload-image-preview'); // ID from edit_texte.html form | |
if (imageFileInput && imagePreview) { | |
imageFileInput.addEventListener('change', function() { | |
if (this.files && this.files[0]) { | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
imagePreview.src = e.target.result; | |
imagePreview.style.display = 'block'; // Show preview | |
}; | |
reader.readAsDataURL(this.files[0]); | |
} else { | |
// Clear preview if no file selected | |
imagePreview.src = '#'; | |
imagePreview.style.display = 'none'; | |
} | |
}); | |
} | |
} | |
// Setup image gallery modal interaction | |
function setupImageGallery() { | |
const galleryModalElement = document.getElementById('image-gallery-modal'); | |
if (!galleryModalElement) return; | |
const galleryContent = galleryModalElement.querySelector('#image-gallery-content'); // Container for images | |
// Use event delegation on the modal body for image clicks | |
galleryModalElement.addEventListener('click', function(event) { | |
const galleryItem = event.target.closest('.gallery-item'); | |
if (!galleryItem) return; // Clicked outside an item | |
const imageId = galleryItem.getAttribute('data-image-id'); | |
const imageElement = galleryItem.querySelector('img'); | |
const imageSrc = imageElement ? imageElement.src : ''; | |
const imageAlt = imageElement ? imageElement.alt : ''; | |
// Get the ID of the block that opened the modal | |
const targetBlockId = galleryModalElement.getAttribute('data-target-block'); | |
if (!targetBlockId) { | |
console.error("Target block ID not found on modal."); | |
return; | |
} | |
// Find the target block editor element | |
const blockEditor = document.querySelector(`.block-editor[data-block-id="${targetBlockId}"]`); | |
if (blockEditor && imageId && imageSrc) { | |
// Update the block's image ID input | |
const imageIdInput = blockEditor.querySelector('.block-image-id'); | |
if (imageIdInput) imageIdInput.value = imageId; | |
// Update the block's image preview | |
const imagePreview = blockEditor.querySelector('.image-preview'); | |
if (imagePreview) { | |
imagePreview.src = imageSrc; | |
imagePreview.alt = imageAlt || 'Prévisualisation'; | |
imagePreview.style.display = 'block'; // Ensure preview is visible | |
} | |
// Toggle visibility of select/remove buttons | |
const selectButton = blockEditor.querySelector('.select-image-btn'); | |
const removeButton = blockEditor.querySelector('.remove-image-btn'); | |
if (selectButton) selectButton.style.display = 'none'; | |
if (removeButton) removeButton.style.display = 'inline-block'; | |
// Show position controls (handled by specific function in edit_texte.html) | |
const customEvent = new CustomEvent('imageSelected', { bubbles: true, detail: { blockEditor: blockEditor } }); | |
blockEditor.dispatchEvent(customEvent); | |
// Close the modal | |
const galleryModalInstance = bootstrap.Modal.getInstance(galleryModalElement); | |
if (galleryModalInstance) { | |
galleryModalInstance.hide(); | |
} | |
} else { | |
console.error(`Failed to update block ${targetBlockId}. Block editor or image data missing.`); | |
} | |
}); | |
// Optional: Add logic here to dynamically load gallery images via AJAX if needed | |
// E.g., on modal 'show.bs.modal' event | |
} |