Docfile commited on
Commit
74828dc
·
verified ·
1 Parent(s): ee7781a

Update api/static/js/admin.js

Browse files
Files changed (1) hide show
  1. api/static/js/admin.js +438 -585
api/static/js/admin.js CHANGED
@@ -1,31 +1,24 @@
1
  // Admin JavaScript for the backend management interface
2
 
3
- // Global object to hold Quill instances
4
- let quillInstances = {};
5
-
6
  document.addEventListener('DOMContentLoaded', function() {
7
  // Initialize theme
8
  initTheme();
9
-
10
  // Setup dashboard functionality
11
  setupDashboardCards();
12
-
13
  // Setup admin forms
14
  setupMatiereForm();
15
  setupSousCategorieForm();
16
- setupTexteForm(); // Basic setup (e.g., category dropdowns if needed)
17
-
18
- // Setup content block editor specifically for edit_texte.html
19
- if (document.getElementById('blocks-container')) {
20
- setupContentBlockEditor();
21
- }
22
-
23
- // Setup image management (gallery interaction, maybe general upload elsewhere)
24
- setupImageGallery(); // Handles selecting from the modal
25
- // Note: The specific AJAX upload logic is now mainly in edit_texte.html's script block
26
- // but we keep setupImageUploader for potential preview logic or other upload forms.
27
- setupImageUploader(); // Sets up preview for the upload form
28
-
29
  // Setup theme toggle
30
  setupThemeToggle();
31
  });
@@ -34,6 +27,8 @@ document.addEventListener('DOMContentLoaded', function() {
34
  function initTheme() {
35
  const userPreference = localStorage.getItem('theme') || 'light';
36
  document.documentElement.setAttribute('data-theme', userPreference);
 
 
37
  updateThemeIcon(userPreference);
38
  }
39
 
@@ -41,14 +36,22 @@ function initTheme() {
41
  function setupThemeToggle() {
42
  const themeToggle = document.getElementById('theme-toggle');
43
  if (!themeToggle) return;
44
-
45
  themeToggle.addEventListener('click', function() {
46
  const currentTheme = document.documentElement.getAttribute('data-theme');
47
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
 
 
48
  document.documentElement.setAttribute('data-theme', newTheme);
 
 
49
  localStorage.setItem('theme', newTheme);
 
 
50
  updateThemeIcon(newTheme);
51
- saveThemePreference(newTheme); // Optional: Inform server
 
 
52
  });
53
  }
54
 
@@ -56,53 +59,46 @@ function setupThemeToggle() {
56
  function updateThemeIcon(theme) {
57
  const themeToggle = document.getElementById('theme-toggle');
58
  if (!themeToggle) return;
59
- const icon = themeToggle.querySelector('i');
60
- if (!icon) return;
61
-
62
  if (theme === 'dark') {
63
- icon.classList.remove('fa-moon');
64
- icon.classList.add('fa-sun');
65
  themeToggle.setAttribute('title', 'Activer le mode clair');
66
  } else {
67
- icon.classList.remove('fa-sun');
68
- icon.classList.add('fa-moon');
69
  themeToggle.setAttribute('title', 'Activer le mode sombre');
70
  }
71
  }
72
 
73
- // Save theme preference to server (Optional)
74
  function saveThemePreference(theme) {
75
- // Example using fetch API - adjust URL and method as needed
76
- /*
77
- fetch('/set_theme_preference', { // Replace with your actual endpoint
 
78
  method: 'POST',
79
- headers: {
80
- 'Content-Type': 'application/json',
81
- // Add CSRF token header if needed
82
- // 'X-CSRFToken': getCsrfToken()
83
- },
84
- body: JSON.stringify({ theme: theme })
85
  })
86
  .then(response => response.json())
87
  .then(data => {
88
- if(data.success) console.log('Theme preference saved on server.');
89
- else console.error('Failed to save theme preference on server.');
90
  })
91
  .catch(error => {
92
  console.error('Error saving theme preference:', error);
93
  });
94
- */
95
  }
96
 
97
  // Setup dashboard cards with hover effects
98
  function setupDashboardCards() {
99
  const dashboardCards = document.querySelectorAll('.admin-card');
 
100
  dashboardCards.forEach(card => {
101
  card.addEventListener('mouseenter', function() {
102
  this.style.transform = 'translateY(-5px)';
103
  this.style.boxShadow = 'var(--hover-shadow)';
104
- // Keep transition consistent (defined in CSS is better)
105
  });
 
106
  card.addEventListener('mouseleave', function() {
107
  this.style.transform = 'translateY(0)';
108
  this.style.boxShadow = 'var(--shadow)';
@@ -110,714 +106,571 @@ function setupDashboardCards() {
110
  });
111
  }
112
 
113
- // Setup matiere form functionality (if on matiere page)
114
  function setupMatiereForm() {
 
115
  const editButtons = document.querySelectorAll('.edit-matiere-btn');
116
  editButtons.forEach(button => {
117
  button.addEventListener('click', function() {
118
  const matiereId = this.getAttribute('data-id');
119
  const matiereName = this.getAttribute('data-name');
120
  const matiereColor = this.getAttribute('data-color');
121
-
122
  const editForm = document.getElementById('edit-matiere-form');
123
- const addSection = document.getElementById('add-matiere-section');
124
- const editSection = document.getElementById('edit-matiere-section');
125
-
126
- if (editForm && addSection && editSection) {
127
- editForm.querySelector('input[name="matiere_id"]').value = matiereId;
128
- editForm.querySelector('input[name="nom"]').value = matiereName;
129
  const colorInput = editForm.querySelector('input[name="color_code"]');
 
 
 
130
  colorInput.value = matiereColor;
131
- // Trigger input event to update preview if color picker exists
132
- if(colorInput) colorInput.dispatchEvent(new Event('input'));
133
-
134
-
135
- addSection.classList.add('d-none');
136
- editSection.classList.remove('d-none');
137
- editSection.scrollIntoView({ behavior: 'smooth' });
138
  }
139
  });
140
  });
141
-
 
142
  const cancelEditButton = document.getElementById('cancel-edit-matiere');
143
  if (cancelEditButton) {
144
  cancelEditButton.addEventListener('click', function(e) {
145
  e.preventDefault();
146
- document.getElementById('add-matiere-section')?.classList.remove('d-none');
147
- document.getElementById('edit-matiere-section')?.classList.add('d-none');
148
  });
149
  }
150
-
151
- // Color picker preview logic
152
  const colorPickers = document.querySelectorAll('input[type="color"]');
153
  colorPickers.forEach(picker => {
154
- const previewId = picker.id + '-preview'; // Assuming a convention like id="color" and preview span id="color-preview"
155
- let preview = document.getElementById(previewId);
156
- if (!preview) { // Create preview dynamically if not present
157
- preview = document.createElement('span');
158
- preview.className = 'color-preview';
159
- preview.id = previewId;
160
- preview.style.display = 'inline-block';
161
- preview.style.width = '24px';
162
- preview.style.height = '24px';
163
- preview.style.borderRadius = '4px';
164
- preview.style.marginLeft = '10px';
165
- preview.style.verticalAlign = 'middle';
166
- preview.style.border = '1px solid var(--border-color)';
167
- picker.parentNode.insertBefore(preview, picker.nextSibling);
168
- }
169
-
170
  picker.addEventListener('input', function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  preview.style.backgroundColor = this.value;
172
  });
173
- // Initialize preview color
174
- preview.style.backgroundColor = picker.value;
 
 
175
  });
176
  }
177
 
178
- // Setup sous categorie form functionality (if on sous_categorie page)
179
  function setupSousCategorieForm() {
 
180
  const editButtons = document.querySelectorAll('.edit-sous-categorie-btn');
181
  editButtons.forEach(button => {
182
  button.addEventListener('click', function() {
183
  const sousCategorieId = this.getAttribute('data-id');
184
  const sousCategorieName = this.getAttribute('data-name');
185
  const matiereId = this.getAttribute('data-matiere-id');
186
-
187
  const editForm = document.getElementById('edit-sous-categorie-form');
188
- const addSection = document.getElementById('add-sous-categorie-section');
189
- const editSection = document.getElementById('edit-sous-categorie-section');
190
-
191
- if (editForm && addSection && editSection) {
192
- editForm.querySelector('input[name="sous_categorie_id"]').value = sousCategorieId;
193
- editForm.querySelector('input[name="nom"]').value = sousCategorieName;
194
- editForm.querySelector('select[name="matiere_id"]').value = matiereId;
195
-
196
- addSection.classList.add('d-none');
197
- editSection.classList.remove('d-none');
198
- editSection.scrollIntoView({ behavior: 'smooth' });
 
 
 
 
199
  }
200
  });
201
  });
202
-
 
203
  const cancelEditButton = document.getElementById('cancel-edit-sous-categorie');
204
  if (cancelEditButton) {
205
  cancelEditButton.addEventListener('click', function(e) {
206
  e.preventDefault();
207
- document.getElementById('add-sous-categorie-section')?.classList.remove('d-none');
208
- document.getElementById('edit-sous-categorie-section')?.classList.add('d-none');
209
  });
210
  }
211
-
212
- // Filter logic if present
213
  const matiereFilterSelect = document.getElementById('matiere-filter');
214
  if (matiereFilterSelect) {
215
  matiereFilterSelect.addEventListener('change', function() {
216
  const selectedMatiereId = this.value;
217
- const tableBody = document.querySelector('.table tbody'); // Adjust selector if needed
218
- if (!tableBody) return;
219
- const sousCategorieRows = tableBody.querySelectorAll('tr'); // Assuming each row represents a sous-categorie
220
-
221
  sousCategorieRows.forEach(row => {
222
- // Check if the row has a data attribute identifying the matiere
223
- const rowMatiereId = row.getAttribute('data-matiere-id');
224
- if (rowMatiereId) {
225
- if (selectedMatiereId === '' || rowMatiereId === selectedMatiereId) {
226
- row.style.display = ''; // Show row
227
- } else {
228
- row.style.display = 'none'; // Hide row
229
- }
230
- }
231
  });
232
  });
233
- // Trigger change on load to apply initial filter if a value is pre-selected
234
- matiereFilterSelect.dispatchEvent(new Event('change'));
235
  }
236
  }
237
 
238
- // Setup general texte form functionality (e.g., dynamic dropdowns if creating new text)
239
  function setupTexteForm() {
240
- // Example: If there's a matiere dropdown that populates sous-categories on a *creation* page
241
- const matiereSelect = document.getElementById('matiere-select'); // Adjust ID if different
242
- const sousCategorieSelect = document.getElementById('sous-categorie-select'); // Adjust ID
243
-
244
- if (matiereSelect && sousCategorieSelect) {
245
  matiereSelect.addEventListener('change', function() {
246
  const matiereId = this.value;
247
- // Clear current sous-categorie options (except the default placeholder)
248
- sousCategorieSelect.innerHTML = '<option value="">Sélectionnez une sous-catégorie</option>';
249
- sousCategorieSelect.disabled = true;
250
-
251
- if (matiereId) {
 
252
  // Fetch sous-categories for the selected matiere
253
- fetch(`/api/get_sous_categories/${matiereId}`) // Adjust API endpoint
254
- .then(response => {
255
- if (!response.ok) {
256
- throw new Error('Network response was not ok');
257
- }
258
- return response.json();
259
- })
260
  .then(data => {
261
- if (data && data.length > 0) {
262
- data.forEach(sc => {
263
- const option = document.createElement('option');
264
- option.value = sc.id;
265
- option.textContent = sc.nom;
266
- sousCategorieSelect.appendChild(option);
267
- });
268
- sousCategorieSelect.disabled = false;
269
- } else {
270
- // Handle case with no sous-categories
271
- const option = document.createElement('option');
272
- option.textContent = 'Aucune sous-catégorie trouvée';
273
- option.disabled = true;
274
- sousCategorieSelect.appendChild(option);
275
- }
276
  })
277
  .catch(error => {
278
  console.error('Error loading sous-categories:', error);
279
- // Optionally display an error message to the user
280
- const option = document.createElement('option');
281
- option.textContent = 'Erreur de chargement';
282
- option.disabled = true;
283
- sousCategorieSelect.appendChild(option);
284
  });
285
  }
286
  });
287
- // Trigger change on load if a matiere is pre-selected
288
- if(matiereSelect.value) {
289
- matiereSelect.dispatchEvent(new Event('change'));
290
- }
291
  }
292
  }
293
 
294
- // --- Content Block Editor Specific Functions ---
295
-
296
  function setupContentBlockEditor() {
297
  const blocksContainer = document.getElementById('blocks-container');
298
  const addBlockButton = document.getElementById('add-block-button');
299
  const saveBlocksButton = document.getElementById('save-blocks-button');
300
-
301
- if (!blocksContainer) return; // Only run if the container exists
302
-
303
- // Initialize SortableJS for drag-and-drop
 
 
 
 
 
 
 
304
  if (window.Sortable) {
305
  new Sortable(blocksContainer, {
306
  animation: 150,
307
- handle: '.block-handle', // Class for the drag handle element
308
- ghostClass: 'block-ghost', // Class applied to the ghost element
309
- onEnd: function(evt) {
310
- updateBlockOrder(); // Renumber blocks after drag
 
311
  }
312
  });
313
- } else {
314
- console.warn('SortableJS not loaded. Drag and drop for blocks disabled.');
315
- }
316
-
317
- // Add new block button listener
318
- if (addBlockButton) {
319
- addBlockButton.addEventListener('click', function() {
320
- addContentBlock(); // Add an empty block
321
- });
322
  }
323
-
324
- // Save blocks button listener
325
  if (saveBlocksButton) {
326
  saveBlocksButton.addEventListener('click', function() {
327
- saveContentBlocks(); // Gather data and submit the form
328
  });
329
  }
330
-
331
- // Setup controls and Quill for existing blocks on page load
332
  setupExistingBlockControls();
333
  }
334
 
335
- // Initialize Quill Editor on a given container
336
- function initializeQuillEditor(container, hiddenInput, initialContent = '') {
337
- if (!container || !hiddenInput) {
338
- console.error("Quill initialization failed: Container or hidden input missing.");
339
- return;
340
- }
341
-
342
- const blockId = container.id.replace('quill-editor-', '');
343
-
344
- // Prevent re-initialization
345
- if (quillInstances[blockId]) {
346
- console.warn(`Quill instance for block ${blockId} already exists.`);
347
- return quillInstances[blockId]; // Return existing instance
348
- }
349
-
350
- try {
351
- const quill = new Quill(container, {
352
- theme: 'snow', // Use the 'snow' theme (matches the CSS)
353
- modules: {
354
- toolbar: [
355
- [{ 'header': [1, 2, 3, 4, false] }],
356
- ['bold', 'italic', 'underline', 'strike'], // toggled buttons
357
- [{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
358
- [{ 'list': 'ordered'}, { 'list': 'bullet' }],
359
- [{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript
360
- [{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
361
- [{ 'direction': 'rtl' }], // text direction
362
- [{ 'align': [] }],
363
- ['link'], // Add link button // Removed 'image' and 'video' as they are handled per block
364
- ['blockquote', 'code-block'],
365
- ['clean'] // remove formatting button
366
- ]
367
- },
368
- placeholder: 'Saisissez le contenu du bloc ici...',
369
  });
370
-
371
- // Set initial content if provided. Use dangerouslyPasteHTML for raw HTML.
372
- if (initialContent && initialContent.trim() !== '<p><br></p>') { // Avoid pasting empty paragraph
373
- quill.clipboard.dangerouslyPasteHTML(0, initialContent);
374
- }
375
-
376
- // Update hidden input whenever text changes
377
- quill.on('text-change', (delta, oldDelta, source) => {
378
- // Get HTML content from Quill. Handles empty case too.
379
- let htmlContent = quill.root.innerHTML;
380
- // Quill often leaves an empty paragraph tag (<p><br></p>) when cleared. Treat it as empty.
381
- if (htmlContent === '<p><br></p>') {
382
- htmlContent = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  }
384
- hiddenInput.value = htmlContent;
385
  });
386
-
387
- quillInstances[blockId] = quill; // Store the instance
388
- console.log(`Quill initialized for block ${blockId}`);
389
- return quill;
390
-
391
- } catch (error) {
392
- console.error(`Error initializing Quill for block ${blockId}:`, error);
393
- 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>`;
394
- // Still update the hidden input with the original content for safety
395
- hiddenInput.value = initialContent;
396
- return null;
397
- }
398
- }
399
-
400
-
401
- // Setup controls for existing blocks on page load
402
- function setupExistingBlockControls() {
403
- const blockEditors = document.querySelectorAll('#blocks-container .block-editor');
404
-
405
- blockEditors.forEach((blockEditor, index) => {
406
- const blockId = blockEditor.getAttribute('data-block-id');
407
- if (!blockId) {
408
- console.error("Block editor found without data-block-id attribute.", blockEditor);
409
- return; // Skip this block
410
- }
411
-
412
- // Initialize Quill for this block
413
- const editorContainer = blockEditor.querySelector(`#quill-editor-${blockId}`);
414
- const hiddenInput = blockEditor.querySelector(`#block-${blockId}-content`);
415
- if (editorContainer && hiddenInput) {
416
- initializeQuillEditor(editorContainer, hiddenInput, hiddenInput.value); // Pass initial content from hidden input
417
- } else {
418
- console.error(`Could not find editor container or hidden input for block ${blockId}`);
419
- }
420
-
421
- // Setup Delete Button
422
- const deleteButton = blockEditor.querySelector('.delete-block-btn');
423
- if (deleteButton) {
424
- deleteButton.addEventListener('click', function() {
425
- if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ? Cette action est irréversible.')) {
426
- // Clean up Quill instance before removing the element
427
- if (quillInstances[blockId]) {
428
- delete quillInstances[blockId]; // Remove reference
429
- console.log(`Quill instance for block ${blockId} cleaned up.`);
430
- }
431
- blockEditor.remove();
432
- updateBlockOrder(); // Renumber remaining blocks
433
- }
434
- });
435
- } else {
436
- console.warn(`Delete button not found for block ${blockId}`);
437
- }
438
-
439
- // Setup Image Selection Button
440
- const selectButton = blockEditor.querySelector('.select-image-btn');
441
- if (selectButton) {
442
- selectButton.addEventListener('click', function() {
443
- const galleryModalElement = document.getElementById('image-gallery-modal');
444
- if (galleryModalElement) {
445
- // Store the target block ID on the modal before showing
446
- galleryModalElement.setAttribute('data-target-block', blockId);
447
- // Use Bootstrap's JS to show the modal
448
- const galleryModal = bootstrap.Modal.getOrCreateInstance(galleryModalElement);
449
- galleryModal.show();
450
- } else {
451
- alert("Erreur : La galerie d'images n'a pas pu être trouvée.");
452
- }
453
- });
454
- } else {
455
- console.warn(`Select image button not found for block ${blockId}`);
456
- }
457
-
458
- // Setup Image Removal Button
459
- const removeButton = blockEditor.querySelector('.remove-image-btn');
460
- if (removeButton) {
461
- removeButton.addEventListener('click', function() {
462
- const imageIdInput = blockEditor.querySelector('.block-image-id');
463
- const imagePreview = blockEditor.querySelector('.image-preview');
464
-
465
- if (imageIdInput) imageIdInput.value = ''; // Clear the hidden ID
466
- if (imagePreview) {
467
- imagePreview.src = '';
468
- imagePreview.style.display = 'none'; // Hide preview
469
- imagePreview.alt = 'Preview';
470
- }
471
-
472
- // Toggle button visibility
473
- this.style.display = 'none'; // Hide remove button
474
- const selectBtn = blockEditor.querySelector('.select-image-btn');
475
- if(selectBtn) selectBtn.style.display = 'inline-block'; // Show select button
476
-
477
- // Hide position controls (handled by specific function in edit_texte.html)
478
- const event = new CustomEvent('imageRemoved', { bubbles: true, detail: { blockEditor: blockEditor } });
479
- blockEditor.dispatchEvent(event);
480
- });
481
- } else {
482
- console.warn(`Remove image button not found for block ${blockId}`);
483
- }
484
-
485
- // Setup Image Position Select (if needed, though primary logic might be in HTML script)
486
- const positionSelect = blockEditor.querySelector('.image-position-select');
487
- if (positionSelect) {
488
- positionSelect.addEventListener('change', function() {
489
- // Optional: Add JS logic here if needed, e.g., apply preview classes
490
- console.log(`Block ${blockId} image position changed to: ${this.value}`);
491
- });
492
- }
493
  });
494
-
495
- updateBlockOrder(); // Ensure initial numbering is correct
496
  }
497
 
498
-
499
  // Add a new content block to the editor
500
  function addContentBlock(data = null) {
501
  const blocksContainer = document.getElementById('blocks-container');
502
  if (!blocksContainer) return;
503
-
504
- const blockCount = blocksContainer.children.length;
505
- const newBlockIndex = blockCount + 1;
506
- const blockId = 'new-block-' + Date.now(); // Unique ID for new blocks
507
-
508
- // Default values if data is not provided
509
- const initialData = {
510
- id: blockId, // Use the generated temporary ID
511
- title: data?.title || '',
512
- content: data?.content || '', // Default empty content for Quill
513
- image: data?.image || null, // { id: ..., src: ..., alt: ... }
514
- image_position: data?.image_position || 'left'
515
- };
516
-
517
- // Create block HTML using template literals
518
  const blockHtml = `
519
- <div class="block-editor mb-4" data-block-id="${initialData.id}">
520
  <div class="block-editor-header">
521
  <div class="d-flex align-items-center">
522
  <span class="block-handle"><i class="fas fa-grip-vertical"></i></span>
523
- <h3 class="block-editor-title">Bloc #${newBlockIndex}</h3>
524
  </div>
525
  <div class="block-editor-actions">
526
- <button type="button" class="btn btn-danger btn-sm delete-block-btn" title="Supprimer ce bloc">
527
  <i class="fas fa-trash"></i>
528
  </button>
529
  </div>
530
  </div>
531
- <div class="form-group mb-3">
532
- <label for="block-${initialData.id}-title">Titre du bloc (optionnel)</label>
533
- <input type="text" class="form-control block-title" id="block-${initialData.id}-title" value="${initialData.title}">
534
  </div>
535
- <div class="form-group mb-3">
536
- <label for="quill-editor-${initialData.id}">Contenu du bloc</label>
537
- <!-- Quill editor container -->
538
- <div id="quill-editor-${initialData.id}" class="block-content-editor"></div>
539
- <!-- Hidden input to store Quill's HTML content -->
540
- <input type="hidden" class="block-content-hidden" id="block-${initialData.id}-content" value="${initialData.content}">
541
  </div>
542
  <div class="form-group">
543
- <label>Image (optionnel)</label>
544
  <div class="d-flex align-items-center mb-2">
545
- <button type="button" class="btn btn-primary btn-sm select-image-btn" style="${initialData.image ? 'display:none;' : ''}">
546
  <i class="fas fa-image"></i> Sélectionner une image
547
  </button>
548
- <button type="button" class="btn btn-warning btn-sm remove-image-btn ml-2" style="${initialData.image ? '' : 'display:none;'}">
549
  <i class="fas fa-times"></i> Retirer l'image
550
  </button>
551
  </div>
552
- <input type="hidden" class="block-image-id" value="${initialData.image?.id || ''}">
553
-
554
- <div class="block-image-container">
555
- <img src="${initialData.image?.src || ''}" alt="${initialData.image?.alt || 'Prévisualisation'}" class="image-preview" style="${initialData.image ? '' : 'display:none;'}">
556
- </div>
557
-
558
- <div class="form-group mt-3 image-position-controls ${initialData.image ? '' : 'd-none'}">
559
- <label for="block-${initialData.id}-image-position">Position de l'image</label>
560
- <select class="form-control image-position-select" id="block-${initialData.id}-image-position">
561
- <option value="left" ${initialData.image_position === 'left' ? 'selected' : ''}>Gauche</option>
562
- <option value="right" ${initialData.image_position === 'right' ? 'selected' : ''}>Droite</option>
563
- <option value="top" ${initialData.image_position === 'top' ? 'selected' : ''}>Haut</option>
564
  </select>
565
  </div>
566
  </div>
567
  </div>
568
  `;
569
-
570
- // Add the block HTML to the container
571
  blocksContainer.insertAdjacentHTML('beforeend', blockHtml);
572
-
573
- // Get the newly added block element
574
- const newBlockElement = blocksContainer.lastElementChild;
575
-
576
- // Initialize Quill for the new block
577
- const editorContainer = newBlockElement.querySelector(`#quill-editor-${initialData.id}`);
578
- const hiddenInput = newBlockElement.querySelector(`#block-${initialData.id}-content`);
579
- if(editorContainer && hiddenInput) {
580
- initializeQuillEditor(editorContainer, hiddenInput, initialData.content);
581
- } else {
582
- console.error(`Failed to find editor elements for new block ${initialData.id}`);
583
- }
584
-
585
-
586
- // Add event listeners for the new block's controls (delete, select image, remove image)
587
- // Delegate event listeners might be more efficient, but direct binding is simpler here
588
-
589
  // Delete button
590
- const deleteButton = newBlockElement.querySelector('.delete-block-btn');
591
  if (deleteButton) {
592
  deleteButton.addEventListener('click', function() {
593
- if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ?')) {
594
- // Clean up Quill instance
595
- if (quillInstances[initialData.id]) {
596
- delete quillInstances[initialData.id];
597
- console.log(`Quill instance for block ${initialData.id} cleaned up.`);
598
- }
599
- newBlockElement.remove();
600
- updateBlockOrder();
601
- }
602
  });
603
  }
604
-
605
- // Select image button
606
- const selectButton = newBlockElement.querySelector('.select-image-btn');
607
- if (selectButton) {
608
- selectButton.addEventListener('click', function() {
609
- const galleryModalElement = document.getElementById('image-gallery-modal');
610
- if (galleryModalElement) {
611
- galleryModalElement.setAttribute('data-target-block', initialData.id);
612
- const galleryModal = bootstrap.Modal.getOrCreateInstance(galleryModalElement);
613
- galleryModal.show();
614
- } else {
615
- alert("Erreur : La galerie d'images n'a pas pu être trouvée.");
616
- }
617
  });
618
  }
619
-
620
- // Remove image button
621
- const removeButton = newBlockElement.querySelector('.remove-image-btn');
622
- if (removeButton) {
623
- removeButton.addEventListener('click', function() {
624
- const imageIdInput = newBlockElement.querySelector('.block-image-id');
625
- const imagePreview = newBlockElement.querySelector('.image-preview');
626
-
627
- if (imageIdInput) imageIdInput.value = '';
628
- if (imagePreview) {
629
- imagePreview.src = '';
630
- imagePreview.style.display = 'none';
631
- imagePreview.alt = 'Preview';
632
- }
633
-
634
- this.style.display = 'none'; // Hide remove button
635
- const selectBtn = newBlockElement.querySelector('.select-image-btn');
636
- if (selectBtn) selectBtn.style.display = 'inline-block'; // Show select button
637
-
638
- // Hide position controls
639
- const event = new CustomEvent('imageRemoved', { bubbles: true, detail: { blockEditor: newBlockElement } });
640
- newBlockElement.dispatchEvent(event);
641
- });
642
- }
643
-
644
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  // Scroll to the new block
646
- newBlockElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
647
  }
648
 
649
-
650
- // Update block order numbers in the UI titles
651
  function updateBlockOrder() {
652
- const blocks = document.querySelectorAll('#blocks-container .block-editor');
653
  blocks.forEach((block, index) => {
654
  const titleEl = block.querySelector('.block-editor-title');
655
  if (titleEl) {
656
  titleEl.textContent = `Bloc #${index + 1}`;
657
  }
658
  });
659
- console.log("Block order updated.");
660
  }
661
 
662
- // Save content blocks by gathering data and submitting the form
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  function saveContentBlocks() {
664
  const blocksContainer = document.getElementById('blocks-container');
665
  const blocksDataInput = document.getElementById('blocks-data');
666
- const blocksForm = document.getElementById('blocks-form');
667
-
668
- if (!blocksContainer || !blocksDataInput || !blocksForm) {
669
- console.error("Cannot save blocks: Missing container, data input, or form element.");
670
- alert("Erreur : Impossible de sauvegarder les blocs. Éléments de formulaire manquants.");
671
- return;
672
- }
673
-
674
- const blockElements = blocksContainer.querySelectorAll('.block-editor');
675
  const blocksData = [];
676
-
677
- console.log(`Gathering data for ${blockElements.length} blocks...`);
678
-
679
- blockElements.forEach((blockElement, index) => {
680
- const blockId = blockElement.getAttribute('data-block-id');
681
- const titleInput = blockElement.querySelector('.block-title');
682
- const hiddenContentInput = blockElement.querySelector('.block-content-hidden');
683
- const imageIdInput = blockElement.querySelector('.block-image-id');
684
- const imagePositionSelect = blockElement.querySelector('.image-position-select');
685
-
686
- let content = '';
687
- // Crucially, get the latest content from Quill instance if available
688
- if (quillInstances[blockId]) {
689
- content = quillInstances[blockId].root.innerHTML;
690
- // Treat empty Quill editor as empty string
691
- if (content === '<p><br></p>') {
692
- content = '';
693
- }
694
- // Ensure hidden input is also synced before submission (belt and suspenders)
695
- if (hiddenContentInput) hiddenContentInput.value = content;
696
- } else if (hiddenContentInput) {
697
- // Fallback to hidden input if Quill instance is missing (error condition)
698
- content = hiddenContentInput.value;
699
- console.warn(`Quill instance missing for block ${blockId}. Using hidden input value.`);
700
- } else {
701
- console.error(`Cannot get content for block ${blockId}: No Quill instance and no hidden input.`);
702
- }
703
-
704
- const blockDataItem = {
705
- // Include the original block ID if it's not a new one,
706
- // otherwise, the backend should know how to handle blocks without a numeric ID
707
- id: blockId.startsWith('new-block-') ? null : blockId,
708
- temp_id: blockId.startsWith('new-block-') ? blockId : null, // Send temp ID for new blocks if needed
709
- title: titleInput ? titleInput.value.trim() : '',
710
  content: content,
711
- image_id: imageIdInput ? (imageIdInput.value || null) : null, // Send null if empty
712
- image_position: imagePositionSelect ? imagePositionSelect.value : 'left',
713
- order: index // Set the order based on current position
714
- };
715
- blocksData.push(blockDataItem);
716
  });
717
-
718
- // Set the JSON data in the hidden input field
719
  blocksDataInput.value = JSON.stringify(blocksData);
720
-
721
- console.log("Blocks data prepared:", blocksDataInput.value);
722
-
723
  // Submit the form
724
- // Add loading indicator?
725
- const saveButton = document.getElementById('save-blocks-button');
726
- if(saveButton) {
727
- saveButton.disabled = true;
728
- saveButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sauvegarde...';
729
  }
730
-
731
- blocksForm.submit();
732
  }
733
 
734
- // --- Image Management Functions ---
735
-
736
- // Setup image uploader preview (for the form in edit_texte.html)
737
  function setupImageUploader() {
738
- const imageFileInput = document.getElementById('image-file'); // ID from edit_texte.html form
739
- const imagePreview = document.getElementById('upload-image-preview'); // ID from edit_texte.html form
740
-
 
741
  if (imageFileInput && imagePreview) {
742
  imageFileInput.addEventListener('change', function() {
743
  if (this.files && this.files[0]) {
744
  const reader = new FileReader();
 
745
  reader.onload = function(e) {
746
  imagePreview.src = e.target.result;
747
- imagePreview.style.display = 'block'; // Show preview
748
  };
 
749
  reader.readAsDataURL(this.files[0]);
750
- } else {
751
- // Clear preview if no file selected
752
- imagePreview.src = '#';
753
- imagePreview.style.display = 'none';
 
 
 
 
 
 
 
754
  }
755
  });
756
  }
757
  }
758
 
759
- // Setup image gallery modal interaction
760
  function setupImageGallery() {
761
- const galleryModalElement = document.getElementById('image-gallery-modal');
762
- if (!galleryModalElement) return;
763
-
764
- const galleryContent = galleryModalElement.querySelector('#image-gallery-content'); // Container for images
765
-
766
- // Use event delegation on the modal body for image clicks
767
- galleryModalElement.addEventListener('click', function(event) {
768
- const galleryItem = event.target.closest('.gallery-item');
769
- if (!galleryItem) return; // Clicked outside an item
770
-
771
- const imageId = galleryItem.getAttribute('data-image-id');
772
- const imageElement = galleryItem.querySelector('img');
773
- const imageSrc = imageElement ? imageElement.src : '';
774
- const imageAlt = imageElement ? imageElement.alt : '';
775
-
776
- // Get the ID of the block that opened the modal
777
- const targetBlockId = galleryModalElement.getAttribute('data-target-block');
778
-
779
- if (!targetBlockId) {
780
- console.error("Target block ID not found on modal.");
781
- return;
782
- }
783
-
784
- // Find the target block editor element
785
- const blockEditor = document.querySelector(`.block-editor[data-block-id="${targetBlockId}"]`);
786
-
787
- if (blockEditor && imageId && imageSrc) {
788
- // Update the block's image ID input
789
- const imageIdInput = blockEditor.querySelector('.block-image-id');
790
- if (imageIdInput) imageIdInput.value = imageId;
791
-
792
- // Update the block's image preview
793
- const imagePreview = blockEditor.querySelector('.image-preview');
794
- if (imagePreview) {
795
- imagePreview.src = imageSrc;
796
- imagePreview.alt = imageAlt || 'Prévisualisation';
797
- imagePreview.style.display = 'block'; // Ensure preview is visible
798
- }
799
-
800
- // Toggle visibility of select/remove buttons
801
- const selectButton = blockEditor.querySelector('.select-image-btn');
802
- const removeButton = blockEditor.querySelector('.remove-image-btn');
803
- if (selectButton) selectButton.style.display = 'none';
804
- if (removeButton) removeButton.style.display = 'inline-block';
805
-
806
- // Show position controls (handled by specific function in edit_texte.html)
807
- const customEvent = new CustomEvent('imageSelected', { bubbles: true, detail: { blockEditor: blockEditor } });
808
- blockEditor.dispatchEvent(customEvent);
809
-
810
-
811
- // Close the modal
812
- const galleryModalInstance = bootstrap.Modal.getInstance(galleryModalElement);
813
- if (galleryModalInstance) {
814
- galleryModalInstance.hide();
815
  }
816
- } else {
817
- console.error(`Failed to update block ${targetBlockId}. Block editor or image data missing.`);
818
- }
819
  });
820
-
821
- // Optional: Add logic here to dynamically load gallery images via AJAX if needed
822
- // E.g., on modal 'show.bs.modal' event
823
- }
 
1
  // Admin JavaScript for the backend management interface
2
 
 
 
 
3
  document.addEventListener('DOMContentLoaded', function() {
4
  // Initialize theme
5
  initTheme();
6
+
7
  // Setup dashboard functionality
8
  setupDashboardCards();
9
+
10
  // Setup admin forms
11
  setupMatiereForm();
12
  setupSousCategorieForm();
13
+ setupTexteForm();
14
+
15
+ // Setup content block editor
16
+ setupContentBlockEditor();
17
+
18
+ // Setup image management
19
+ setupImageUploader();
20
+ setupImageGallery();
21
+
 
 
 
 
22
  // Setup theme toggle
23
  setupThemeToggle();
24
  });
 
27
  function initTheme() {
28
  const userPreference = localStorage.getItem('theme') || 'light';
29
  document.documentElement.setAttribute('data-theme', userPreference);
30
+
31
+ // Update theme icon
32
  updateThemeIcon(userPreference);
33
  }
34
 
 
36
  function setupThemeToggle() {
37
  const themeToggle = document.getElementById('theme-toggle');
38
  if (!themeToggle) return;
39
+
40
  themeToggle.addEventListener('click', function() {
41
  const currentTheme = document.documentElement.getAttribute('data-theme');
42
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
43
+
44
+ // Update theme attribute
45
  document.documentElement.setAttribute('data-theme', newTheme);
46
+
47
+ // Save preference to localStorage
48
  localStorage.setItem('theme', newTheme);
49
+
50
+ // Update icon
51
  updateThemeIcon(newTheme);
52
+
53
+ // Send theme preference to server
54
+ saveThemePreference(newTheme);
55
  });
56
  }
57
 
 
59
  function updateThemeIcon(theme) {
60
  const themeToggle = document.getElementById('theme-toggle');
61
  if (!themeToggle) return;
62
+
63
+ // Update icon based on theme
 
64
  if (theme === 'dark') {
65
+ themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
 
66
  themeToggle.setAttribute('title', 'Activer le mode clair');
67
  } else {
68
+ themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
 
69
  themeToggle.setAttribute('title', 'Activer le mode sombre');
70
  }
71
  }
72
 
73
+ // Save theme preference to server
74
  function saveThemePreference(theme) {
75
+ const formData = new FormData();
76
+ formData.append('theme', theme);
77
+
78
+ fetch('/set_theme', {
79
  method: 'POST',
80
+ body: formData
 
 
 
 
 
81
  })
82
  .then(response => response.json())
83
  .then(data => {
84
+ console.log('Theme preference saved:', data);
 
85
  })
86
  .catch(error => {
87
  console.error('Error saving theme preference:', error);
88
  });
 
89
  }
90
 
91
  // Setup dashboard cards with hover effects
92
  function setupDashboardCards() {
93
  const dashboardCards = document.querySelectorAll('.admin-card');
94
+
95
  dashboardCards.forEach(card => {
96
  card.addEventListener('mouseenter', function() {
97
  this.style.transform = 'translateY(-5px)';
98
  this.style.boxShadow = 'var(--hover-shadow)';
99
+ this.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease';
100
  });
101
+
102
  card.addEventListener('mouseleave', function() {
103
  this.style.transform = 'translateY(0)';
104
  this.style.boxShadow = 'var(--shadow)';
 
106
  });
107
  }
108
 
109
+ // Setup matiere form functionality
110
  function setupMatiereForm() {
111
+ // Show edit form when edit button is clicked
112
  const editButtons = document.querySelectorAll('.edit-matiere-btn');
113
  editButtons.forEach(button => {
114
  button.addEventListener('click', function() {
115
  const matiereId = this.getAttribute('data-id');
116
  const matiereName = this.getAttribute('data-name');
117
  const matiereColor = this.getAttribute('data-color');
118
+
119
  const editForm = document.getElementById('edit-matiere-form');
120
+ if (editForm) {
121
+ const idInput = editForm.querySelector('input[name="matiere_id"]');
122
+ const nameInput = editForm.querySelector('input[name="nom"]');
 
 
 
123
  const colorInput = editForm.querySelector('input[name="color_code"]');
124
+
125
+ idInput.value = matiereId;
126
+ nameInput.value = matiereName;
127
  colorInput.value = matiereColor;
128
+
129
+ // Show the edit form
130
+ document.getElementById('add-matiere-section').classList.add('d-none');
131
+ document.getElementById('edit-matiere-section').classList.remove('d-none');
132
+
133
+ // Scroll to edit form
134
+ editForm.scrollIntoView({ behavior: 'smooth' });
135
  }
136
  });
137
  });
138
+
139
+ // Cancel edit button
140
  const cancelEditButton = document.getElementById('cancel-edit-matiere');
141
  if (cancelEditButton) {
142
  cancelEditButton.addEventListener('click', function(e) {
143
  e.preventDefault();
144
+ document.getElementById('add-matiere-section').classList.remove('d-none');
145
+ document.getElementById('edit-matiere-section').classList.add('d-none');
146
  });
147
  }
148
+
149
+ // Color picker preview
150
  const colorPickers = document.querySelectorAll('input[type="color"]');
151
  colorPickers.forEach(picker => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  picker.addEventListener('input', function() {
153
+ // Find adjacent preview element or create one
154
+ let preview = this.nextElementSibling;
155
+ if (!preview || !preview.classList.contains('color-preview')) {
156
+ preview = document.createElement('span');
157
+ preview.className = 'color-preview';
158
+ preview.style.display = 'inline-block';
159
+ preview.style.width = '24px';
160
+ preview.style.height = '24px';
161
+ preview.style.borderRadius = '50%';
162
+ preview.style.marginLeft = '10px';
163
+ this.parentNode.insertBefore(preview, this.nextSibling);
164
+ }
165
+
166
  preview.style.backgroundColor = this.value;
167
  });
168
+
169
+ // Trigger once to initialize
170
+ const event = new Event('input');
171
+ picker.dispatchEvent(event);
172
  });
173
  }
174
 
175
+ // Setup sous categorie form functionality
176
  function setupSousCategorieForm() {
177
+ // Show edit form when edit button is clicked
178
  const editButtons = document.querySelectorAll('.edit-sous-categorie-btn');
179
  editButtons.forEach(button => {
180
  button.addEventListener('click', function() {
181
  const sousCategorieId = this.getAttribute('data-id');
182
  const sousCategorieName = this.getAttribute('data-name');
183
  const matiereId = this.getAttribute('data-matiere-id');
184
+
185
  const editForm = document.getElementById('edit-sous-categorie-form');
186
+ if (editForm) {
187
+ const idInput = editForm.querySelector('input[name="sous_categorie_id"]');
188
+ const nameInput = editForm.querySelector('input[name="nom"]');
189
+ const matiereSelect = editForm.querySelector('select[name="matiere_id"]');
190
+
191
+ idInput.value = sousCategorieId;
192
+ nameInput.value = sousCategorieName;
193
+ matiereSelect.value = matiereId;
194
+
195
+ // Show the edit form
196
+ document.getElementById('add-sous-categorie-section').classList.add('d-none');
197
+ document.getElementById('edit-sous-categorie-section').classList.remove('d-none');
198
+
199
+ // Scroll to edit form
200
+ editForm.scrollIntoView({ behavior: 'smooth' });
201
  }
202
  });
203
  });
204
+
205
+ // Cancel edit button
206
  const cancelEditButton = document.getElementById('cancel-edit-sous-categorie');
207
  if (cancelEditButton) {
208
  cancelEditButton.addEventListener('click', function(e) {
209
  e.preventDefault();
210
+ document.getElementById('add-sous-categorie-section').classList.remove('d-none');
211
+ document.getElementById('edit-sous-categorie-section').classList.add('d-none');
212
  });
213
  }
214
+
215
+ // Matiere select filter
216
  const matiereFilterSelect = document.getElementById('matiere-filter');
217
  if (matiereFilterSelect) {
218
  matiereFilterSelect.addEventListener('change', function() {
219
  const selectedMatiereId = this.value;
220
+ const sousCategorieRows = document.querySelectorAll('.sous-categorie-row');
221
+
 
 
222
  sousCategorieRows.forEach(row => {
223
+ if (selectedMatiereId === '' || row.getAttribute('data-matiere-id') === selectedMatiereId) {
224
+ row.style.display = '';
225
+ } else {
226
+ row.style.display = 'none';
227
+ }
 
 
 
 
228
  });
229
  });
 
 
230
  }
231
  }
232
 
233
+ // Setup texte form functionality
234
  function setupTexteForm() {
235
+ // Matiere select change - populate sous-categories
236
+ const matiereSelect = document.getElementById('matiere-select');
237
+ if (matiereSelect) {
 
 
238
  matiereSelect.addEventListener('change', function() {
239
  const matiereId = this.value;
240
+ const sousCategorieSelect = document.getElementById('sous-categorie-select');
241
+
242
+ if (matiereId && sousCategorieSelect) {
243
+ // Clear current options
244
+ sousCategorieSelect.innerHTML = '<option value="">Sélectionnez une sous-catégorie</option>';
245
+
246
  // Fetch sous-categories for the selected matiere
247
+ fetch(`/get_sous_categories/${matiereId}`)
248
+ .then(response => response.json())
 
 
 
 
 
249
  .then(data => {
250
+ data.forEach(sousCategorie => {
251
+ const option = document.createElement('option');
252
+ option.value = sousCategorie.id;
253
+ option.textContent = sousCategorie.nom;
254
+ sousCategorieSelect.appendChild(option);
255
+ });
 
 
 
 
 
 
 
 
 
256
  })
257
  .catch(error => {
258
  console.error('Error loading sous-categories:', error);
 
 
 
 
 
259
  });
260
  }
261
  });
 
 
 
 
262
  }
263
  }
264
 
265
+ // Setup content block editor
 
266
  function setupContentBlockEditor() {
267
  const blocksContainer = document.getElementById('blocks-container');
268
  const addBlockButton = document.getElementById('add-block-button');
269
  const saveBlocksButton = document.getElementById('save-blocks-button');
270
+
271
+ if (!blocksContainer) return;
272
+
273
+ // Add new block
274
+ if (addBlockButton) {
275
+ addBlockButton.addEventListener('click', function() {
276
+ addContentBlock();
277
+ });
278
+ }
279
+
280
+ // Make blocks sortable
281
  if (window.Sortable) {
282
  new Sortable(blocksContainer, {
283
  animation: 150,
284
+ handle: '.block-handle',
285
+ ghostClass: 'block-ghost',
286
+ onEnd: function() {
287
+ // Update order numbers
288
+ updateBlockOrder();
289
  }
290
  });
 
 
 
 
 
 
 
 
 
291
  }
292
+
293
+ // Save blocks
294
  if (saveBlocksButton) {
295
  saveBlocksButton.addEventListener('click', function() {
296
+ saveContentBlocks();
297
  });
298
  }
299
+
300
+ // Add event listeners for existing blocks
301
  setupExistingBlockControls();
302
  }
303
 
304
+ // Setup controls for existing blocks
305
+ function setupExistingBlockControls() {
306
+ // Setup delete buttons
307
+ const deleteButtons = document.querySelectorAll('.delete-block-btn');
308
+ deleteButtons.forEach(button => {
309
+ button.addEventListener('click', function() {
310
+ if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ?')) {
311
+ const blockEditor = this.closest('.block-editor');
312
+ blockEditor.remove();
313
+ updateBlockOrder();
314
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  });
316
+ });
317
+
318
+ // Setup image position selects
319
+ const positionSelects = document.querySelectorAll('.image-position-select');
320
+ positionSelects.forEach(select => {
321
+ select.addEventListener('change', function() {
322
+ updateBlockImagePreview(this.closest('.block-editor'));
323
+ });
324
+ });
325
+
326
+ // Setup image selection buttons
327
+ const imageSelectButtons = document.querySelectorAll('.select-image-btn');
328
+ imageSelectButtons.forEach(button => {
329
+ button.addEventListener('click', function() {
330
+ const blockEditor = this.closest('.block-editor');
331
+ const galleryModal = document.getElementById('image-gallery-modal');
332
+
333
+ if (galleryModal) {
334
+ // Set current block ID as data attribute for the modal
335
+ galleryModal.setAttribute('data-target-block', blockEditor.getAttribute('data-block-id'));
336
+
337
+ // Show the modal
338
+ const modal = new bootstrap.Modal(galleryModal);
339
+ modal.show();
340
+ }
341
+ });
342
+ });
343
+
344
+ // Setup image remove buttons
345
+ const removeImageButtons = document.querySelectorAll('.remove-image-btn');
346
+ removeImageButtons.forEach(button => {
347
+ button.addEventListener('click', function() {
348
+ const blockEditor = this.closest('.block-editor');
349
+ const imageIdInput = blockEditor.querySelector('.block-image-id');
350
+ const imagePreview = blockEditor.querySelector('.image-preview');
351
+
352
+ if (imageIdInput) {
353
+ imageIdInput.value = '';
354
+ }
355
+
356
+ if (imagePreview) {
357
+ imagePreview.src = '';
358
+ imagePreview.style.display = 'none';
359
+ }
360
+
361
+ // Hide remove button
362
+ this.style.display = 'none';
363
+
364
+ // Show select button
365
+ const selectButton = blockEditor.querySelector('.select-image-btn');
366
+ if (selectButton) {
367
+ selectButton.style.display = 'inline-block';
368
  }
 
369
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  });
 
 
371
  }
372
 
 
373
  // Add a new content block to the editor
374
  function addContentBlock(data = null) {
375
  const blocksContainer = document.getElementById('blocks-container');
376
  if (!blocksContainer) return;
377
+
378
+ // Generate a unique ID for the block
379
+ const blockId = 'block-' + Date.now();
380
+
381
+ // Create block HTML
 
 
 
 
 
 
 
 
 
 
382
  const blockHtml = `
383
+ <div class="block-editor" data-block-id="${blockId}">
384
  <div class="block-editor-header">
385
  <div class="d-flex align-items-center">
386
  <span class="block-handle"><i class="fas fa-grip-vertical"></i></span>
387
+ <h3 class="block-editor-title">Bloc #${blocksContainer.children.length + 1}</h3>
388
  </div>
389
  <div class="block-editor-actions">
390
+ <button type="button" class="btn btn-danger btn-sm delete-block-btn">
391
  <i class="fas fa-trash"></i>
392
  </button>
393
  </div>
394
  </div>
395
+ <div class="form-group">
396
+ <label for="${blockId}-title">Titre du bloc (optionnel)</label>
397
+ <input type="text" class="form-control block-title" id="${blockId}-title" value="${data?.title || ''}">
398
  </div>
399
+ <div class="form-group">
400
+ <label for="${blockId}-content">Contenu du bloc</label>
401
+ <textarea class="form-control block-content" id="${blockId}-content" rows="5">${data?.content || ''}</textarea>
 
 
 
402
  </div>
403
  <div class="form-group">
404
+ <label>Image</label>
405
  <div class="d-flex align-items-center mb-2">
406
+ <button type="button" class="btn btn-primary btn-sm select-image-btn" style="${data?.image ? 'display:none;' : ''}">
407
  <i class="fas fa-image"></i> Sélectionner une image
408
  </button>
409
+ <button type="button" class="btn btn-warning btn-sm remove-image-btn ml-2" style="${data?.image ? '' : 'display:none;'}">
410
  <i class="fas fa-times"></i> Retirer l'image
411
  </button>
412
  </div>
413
+ <input type="hidden" class="block-image-id" value="${data?.image?.id || ''}">
414
+ <img src="${data?.image?.src || ''}" alt="Preview" class="image-preview mb-2" style="${data?.image ? '' : 'display:none;'}">
415
+ <div class="form-group">
416
+ <label for="${blockId}-image-position">Position de l'image</label>
417
+ <select class="form-control image-position-select" id="${blockId}-image-position">
418
+ <option value="left" ${data?.image_position === 'left' ? 'selected' : ''}>Gauche</option>
419
+ <option value="right" ${data?.image_position === 'right' ? 'selected' : ''}>Droite</option>
420
+ <option value="top" ${data?.image_position === 'top' ? 'selected' : ''}>Haut</option>
 
 
 
 
421
  </select>
422
  </div>
423
  </div>
424
  </div>
425
  `;
426
+
427
+ // Add the block to the container
428
  blocksContainer.insertAdjacentHTML('beforeend', blockHtml);
429
+
430
+ // Setup event listeners for the new block
431
+ const newBlock = blocksContainer.lastElementChild;
432
+
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  // Delete button
434
+ const deleteButton = newBlock.querySelector('.delete-block-btn');
435
  if (deleteButton) {
436
  deleteButton.addEventListener('click', function() {
437
+ if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ?')) {
438
+ newBlock.remove();
439
+ updateBlockOrder();
440
+ }
 
 
 
 
 
441
  });
442
  }
443
+
444
+ // Image position select
445
+ const positionSelect = newBlock.querySelector('.image-position-select');
446
+ if (positionSelect) {
447
+ positionSelect.addEventListener('change', function() {
448
+ updateBlockImagePreview(newBlock);
 
 
 
 
 
 
 
449
  });
450
  }
451
+
452
+ // Image selection button
453
+ const imageSelectButton = newBlock.querySelector('.select-image-btn');
454
+ if (imageSelectButton) {
455
+ imageSelectButton.addEventListener('click', function() {
456
+ const galleryModal = document.getElementById('image-gallery-modal');
457
+
458
+ if (galleryModal) {
459
+ // Set current block ID as data attribute for the modal
460
+ galleryModal.setAttribute('data-target-block', blockId);
461
+
462
+ // Show the modal
463
+ const modal = new bootstrap.Modal(galleryModal);
464
+ modal.show();
465
+ }
466
+ });
467
+ }
468
+
469
+ // Image remove button
470
+ const removeImageButton = newBlock.querySelector('.remove-image-btn');
471
+ if (removeImageButton) {
472
+ removeImageButton.addEventListener('click', function() {
473
+ const imageIdInput = newBlock.querySelector('.block-image-id');
474
+ const imagePreview = newBlock.querySelector('.image-preview');
475
+
476
+ if (imageIdInput) {
477
+ imageIdInput.value = '';
478
+ }
479
+
480
+ if (imagePreview) {
481
+ imagePreview.src = '';
482
+ imagePreview.style.display = 'none';
483
+ }
484
+
485
+ // Hide remove button
486
+ removeImageButton.style.display = 'none';
487
+
488
+ // Show select button
489
+ if (imageSelectButton) {
490
+ imageSelectButton.style.display = 'inline-block';
491
+ }
492
+ });
493
+ }
494
+
495
  // Scroll to the new block
496
+ newBlock.scrollIntoView({ behavior: 'smooth' });
497
  }
498
 
499
+ // Update block order numbers in the UI
 
500
  function updateBlockOrder() {
501
+ const blocks = document.querySelectorAll('.block-editor');
502
  blocks.forEach((block, index) => {
503
  const titleEl = block.querySelector('.block-editor-title');
504
  if (titleEl) {
505
  titleEl.textContent = `Bloc #${index + 1}`;
506
  }
507
  });
 
508
  }
509
 
510
+ // Update image preview based on position
511
+ function updateBlockImagePreview(blockEditor) {
512
+ // This function would apply CSS classes to show how the image position
513
+ // will look in the frontend
514
+ const positionSelect = blockEditor.querySelector('.image-position-select');
515
+ const imagePreview = blockEditor.querySelector('.image-preview');
516
+
517
+ if (!positionSelect || !imagePreview || imagePreview.style.display === 'none') {
518
+ return;
519
+ }
520
+
521
+ const position = positionSelect.value;
522
+
523
+ // Remove existing position classes
524
+ imagePreview.classList.remove('position-left', 'position-right', 'position-top');
525
+
526
+ // Add the selected position class
527
+ imagePreview.classList.add(`position-${position}`);
528
+
529
+ // Apply some simple styling to demonstrate the position
530
+ switch (position) {
531
+ case 'left':
532
+ imagePreview.style.float = 'left';
533
+ imagePreview.style.marginRight = '15px';
534
+ imagePreview.style.marginBottom = '10px';
535
+ imagePreview.style.width = '30%';
536
+ break;
537
+ case 'right':
538
+ imagePreview.style.float = 'right';
539
+ imagePreview.style.marginLeft = '15px';
540
+ imagePreview.style.marginBottom = '10px';
541
+ imagePreview.style.width = '30%';
542
+ break;
543
+ case 'top':
544
+ imagePreview.style.float = 'none';
545
+ imagePreview.style.marginRight = '0';
546
+ imagePreview.style.marginLeft = '0';
547
+ imagePreview.style.marginBottom = '15px';
548
+ imagePreview.style.width = '100%';
549
+ break;
550
+ }
551
+ }
552
+
553
+ // Save content blocks
554
  function saveContentBlocks() {
555
  const blocksContainer = document.getElementById('blocks-container');
556
  const blocksDataInput = document.getElementById('blocks-data');
557
+
558
+ if (!blocksContainer || !blocksDataInput) return;
559
+
560
+ const blocks = blocksContainer.querySelectorAll('.block-editor');
 
 
 
 
 
561
  const blocksData = [];
562
+
563
+ blocks.forEach((block, index) => {
564
+ const blockId = block.getAttribute('data-block-id');
565
+ const title = block.querySelector('.block-title').value;
566
+ const content = block.querySelector('.block-content').value;
567
+ const imageId = block.querySelector('.block-image-id').value;
568
+ const imagePosition = block.querySelector('.image-position-select').value;
569
+
570
+ blocksData.push({
571
+ id: blockId,
572
+ title: title,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
  content: content,
574
+ image_id: imageId,
575
+ image_position: imagePosition,
576
+ order: index
577
+ });
 
578
  });
579
+
580
+ // Set the blocks data as JSON in the hidden input
581
  blocksDataInput.value = JSON.stringify(blocksData);
582
+
 
 
583
  // Submit the form
584
+ const form = document.getElementById('blocks-form');
585
+ if (form) {
586
+ form.submit();
 
 
587
  }
 
 
588
  }
589
 
590
+ // Setup image uploader
 
 
591
  function setupImageUploader() {
592
+ const imageUploadForm = document.getElementById('image-upload-form');
593
+ const imageFileInput = document.getElementById('image-file');
594
+ const imagePreview = document.getElementById('upload-image-preview');
595
+
596
  if (imageFileInput && imagePreview) {
597
  imageFileInput.addEventListener('change', function() {
598
  if (this.files && this.files[0]) {
599
  const reader = new FileReader();
600
+
601
  reader.onload = function(e) {
602
  imagePreview.src = e.target.result;
603
+ imagePreview.style.display = 'block';
604
  };
605
+
606
  reader.readAsDataURL(this.files[0]);
607
+ }
608
+ });
609
+ }
610
+
611
+ if (imageUploadForm) {
612
+ imageUploadForm.addEventListener('submit', function(e) {
613
+ const fileInput = this.querySelector('#image-file');
614
+
615
+ if (!fileInput.files || fileInput.files.length === 0) {
616
+ e.preventDefault();
617
+ alert('Veuillez sélectionner une image.');
618
  }
619
  });
620
  }
621
  }
622
 
623
+ // Setup image gallery
624
  function setupImageGallery() {
625
+ // Handle image selection from gallery
626
+ const galleryItems = document.querySelectorAll('.gallery-item');
627
+
628
+ galleryItems.forEach(item => {
629
+ item.addEventListener('click', function() {
630
+ const imageId = this.getAttribute('data-image-id');
631
+ const imageSrc = this.querySelector('img').src;
632
+ const galleryModal = document.getElementById('image-gallery-modal');
633
+
634
+ if (galleryModal) {
635
+ const targetBlockId = galleryModal.getAttribute('data-target-block');
636
+ const blockEditor = document.querySelector(`.block-editor[data-block-id="${targetBlockId}"]`);
637
+
638
+ if (blockEditor) {
639
+ // Update the image ID input
640
+ const imageIdInput = blockEditor.querySelector('.block-image-id');
641
+ if (imageIdInput) {
642
+ imageIdInput.value = imageId;
643
+ }
644
+
645
+ // Update the image preview
646
+ const imagePreview = blockEditor.querySelector('.image-preview');
647
+ if (imagePreview) {
648
+ imagePreview.src = imageSrc;
649
+ imagePreview.style.display = 'block';
650
+ }
651
+
652
+ // Hide select button and show remove button
653
+ const selectButton = blockEditor.querySelector('.select-image-btn');
654
+ const removeButton = blockEditor.querySelector('.remove-image-btn');
655
+
656
+ if (selectButton) {
657
+ selectButton.style.display = 'none';
658
+ }
659
+
660
+ if (removeButton) {
661
+ removeButton.style.display = 'inline-block';
662
+ }
663
+
664
+ // Update image preview position
665
+ updateBlockImagePreview(blockEditor);
666
+ }
667
+
668
+ // Close the modal
669
+ const modal = bootstrap.Modal.getInstance(galleryModal);
670
+ if (modal) {
671
+ modal.hide();
672
+ }
 
 
 
 
 
 
673
  }
674
+ });
 
 
675
  });
676
+ }