// Application principale class ConceptionAssistant { constructor() { this.currentJournalId = null; this.editor = null; this.undoStack = []; this.redoStack = []; this.tocTimer = null; this.isPreviewMode = false; this.originalContent = ''; this.init(); } async init() { this.setupEditor(); this.setupEventListeners(); await this.loadJournalList(); this.generateTOC(); } setupEditor() { this.editor = document.getElementById('journal-editor'); // Générer TOC avec debounce pour éviter de perturber la saisie this.editor.addEventListener('input', () => { this.debounceTOC(); }); // Gestion des raccourcis clavier this.editor.addEventListener('keydown', (e) => { // Ctrl+S pour sauvegarder if (e.ctrlKey && e.key === 's') { e.preventDefault(); this.saveJournal(); } // Ctrl+Z pour annuler if (e.ctrlKey && e.key === 'z' && !e.shiftKey) { e.preventDefault(); this.undo(); } // Ctrl+Y ou Ctrl+Shift+Z pour refaire if (e.ctrlKey && (e.key === 'y' || (e.shiftKey && e.key === 'Z'))) { e.preventDefault(); this.redo(); } // Tab pour indentation if (e.key === 'Tab') { e.preventDefault(); document.execCommand('insertText', false, ' '); } }); // Sauvegarder l'état pour undo/redo avant modifications importantes this.editor.addEventListener('input', () => { this.saveState(); }); } showEditorPlaceholder() { // Le placeholder est maintenant géré via CSS ::before // Cette fonction peut être supprimée ou laissée vide pour compatibilité } setupEventListeners() { // Boutons de contrôle des journaux document.getElementById('save-journal')?.addEventListener('click', () => this.saveJournal()); document.getElementById('load-journal')?.addEventListener('click', () => this.showJournalSelector()); document.getElementById('preview-toggle')?.addEventListener('click', async () => await this.togglePreview()); // Table des matières document.getElementById('refresh-toc')?.addEventListener('click', () => this.generateTOC()); // Export/Import document.getElementById('export-md')?.addEventListener('click', () => this.exportMarkdown()); document.getElementById('import-md')?.addEventListener('change', (e) => this.importMarkdown(e)); // Thème document.getElementById('theme-toggle')?.addEventListener('click', () => this.toggleTheme()); // Assistant IA (simulé pour le MVP) document.getElementById('activate-rephrase')?.addEventListener('click', () => this.handleAI('rephrase')); document.getElementById('check-inconsistencies')?.addEventListener('click', () => this.handleAI('inconsistencies')); document.getElementById('check-duplications')?.addEventListener('click', () => this.handleAI('duplications')); document.getElementById('give-advice')?.addEventListener('click', () => this.handleAI('advice')); document.getElementById('liberty-mode')?.addEventListener('click', () => this.handleAI('liberty')); // Modal de chargement document.getElementById('close-journal-modal')?.addEventListener('click', () => this.closeModal()); // Fermer le modal en cliquant sur l'overlay document.getElementById('journal-modal')?.addEventListener('click', (e) => { if (e.target.id === 'journal-modal') { this.closeModal(); } }); // Bouton scroll to top document.getElementById('scroll-to-top')?.addEventListener('click', () => this.scrollToTop()); // Gestion de l'affichage du bouton scroll to top this.setupScrollToTop(); } setupScrollToTop() { const scrollButton = document.getElementById('scroll-to-top'); if (!scrollButton) return; window.addEventListener('scroll', () => { if (window.scrollY > 300) { scrollButton.classList.add('visible'); } else { scrollButton.classList.remove('visible'); } }); } scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); this.showNotification('Retour en haut', 'success'); } saveState() { // Éviter de sauvegarder trop souvent (debounce) if (this.saveStateTimer) { clearTimeout(this.saveStateTimer); } this.saveStateTimer = setTimeout(() => { const currentContent = this.editor.innerText; // Ne pas sauvegarder si le contenu n'a pas changé if (this.undoStack.length > 0 && this.undoStack[this.undoStack.length - 1] === currentContent) { return; } this.undoStack.push(currentContent); // Limiter la pile d'annulation à 50 éléments if (this.undoStack.length > 50) { this.undoStack.shift(); } // Vider la pile de refaire car on a fait une nouvelle action this.redoStack = []; }, 1000); // Sauvegarder après 1 seconde d'inactivité } undo() { if (this.undoStack.length > 1) { const currentContent = this.undoStack.pop(); this.redoStack.push(currentContent); const previousContent = this.undoStack[this.undoStack.length - 1]; this.editor.innerText = previousContent; // Régénérer la table des matières this.generateTOC(); this.showNotification('Annulation effectuée', 'success'); } else { this.showNotification('Rien à annuler', 'warning'); } } redo() { if (this.redoStack.length > 0) { const nextContent = this.redoStack.pop(); this.undoStack.push(nextContent); this.editor.innerText = nextContent; // Régénérer la table des matières this.generateTOC(); this.showNotification('Rétablissement effectué', 'success'); } else { this.showNotification('Rien à rétablir', 'warning'); } } async saveJournal() { const content = this.editor.innerText; if (!content) return; const statusEl = document.getElementById('save-status'); const saveBtn = document.getElementById('save-journal'); saveBtn.classList.add('loading'); statusEl.textContent = 'Sauvegarde...'; try { let response; if (this.currentJournalId) { // Mise à jour response = await fetch(`/api/journals/${this.currentJournalId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); } else { // Création response = await fetch('/api/journals', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); } const result = await response.json(); if (result.success) { if (!this.currentJournalId) { this.currentJournalId = result.data.id; } statusEl.textContent = 'Sauvegardé'; this.showNotification('Journal sauvegardé avec succès', 'success'); setTimeout(() => statusEl.textContent = '', 3000); } else { throw new Error(result.error || 'Erreur de sauvegarde'); } } catch (error) { console.error('Erreur:', error); statusEl.textContent = 'Erreur'; this.showNotification('Erreur lors de la sauvegarde: ' + error.message, 'error'); setTimeout(() => statusEl.textContent = '', 3000); } finally { saveBtn.classList.remove('loading'); } } async loadJournalList() { try { const response = await fetch('/api/journals'); const result = await response.json(); if (result.success && result.data.length > 0) { // Charger le dernier journal automatiquement const lastJournal = result.data[result.data.length - 1]; await this.loadJournal(lastJournal.id); } } catch (error) { console.error('Erreur chargement liste:', error); } } async loadJournal(id) { try { const response = await fetch(`/api/journals/${id}`); const result = await response.json(); if (result.success && result.data.length > 0) { const journal = result.data[0]; this.currentJournalId = id; this.editor.innerText = journal.markdownContent; this.generateTOC(); this.showNotification('Journal chargé', 'success'); } } catch (error) { console.error('Erreur chargement journal:', error); this.showNotification('Erreur lors du chargement', 'error'); } } async showJournalSelector() { try { const response = await fetch('/api/journals'); const result = await response.json(); if (result.success) { const journalList = result.data.map(journal => { // Extraire les premières lignes pour l'aperçu const lines = journal.markdownContent.split('\n'); const firstLines = lines.slice(0, 3).join('\n'); const preview = firstLines.length > 150 ? firstLines.substring(0, 150) + '...' : firstLines; return `
`; }).join(''); const modalBody = document.getElementById('journal-modal-body'); modalBody.innerHTML = `
${journalList}
`; // Afficher le modal avec animation const modal = document.getElementById('journal-modal'); modal.style.display = 'flex'; setTimeout(() => modal.classList.add('show'), 10); // Ajouter les event listeners document.querySelectorAll('.journal-item').forEach(btn => { btn.addEventListener('click', () => { this.loadJournal(btn.dataset.id); this.closeModal(); }); }); } } catch (error) { console.error('Erreur:', error); this.showNotification('Erreur lors du chargement de la liste', 'error'); } } closeModal() { const modal = document.getElementById('journal-modal'); modal.classList.remove('show'); setTimeout(() => { modal.style.display = 'none'; }, 300); } createNewJournal() { this.currentJournalId = null; this.editor.innerText = ''; this.generateTOC(); this.clearFeedback(); this.showNotification('Nouveau journal créé', 'success'); } debounceTOC() { if (this.tocTimer) { clearTimeout(this.tocTimer); } this.tocTimer = setTimeout(() => { this.generateTOC(); }, 500); // Attendre 500ms après la dernière frappe } saveSelection() { const selection = window.getSelection(); if (selection.rangeCount > 0) { return selection.getRangeAt(0).cloneRange(); } return null; } restoreSelection(range) { if (range) { const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); } } generateTOC() { // Sauvegarder la position du curseur const savedRange = this.saveSelection(); const content = this.editor.innerText; const lines = content.split('\n'); const toc = []; let tocHtml = ''; // Supprimer les ancres existantes const existingAnchors = this.editor.querySelectorAll('.heading-anchor'); existingAnchors.forEach(anchor => anchor.remove()); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('#')) { const level = line.match(/^#+/)[0].length; const title = line.replace(/^#+\s*/, ''); const id = 'heading-' + i; toc.push({ level, title, id, lineIndex: i }); } } // Désactivé temporairement pour éviter de perturber la saisie // this.addHeadingAnchors(lines, toc); if (toc.length > 0) { tocHtml = ''; } else { tocHtml = '

Ajoutez des titres (# ## ###) à votre journal pour générer la table des matières.

'; } document.getElementById('toc-nav').innerHTML = tocHtml; // Restaurer la position du curseur this.restoreSelection(savedRange); } addHeadingAnchors(lines, toc) { const editorLines = this.editor.innerHTML.split('
'); let newContent = ''; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const tocItem = toc.find(item => item.lineIndex === i); if (tocItem) { newContent += ``; } newContent += line; if (i < lines.length - 1) newContent += '
'; } this.editor.innerHTML = newContent; } scrollToHeading(title) { const content = this.editor.innerText; const lines = content.split('\n'); // Chercher la ligne qui correspond au titre for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('#') && line.replace(/^#+\s*/, '') === title) { try { // Approche plus simple : utiliser un élément temporaire avec une position spécifique const tempElement = document.createElement('div'); tempElement.style.position = 'absolute'; tempElement.style.visibility = 'hidden'; tempElement.style.height = '1px'; // Calculer la position approximative en fonction de la ligne const editorRect = this.editor.getBoundingClientRect(); const computedStyle = window.getComputedStyle(this.editor); const lineHeight = parseInt(computedStyle.lineHeight) || 20; // Insérer l'élément temporaire dans l'éditeur this.editor.appendChild(tempElement); // Calculer la position de scroll basée sur le numéro de ligne const targetPosition = i * lineHeight; // Scroller vers la position calculée this.editor.scrollTo({ top: targetPosition, behavior: 'smooth' }); // Si l'éditeur n'a pas de scroll, utiliser le parent if (this.editor.scrollHeight <= this.editor.clientHeight) { const scrollContainer = this.editor.parentElement || document.documentElement; const editorTop = this.editor.offsetTop; scrollContainer.scrollTo({ top: editorTop + targetPosition - 100, // -100 pour un padding behavior: 'smooth' }); } // Nettoyer l'élément temporaire setTimeout(() => { if (tempElement.parentNode) { tempElement.parentNode.removeChild(tempElement); } }, 100); this.showNotification('Navigation vers la section', 'success'); return; } catch (error) { console.log('Erreur de scroll:', error); } } } this.showNotification('Section non trouvée', 'warning'); } exportMarkdown() { const content = this.editor.innerText; if (!content.trim()) { this.showNotification('Aucun contenu à exporter', 'warning'); return; } const blob = new Blob([content], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'journal-conception.md'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showNotification('Fichier Markdown exporté', 'success'); } importMarkdown(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { this.editor.innerText = e.target.result; this.generateTOC(); this.currentJournalId = null; // Nouveau journal this.showNotification('Fichier Markdown importé', 'success'); }; reader.readAsText(file); } toggleTheme() { document.body.classList.toggle('dark-theme'); const isDark = document.body.classList.contains('dark-theme'); localStorage.setItem('theme', isDark ? 'dark' : 'light'); this.showNotification(`Mode ${isDark ? 'sombre' : 'clair'} activé`, 'success'); } async handleAI(action) { const selection = window.getSelection().toString().trim(); const fullContent = this.editor.innerText; // Vérifier les conditions selon l'action if (action === 'rephrase' && !selection) { this.showAIFeedback('Veuillez sélectionner du texte à reformuler'); return; } // Déterminer le contenu à utiliser selon l'action let contentToUse; switch (action) { case 'rephrase': contentToUse = selection; break; case 'inconsistencies': case 'duplications': case 'advice': case 'liberty': contentToUse = selection || fullContent; break; default: contentToUse = fullContent; } // Afficher un message de chargement this.showAIFeedback('
🤖 Traitement en cours...
'); try { let result; switch (action) { case 'rephrase': result = await this.callAI('/api/ai/rephrase', { text: selection.trim(), context: fullContent.substring(0, 500) }); // Récupérer directement le texte reformulé const rephrasedText = result.rephrased || result.data || result; // Stocker la suggestion pour la validation this.lastRephraseData = { original: selection.trim(), rephrased: rephrasedText, selection: window.getSelection() }; this.showAIFeedback(` Reformulation

Texte original :
${selection.substring(0, 200)}${selection.length > 200 ? '...' : ''}
Version améliorée :
${rephrasedText}
`); // Ajouter les event listeners pour les boutons document.getElementById('validate-rephrase')?.addEventListener('click', () => this.validateRephrase()); document.getElementById('cancel-rephrase')?.addEventListener('click', () => this.clearFeedback()); break; case 'inconsistencies': result = await this.callAI('/api/ai/check-inconsistencies', { content: contentToUse }); this.showAIFeedback(`Analyse des incohérences

${this.formatAIResponse(result.analysis)}`); break; case 'duplications': result = await this.callAI('/api/ai/check-duplications', { content: contentToUse }); this.showAIFeedback(`Vérification des doublons

${this.formatAIResponse(result.analysis)}`); break; case 'advice': result = await this.callAI('/api/ai/give-advice', { content: contentToUse, domain: 'conception' }); this.showAIFeedback(`Conseils d'amélioration

${this.formatAIResponse(result.advice)}`); break; case 'liberty': const count = document.getElementById('liberty-repeat-count')?.value || 3; // Mode liberté utilise toujours le document complet result = await this.callAI('/api/ai/liberty-mode', { content: fullContent, iterations: count, focus: 'conception' }); // Utiliser le contenu final mis à jour par le backend if (result.finalContent) { this.editor.innerText = result.finalContent; this.generateTOC(); this.saveState(); } let libertyHTML = `Mode Liberté Intelligent (${result.iterations} itérations)

`; // Vérifier si l'IA s'est arrêtée prématurément const lastIteration = result.results[result.results.length - 1]; if (lastIteration && lastIteration.stopped) { libertyHTML += `

Analyse terminée après ${result.iterations} itérations - Aucune amélioration évidente supplémentaire détectée.

`; } else { libertyHTML += `

Les ${result.iterations} itérations d'amélioration ont été automatiquement appliquées au document.

`; } result.results.forEach(iteration => { const borderColor = iteration.stopped ? 'var(--warning-color)' : 'var(--success-color)'; const icon = iteration.stopped ? 'STOP' : 'OK'; libertyHTML += `
${icon} Itération ${iteration.iteration} ${iteration.stopped ? '(Arrêt)' : '(Appliquée)'} :

${this.formatAIResponse(iteration.content)}
`; }); this.showAIFeedback(libertyHTML); break; } } catch (error) { console.error('Erreur IA:', error); this.showAIFeedback(`Erreur

Une erreur s'est produite : ${error.message}

Vérifiez votre connexion et la configuration de l'API.`); } } async callAI(endpoint, data) { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Erreur inconnue'); } return result.data; } formatAIResponse(text) { if (!text) return ''; // Parser la réponse pour séparer le raisonnement du contenu principal const sections = text.split('##'); let formattedHTML = ''; const self = this; sections.forEach((section, index) => { if (index === 0 && section.trim()) { // Première section sans titre formattedHTML += `
${self.parseMarkdown(section.trim())}
`; } else if (section.trim()) { const lines = section.split('\n'); const title = lines[0].trim(); const content = lines.slice(1).join('\n').trim(); // Sections normales - avec markdown formattedHTML += `
${title}

${self.parseMarkdown(content)}
`; } }); return formattedHTML || text; } parseMarkdown(text) { if (!text) return ''; return text // Titres H1-H6 (#, ##, ###, etc.) .replace(/^(#{1,6})\s+(.+)$/gm, (match, hashes, title) => { const level = hashes.length; return `${title}`; }) // Listes à puces (- ou *) .replace(/^[\s]*[-\*]\s+(.+)$/gm, '
  • $1
  • ') // Listes numérotées .replace(/^[\s]*\d+\.\s+(.+)$/gm, '
  • $1
  • ') // Code blocks avec ``` .replace(/```([\s\S]*?)```/g, '
    $1
    ') // Citations avec > .replace(/^>\s+(.+)$/gm, '
    $1
    ') // Gras **texte** .replace(/\*\*(.*?)\*\*/g, '$1') // Italique *texte* .replace(/\*(.*?)\*/g, '$1') // Code inline `code` .replace(/`([^`]+)`/g, '$1') // Liens [texte](url) .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') // Séparer les listes en blocs `; } return match; }) // Sauts de ligne doubles pour paragraphes .replace(/\n\n+/g, '\n\n') .replace(/\n\n/g, '

    ') // Sauts de ligne simples .replace(/\n/g, '
    ') // Encapsuler dans un paragraphe si pas déjà fait .replace(/^(?!<[h1-6|ul|ol|pre|blockquote])/i, '

    ') .replace(/$/i, '

    '); } showAIFeedback(message) { const feedback = document.getElementById('ai-assistant-feedback'); feedback.innerHTML = `
    ${message}
    `; // Scroll vers le haut du feedback pour voir le résultat feedback.scrollTop = 0; } validateRephrase() { if (!this.lastRephraseData) return; try { // Remplacer le texte dans l'éditeur const range = this.lastRephraseData.selection.getRangeAt(0); range.deleteContents(); range.insertNode(document.createTextNode(this.lastRephraseData.rephrased)); // Nettoyer la sélection et régénérer la TOC window.getSelection().removeAllRanges(); this.generateTOC(); // Afficher un message de succès this.showNotification('Reformulation appliquée avec succès', 'success'); this.clearFeedback(); // Nettoyer les données this.lastRephraseData = null; } catch (error) { console.error('Erreur application reformulation:', error); this.showNotification('Erreur lors de l\'application de la reformulation', 'error'); } } clearFeedback() { const feedback = document.getElementById('ai-assistant-feedback'); feedback.innerHTML = `
    Assistant IA prêt
    Sélectionnez du texte dans l'éditeur et cliquez sur une action pour commencer.
    `; } showNotification(message, type = 'success') { const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => notification.classList.add('show'), 100); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => document.body.removeChild(notification), 300); }, 3000); } async togglePreview() { const previewBtn = document.getElementById('preview-toggle'); if (!this.isPreviewMode) { // Passer en mode prévisualisation this.originalContent = this.editor.innerHTML; const markdownContent = this.editor.innerText; // Configurer Marked pour un rendu compatible GitHub if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true, headerIds: true, sanitize: false, smartypants: false }); } // Convertir le Markdown en HTML avec Marked (compatible GitHub) let previewHTML = ''; if (typeof marked !== 'undefined') { previewHTML = marked.parse(markdownContent); } else { // Fallback vers notre parseur maison si Marked n'est pas chargé previewHTML = this.parseMarkdown(markdownContent); } // Désactiver l'édition et appliquer le style preview GitHub this.editor.contentEditable = false; this.editor.innerHTML = `
    ${previewHTML}
    `; this.editor.style.background = ''; this.editor.style.border = ''; this.editor.style.borderRadius = ''; // Traiter les diagrammes Mermaid après le rendu if (typeof mermaid !== 'undefined') { try { mermaid.initialize({ startOnLoad: false, theme: document.body.classList.contains('dark-theme') ? 'dark' : 'default', securityLevel: 'loose', fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif' }); // Attendre que le DOM soit prêt pour traiter les diagrammes Mermaid setTimeout(() => { const preview = this.editor.querySelector('.markdown-preview'); if (preview) { // Chercher tous les blocs de code avec la langue 'mermaid' const mermaidBlocks = preview.querySelectorAll('code.language-mermaid, pre code.language-mermaid'); mermaidBlocks.forEach((block, index) => { try { const mermaidCode = block.textContent; const uniqueId = `mermaid-${Date.now()}-${index}`; // Créer un div avec l'ID unique pour Mermaid const mermaidDiv = document.createElement('div'); mermaidDiv.id = uniqueId; mermaidDiv.className = 'mermaid'; mermaidDiv.textContent = mermaidCode; // Remplacer le bloc de code par le div Mermaid const pre = block.closest('pre') || block; pre.parentNode.replaceChild(mermaidDiv, pre); // Render le diagramme mermaid.render(uniqueId + '-svg', mermaidCode).then(({ svg }) => { mermaidDiv.innerHTML = svg; }).catch(err => { console.warn('Erreur rendu Mermaid:', err); mermaidDiv.innerHTML = `
    Erreur de rendu Mermaid: ${err.message}
    `; }); } catch (error) { console.warn('Erreur traitement Mermaid:', error); } }); } }, 200); } catch (error) { console.warn('Erreur initialisation Mermaid:', error); } } // Changer le bouton previewBtn.innerHTML = 'Éditer'; previewBtn.classList.remove('primary'); previewBtn.classList.add('secondary'); this.isPreviewMode = true; this.showNotification('Mode prévisualisation activé', 'success'); } else { // Revenir en mode édition this.editor.contentEditable = true; this.editor.innerHTML = this.originalContent; this.editor.style.background = ''; this.editor.style.border = ''; this.editor.style.borderRadius = ''; // Changer le bouton previewBtn.innerHTML = 'Visualiser'; previewBtn.classList.remove('secondary'); previewBtn.classList.add('primary'); this.isPreviewMode = false; this.showNotification('Mode édition activé', 'success'); } } } // Gestion des panneaux latéraux function togglePanel(side) { const panel = document.getElementById(`${side}-panel`); const overlay = document.getElementById('panel-overlay'); const otherPanel = document.getElementById(side === 'left' ? 'right-panel' : 'left-panel'); // Fermer l'autre panneau s'il est ouvert if (otherPanel && otherPanel.classList.contains('open')) { otherPanel.classList.remove('open'); } // Toggle le panneau actuel if (panel.classList.contains('open')) { panel.classList.remove('open'); overlay.classList.remove('active'); } else { panel.classList.add('open'); overlay.classList.add('active'); } } function closeAllPanels() { const leftPanel = document.getElementById('left-panel'); const rightPanel = document.getElementById('right-panel'); const overlay = document.getElementById('panel-overlay'); if (leftPanel) leftPanel.classList.remove('open'); if (rightPanel) rightPanel.classList.remove('open'); if (overlay) overlay.classList.remove('active'); } // Fermer les panneaux avec Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeAllPanels(); } }); // Initialisation de l'application let app; document.addEventListener('DOMContentLoaded', () => { app = new ConceptionAssistant(); // Charger le thème sauvegardé const savedTheme = localStorage.getItem('theme'); if (savedTheme === 'dark') { document.body.classList.add('dark-theme'); } // Initialiser les panneaux initializePanels(); }); function initializePanels() { // Initialiser la gestion des templates initializeTemplateForm(); } function initializeTemplateForm() { const domainSelect = document.getElementById('domain-select'); const levelSelect = document.getElementById('level-select'); const loadTemplateBtn = document.getElementById('load-template'); // Gestion du changement de domaine if (domainSelect) { domainSelect.addEventListener('change', async () => { const domain = domainSelect.value; if (!domain) { levelSelect.disabled = true; levelSelect.innerHTML = ''; loadTemplateBtn.disabled = true; return; } // Activer le select de niveau levelSelect.disabled = false; levelSelect.innerHTML = ` `; loadTemplateBtn.disabled = true; }); } // Gestion du changement de niveau if (levelSelect) { levelSelect.addEventListener('change', async () => { const domain = domainSelect.value; const level = levelSelect.value; if (!domain || !level) { loadTemplateBtn.disabled = true; return; } // Activer le bouton de chargement directement loadTemplateBtn.disabled = false; }); } // Gestion du chargement du template if (loadTemplateBtn) { loadTemplateBtn.addEventListener('click', async () => { const domain = domainSelect.value; const level = levelSelect.value; if (!domain || !level) { app.showNotification('Veuillez sélectionner un domaine et un niveau', 'warning'); return; } try { loadTemplateBtn.classList.add('loading'); const response = await fetch(`/api/templates/${domain}/${level}`); const result = await response.json(); if (result.success) { // Charger le template dans l'éditeur app.editor.innerText = result.data.content; app.generateTOC(); app.currentJournalId = null; // Nouveau journal app.showNotification(`Template ${domain}/${level} chargé avec succès`, 'success'); closeAllPanels(); } else { app.showNotification('Erreur lors du chargement du template', 'error'); } } catch (error) { console.error('Erreur:', error); app.showNotification('Erreur lors du chargement du template', 'error'); } finally { loadTemplateBtn.classList.remove('loading'); } }); } } // S'assurer que togglePanel est accessible globalement window.togglePanel = togglePanel;