From 45d6344b18a1239c09a5da27ce5bba64279fca4a Mon Sep 17 00:00:00 2001 From: Augustin Date: Thu, 25 Sep 2025 20:19:01 +0200 Subject: [PATCH] =?UTF-8?q?Am=C3=A9liorations=20majeures=20UX=20et=20templ?= =?UTF-8?q?ates=20professionnels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎯 Améliorations UX critiques : - Fix curseur qui revenait au début lors de la saisie - Suppression autosauvegarde automatique - Centrage flèche bouton scroll-to-top - Mode liberté applique automatiquement les itérations 🤖 IA optimisée : - Migration vers mistral-medium classique - Suppression raisonnement IA pour réponses directes - Prompt reformulation strict (texte seul) - Routes IA complètes fonctionnelles 📚 Templates professionnels complets : - Structure 12 sections selon standards académiques/industrie - 6 domaines : informatique, math, business, design, recherche, ingénierie - 3 niveaux : simple (9 sections), détaillé, complet (12 sections) - Méthodologies spécialisées par domaine ✨ Nouvelles fonctionnalités : - Debounce TOC pour performance saisie - Navigation sections améliorée - Sauvegarde/restauration position curseur 🧠 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app.js | 2 + assets/css/style.css | 287 +++- assets/js/app.js | 590 +++++++-- config/.env.example | 6 +- data/936a1104-7afa-438f-b352-aa1f9294132d.md | 1 + data/cb853f57-e578-42b4-8d39-bd71319d4d47.md | 88 ++ data/d8ec4a9b-4c0f-4ea8-b374-1c85c13fd954.md | 94 ++ data/uuid_map.json | 5 + package.json | 1 + routes/ai.js | 350 +++++ templates/business/complet.md | 304 +++++ templates/business/detaille.md | 148 +++ templates/business/simple.md | 153 ++- templates/design/complet.md | 319 +++++ templates/design/detaille.md | 154 +++ templates/design/simple.md | 154 ++- templates/informatique/complet.md | 1250 ++---------------- templates/informatique/detaille.md | 46 +- templates/informatique/simple.md | 135 +- templates/ingenierie/complet.md | 301 +++++ templates/ingenierie/detaille.md | 149 +++ templates/ingenierie/simple.md | 94 ++ templates/math/complet.md | 322 +++++ templates/math/detaille.md | 141 ++ templates/math/simple.md | 138 +- templates/recherche/complet.md | 258 ++++ templates/recherche/simple.md | 114 +- views/header.js | 14 - views/main.js | 72 +- views/page.js | 2 + 30 files changed, 4010 insertions(+), 1682 deletions(-) create mode 100644 data/936a1104-7afa-438f-b352-aa1f9294132d.md create mode 100644 data/cb853f57-e578-42b4-8d39-bd71319d4d47.md create mode 100644 data/d8ec4a9b-4c0f-4ea8-b374-1c85c13fd954.md create mode 100644 data/uuid_map.json create mode 100644 routes/ai.js create mode 100644 templates/business/complet.md create mode 100644 templates/business/detaille.md create mode 100644 templates/design/complet.md create mode 100644 templates/design/detaille.md create mode 100644 templates/ingenierie/complet.md create mode 100644 templates/ingenierie/detaille.md create mode 100644 templates/ingenierie/simple.md create mode 100644 templates/math/complet.md create mode 100644 templates/math/detaille.md create mode 100644 templates/recherche/complet.md diff --git a/app.js b/app.js index 54c063a..c59a46b 100644 --- a/app.js +++ b/app.js @@ -5,6 +5,7 @@ const indexRoutes = require('./routes/index'); const apiRoutes = require('./routes/api'); const templatesRoutes = require('./routes/templates'); const exportRoutes = require('./routes/export'); +const aiRoutes = require('./routes/ai'); const app = express(); const port = process.env.PORT || 3000; @@ -25,6 +26,7 @@ app.use('/', indexRoutes); app.use('/api', apiRoutes); app.use('/api/templates', templatesRoutes); app.use('/api/export', exportRoutes); +app.use('/api/ai', aiRoutes); app.listen(port, () => { console.log(`Server running on port ${port}`); diff --git a/assets/css/style.css b/assets/css/style.css index 84d1a4f..f31f88a 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -63,7 +63,7 @@ header > div { display: flex; align-items: center; justify-content: center; - padding: 2rem; + padding: 1.2rem; max-width: 1400px; margin: 0 auto; } @@ -94,7 +94,7 @@ header h1::before { border: 1px solid var(--border-color); border-radius: var(--border-radius); box-shadow: var(--shadow-hover); - z-index: 90; + z-index: 1000; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(10px); } @@ -159,7 +159,7 @@ header h1::before { color: var(--text-color); transition: all 0.3s ease; box-shadow: var(--shadow); - z-index: 91; + z-index: 1001; } .left-panel .panel-toggle { @@ -248,24 +248,6 @@ header h1::before { background: var(--background-color); } -.template-preview { - padding: 1rem; - background: var(--background-color); - border-radius: var(--border-radius); - border: 1px solid var(--border-color); -} - -.template-preview h4 { - margin: 0 0 0.75rem 0; - color: var(--primary-color); - font-size: 0.95rem; -} - -.preview-content { - font-size: 0.85rem; - color: var(--text-light); - line-height: 1.4; -} .form-actions { display: flex; @@ -377,27 +359,6 @@ header h1::before { visibility: visible; } -#skeleton-select { - flex: 1; - padding: 0.75rem 1rem; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - background: var(--surface-color); - color: var(--text-color); - font-size: 0.9rem; - cursor: pointer; - transition: var(--transition); -} - -#skeleton-select:hover { - border-color: var(--secondary-color); -} - -#skeleton-select:focus { - outline: none; - border-color: var(--secondary-color); - box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); -} /* Responsive design */ @media (max-width: 768px) { @@ -449,11 +410,7 @@ header h1::before { padding: 1rem; } - #table-of-contents, - #ai-assistant { - position: static; - max-height: none; - } + /* Sections déjà en mode normal */ } @media (max-width: 480px) { @@ -517,14 +474,15 @@ header button:hover, header .btn:hover { } button.secondary { - background: rgba(255,255,255,0.9); - color: var(--primary-color); - border: 1px solid rgba(255,255,255,0.3); + background: var(--surface-color); + color: var(--text-color); + border: 1px solid var(--border-color); } button.secondary:hover { - background: white; + background: var(--background-color); color: var(--secondary-color); + border-color: var(--secondary-color); } button.danger { @@ -535,6 +493,7 @@ button.danger:hover { background: #c0392b; } + button.success { background: var(--success-color); } @@ -614,10 +573,7 @@ section h2 { /* Table des mati�res */ #table-of-contents { - position: sticky; - top: 100px; - max-height: calc(100vh - 120px); - overflow-y: auto; + /* Comportement normal de scroll */ } #toc-nav { @@ -718,11 +674,9 @@ section h2 { /* Assistant IA */ #ai-assistant { - position: sticky; - top: 100px; - max-height: calc(100vh - 120px); display: flex; flex-direction: column; + /* Comportement normal de scroll */ } /* Styles pour "Coming Soon" */ @@ -860,7 +814,7 @@ footer a:hover { color: var(--secondary-color); } -/* �tats de chargement */ +/* états de chargement */ .loading { position: relative; opacity: 0.7; @@ -955,7 +909,7 @@ footer a:hover { background: var(--text-light); } -/* S�lection de texte */ +/* Sélection de texte */ ::selection { background: var(--secondary-color); color: white; @@ -969,6 +923,219 @@ select:focus-visible { outline-offset: 2px; } +/* Modal */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; + backdrop-filter: blur(5px); +} + +.modal-content { + background: var(--surface-color); + border-radius: var(--border-radius); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + width: 90%; + max-width: 600px; + max-height: 80vh; + display: flex; + flex-direction: column; + border: 1px solid var(--border-color); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem; + border-bottom: 1px solid var(--border-color); + background: var(--background-color); + border-radius: var(--border-radius) var(--border-radius) 0 0; +} + +.modal-header h3 { + margin: 0; + color: var(--primary-color); + font-size: 1.2rem; + font-weight: 600; +} + +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--text-light); + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: var(--transition); + padding: 0; +} + +.modal-close:hover { + background: var(--accent-color); + color: white; + transform: scale(1.1); +} + +.modal-body { + padding: 1.5rem; + overflow-y: auto; + flex: 1; +} + +.journal-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.journal-item-container { + margin-bottom: 0; +} + +.journal-item { + width: 100%; + text-align: left; + padding: 1rem; + transition: var(--transition); + border: 1px solid var(--border-color); + background: var(--surface-color); +} + +.journal-item:hover { + border-color: var(--secondary-color); + background: var(--surface-color); + transform: translateY(-1px); + box-shadow: var(--shadow-hover); +} + +.journal-preview { + display: flex; + flex-direction: column; +} + +.journal-preview strong { + color: var(--primary-color); + margin-bottom: 0.5rem; + display: block; +} + +.journal-preview div { + font-size: 0.85rem; + color: var(--text-light); + white-space: pre-wrap; + line-height: 1.4; +} + +/* Animation d'apparition du modal */ +.modal-overlay.show { + animation: modalFadeIn 0.3s ease-out; +} + +.modal-overlay.show .modal-content { + animation: modalSlideIn 0.3s ease-out; +} + +@keyframes modalFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* Responsive modal */ +@media (max-width: 768px) { + .modal-content { + width: 95%; + max-height: 90vh; + margin: 1rem; + } + + .modal-header, .modal-body { + padding: 1rem; + } + + .modal-header h3 { + font-size: 1.1rem; + } +} + +/* Bouton Scroll to Top */ +.scroll-to-top { + position: fixed; + bottom: 2rem; + right: 2rem; + width: 50px; + height: 50px; + background: var(--secondary-color); + color: white; + border: none; + border-radius: 50%; + font-size: 1.2rem; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + transition: all 0.3s ease; + z-index: 1500; + opacity: 0; + visibility: hidden; + transform: translateY(20px); + display: flex; + align-items: center; + justify-content: center; +} + +.scroll-to-top.visible { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.scroll-to-top:hover { + background: var(--primary-color); + transform: translateY(-2px) scale(1.05); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3); +} + +.scroll-to-top:active { + transform: translateY(0) scale(0.95); +} + +/* Responsive scroll to top */ +@media (max-width: 768px) { + .scroll-to-top { + bottom: 1rem; + right: 1rem; + width: 45px; + height: 45px; + font-size: 1rem; + } +} + /* Utilitaires */ .hidden { display: none !important; diff --git a/assets/js/app.js b/assets/js/app.js index 21f47c9..14d5195 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -3,14 +3,15 @@ class ConceptionAssistant { constructor() { this.currentJournalId = null; this.editor = null; - this.autoSaveTimer = null; + this.undoStack = []; + this.redoStack = []; + this.tocTimer = null; this.init(); } async init() { this.setupEditor(); this.setupEventListeners(); - this.setupAutoSave(); await this.loadJournalList(); this.generateTOC(); } @@ -18,10 +19,9 @@ class ConceptionAssistant { setupEditor() { this.editor = document.getElementById('journal-editor'); - // Générer TOC à chaque modification + // Générer TOC avec debounce pour éviter de perturber la saisie this.editor.addEventListener('input', () => { - this.generateTOC(); - this.resetAutoSave(); + this.debounceTOC(); }); // Gestion des raccourcis clavier @@ -32,12 +32,29 @@ class ConceptionAssistant { 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() { @@ -55,7 +72,6 @@ class ConceptionAssistant { // Export/Import document.getElementById('export-md')?.addEventListener('click', () => this.exportMarkdown()); - document.getElementById('export-pdf')?.addEventListener('click', () => this.exportPDF()); document.getElementById('import-md')?.addEventListener('change', (e) => this.importMarkdown(e)); // Thème @@ -67,40 +83,114 @@ class ConceptionAssistant { 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')); - } - setupAutoSave() { - // Sauvegarde automatique toutes les 30 secondes - setInterval(() => { - if (this.currentJournalId && this.editor.innerText.trim()) { - this.saveJournal(true); + // 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(); } - }, 30000); + }); + + // Bouton scroll to top + document.getElementById('scroll-to-top')?.addEventListener('click', () => this.scrollToTop()); + + // Gestion de l'affichage du bouton scroll to top + this.setupScrollToTop(); } - resetAutoSave() { - if (this.autoSaveTimer) { - clearTimeout(this.autoSaveTimer); + 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.autoSaveTimer = setTimeout(() => { - if (this.currentJournalId && this.editor.innerText.trim()) { - this.saveJournal(true); + 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; } - }, 3000); + + 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é } - async saveJournal(isAutoSave = false) { + 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'); - if (!isAutoSave) { - saveBtn.classList.add('loading'); - statusEl.textContent = 'Sauvegarde...'; - } + saveBtn.classList.add('loading'); + statusEl.textContent = 'Sauvegarde...'; try { let response; @@ -128,28 +218,19 @@ class ConceptionAssistant { this.currentJournalId = result.data.id; } - if (!isAutoSave) { - statusEl.textContent = '✅ Sauvegardé'; - this.showNotification('Journal sauvegardé avec succès', 'success'); - setTimeout(() => statusEl.textContent = '', 3000); - } else { - statusEl.textContent = '💾 Auto-sauvegardé'; - setTimeout(() => statusEl.textContent = '', 2000); - } + 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'; - if (!isAutoSave) { - this.showNotification('Erreur lors de la sauvegarde: ' + error.message, 'error'); - } + this.showNotification('Erreur lors de la sauvegarde: ' + error.message, 'error'); setTimeout(() => statusEl.textContent = '', 3000); } finally { - if (!isAutoSave) { - saveBtn.classList.remove('loading'); - } + saveBtn.classList.remove('loading'); } } @@ -193,26 +274,43 @@ class ConceptionAssistant { if (result.success) { const journalList = result.data.map(journal => { - const preview = journal.markdownContent.substring(0, 100).replace(/\n/g, ' '); - return ``; + // 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 feedback = document.getElementById('ai-assistant-feedback'); - feedback.innerHTML = ` - @@ -84,10 +74,6 @@ function getRightPanel() {