Amélioration complète de l'application web Journal de Conception
- Refonte complète du design avec système de panneaux latéraux rétractables - Ajout de templates de projets par domaine (recherche, informatique, mathématiques, etc.) - Implémentation système d'export PDF avec Puppeteer - Amélioration de l'API REST avec nouvelles routes d'export et templates - Ajout de JavaScript client pour interactions dynamiques - Configuration environnement étendue pour futures fonctionnalités IA - Amélioration responsive design et expérience utilisateur 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
750e2c4fe8
commit
4a8b6d1cf8
11
app.js
11
app.js
@ -3,6 +3,8 @@ const path = require('path');
|
|||||||
|
|
||||||
const indexRoutes = require('./routes/index');
|
const indexRoutes = require('./routes/index');
|
||||||
const apiRoutes = require('./routes/api');
|
const apiRoutes = require('./routes/api');
|
||||||
|
const templatesRoutes = require('./routes/templates');
|
||||||
|
const exportRoutes = require('./routes/export');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
@ -12,8 +14,17 @@ app.use(express.urlencoded({ extended: true }));
|
|||||||
|
|
||||||
app.use('/assets', express.static(path.join(__dirname, 'assets')));
|
app.use('/assets', express.static(path.join(__dirname, 'assets')));
|
||||||
|
|
||||||
|
// Créer le dossier data s'il n'existe pas
|
||||||
|
const fs = require('fs');
|
||||||
|
const dataDir = path.join(__dirname, 'data');
|
||||||
|
if (!fs.existsSync(dataDir)) {
|
||||||
|
fs.mkdirSync(dataDir);
|
||||||
|
}
|
||||||
|
|
||||||
app.use('/', indexRoutes);
|
app.use('/', indexRoutes);
|
||||||
app.use('/api', apiRoutes);
|
app.use('/api', apiRoutes);
|
||||||
|
app.use('/api/templates', templatesRoutes);
|
||||||
|
app.use('/api/export', exportRoutes);
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server running on port ${port}`);
|
console.log(`Server running on port ${port}`);
|
||||||
|
@ -0,0 +1,998 @@
|
|||||||
|
/* Variables CSS */
|
||||||
|
:root {
|
||||||
|
--primary-color: #2c3e50;
|
||||||
|
--secondary-color: #3498db;
|
||||||
|
--accent-color: #e74c3c;
|
||||||
|
--success-color: #27ae60;
|
||||||
|
--warning-color: #f39c12;
|
||||||
|
--background-color: #ecf0f1;
|
||||||
|
--surface-color: #ffffff;
|
||||||
|
--text-color: #2c3e50;
|
||||||
|
--text-light: #7f8c8d;
|
||||||
|
--border-color: #bdc3c7;
|
||||||
|
--shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
--shadow-hover: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
|
--border-radius: 8px;
|
||||||
|
--transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode sombre */
|
||||||
|
body.dark-theme {
|
||||||
|
--primary-color: #ecf0f1;
|
||||||
|
--secondary-color: #3498db;
|
||||||
|
--background-color: #1a1a1a;
|
||||||
|
--surface-color: #2c2c2c;
|
||||||
|
--text-color: #ecf0f1;
|
||||||
|
--text-light: #95a5a6;
|
||||||
|
--border-color: #34495e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset et base */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout principal */
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header professionnel */
|
||||||
|
header {
|
||||||
|
background: var(--surface-color);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1::before {
|
||||||
|
content: "📝";
|
||||||
|
font-size: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panneaux latéraux rétractables */
|
||||||
|
.side-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: var(--surface-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
z-index: 90;
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panneau gauche - Configuration */
|
||||||
|
.left-panel {
|
||||||
|
left: -320px;
|
||||||
|
width: 320px;
|
||||||
|
border-left: none;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel.open {
|
||||||
|
transform: translateY(-50%) translateX(320px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panneau droit - Navigation */
|
||||||
|
.right-panel {
|
||||||
|
right: -320px;
|
||||||
|
width: 320px;
|
||||||
|
border-right: none;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel.open {
|
||||||
|
transform: translateY(-50%) translateX(-320px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contenu des panneaux */
|
||||||
|
.panel-content {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flèches d'ouverture */
|
||||||
|
.panel-toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 40px;
|
||||||
|
height: 80px;
|
||||||
|
background: var(--surface-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
z-index: 91;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel .panel-toggle {
|
||||||
|
right: -40px;
|
||||||
|
border-left: none;
|
||||||
|
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel .panel-toggle {
|
||||||
|
left: -40px;
|
||||||
|
border-right: none;
|
||||||
|
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-toggle:hover {
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-50%) scale(1.05);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-toggle:active {
|
||||||
|
transform: translateY(-50%) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rotation de la flèche selon l'état */
|
||||||
|
.left-panel .panel-toggle .arrow {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel.open .panel-toggle .arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel .panel-toggle .arrow {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel.open .panel-toggle .arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles pour le formulaire de templates */
|
||||||
|
.template-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-light);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group select {
|
||||||
|
padding: 0.75rem;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group select:hover:not(:disabled) {
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group select:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
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;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions button {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles spécifiques au panneau de navigation */
|
||||||
|
.nav-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-section h4 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-light);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn:hover {
|
||||||
|
background: var(--background-color);
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
color: var(--secondary-color);
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn .icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File input dans le panneau */
|
||||||
|
.panel-file-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: left;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-file-input:hover {
|
||||||
|
background: var(--background-color);
|
||||||
|
border-color: var(--warning-color);
|
||||||
|
color: var(--warning-color);
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-file-input .icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation d'overlay quand les panneaux sont ouverts */
|
||||||
|
.panel-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 89;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
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) {
|
||||||
|
.side-panel {
|
||||||
|
width: 280px;
|
||||||
|
top: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
left: -280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel.open {
|
||||||
|
transform: translateY(-60%) translateX(280px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
right: -280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel.open {
|
||||||
|
transform: translateY(-60%) translateX(-280px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-toggle {
|
||||||
|
width: 35px;
|
||||||
|
height: 70px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1::before {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > div {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-of-contents,
|
||||||
|
#ai-assistant {
|
||||||
|
position: static;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.side-panel {
|
||||||
|
width: calc(100vw - 40px);
|
||||||
|
left: -100vw;
|
||||||
|
right: -100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel.open {
|
||||||
|
transform: translateY(-60%) translateX(calc(100vw - 20px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel.open {
|
||||||
|
transform: translateY(-60%) translateX(calc(-100vw + 20px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Boutons */
|
||||||
|
button, .btn {
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover, .btn:hover {
|
||||||
|
background: #2980b9;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active, .btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Boutons dans le header */
|
||||||
|
header button, header .btn {
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
color: var(--primary-color);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
header button:hover, header .btn:hover {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary {
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
color: var(--primary-color);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary:hover {
|
||||||
|
background: white;
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.danger {
|
||||||
|
background: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.danger:hover {
|
||||||
|
background: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.success {
|
||||||
|
background: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.success:hover {
|
||||||
|
background: #229954;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input file personnalis<69> */
|
||||||
|
.file-input-label {
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: var(--warning-color);
|
||||||
|
color: white;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input-label:hover {
|
||||||
|
background: #e67e22;
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main content */
|
||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 1fr 350px;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
min-height: calc(100vh - 140px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
main {
|
||||||
|
grid-template-columns: 250px 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ai-assistant {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
main {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections */
|
||||||
|
section {
|
||||||
|
background: var(--surface-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
section h2 {
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table des mati<74>res */
|
||||||
|
#table-of-contents {
|
||||||
|
position: sticky;
|
||||||
|
top: 100px;
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-nav {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-nav ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-nav li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-nav a {
|
||||||
|
color: var(--text-color);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
transition: var(--transition);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-nav a:hover {
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-nav ul ul {
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toc-nav ul ul a {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zone d'<27>criture */
|
||||||
|
#design-journal {
|
||||||
|
min-height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor {
|
||||||
|
min-height: 500px;
|
||||||
|
padding: 2rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
background: var(--surface-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
resize: vertical;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor:focus {
|
||||||
|
background: var(--background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor:empty::before {
|
||||||
|
content: "Commencez à écrire votre journal de conception...\A\A# Titre de votre projet\A\A## Contexte et objectifs\A\ADécrivez ici le contexte de votre projet...\A\A## Architecture\A\A### Composants principaux\A\A### Technologies utilisées\A\A## Décisions de conception\A\A### Décision 1\A\A**Problème :**\A**Options considérées :**\A**Décision :**\A**Justification :**\A\A## Prochaines étapes\A\A- [ ] Tâche 1\A- [ ] Tâche 2";
|
||||||
|
color: var(--text-light);
|
||||||
|
font-style: italic;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor h1, #journal-editor h2, #journal-editor h3 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin: 1.5rem 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
border-bottom: 3px solid var(--secondary-color);
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor h2 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
padding-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#journal-editor h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assistant IA */
|
||||||
|
#ai-assistant {
|
||||||
|
position: sticky;
|
||||||
|
top: 100px;
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles pour "Coming Soon" */
|
||||||
|
.ai-coming-soon {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: linear-gradient(135deg, var(--background-color), var(--surface-color));
|
||||||
|
}
|
||||||
|
|
||||||
|
.coming-soon-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, var(--warning-color), #f39c12);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
box-shadow: 0 2px 8px rgba(243, 156, 18, 0.3);
|
||||||
|
animation: glow 2s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
from { box-shadow: 0 2px 8px rgba(243, 156, 18, 0.3); }
|
||||||
|
to { box-shadow: 0 4px 16px rgba(243, 156, 18, 0.5); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
animation: bounce 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-2px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-preview h3 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-features-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-features-list li {
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-features-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tech-stack {
|
||||||
|
background: var(--surface-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tech-stack p {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tech-stack strong {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ai-assistant-feedback {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
background: var(--background-color);
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-message {
|
||||||
|
background: var(--surface-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coming-soon-message {
|
||||||
|
background: linear-gradient(135deg, var(--surface-color), var(--background-color));
|
||||||
|
border: 2px dashed var(--warning-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-message.loading {
|
||||||
|
background: var(--background-color);
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
animation: pulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer {
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <20>tats de chargement */
|
||||||
|
.loading {
|
||||||
|
position: relative;
|
||||||
|
opacity: 0.7;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: -10px 0 0 -10px;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-top: 2px solid var(--secondary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notifications */
|
||||||
|
.notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
background: var(--success-color);
|
||||||
|
color: white;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
z-index: 1000;
|
||||||
|
transform: translateX(400px);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.show {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.error {
|
||||||
|
background: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.warning {
|
||||||
|
background: var(--warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
header > div {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-controls {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, .btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbars personnalis<69>es */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* S<>lection de texte */
|
||||||
|
::selection {
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus outline */
|
||||||
|
button:focus-visible,
|
||||||
|
input:focus-visible,
|
||||||
|
select:focus-visible {
|
||||||
|
outline: 2px solid var(--secondary-color);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilitaires */
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-1 { margin-bottom: 0.5rem; }
|
||||||
|
.mb-2 { margin-bottom: 1rem; }
|
||||||
|
.mb-3 { margin-bottom: 1.5rem; }
|
||||||
|
|
||||||
|
.mt-1 { margin-top: 0.5rem; }
|
||||||
|
.mt-2 { margin-top: 1rem; }
|
||||||
|
.mt-3 { margin-top: 1.5rem; }
|
583
assets/js/app.js
Normal file
583
assets/js/app.js
Normal file
@ -0,0 +1,583 @@
|
|||||||
|
// Application principale
|
||||||
|
class ConceptionAssistant {
|
||||||
|
constructor() {
|
||||||
|
this.currentJournalId = null;
|
||||||
|
this.editor = null;
|
||||||
|
this.autoSaveTimer = null;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.setupEditor();
|
||||||
|
this.setupEventListeners();
|
||||||
|
this.setupAutoSave();
|
||||||
|
await this.loadJournalList();
|
||||||
|
this.generateTOC();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEditor() {
|
||||||
|
this.editor = document.getElementById('journal-editor');
|
||||||
|
|
||||||
|
// Générer TOC à chaque modification
|
||||||
|
this.editor.addEventListener('input', () => {
|
||||||
|
this.generateTOC();
|
||||||
|
this.resetAutoSave();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestion des raccourcis clavier
|
||||||
|
this.editor.addEventListener('keydown', (e) => {
|
||||||
|
// Ctrl+S pour sauvegarder
|
||||||
|
if (e.ctrlKey && e.key === 's') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.saveJournal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab pour indentation
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
document.execCommand('insertText', false, ' ');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
// Table des matières
|
||||||
|
document.getElementById('refresh-toc')?.addEventListener('click', () => this.generateTOC());
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAutoSave() {
|
||||||
|
// Sauvegarde automatique toutes les 30 secondes
|
||||||
|
setInterval(() => {
|
||||||
|
if (this.currentJournalId && this.editor.innerText.trim()) {
|
||||||
|
this.saveJournal(true);
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAutoSave() {
|
||||||
|
if (this.autoSaveTimer) {
|
||||||
|
clearTimeout(this.autoSaveTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.autoSaveTimer = setTimeout(() => {
|
||||||
|
if (this.currentJournalId && this.editor.innerText.trim()) {
|
||||||
|
this.saveJournal(true);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveJournal(isAutoSave = false) {
|
||||||
|
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...';
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} 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');
|
||||||
|
}
|
||||||
|
setTimeout(() => statusEl.textContent = '', 3000);
|
||||||
|
} finally {
|
||||||
|
if (!isAutoSave) {
|
||||||
|
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 => {
|
||||||
|
const preview = journal.markdownContent.substring(0, 100).replace(/\n/g, ' ');
|
||||||
|
return `<button class="journal-item btn secondary mb-1" data-id="${journal.id}">${preview}...</button>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
const feedback = document.getElementById('ai-assistant-feedback');
|
||||||
|
feedback.innerHTML = `
|
||||||
|
<div class="feedback-message">
|
||||||
|
<h3>📂 Sélectionner un journal</h3>
|
||||||
|
<div class="journal-list">
|
||||||
|
<button class="btn mb-2" onclick="app.createNewJournal()">+ Nouveau journal</button>
|
||||||
|
${journalList}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Ajouter les event listeners
|
||||||
|
document.querySelectorAll('.journal-item').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
this.loadJournal(btn.dataset.id);
|
||||||
|
this.clearFeedback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
this.showNotification('Erreur lors du chargement de la liste', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewJournal() {
|
||||||
|
this.currentJournalId = null;
|
||||||
|
this.editor.innerText = '';
|
||||||
|
this.generateTOC();
|
||||||
|
this.clearFeedback();
|
||||||
|
this.showNotification('Nouveau journal créé', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTOC() {
|
||||||
|
const content = this.editor.innerText;
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const toc = [];
|
||||||
|
let tocHtml = '';
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toc.length > 0) {
|
||||||
|
tocHtml = '<ul>';
|
||||||
|
let currentLevel = 0;
|
||||||
|
|
||||||
|
for (const item of toc) {
|
||||||
|
if (item.level > currentLevel) {
|
||||||
|
for (let i = currentLevel; i < item.level - 1; i++) {
|
||||||
|
tocHtml += '<ul>';
|
||||||
|
}
|
||||||
|
} else if (item.level < currentLevel) {
|
||||||
|
for (let i = item.level; i < currentLevel; i++) {
|
||||||
|
tocHtml += '</ul>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tocHtml += `<li><a href="#${item.id}" onclick="app.scrollToHeading('${item.id}')">${item.title}</a></li>`;
|
||||||
|
currentLevel = item.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
tocHtml += '</ul>';
|
||||||
|
} else {
|
||||||
|
tocHtml = '<div class="toc-placeholder"><p>Ajoutez des titres (# ## ###) à votre journal pour générer la table des matières.</p></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('toc-nav').innerHTML = tocHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToHeading(headingId) {
|
||||||
|
// Simulation du scroll vers les titres
|
||||||
|
this.showNotification('Navigation vers la section', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
exportPDF() {
|
||||||
|
// Simulation export PDF (nécessite une vraie implémentation côté serveur)
|
||||||
|
this.showNotification('Export PDF : fonctionnalité à venir', 'warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAI(action) {
|
||||||
|
const selection = window.getSelection().toString();
|
||||||
|
let message = '';
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'rephrase':
|
||||||
|
if (!selection) {
|
||||||
|
message = '⚠️ Sélectionnez du texte à reformuler';
|
||||||
|
} else {
|
||||||
|
message = `✨ Reformulation suggérée pour:<br><em>"${selection.substring(0, 100)}..."</em><br><br><strong>Version reformulée :</strong><br>"${this.simulateRephrase(selection)}"`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'inconsistencies':
|
||||||
|
message = '🔍 Analyse des incohérences...<br><br>✅ Aucune incohérence majeure détectée dans le document.';
|
||||||
|
break;
|
||||||
|
case 'duplications':
|
||||||
|
message = '📋 Vérification des duplications...<br><br>✅ Aucune duplication significative trouvée.';
|
||||||
|
break;
|
||||||
|
case 'advice':
|
||||||
|
message = '💡 Conseils pour améliorer votre journal :<br><br>• Ajoutez plus de détails sur les alternatives considérées<br>• Documentez les raisons des choix techniques<br>• Incluez des diagrammes ou schémas<br>• Ajoutez une section "Lessons learned"';
|
||||||
|
break;
|
||||||
|
case 'liberty':
|
||||||
|
const count = document.getElementById('liberty-repeat-count').value || 3;
|
||||||
|
message = `🚀 Mode Liberté activé (${count} itérations)<br><br>Génération de contenus automatiques basée sur le contexte existant...<br><br><em>Cette fonctionnalité nécessite une intégration IA complète.</em>`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showAIFeedback(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
simulateRephrase(text) {
|
||||||
|
// Simulation simple de reformulation
|
||||||
|
const replacements = {
|
||||||
|
'nous devons': 'il convient de',
|
||||||
|
'c\'est important': 'cela revêt une importance',
|
||||||
|
'il faut': 'il est nécessaire de',
|
||||||
|
'très': 'particulièrement',
|
||||||
|
'beaucoup': 'considérablement'
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = text;
|
||||||
|
Object.entries(replacements).forEach(([from, to]) => {
|
||||||
|
result = result.replace(new RegExp(from, 'gi'), to);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result || text;
|
||||||
|
}
|
||||||
|
|
||||||
|
showAIFeedback(message) {
|
||||||
|
const feedback = document.getElementById('ai-assistant-feedback');
|
||||||
|
feedback.innerHTML = `<div class="feedback-message">${message}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFeedback() {
|
||||||
|
const feedback = document.getElementById('ai-assistant-feedback');
|
||||||
|
feedback.innerHTML = `
|
||||||
|
<div class="feedback-message">
|
||||||
|
<strong>🎯 Assistant prêt</strong><br>
|
||||||
|
Sélectionnez du texte dans l'éditeur et cliquez sur une action pour commencer.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
const resetTemplateBtn = document.getElementById('reset-template');
|
||||||
|
const templatePreview = document.getElementById('template-preview');
|
||||||
|
const previewContent = document.getElementById('preview-content');
|
||||||
|
|
||||||
|
// Gestion du changement de domaine
|
||||||
|
if (domainSelect) {
|
||||||
|
domainSelect.addEventListener('change', async () => {
|
||||||
|
const domain = domainSelect.value;
|
||||||
|
|
||||||
|
if (!domain) {
|
||||||
|
levelSelect.disabled = true;
|
||||||
|
levelSelect.innerHTML = '<option value="">-- Choisir d\'abord un domaine --</option>';
|
||||||
|
loadTemplateBtn.disabled = true;
|
||||||
|
templatePreview.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activer le select de niveau
|
||||||
|
levelSelect.disabled = false;
|
||||||
|
levelSelect.innerHTML = `
|
||||||
|
<option value="">-- Choisir le niveau --</option>
|
||||||
|
<option value="simple">📄 Simple</option>
|
||||||
|
<option value="detaille">📋 Détaillé</option>
|
||||||
|
<option value="complet">📚 Complet</option>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Cacher l'aperçu
|
||||||
|
templatePreview.style.display = 'none';
|
||||||
|
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) {
|
||||||
|
templatePreview.style.display = 'none';
|
||||||
|
loadTemplateBtn.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Charger l'aperçu du template
|
||||||
|
const response = await fetch(`/api/templates/${domain}/${level}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Afficher un aperçu (premières lignes)
|
||||||
|
const lines = result.data.content.split('\n');
|
||||||
|
const preview = lines.slice(0, 8).join('\n');
|
||||||
|
|
||||||
|
previewContent.textContent = preview + (lines.length > 8 ? '\n...' : '');
|
||||||
|
templatePreview.style.display = 'block';
|
||||||
|
loadTemplateBtn.disabled = false;
|
||||||
|
} else {
|
||||||
|
app.showNotification('Erreur lors du chargement de l\'aperçu', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
app.showNotification('Erreur lors du chargement de l\'aperçu', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion du reset
|
||||||
|
if (resetTemplateBtn) {
|
||||||
|
resetTemplateBtn.addEventListener('click', () => {
|
||||||
|
domainSelect.value = '';
|
||||||
|
levelSelect.value = '';
|
||||||
|
levelSelect.disabled = true;
|
||||||
|
levelSelect.innerHTML = '<option value="">-- Choisir d\'abord un domaine --</option>';
|
||||||
|
templatePreview.style.display = 'none';
|
||||||
|
loadTemplateBtn.disabled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,21 @@
|
|||||||
# Server Configuration
|
# Server Configuration
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
|
# Mistral AI Configuration
|
||||||
|
MISTRAL_API_KEY=your_mistral_api_key_here
|
||||||
|
MISTRAL_MODEL=mistral-large-latest
|
||||||
|
MISTRAL_BASE_URL=https://api.mistral.ai/v1
|
||||||
|
|
||||||
|
# AI Features Configuration
|
||||||
|
AI_ENABLED=false
|
||||||
|
AI_MAX_TOKENS=4000
|
||||||
|
AI_TEMPERATURE=0.7
|
||||||
|
AI_TOP_P=0.95
|
||||||
|
|
||||||
|
# Rate Limiting for AI
|
||||||
|
AI_RATE_LIMIT_REQUESTS=10
|
||||||
|
AI_RATE_LIMIT_WINDOW=60000
|
||||||
|
|
||||||
|
# PDF Export Configuration
|
||||||
|
PDF_MAX_SIZE=10485760
|
||||||
|
PDF_TIMEOUT=30000
|
@ -7,7 +7,8 @@
|
|||||||
"test": "node tests/all.test.js"
|
"test": "node tests/all.test.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"uuid": "^13.0.0",
|
"express": "^4.18.2",
|
||||||
"express": "^4.18.2"
|
"puppeteer": "^24.22.3",
|
||||||
|
"uuid": "^13.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
|
// Importer le module d'export
|
||||||
|
const exportRouter = require('./export');
|
||||||
|
|
||||||
function modifMd(id, modifications) {
|
function modifMd(id, modifications) {
|
||||||
if (id === undefined) throw new Error('id obligatoire');
|
if (id === undefined) throw new Error('id obligatoire');
|
||||||
if (!Array.isArray(modifications) || modifications.length === 0) throw new Error('modifications requises');
|
if (!Array.isArray(modifications) || modifications.length === 0) throw new Error('modifications requises');
|
||||||
@ -101,7 +104,7 @@ function readMd(id = undefined) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function modifMd(id, newMarkdownContent) {
|
function updateMd(id, newMarkdownContent) {
|
||||||
if (id === undefined) throw new Error('id obligatoire');
|
if (id === undefined) throw new Error('id obligatoire');
|
||||||
const dataDir = path.resolve(__dirname, '../data');
|
const dataDir = path.resolve(__dirname, '../data');
|
||||||
const mapPath = path.join(dataDir, 'uuid_map.json');
|
const mapPath = path.join(dataDir, 'uuid_map.json');
|
||||||
@ -112,7 +115,7 @@ function modifMd(id, newMarkdownContent) {
|
|||||||
if (!uuid) throw new Error(`Aucun fichier trouvé pour l'id ${id}`);
|
if (!uuid) throw new Error(`Aucun fichier trouvé pour l'id ${id}`);
|
||||||
|
|
||||||
const mdPath = path.join(dataDir, `${uuid}.md`);
|
const mdPath = path.join(dataDir, `${uuid}.md`);
|
||||||
if (!fs.existsSync(mdPath)) throw new Error('Le fichier markdown n’existe pas');
|
if (!fs.existsSync(mdPath)) throw new Error('Le fichier markdown n\'existe pas');
|
||||||
fs.writeFileSync(mdPath, newMarkdownContent, { encoding: 'utf8', flag: 'w' });
|
fs.writeFileSync(mdPath, newMarkdownContent, { encoding: 'utf8', flag: 'w' });
|
||||||
return { id, uuid, path: mdPath, newMarkdownContent };
|
return { id, uuid, path: mdPath, newMarkdownContent };
|
||||||
}
|
}
|
||||||
@ -166,12 +169,33 @@ router.get('/journals/:id', (req, res) => {
|
|||||||
// PUT /api/journals/:id - Mettre à jour un journal
|
// PUT /api/journals/:id - Mettre à jour un journal
|
||||||
router.put('/journals/:id', (req, res) => {
|
router.put('/journals/:id', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { start, end, content } = req.body;
|
const { content, modifications } = req.body;
|
||||||
|
|
||||||
res.json({
|
try {
|
||||||
success: true,
|
let result;
|
||||||
data: modifMd(id, {start, end, content})
|
if (content) {
|
||||||
});
|
// Mise à jour complète du contenu
|
||||||
|
result = updateMd(id, content);
|
||||||
|
} else if (modifications) {
|
||||||
|
// Modifications partielles (pour compatibilité future)
|
||||||
|
result = modifMd(id, modifications);
|
||||||
|
} else {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Content ou modifications requis'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: result
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// DELETE /api/journals/:id - Supprimer un journal
|
// DELETE /api/journals/:id - Supprimer un journal
|
||||||
@ -184,4 +208,7 @@ router.delete('/journals/:id', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Intégrer les routes d'export
|
||||||
|
router.use('/export', exportRouter);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
512
routes/export.js
Normal file
512
routes/export.js
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// POST /api/export/pdf - Générer un PDF depuis le contenu markdown
|
||||||
|
router.post('/pdf', async (req, res) => {
|
||||||
|
const { content, title = 'Journal de Conception' } = req.body;
|
||||||
|
|
||||||
|
if (!content || content.trim() === '') {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Contenu requis pour générer le PDF'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let browser;
|
||||||
|
try {
|
||||||
|
// Convertir le markdown en HTML avec styles
|
||||||
|
const htmlContent = generateStyledHTML(content, title);
|
||||||
|
|
||||||
|
// Lancer Puppeteer
|
||||||
|
browser = await puppeteer.launch({
|
||||||
|
headless: true,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
// Définir le contenu HTML
|
||||||
|
await page.setContent(htmlContent, {
|
||||||
|
waitUntil: 'domcontentloaded',
|
||||||
|
timeout: parseInt(process.env.PDF_TIMEOUT) || 30000
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attendre que Mermaid soit chargé et les diagrammes rendus
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
// Vérifier si des diagrammes Mermaid existent et attendre qu'ils se chargent
|
||||||
|
try {
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
const mermaidElements = document.querySelectorAll('.mermaid');
|
||||||
|
if (mermaidElements.length === 0) return true;
|
||||||
|
return Array.from(mermaidElements).every(el =>
|
||||||
|
el.querySelector('svg') || el.textContent.trim() === '' || el.classList.contains('error')
|
||||||
|
);
|
||||||
|
}, { timeout: 10000 });
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Timeout waiting for Mermaid, proceeding...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer le PDF
|
||||||
|
const pdfBuffer = await page.pdf({
|
||||||
|
format: 'A4',
|
||||||
|
margin: {
|
||||||
|
top: '2cm',
|
||||||
|
right: '2cm',
|
||||||
|
bottom: '2cm',
|
||||||
|
left: '2cm'
|
||||||
|
},
|
||||||
|
printBackground: true,
|
||||||
|
displayHeaderFooter: true,
|
||||||
|
headerTemplate: `
|
||||||
|
<div style="font-size: 10px; width: 100%; text-align: center; color: #666; margin-top: 1cm;">
|
||||||
|
<span>${title}</span>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
footerTemplate: `
|
||||||
|
<div style="font-size: 10px; width: 100%; text-align: center; color: #666; margin-bottom: 1cm;">
|
||||||
|
<span>Page <span class="pageNumber"></span> sur <span class="totalPages"></span> - Généré le ${new Date().toLocaleDateString('fr-FR')}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
// Vérifier la taille du PDF
|
||||||
|
const maxSize = parseInt(process.env.PDF_MAX_SIZE) || 10485760; // 10MB par défaut
|
||||||
|
if (pdfBuffer.length > maxSize) {
|
||||||
|
return res.status(413).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Le PDF généré dépasse la taille maximale autorisée'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envoyer le PDF
|
||||||
|
res.setHeader('Content-Type', 'application/pdf');
|
||||||
|
res.setHeader('Content-Disposition', `attachment; filename="${sanitizeFilename(title)}.pdf"`);
|
||||||
|
res.setHeader('Content-Length', pdfBuffer.length);
|
||||||
|
|
||||||
|
res.send(pdfBuffer);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur génération PDF:', error);
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Erreur lors de la génération du PDF'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fonction utilitaire pour échapper le HTML
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const map = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": '''
|
||||||
|
};
|
||||||
|
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour convertir le markdown en HTML stylé avec support Mermaid
|
||||||
|
function generateStyledHTML(content, title) {
|
||||||
|
let html = content;
|
||||||
|
|
||||||
|
// Traitement des diagrammes Mermaid AVANT les blocs de code
|
||||||
|
html = html.replace(/```mermaid\n([\s\S]*?)```/g, '<div class="mermaid">$1</div>');
|
||||||
|
|
||||||
|
// Traitement des blocs de code avec langage
|
||||||
|
html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
|
||||||
|
const language = lang ? ` class="language-${lang}"` : '';
|
||||||
|
return `<pre><code${language}>${escapeHtml(code)}</code></pre>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Code en ligne
|
||||||
|
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||||
|
|
||||||
|
// Traitement des tableaux amélioré
|
||||||
|
html = html.replace(/(\|.*\|\s*\n)+/g, (match) => {
|
||||||
|
const lines = match.trim().split('\n').filter(line => line.trim());
|
||||||
|
let tableHtml = '<table>\n';
|
||||||
|
let inHeader = true;
|
||||||
|
|
||||||
|
lines.forEach((line, index) => {
|
||||||
|
// Ignorer les lignes de séparation
|
||||||
|
if (line.match(/^\s*\|[\s\-:]*\|\s*$/)) {
|
||||||
|
inHeader = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cells = line.split('|')
|
||||||
|
.map(cell => cell.trim())
|
||||||
|
.filter((cell, idx, arr) => idx > 0 && idx < arr.length - 1);
|
||||||
|
|
||||||
|
if (cells.length === 0) return;
|
||||||
|
|
||||||
|
const tag = inHeader ? 'th' : 'td';
|
||||||
|
tableHtml += ' <tr>\n';
|
||||||
|
cells.forEach(cell => {
|
||||||
|
tableHtml += ` <${tag}>${cell}</${tag}>\n`;
|
||||||
|
});
|
||||||
|
tableHtml += ' </tr>\n';
|
||||||
|
|
||||||
|
if (inHeader && index === 0) inHeader = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
tableHtml += '</table>\n';
|
||||||
|
return tableHtml;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Conversion markdown vers HTML
|
||||||
|
html = html
|
||||||
|
// Titres (en ordre décroissant)
|
||||||
|
.replace(/^#{6}\s+(.*$)/gm, '<h6>$1</h6>')
|
||||||
|
.replace(/^#{5}\s+(.*$)/gm, '<h5>$1</h5>')
|
||||||
|
.replace(/^#{4}\s+(.*$)/gm, '<h4>$1</h4>')
|
||||||
|
.replace(/^#{3}\s+(.*$)/gm, '<h3>$1</h3>')
|
||||||
|
.replace(/^#{2}\s+(.*$)/gm, '<h2>$1</h2>')
|
||||||
|
.replace(/^#{1}\s+(.*$)/gm, '<h1>$1</h1>')
|
||||||
|
// Citations
|
||||||
|
.replace(/^>\s+(.*$)/gm, '<blockquote>$1</blockquote>')
|
||||||
|
// Gras et italique (ordre important)
|
||||||
|
.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>')
|
||||||
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||||
|
// Barré
|
||||||
|
.replace(/~~(.*?)~~/g, '<del>$1</del>')
|
||||||
|
// Listes numérotées
|
||||||
|
.replace(/^(\s*)(\d+)\. (.*$)/gm, '<li class="numbered">$3</li>')
|
||||||
|
// Listes à puces et tâches
|
||||||
|
.replace(/^(\s*)- \[x\] (.*$)/gm, '<li class="todo-done">✅ $2</li>')
|
||||||
|
.replace(/^(\s*)- \[ \] (.*$)/gm, '<li class="todo">☐ $2</li>')
|
||||||
|
.replace(/^(\s*)[*\-+] (.*$)/gm, '<li>$2</li>')
|
||||||
|
// Liens
|
||||||
|
.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
|
||||||
|
// Ligne horizontale
|
||||||
|
.replace(/^---+$/gm, '<hr>');
|
||||||
|
|
||||||
|
// Grouper les listes consécutives
|
||||||
|
html = html.replace(/(<li[^>]*>.*?<\/li>\s*)+/gs, (match) => {
|
||||||
|
if (match.includes('class="numbered"')) {
|
||||||
|
return '<ol>' + match.replace(/ class="numbered"/g, '') + '</ol>';
|
||||||
|
} else {
|
||||||
|
return '<ul>' + match + '</ul>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nettoyer les listes multiples consécutives
|
||||||
|
html = html.replace(/(<\/[uo]l>\s*<[uo]l>)/g, '');
|
||||||
|
|
||||||
|
// Grouper les citations consécutives
|
||||||
|
html = html.replace(/(<blockquote>.*?<\/blockquote>\s*)+/gs, (match) => {
|
||||||
|
const content = match.replace(/<\/?blockquote>/g, '');
|
||||||
|
return '<blockquote>' + content + '</blockquote>';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Traiter les paragraphes
|
||||||
|
html = html.split('\n\n').map(paragraph => {
|
||||||
|
paragraph = paragraph.trim();
|
||||||
|
if (!paragraph) return '';
|
||||||
|
|
||||||
|
// Ne pas entourer les éléments de bloc dans des paragraphes
|
||||||
|
if (paragraph.match(/^<(h[1-6]|div|table|ul|ol|blockquote|hr|pre)/)) {
|
||||||
|
return paragraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<p>' + paragraph.replace(/\n/g, '<br>') + '</p>';
|
||||||
|
}).join('\n\n');
|
||||||
|
|
||||||
|
const styledHTML = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>${title}</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
|
||||||
|
<script>
|
||||||
|
mermaid.initialize({
|
||||||
|
startOnLoad: true,
|
||||||
|
theme: 'default',
|
||||||
|
themeVariables: {
|
||||||
|
primaryColor: '#0969da',
|
||||||
|
primaryTextColor: '#24292f',
|
||||||
|
primaryBorderColor: '#0969da',
|
||||||
|
lineColor: '#656d76',
|
||||||
|
sectionBkgColor: '#f6f8fa',
|
||||||
|
altSectionBkgColor: '#ffffff',
|
||||||
|
gridColor: '#d1d9e0',
|
||||||
|
secondaryColor: '#f6f8fa',
|
||||||
|
tertiaryColor: '#0969da'
|
||||||
|
},
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
||||||
|
fontSize: 14
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 2cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #24292f;
|
||||||
|
font-size: 11pt;
|
||||||
|
background: white;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Titres style GitHub */
|
||||||
|
h1 {
|
||||||
|
color: #24292f;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 16pt 0;
|
||||||
|
padding-bottom: 8pt;
|
||||||
|
border-bottom: 1px solid #d1d9e0;
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #24292f;
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 24pt 0 12pt 0;
|
||||||
|
padding-bottom: 6pt;
|
||||||
|
border-bottom: 1px solid #d1d9e0;
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #24292f;
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 18pt 0 8pt 0;
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
color: #24292f;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 16pt 0 6pt 0;
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5, h6 {
|
||||||
|
color: #24292f;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 14pt 0 6pt 0;
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Paragraphes */
|
||||||
|
p {
|
||||||
|
margin: 0 0 12pt 0;
|
||||||
|
orphans: 2;
|
||||||
|
widows: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Listes */
|
||||||
|
ul, ol {
|
||||||
|
margin: 0 0 12pt 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0 0 4pt 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tâches */
|
||||||
|
li.todo {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: -2em;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.todo-done {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: -2em;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
color: #656d76;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mise en forme du texte */
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
del {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #656d76;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Liens */
|
||||||
|
a {
|
||||||
|
color: #0969da;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Citations */
|
||||||
|
blockquote {
|
||||||
|
margin: 12pt 0;
|
||||||
|
padding: 0 12pt;
|
||||||
|
border-left: 4px solid #d1d9e0;
|
||||||
|
color: #656d76;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ligne horizontale */
|
||||||
|
hr {
|
||||||
|
margin: 24pt 0;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #d1d9e0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tableaux style GitHub */
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin: 12pt 0;
|
||||||
|
font-size: 10.5pt;
|
||||||
|
border: 1px solid #d1d9e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #d1d9e0;
|
||||||
|
padding: 6pt 10pt;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code style GitHub */
|
||||||
|
code {
|
||||||
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
background-color: rgba(175, 184, 193, 0.2);
|
||||||
|
padding: 1pt 4pt;
|
||||||
|
border-radius: 3pt;
|
||||||
|
font-size: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
padding: 12pt;
|
||||||
|
border-radius: 6pt;
|
||||||
|
border: 1px solid #d1d9e0;
|
||||||
|
margin: 12pt 0;
|
||||||
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 1.45;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Diagrammes Mermaid */
|
||||||
|
.mermaid {
|
||||||
|
text-align: center;
|
||||||
|
margin: 16pt 0;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid svg {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Éviter les coupures de page */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, li {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
orphans: 2;
|
||||||
|
widows: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, table {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Améliorer la lisibilité */
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${html}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return styledHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour nettoyer le nom de fichier
|
||||||
|
function sanitizeFilename(filename) {
|
||||||
|
return filename
|
||||||
|
.replace(/[^\w\s-]/g, '') // Supprimer caractères spéciaux
|
||||||
|
.replace(/\s+/g, '-') // Remplacer espaces par tirets
|
||||||
|
.toLowerCase() // Minuscules
|
||||||
|
.substring(0, 100); // Limiter la longueur
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = router;
|
@ -9,6 +9,9 @@ router.get('/', (req, res) => {
|
|||||||
|
|
||||||
// Route à propos
|
// Route à propos
|
||||||
router.get('/about', (req, res) => {
|
router.get('/about', (req, res) => {
|
||||||
|
const { getHeader } = require('../views/header');
|
||||||
|
const { getFooter } = require('../views/footer');
|
||||||
|
|
||||||
res.send(`
|
res.send(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
@ -19,31 +22,60 @@ router.get('/about', (req, res) => {
|
|||||||
<link rel="stylesheet" href="/assets/css/style.css">
|
<link rel="stylesheet" href="/assets/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Journal de Conception</h1>
|
${getHeader()}
|
||||||
<p>
|
|
||||||
Cette application aide les équipes à réaliser un suivi structuré et collaboratif de la conception de leurs projets. Elle permet d’archiver les étapes clés, d’assurer la traçabilité des décisions, et de simplifier la coordination.
|
<main style="display: block; max-width: 800px; margin: 0 auto; padding: 2rem;">
|
||||||
</p>
|
<section>
|
||||||
<h2>Historique</h2>
|
<h2>À propos de l'application</h2>
|
||||||
<p>
|
<div style="padding: 2rem;">
|
||||||
Ce projet est né du besoin de centraliser et d’organiser les notes de conception lors du développement de projets techniques.
|
<p>
|
||||||
</p>
|
Cette application aide les équipes à réaliser un suivi structuré et collaboratif de la conception de leurs projets. Elle permet d'archiver les étapes clés, d'assurer la traçabilité des décisions, et de simplifier la coordination.
|
||||||
<h2>Équipe</h2>
|
</p>
|
||||||
<ul>
|
</div>
|
||||||
<li>Augustin ROUX – Développeur et Concepteur principal</li>
|
</section>
|
||||||
</ul>
|
|
||||||
<h2>Contact</h2>
|
<section style="margin-top: 2rem;">
|
||||||
<p>
|
<h2>Historique</h2>
|
||||||
Email : augustin.j.l.roux@gmail.com<br>
|
<div style="padding: 2rem;">
|
||||||
Dépôt Git : https://gitea.legion-muyue.fr/Muyue/conception-assistant
|
<p>
|
||||||
</p>
|
Ce projet est né du besoin de centraliser et d'organiser les notes de conception lors du développement de projets techniques. Il offre un environnement intuitif pour documenter les décisions architecturales et suivre l'évolution des projets.
|
||||||
<footer>
|
</p>
|
||||||
<p>
|
</div>
|
||||||
© 2025 Journal de Conception.
|
</section>
|
||||||
</p>
|
|
||||||
</footer>
|
<section style="margin-top: 2rem;">
|
||||||
|
<h2>Équipe</h2>
|
||||||
|
<div style="padding: 2rem;">
|
||||||
|
<ul style="list-style: none; padding: 0;">
|
||||||
|
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
|
||||||
|
<strong>Augustin ROUX</strong> – Développeur et Concepteur principal
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-top: 2rem;">
|
||||||
|
<h2>Contact</h2>
|
||||||
|
<div style="padding: 2rem;">
|
||||||
|
<p>
|
||||||
|
<strong>Email :</strong> <a href="mailto:augustin.j.l.roux@gmail.com" style="color: var(--secondary-color);">augustin.j.l.roux@gmail.com</a><br><br>
|
||||||
|
<strong>Dépôt Git :</strong> <a href="https://gitea.legion-muyue.fr/Muyue/conception-assistant" target="_blank" style="color: var(--secondary-color);">https://gitea.legion-muyue.fr/Muyue/conception-assistant</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin: 3rem 0 2rem 0;">
|
||||||
|
<a href="/" style="background: var(--secondary-color); color: white; padding: 0.8rem 2rem; text-decoration: none; border-radius: 25px; display: inline-block; transition: all 0.3s ease;">
|
||||||
|
← Retour à l'application
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
${getFooter()}
|
||||||
|
|
||||||
|
<script src="/assets/js/theme.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
117
routes/templates.js
Normal file
117
routes/templates.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// GET /api/templates/:domain/:level - Récupérer un template spécifique
|
||||||
|
router.get('/:domain/:level', (req, res) => {
|
||||||
|
const { domain, level } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const templatePath = path.join(__dirname, '../templates', domain, `${level}.md`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(templatePath)) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: `Template non trouvé : ${domain}/${level}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
domain,
|
||||||
|
level,
|
||||||
|
content: templateContent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la lecture du template:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Erreur serveur lors de la lecture du template'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/templates/:domain - Lister les niveaux disponibles pour un domaine
|
||||||
|
router.get('/:domain', (req, res) => {
|
||||||
|
const { domain } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const domainPath = path.join(__dirname, '../templates', domain);
|
||||||
|
|
||||||
|
if (!fs.existsSync(domainPath)) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: `Domaine non trouvé : ${domain}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(domainPath);
|
||||||
|
const levels = files
|
||||||
|
.filter(file => file.endsWith('.md'))
|
||||||
|
.map(file => file.replace('.md', ''));
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
domain,
|
||||||
|
levels
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la lecture du domaine:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Erreur serveur lors de la lecture du domaine'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/templates - Lister tous les domaines disponibles
|
||||||
|
router.get('/', (req, res) => {
|
||||||
|
try {
|
||||||
|
const templatesPath = path.join(__dirname, '../templates');
|
||||||
|
|
||||||
|
if (!fs.existsSync(templatesPath)) {
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const domains = fs.readdirSync(templatesPath, { withFileTypes: true })
|
||||||
|
.filter(dirent => dirent.isDirectory())
|
||||||
|
.map(dirent => dirent.name);
|
||||||
|
|
||||||
|
// Pour chaque domaine, récupérer les niveaux disponibles
|
||||||
|
const domainsWithLevels = domains.map(domain => {
|
||||||
|
const domainPath = path.join(templatesPath, domain);
|
||||||
|
const files = fs.readdirSync(domainPath);
|
||||||
|
const levels = files
|
||||||
|
.filter(file => file.endsWith('.md'))
|
||||||
|
.map(file => file.replace('.md', ''));
|
||||||
|
|
||||||
|
return {
|
||||||
|
domain,
|
||||||
|
levels
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: domainsWithLevels
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la lecture des templates:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Erreur serveur lors de la lecture des templates'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
97
templates/business/simple.md
Normal file
97
templates/business/simple.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# Projet Business
|
||||||
|
|
||||||
|
## 1. Présentation du projet
|
||||||
|
|
||||||
|
### Contexte
|
||||||
|
[Description du contexte économique/business]
|
||||||
|
|
||||||
|
### Opportunité
|
||||||
|
[Opportunité de marché identifiée]
|
||||||
|
|
||||||
|
### Proposition de valeur
|
||||||
|
[En quoi votre solution est unique]
|
||||||
|
|
||||||
|
## 2. Analyse de marché
|
||||||
|
|
||||||
|
### Marché cible
|
||||||
|
- Segment principal :
|
||||||
|
- Taille du marché :
|
||||||
|
- Tendances :
|
||||||
|
|
||||||
|
### Concurrence
|
||||||
|
- **Concurrent 1** : [Forces/Faiblesses]
|
||||||
|
- **Concurrent 2** : [Forces/Faiblesses]
|
||||||
|
|
||||||
|
### Positionnement
|
||||||
|
[Comment vous vous différenciez]
|
||||||
|
|
||||||
|
## 3. Modèle économique
|
||||||
|
|
||||||
|
### Sources de revenus
|
||||||
|
- Revenue stream 1 :
|
||||||
|
- Revenue stream 2 :
|
||||||
|
|
||||||
|
### Structure de coûts
|
||||||
|
- Coûts fixes :
|
||||||
|
- Coûts variables :
|
||||||
|
|
||||||
|
### Pricing
|
||||||
|
[Stratégie de tarification]
|
||||||
|
|
||||||
|
## 4. Stratégie
|
||||||
|
|
||||||
|
### Objectifs
|
||||||
|
- Court terme (6 mois) :
|
||||||
|
- Moyen terme (18 mois) :
|
||||||
|
- Long terme (3 ans) :
|
||||||
|
|
||||||
|
### Stratégie marketing
|
||||||
|
- Canaux d'acquisition :
|
||||||
|
- Message marketing :
|
||||||
|
|
||||||
|
### Stratégie opérationnelle
|
||||||
|
- Ressources clés :
|
||||||
|
- Partenaires :
|
||||||
|
|
||||||
|
## 5. Planning
|
||||||
|
|
||||||
|
### Phase 1 : Lancement (3 mois)
|
||||||
|
- [ ] Développement MVP
|
||||||
|
- [ ] Tests utilisateurs
|
||||||
|
- [ ] Go-to-market
|
||||||
|
|
||||||
|
### Phase 2 : Croissance (6 mois)
|
||||||
|
- [ ] Acquisition clients
|
||||||
|
- [ ] Amélioration produit
|
||||||
|
- [ ] Expansion équipe
|
||||||
|
|
||||||
|
### Phase 3 : Scale (12 mois)
|
||||||
|
- [ ] Expansion géographique
|
||||||
|
- [ ] Nouveaux produits
|
||||||
|
- [ ] Levée de fonds
|
||||||
|
|
||||||
|
## 6. Risques et opportunités
|
||||||
|
|
||||||
|
### Risques
|
||||||
|
- Risque 1 :
|
||||||
|
- Risque 2 :
|
||||||
|
|
||||||
|
### Opportunités
|
||||||
|
- Opportunité 1 :
|
||||||
|
- Opportunité 2 :
|
||||||
|
|
||||||
|
## 7. Métriques clés
|
||||||
|
|
||||||
|
### KPIs business
|
||||||
|
- Chiffre d'affaires :
|
||||||
|
- Nombre de clients :
|
||||||
|
- Coût d'acquisition :
|
||||||
|
|
||||||
|
### KPIs produit
|
||||||
|
- Utilisateurs actifs :
|
||||||
|
- Rétention :
|
||||||
|
- NPS :
|
||||||
|
|
||||||
|
## 8. Notes et actions
|
||||||
|
|
||||||
|
[Espace pour suivi des actions et notes]
|
96
templates/design/simple.md
Normal file
96
templates/design/simple.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Projet Design
|
||||||
|
|
||||||
|
## 1. Brief créatif
|
||||||
|
|
||||||
|
### Contexte du projet
|
||||||
|
[Description du projet et du client]
|
||||||
|
|
||||||
|
### Objectifs
|
||||||
|
- Objectif principal :
|
||||||
|
- Cible :
|
||||||
|
- Message à transmettre :
|
||||||
|
|
||||||
|
### Contraintes
|
||||||
|
- Budget :
|
||||||
|
- Délais :
|
||||||
|
- Contraintes techniques :
|
||||||
|
|
||||||
|
## 2. Recherche et inspiration
|
||||||
|
|
||||||
|
### Analyse de la concurrence
|
||||||
|
[Étude des solutions existantes]
|
||||||
|
|
||||||
|
### Mood board
|
||||||
|
[Sources d'inspiration visuelles]
|
||||||
|
|
||||||
|
### Références
|
||||||
|
- Style graphique :
|
||||||
|
- Palette couleur :
|
||||||
|
- Typographie :
|
||||||
|
|
||||||
|
## 3. Concept créatif
|
||||||
|
|
||||||
|
### Idée directrice
|
||||||
|
[Concept principal du design]
|
||||||
|
|
||||||
|
### Parti pris esthétique
|
||||||
|
- Style : [Moderne, classique, minimaliste...]
|
||||||
|
- Ambiance : [Chaleureuse, professionnelle, fun...]
|
||||||
|
- Tonalité : [Sérieuse, décalée, premium...]
|
||||||
|
|
||||||
|
## 4. Éléments graphiques
|
||||||
|
|
||||||
|
### Palette de couleurs
|
||||||
|
- Couleur principale : #XXXXXX
|
||||||
|
- Couleur secondaire : #XXXXXX
|
||||||
|
- Couleurs d'accent : #XXXXXX, #XXXXXX
|
||||||
|
|
||||||
|
### Typographie
|
||||||
|
- Titre : [Police]
|
||||||
|
- Texte : [Police]
|
||||||
|
- Accent : [Police]
|
||||||
|
|
||||||
|
### Iconographie
|
||||||
|
- Style d'icônes :
|
||||||
|
- Illustrations :
|
||||||
|
- Photos :
|
||||||
|
|
||||||
|
## 5. Déclinaisons
|
||||||
|
|
||||||
|
### Supports à créer
|
||||||
|
- [ ] Logo et identité
|
||||||
|
- [ ] Site web
|
||||||
|
- [ ] Print
|
||||||
|
- [ ] Réseaux sociaux
|
||||||
|
- [ ] Packaging
|
||||||
|
|
||||||
|
### Guidelines
|
||||||
|
[Règles d'utilisation des éléments graphiques]
|
||||||
|
|
||||||
|
## 6. Validation et itérations
|
||||||
|
|
||||||
|
### Feedbacks client
|
||||||
|
[Retours et ajustements demandés]
|
||||||
|
|
||||||
|
### Tests utilisateurs
|
||||||
|
[Si applicable - tests d'utilisabilité]
|
||||||
|
|
||||||
|
## 7. Livrables
|
||||||
|
|
||||||
|
### Fichiers sources
|
||||||
|
- [ ] Fichiers AI/PSD
|
||||||
|
- [ ] Fichiers vectoriels
|
||||||
|
- [ ] Polices
|
||||||
|
|
||||||
|
### Exports
|
||||||
|
- [ ] PNG/JPG haute définition
|
||||||
|
- [ ] Versions web
|
||||||
|
- [ ] Versions print
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [ ] Guide de style
|
||||||
|
- [ ] Spécifications techniques
|
||||||
|
|
||||||
|
## 8. Notes créatives
|
||||||
|
|
||||||
|
[Espace pour notes et idées créatives]
|
1122
templates/informatique/complet.md
Normal file
1122
templates/informatique/complet.md
Normal file
File diff suppressed because it is too large
Load Diff
386
templates/informatique/detaille.md
Normal file
386
templates/informatique/detaille.md
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
# Projet Informatique - Spécifications Détaillées
|
||||||
|
|
||||||
|
## 1. Introduction
|
||||||
|
|
||||||
|
### 1.1 Contexte et problématique
|
||||||
|
[Description du contexte métier et des problèmes à résoudre]
|
||||||
|
|
||||||
|
### 1.2 Objectifs du projet
|
||||||
|
- **Objectif principal** :
|
||||||
|
- **Objectifs secondaires** :
|
||||||
|
- **Critères de réussite** :
|
||||||
|
|
||||||
|
### 1.3 Périmètre
|
||||||
|
#### Inclus dans le projet
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
#### Exclus du projet
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
### 1.4 Contraintes
|
||||||
|
- **Techniques** :
|
||||||
|
- **Temporelles** :
|
||||||
|
- **Budgétaires** :
|
||||||
|
- **Réglementaires** :
|
||||||
|
|
||||||
|
## 2. Analyse des besoins
|
||||||
|
|
||||||
|
### 2.1 Besoins fonctionnels
|
||||||
|
#### RF-001 : [Nom du besoin]
|
||||||
|
- **Description** :
|
||||||
|
- **Acteur** :
|
||||||
|
- **Priorité** : Haute/Moyenne/Basse
|
||||||
|
- **Critères d'acceptation** :
|
||||||
|
|
||||||
|
#### RF-002 : [Nom du besoin]
|
||||||
|
- **Description** :
|
||||||
|
- **Acteur** :
|
||||||
|
- **Priorité** :
|
||||||
|
- **Critères d'acceptation** :
|
||||||
|
|
||||||
|
### 2.2 Besoins non-fonctionnels
|
||||||
|
#### Performance
|
||||||
|
- Temps de réponse : < 2 secondes
|
||||||
|
- Charge supportée : X utilisateurs simultanés
|
||||||
|
- Disponibilité : 99.9%
|
||||||
|
|
||||||
|
#### Sécurité
|
||||||
|
- Authentification : OAuth 2.0
|
||||||
|
- Autorisation : RBAC
|
||||||
|
- Chiffrement : TLS 1.3
|
||||||
|
|
||||||
|
#### Compatibilité
|
||||||
|
- Navigateurs : Chrome, Firefox, Safari, Edge
|
||||||
|
- Appareils : Desktop, Mobile, Tablette
|
||||||
|
- OS : Windows, macOS, Linux
|
||||||
|
|
||||||
|
## 3. Architecture système
|
||||||
|
|
||||||
|
### 3.1 Architecture générale
|
||||||
|
[Diagramme d'architecture global]
|
||||||
|
|
||||||
|
### 3.2 Stack technique
|
||||||
|
|
||||||
|
#### Frontend
|
||||||
|
- **Framework** : React 18 / Vue 3 / Angular 15
|
||||||
|
- **État** : Redux / Pinia / NgRx
|
||||||
|
- **UI** : Material-UI / Vuetify / Angular Material
|
||||||
|
- **Build** : Vite / Webpack
|
||||||
|
- **Tests** : Jest + Testing Library
|
||||||
|
|
||||||
|
#### Backend
|
||||||
|
- **Framework** : Node.js + Express / Python + FastAPI / Java + Spring
|
||||||
|
- **API** : REST / GraphQL
|
||||||
|
- **Authentification** : JWT / OAuth2
|
||||||
|
- **Tests** : Jest / pytest / JUnit
|
||||||
|
|
||||||
|
#### Base de données
|
||||||
|
- **Principale** : PostgreSQL / MySQL / MongoDB
|
||||||
|
- **Cache** : Redis
|
||||||
|
- **Recherche** : Elasticsearch (si nécessaire)
|
||||||
|
|
||||||
|
#### Infrastructure
|
||||||
|
- **Cloud** : AWS / Azure / GCP
|
||||||
|
- **Conteneurs** : Docker + Kubernetes
|
||||||
|
- **CI/CD** : GitHub Actions / GitLab CI
|
||||||
|
- **Monitoring** : Prometheus + Grafana
|
||||||
|
|
||||||
|
### 3.3 Modèle de données
|
||||||
|
|
||||||
|
#### Entités principales
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Utilisateurs
|
||||||
|
CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
role VARCHAR(50) DEFAULT 'user',
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Contenu principal
|
||||||
|
CREATE TABLE content (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
user_id INTEGER REFERENCES users(id),
|
||||||
|
status VARCHAR(50) DEFAULT 'draft',
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Relations
|
||||||
|
[Diagramme ERD]
|
||||||
|
|
||||||
|
## 4. Conception détaillée
|
||||||
|
|
||||||
|
### 4.1 Frontend - Composants
|
||||||
|
|
||||||
|
#### Composant App
|
||||||
|
```jsx
|
||||||
|
// Structure principale
|
||||||
|
<App>
|
||||||
|
<Router>
|
||||||
|
<Header />
|
||||||
|
<Sidebar />
|
||||||
|
<MainContent />
|
||||||
|
<Footer />
|
||||||
|
</Router>
|
||||||
|
</App>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Composant Dashboard
|
||||||
|
- **Props** : user, data
|
||||||
|
- **State** : loading, filters
|
||||||
|
- **Actions** : loadData, applyFilter
|
||||||
|
|
||||||
|
### 4.2 Backend - API
|
||||||
|
|
||||||
|
#### Endpoints principaux
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/users - Liste des utilisateurs
|
||||||
|
POST /api/users - Création utilisateur
|
||||||
|
GET /api/users/:id - Détail utilisateur
|
||||||
|
PUT /api/users/:id - Modification utilisateur
|
||||||
|
DELETE /api/users/:id - Suppression utilisateur
|
||||||
|
|
||||||
|
GET /api/content - Liste du contenu
|
||||||
|
POST /api/content - Création contenu
|
||||||
|
GET /api/content/:id - Détail contenu
|
||||||
|
PUT /api/content/:id - Modification contenu
|
||||||
|
DELETE /api/content/:id - Suppression contenu
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Middlewares
|
||||||
|
- **Authentication** : Vérification JWT
|
||||||
|
- **Authorization** : Vérification des rôles
|
||||||
|
- **Rate limiting** : Limitation des appels
|
||||||
|
- **Validation** : Validation des données
|
||||||
|
- **Logging** : Journalisation des requêtes
|
||||||
|
|
||||||
|
### 4.3 Sécurité
|
||||||
|
|
||||||
|
#### Authentification
|
||||||
|
```javascript
|
||||||
|
// Flow OAuth2
|
||||||
|
1. Redirection vers provider
|
||||||
|
2. Callback avec code
|
||||||
|
3. Exchange code -> token
|
||||||
|
4. Stockage secure du token
|
||||||
|
5. Utilisation avec Bearer header
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gestion des erreurs
|
||||||
|
```javascript
|
||||||
|
// Structure des erreurs
|
||||||
|
{
|
||||||
|
error: {
|
||||||
|
code: 'USER_NOT_FOUND',
|
||||||
|
message: 'Utilisateur introuvable',
|
||||||
|
details: {},
|
||||||
|
timestamp: '2025-01-01T12:00:00Z'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Planning détaillé
|
||||||
|
|
||||||
|
### Phase 1 : Setup et fondations (3 semaines)
|
||||||
|
#### Semaine 1
|
||||||
|
- [ ] Setup repository Git
|
||||||
|
- [ ] Configuration environnements dev/staging/prod
|
||||||
|
- [ ] Setup CI/CD pipeline
|
||||||
|
- [ ] Configuration base de données
|
||||||
|
- [ ] **Livrable** : Environnements opérationnels
|
||||||
|
|
||||||
|
#### Semaine 2
|
||||||
|
- [ ] Architecture frontend de base
|
||||||
|
- [ ] Setup backend avec authentification
|
||||||
|
- [ ] Intégration base de données
|
||||||
|
- [ ] Tests unitaires de base
|
||||||
|
- [ ] **Livrable** : Stack technique fonctionnelle
|
||||||
|
|
||||||
|
#### Semaine 3
|
||||||
|
- [ ] Interface utilisateur de base
|
||||||
|
- [ ] API CRUD principale
|
||||||
|
- [ ] Tests d'intégration
|
||||||
|
- [ ] Documentation technique
|
||||||
|
- [ ] **Livrable** : MVP fonctionnel
|
||||||
|
|
||||||
|
### Phase 2 : Développement core (4 semaines)
|
||||||
|
#### Semaines 4-5
|
||||||
|
- [ ] Implémentation des fonctionnalités principales
|
||||||
|
- [ ] Interface utilisateur complète
|
||||||
|
- [ ] Gestion des erreurs
|
||||||
|
- [ ] **Livrable** : Version alpha
|
||||||
|
|
||||||
|
#### Semaines 6-7
|
||||||
|
- [ ] Fonctionnalités avancées
|
||||||
|
- [ ] Optimisation des performances
|
||||||
|
- [ ] Tests de charge
|
||||||
|
- [ ] **Livrable** : Version beta
|
||||||
|
|
||||||
|
### Phase 3 : Finalisation (2 semaines)
|
||||||
|
#### Semaine 8
|
||||||
|
- [ ] Tests utilisateur
|
||||||
|
- [ ] Corrections de bugs
|
||||||
|
- [ ] Documentation utilisateur
|
||||||
|
- [ ] Préparation déploiement production
|
||||||
|
|
||||||
|
#### Semaine 9
|
||||||
|
- [ ] Déploiement production
|
||||||
|
- [ ] Monitoring et alertes
|
||||||
|
- [ ] Formation utilisateurs
|
||||||
|
- [ ] **Livrable** : Version 1.0
|
||||||
|
|
||||||
|
## 6. Tests et Qualité
|
||||||
|
|
||||||
|
### 6.1 Stratégie de tests
|
||||||
|
- **Tests unitaires** : > 90% de couverture
|
||||||
|
- **Tests d'intégration** : Tous les endpoints
|
||||||
|
- **Tests end-to-end** : Parcours utilisateur critiques
|
||||||
|
- **Tests de performance** : Charge nominale et pics
|
||||||
|
|
||||||
|
### 6.2 Outils de tests
|
||||||
|
```javascript
|
||||||
|
// Frontend
|
||||||
|
- Jest + Testing Library
|
||||||
|
- Cypress pour E2E
|
||||||
|
- Lighthouse pour performance
|
||||||
|
|
||||||
|
// Backend
|
||||||
|
- Jest / pytest
|
||||||
|
- Supertest pour API
|
||||||
|
- Artillery pour charge
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Critères de qualité
|
||||||
|
- **Code coverage** : > 90%
|
||||||
|
- **Temps de build** : < 5 minutes
|
||||||
|
- **Temps de déploiement** : < 10 minutes
|
||||||
|
- **Performance** : Core Web Vitals > 90
|
||||||
|
|
||||||
|
## 7. Déploiement et Infrastructure
|
||||||
|
|
||||||
|
### 7.1 Environnements
|
||||||
|
#### Développement
|
||||||
|
- **URL** : http://localhost:3000
|
||||||
|
- **Base** : PostgreSQL locale
|
||||||
|
- **Données** : Jeu de test
|
||||||
|
|
||||||
|
#### Staging
|
||||||
|
- **URL** : https://staging.app.com
|
||||||
|
- **Base** : PostgreSQL cloud
|
||||||
|
- **Données** : Réplique anonymisée de prod
|
||||||
|
|
||||||
|
#### Production
|
||||||
|
- **URL** : https://app.com
|
||||||
|
- **Base** : PostgreSQL cluster
|
||||||
|
- **Données** : Données réelles
|
||||||
|
|
||||||
|
### 7.2 Architecture cloud
|
||||||
|
```yaml
|
||||||
|
# Kubernetes deployment
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: app-frontend
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: frontend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: frontend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: app/frontend:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Monitoring
|
||||||
|
- **Application** : Sentry pour erreurs
|
||||||
|
- **Infrastructure** : Prometheus + Grafana
|
||||||
|
- **Logs** : ELK Stack
|
||||||
|
- **Uptime** : Pingdom / UptimeRobot
|
||||||
|
|
||||||
|
## 8. Maintenance et Evolution
|
||||||
|
|
||||||
|
### 8.1 Maintenance corrective
|
||||||
|
- **Bugs critiques** : Correction < 4h
|
||||||
|
- **Bugs majeurs** : Correction < 24h
|
||||||
|
- **Bugs mineurs** : Correction < 1 semaine
|
||||||
|
|
||||||
|
### 8.2 Maintenance évolutive
|
||||||
|
- **Mises à jour sécurité** : Mensuelle
|
||||||
|
- **Nouvelles fonctionnalités** : Trimestrielle
|
||||||
|
- **Refactoring** : Semestrielle
|
||||||
|
|
||||||
|
### 8.3 Roadmap
|
||||||
|
#### V1.1 (3 mois)
|
||||||
|
- [ ] Amélioration UX
|
||||||
|
- [ ] API v2
|
||||||
|
- [ ] Export de données
|
||||||
|
|
||||||
|
#### V2.0 (6 mois)
|
||||||
|
- [ ] Application mobile
|
||||||
|
- [ ] Intelligence artificielle
|
||||||
|
- [ ] Intégrations tierces
|
||||||
|
|
||||||
|
## 9. Documentation
|
||||||
|
|
||||||
|
### 9.1 Documentation technique
|
||||||
|
- [ ] README détaillé
|
||||||
|
- [ ] Guide de contribution
|
||||||
|
- [ ] Documentation API (OpenAPI)
|
||||||
|
- [ ] Architecture Decision Records (ADR)
|
||||||
|
|
||||||
|
### 9.2 Documentation utilisateur
|
||||||
|
- [ ] Guide d'utilisation
|
||||||
|
- [ ] FAQ
|
||||||
|
- [ ] Tutoriels vidéo
|
||||||
|
- [ ] Support en ligne
|
||||||
|
|
||||||
|
## 10. Risques et Mitigation
|
||||||
|
|
||||||
|
| ID | Risque | Impact | Probabilité | Mitigation |
|
||||||
|
|----|--------|---------|-------------|------------|
|
||||||
|
| R1 | Retard de développement | Élevé | Moyenne | Buffer de temps, équipe élargie |
|
||||||
|
| R2 | Problème de performance | Moyen | Faible | Tests de charge réguliers |
|
||||||
|
| R3 | Faille de sécurité | Élevé | Faible | Audits sécurité, code review |
|
||||||
|
| R4 | Indisponibilité cloud | Moyen | Faible | Multi-zone, backup automatique |
|
||||||
|
|
||||||
|
## 11. Journal de Développement
|
||||||
|
|
||||||
|
### [Date] - Setup initial
|
||||||
|
[Notes sur la configuration initiale]
|
||||||
|
|
||||||
|
### [Date] - Développement Feature X
|
||||||
|
[Défis rencontrés et solutions]
|
||||||
|
|
||||||
|
### [Date] - Tests et déploiement
|
||||||
|
[Résultats des tests et déploiement]
|
||||||
|
|
||||||
|
## 12. Ressources et Références
|
||||||
|
|
||||||
|
### Ressources techniques
|
||||||
|
- [Documentation React](https://reactjs.org/)
|
||||||
|
- [Guide Node.js](https://nodejs.org/)
|
||||||
|
- [Best practices sécurité](https://owasp.org/)
|
||||||
|
|
||||||
|
### Outils utilisés
|
||||||
|
- [GitHub Repository](https://github.com/user/project)
|
||||||
|
- [Projet Management](https://trello.com/board)
|
||||||
|
- [Design System](https://figma.com/file)
|
78
templates/informatique/simple.md
Normal file
78
templates/informatique/simple.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Projet Informatique
|
||||||
|
|
||||||
|
## 1. Vue d'ensemble
|
||||||
|
|
||||||
|
### Description du projet
|
||||||
|
[Résumé en quelques lignes]
|
||||||
|
|
||||||
|
### Objectifs
|
||||||
|
- Objectif principal :
|
||||||
|
- Fonctionnalités clés :
|
||||||
|
|
||||||
|
## 2. Spécifications techniques
|
||||||
|
|
||||||
|
### Technologies utilisées
|
||||||
|
- **Frontend** :
|
||||||
|
- **Backend** :
|
||||||
|
- **Base de données** :
|
||||||
|
- **Outils** :
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
[Description simple de l'architecture]
|
||||||
|
|
||||||
|
## 3. Fonctionnalités
|
||||||
|
|
||||||
|
### Fonctionnalités principales
|
||||||
|
- [ ] Feature 1
|
||||||
|
- [ ] Feature 2
|
||||||
|
- [ ] Feature 3
|
||||||
|
|
||||||
|
### Fonctionnalités secondaires
|
||||||
|
- [ ] Feature A
|
||||||
|
- [ ] Feature B
|
||||||
|
|
||||||
|
## 4. Interface utilisateur
|
||||||
|
|
||||||
|
### Pages principales
|
||||||
|
- Page d'accueil :
|
||||||
|
- Page utilisateur :
|
||||||
|
- Page admin :
|
||||||
|
|
||||||
|
## 5. Base de données
|
||||||
|
|
||||||
|
### Modèles principaux
|
||||||
|
- **Utilisateur** : id, nom, email
|
||||||
|
- **Contenu** : id, titre, description
|
||||||
|
- **Session** : id, token, user_id
|
||||||
|
|
||||||
|
## 6. Planning de développement
|
||||||
|
|
||||||
|
### Sprint 1 (2 semaines)
|
||||||
|
- [ ] Setup du projet
|
||||||
|
- [ ] Authentification
|
||||||
|
- [ ] Interface de base
|
||||||
|
|
||||||
|
### Sprint 2 (2 semaines)
|
||||||
|
- [ ] CRUD principal
|
||||||
|
- [ ] Tests unitaires
|
||||||
|
- [ ] Déploiement
|
||||||
|
|
||||||
|
### Sprint 3 (2 semaines)
|
||||||
|
- [ ] Fonctionnalités avancées
|
||||||
|
- [ ] Optimisation
|
||||||
|
- [ ] Documentation
|
||||||
|
|
||||||
|
## 7. Tests et déploiement
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
- [ ] Tests unitaires
|
||||||
|
- [ ] Tests d'intégration
|
||||||
|
- [ ] Tests utilisateur
|
||||||
|
|
||||||
|
### Déploiement
|
||||||
|
- **Environnement dev** :
|
||||||
|
- **Environnement prod** :
|
||||||
|
|
||||||
|
## 8. Notes techniques
|
||||||
|
|
||||||
|
[Espace pour notes de développement]
|
82
templates/math/simple.md
Normal file
82
templates/math/simple.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# Projet Mathématiques
|
||||||
|
|
||||||
|
## 1. Problème étudié
|
||||||
|
|
||||||
|
### Énoncé
|
||||||
|
[Description claire du problème mathématique]
|
||||||
|
|
||||||
|
### Contexte
|
||||||
|
[Domaine mathématique concerné]
|
||||||
|
|
||||||
|
### Objectifs
|
||||||
|
- Objectif principal :
|
||||||
|
- Applications visées :
|
||||||
|
|
||||||
|
## 2. Fondements théoriques
|
||||||
|
|
||||||
|
### Concepts clés
|
||||||
|
- **Concept 1** : [Définition]
|
||||||
|
- **Concept 2** : [Définition]
|
||||||
|
- **Concept 3** : [Définition]
|
||||||
|
|
||||||
|
### Théorèmes principaux
|
||||||
|
- **Théorème A** : [Énoncé]
|
||||||
|
- **Théorème B** : [Énoncé]
|
||||||
|
|
||||||
|
## 3. Approche méthodologique
|
||||||
|
|
||||||
|
### Stratégie de résolution
|
||||||
|
[Description de l'approche choisie]
|
||||||
|
|
||||||
|
### Outils mathématiques
|
||||||
|
- Outils analytiques :
|
||||||
|
- Outils numériques :
|
||||||
|
- Logiciels utilisés :
|
||||||
|
|
||||||
|
## 4. Développement
|
||||||
|
|
||||||
|
### Étape 1 : Formalisation
|
||||||
|
[Mise en équations du problème]
|
||||||
|
|
||||||
|
### Étape 2 : Analyse théorique
|
||||||
|
[Étude des propriétés mathématiques]
|
||||||
|
|
||||||
|
### Étape 3 : Résolution
|
||||||
|
[Méthodes de résolution appliquées]
|
||||||
|
|
||||||
|
### Étape 4 : Validation
|
||||||
|
[Vérification des résultats]
|
||||||
|
|
||||||
|
## 5. Résultats
|
||||||
|
|
||||||
|
### Résultats théoriques
|
||||||
|
[Nouveaux théorèmes ou propriétés démontrés]
|
||||||
|
|
||||||
|
### Résultats numériques
|
||||||
|
[Calculs et simulations]
|
||||||
|
|
||||||
|
### Applications
|
||||||
|
[Domaines d'application des résultats]
|
||||||
|
|
||||||
|
## 6. Conclusions
|
||||||
|
|
||||||
|
### Apports du travail
|
||||||
|
[Ce que ce travail apporte de nouveau]
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
[Limites de l'approche ou des résultats]
|
||||||
|
|
||||||
|
### Perspectives
|
||||||
|
[Travaux futurs envisagés]
|
||||||
|
|
||||||
|
## 7. Références
|
||||||
|
|
||||||
|
[Bibliographie mathématique]
|
||||||
|
|
||||||
|
## 8. Annexes
|
||||||
|
|
||||||
|
### Annexe A : Démonstrations détaillées
|
||||||
|
|
||||||
|
### Annexe B : Calculs numériques
|
||||||
|
|
||||||
|
### Annexe C : Code informatique
|
197
templates/recherche/detaille.md
Normal file
197
templates/recherche/detaille.md
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
# Projet de Recherche - Documentation Détaillée
|
||||||
|
|
||||||
|
## 1. Introduction et Contexte
|
||||||
|
|
||||||
|
### 1.1 Contexte général
|
||||||
|
[Description du domaine de recherche et du contexte académique/industriel]
|
||||||
|
|
||||||
|
### 1.2 Problématique
|
||||||
|
[Énoncé précis du problème de recherche]
|
||||||
|
|
||||||
|
### 1.3 Motivations
|
||||||
|
[Pourquoi cette recherche est-elle importante ?]
|
||||||
|
|
||||||
|
### 1.4 Objectifs
|
||||||
|
- **Objectif principal** :
|
||||||
|
- **Objectifs secondaires** :
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
### 1.5 Contributions attendues
|
||||||
|
[En quoi cette recherche apportera-t-elle du nouveau ?]
|
||||||
|
|
||||||
|
## 2. État de l'art et Positionnement
|
||||||
|
|
||||||
|
### 2.1 Revue de littérature
|
||||||
|
[Analyse détaillée des travaux existants]
|
||||||
|
|
||||||
|
### 2.2 Taxonomie des approches
|
||||||
|
[Classification des méthodes existantes]
|
||||||
|
|
||||||
|
### 2.3 Analyse critique
|
||||||
|
#### Forces des approches existantes
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
#### Limitations identifiées
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
### 2.4 Positionnement de notre approche
|
||||||
|
[Comment notre travail se situe par rapport à l'existant]
|
||||||
|
|
||||||
|
## 3. Méthodologie de Recherche
|
||||||
|
|
||||||
|
### 3.1 Cadre théorique
|
||||||
|
[Fondements théoriques de l'approche]
|
||||||
|
|
||||||
|
### 3.2 Hypothèses de recherche
|
||||||
|
- H1 :
|
||||||
|
- H2 :
|
||||||
|
- H3 :
|
||||||
|
|
||||||
|
### 3.3 Méthode expérimentale
|
||||||
|
#### Design expérimental
|
||||||
|
[Description du protocole]
|
||||||
|
|
||||||
|
#### Variables étudiées
|
||||||
|
- Variables indépendantes :
|
||||||
|
- Variables dépendantes :
|
||||||
|
- Variables de contrôle :
|
||||||
|
|
||||||
|
### 3.4 Outils et technologies
|
||||||
|
- **Outils de développement** :
|
||||||
|
- **Outils d'analyse** :
|
||||||
|
- **Plateformes de test** :
|
||||||
|
|
||||||
|
### 3.5 Jeux de données
|
||||||
|
[Description des données utilisées]
|
||||||
|
|
||||||
|
## 4. Architecture et Conception
|
||||||
|
|
||||||
|
### 4.1 Vue d'ensemble
|
||||||
|
[Schéma général de l'approche]
|
||||||
|
|
||||||
|
### 4.2 Composants principaux
|
||||||
|
#### Composant 1 : [Nom]
|
||||||
|
- **Fonction** :
|
||||||
|
- **Entrées** :
|
||||||
|
- **Sorties** :
|
||||||
|
- **Algorithme** :
|
||||||
|
|
||||||
|
#### Composant 2 : [Nom]
|
||||||
|
- **Fonction** :
|
||||||
|
- **Entrées** :
|
||||||
|
- **Sorties** :
|
||||||
|
- **Algorithme** :
|
||||||
|
|
||||||
|
### 4.3 Interfaces et flux de données
|
||||||
|
[Description des interactions entre composants]
|
||||||
|
|
||||||
|
## 5. Planning Détaillé
|
||||||
|
|
||||||
|
### Phase 1 : Étude préliminaire (Durée : X semaines)
|
||||||
|
- [ ] Revue exhaustive de littérature
|
||||||
|
- [ ] Analyse des outils existants
|
||||||
|
- [ ] Définition précise des spécifications
|
||||||
|
- [ ] **Livrable** : Rapport d'état de l'art
|
||||||
|
|
||||||
|
### Phase 2 : Conception et prototypage (Durée : X semaines)
|
||||||
|
- [ ] Conception architecturale
|
||||||
|
- [ ] Développement du prototype
|
||||||
|
- [ ] Tests unitaires
|
||||||
|
- [ ] **Livrable** : Prototype fonctionnel
|
||||||
|
|
||||||
|
### Phase 3 : Développement (Durée : X semaines)
|
||||||
|
- [ ] Implémentation complète
|
||||||
|
- [ ] Tests d'intégration
|
||||||
|
- [ ] Optimisation des performances
|
||||||
|
- [ ] **Livrable** : Version beta
|
||||||
|
|
||||||
|
### Phase 4 : Validation et évaluation (Durée : X semaines)
|
||||||
|
- [ ] Tests sur jeux de données réels
|
||||||
|
- [ ] Évaluation comparative
|
||||||
|
- [ ] Analyse des résultats
|
||||||
|
- [ ] **Livrable** : Rapport d'évaluation
|
||||||
|
|
||||||
|
### Phase 5 : Finalisation (Durée : X semaines)
|
||||||
|
- [ ] Rédaction finale
|
||||||
|
- [ ] Préparation des publications
|
||||||
|
- [ ] Documentation utilisateur
|
||||||
|
- [ ] **Livrable** : Document final
|
||||||
|
|
||||||
|
## 6. Métriques et Évaluation
|
||||||
|
|
||||||
|
### 6.1 Critères de succès
|
||||||
|
- **Critère 1** : [Métrique] > [Seuil]
|
||||||
|
- **Critère 2** : [Métrique] < [Seuil]
|
||||||
|
|
||||||
|
### 6.2 Protocole d'évaluation
|
||||||
|
[Description de la méthode d'évaluation]
|
||||||
|
|
||||||
|
### 6.3 Benchmarks
|
||||||
|
[Comparaison avec les solutions existantes]
|
||||||
|
|
||||||
|
## 7. Gestion des Risques
|
||||||
|
|
||||||
|
### 7.1 Analyse des risques
|
||||||
|
|
||||||
|
| Risque | Description | Impact | Probabilité | Score | Plan de mitigation |
|
||||||
|
|--------|-------------|---------|-------------|-------|-------------------|
|
||||||
|
| R1 | | Élevé | Moyenne | 6 | |
|
||||||
|
| R2 | | Moyen | Faible | 3 | |
|
||||||
|
| R3 | | Faible | Élevée | 3 | |
|
||||||
|
|
||||||
|
### 7.2 Plans de contingence
|
||||||
|
- **Plan A** : [Si risque majeur]
|
||||||
|
- **Plan B** : [Si retard significatif]
|
||||||
|
|
||||||
|
## 8. Ressources et Budget
|
||||||
|
|
||||||
|
### 8.1 Ressources humaines
|
||||||
|
- **Chercheur principal** : [Temps alloué]
|
||||||
|
- **Collaborateurs** : [Temps alloué]
|
||||||
|
|
||||||
|
### 8.2 Ressources matérielles
|
||||||
|
- **Équipement** :
|
||||||
|
- **Logiciels** :
|
||||||
|
- **Infrastructure** :
|
||||||
|
|
||||||
|
### 8.3 Budget estimé
|
||||||
|
[Si applicable]
|
||||||
|
|
||||||
|
## 9. Livrables et Publications
|
||||||
|
|
||||||
|
### 9.1 Livrables techniques
|
||||||
|
- [ ] Code source documenté
|
||||||
|
- [ ] Documentation technique
|
||||||
|
- [ ] Guide d'utilisation
|
||||||
|
- [ ] Jeux de test
|
||||||
|
|
||||||
|
### 9.2 Publications visées
|
||||||
|
- [ ] Conférence internationale : [Nom]
|
||||||
|
- [ ] Journal : [Nom]
|
||||||
|
- [ ] Workshop : [Nom]
|
||||||
|
|
||||||
|
## 10. Journal de Bord
|
||||||
|
|
||||||
|
### [Date] - [Étape]
|
||||||
|
[Description des activités et découvertes]
|
||||||
|
|
||||||
|
### [Date] - [Étape]
|
||||||
|
[Description des activités et découvertes]
|
||||||
|
|
||||||
|
## 11. Références et Bibliographie
|
||||||
|
|
||||||
|
[1] Auteur, A. (Année). Titre. *Journal*, Volume(Numéro), pages.
|
||||||
|
|
||||||
|
[2] Auteur, B. (Année). *Titre du livre*. Éditeur.
|
||||||
|
|
||||||
|
## Annexes
|
||||||
|
|
||||||
|
### Annexe A : Données complémentaires
|
||||||
|
|
||||||
|
### Annexe B : Code source principal
|
||||||
|
|
||||||
|
### Annexe C : Résultats détaillés
|
55
templates/recherche/simple.md
Normal file
55
templates/recherche/simple.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Projet de Recherche
|
||||||
|
|
||||||
|
## 1. Contexte et Objectifs
|
||||||
|
|
||||||
|
### Problématique
|
||||||
|
[Décrivez le problème de recherche]
|
||||||
|
|
||||||
|
### Objectifs
|
||||||
|
- Objectif principal :
|
||||||
|
- Objectifs secondaires :
|
||||||
|
|
||||||
|
## 2. État de l'art
|
||||||
|
|
||||||
|
### Travaux existants
|
||||||
|
[Résumé des recherches précédentes]
|
||||||
|
|
||||||
|
### Lacunes identifiées
|
||||||
|
[Ce qui manque dans la littérature]
|
||||||
|
|
||||||
|
## 3. Méthodologie
|
||||||
|
|
||||||
|
### Approche
|
||||||
|
[Méthode de recherche choisie]
|
||||||
|
|
||||||
|
### Outils et ressources
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## 4. Planning
|
||||||
|
|
||||||
|
### Phase 1 : Recherche bibliographique
|
||||||
|
- [ ] Collecte des sources
|
||||||
|
- [ ] Analyse critique
|
||||||
|
|
||||||
|
### Phase 2 : Développement
|
||||||
|
- [ ] Conception
|
||||||
|
- [ ] Implémentation
|
||||||
|
|
||||||
|
### Phase 3 : Validation
|
||||||
|
- [ ] Tests
|
||||||
|
- [ ] Évaluation
|
||||||
|
|
||||||
|
## 5. Résultats attendus
|
||||||
|
|
||||||
|
[Description des livrables]
|
||||||
|
|
||||||
|
## 6. Risques et mitigation
|
||||||
|
|
||||||
|
| Risque | Impact | Probabilité | Mitigation |
|
||||||
|
|--------|---------|-------------|------------|
|
||||||
|
| | | | |
|
||||||
|
|
||||||
|
## 7. Notes et références
|
||||||
|
|
||||||
|
[Espace pour notes additionnelles]
|
125
views/header.js
125
views/header.js
@ -3,24 +3,117 @@ function getHeader() {
|
|||||||
<header>
|
<header>
|
||||||
<div>
|
<div>
|
||||||
<h1>Mon Journal de Conception</h1>
|
<h1>Mon Journal de Conception</h1>
|
||||||
<select id="skeleton-select" title="Niveau de squelette">
|
|
||||||
<option value="low">Squelette : Low</option>
|
|
||||||
<option value="medium">Medium</option>
|
|
||||||
<option value="high">High</option>
|
|
||||||
</select>
|
|
||||||
<button id="validate-skeleton" title="Valider niveau de squelette">Valider</button>
|
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
|
||||||
<button id="theme-toggle" title="Basculer Black & White">🌓</button>
|
|
||||||
<button id="export-pdf" title="Exporter en PDF">📄 PDF</button>
|
|
||||||
<button id="export-md" title="Exporter en Markdown">📝 MD</button>
|
|
||||||
<label for="import-md" title="Importer Markdown">
|
|
||||||
<input id="import-md" type="file" accept=".md" style="display:none;">
|
|
||||||
⬆️ MD
|
|
||||||
</label>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</header>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getHeader };
|
function getLeftPanel() {
|
||||||
|
return `
|
||||||
|
<div class="side-panel left-panel" id="left-panel">
|
||||||
|
<div class="panel-toggle" onclick="togglePanel('left')">
|
||||||
|
<span class="arrow">▶</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<h3 class="panel-header">⚙️ Templates de Projet</h3>
|
||||||
|
|
||||||
|
<div class="template-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="domain-select">Domaine :</label>
|
||||||
|
<select id="domain-select" title="Choisir le domaine">
|
||||||
|
<option value="">-- Sélectionner un domaine --</option>
|
||||||
|
<option value="recherche">🔬 Recherche</option>
|
||||||
|
<option value="math">📐 Mathématiques</option>
|
||||||
|
<option value="informatique">💻 Informatique</option>
|
||||||
|
<option value="ingenierie">⚙️ Ingénierie</option>
|
||||||
|
<option value="business">💼 Business</option>
|
||||||
|
<option value="design">🎨 Design</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="level-select">Niveau de détail :</label>
|
||||||
|
<select id="level-select" title="Niveau de détail" disabled>
|
||||||
|
<option value="">-- Choisir d'abord un domaine --</option>
|
||||||
|
<option value="simple">📄 Simple</option>
|
||||||
|
<option value="detaille">📋 Détaillé</option>
|
||||||
|
<option value="complet">📚 Complet</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="template-preview" id="template-preview" style="display: none;">
|
||||||
|
<h4>Aperçu :</h4>
|
||||||
|
<div class="preview-content" id="preview-content">
|
||||||
|
<!-- Aperçu du template sélectionné -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button id="load-template" class="btn success" title="Charger le template" disabled>
|
||||||
|
✨ Charger le template
|
||||||
|
</button>
|
||||||
|
<button id="reset-template" class="btn secondary" title="Réinitialiser">
|
||||||
|
🔄 Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRightPanel() {
|
||||||
|
return `
|
||||||
|
<div class="side-panel right-panel" id="right-panel">
|
||||||
|
<div class="panel-toggle" onclick="togglePanel('right')">
|
||||||
|
<span class="arrow">◀</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<h3 class="panel-header">🛠️ Outils</h3>
|
||||||
|
|
||||||
|
<div class="nav-section">
|
||||||
|
<h4>Apparence</h4>
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<button class="nav-btn" id="theme-toggle" title="Basculer mode sombre">
|
||||||
|
<span class="icon">🌓</span>
|
||||||
|
<span>Basculer le thème</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-section">
|
||||||
|
<h4>Export</h4>
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<button class="nav-btn" id="export-pdf" title="Exporter en PDF">
|
||||||
|
<span class="icon">📄</span>
|
||||||
|
<span>Exporter en PDF</span>
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn" id="export-md" title="Exporter en Markdown">
|
||||||
|
<span class="icon">📝</span>
|
||||||
|
<span>Exporter en Markdown</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-section">
|
||||||
|
<h4>Import</h4>
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<label class="panel-file-input" for="import-md" title="Importer Markdown">
|
||||||
|
<input id="import-md" type="file" accept=".md" style="display:none;">
|
||||||
|
<span class="icon">⬆️</span>
|
||||||
|
<span>Importer Markdown</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPanelOverlay() {
|
||||||
|
return `
|
||||||
|
<div class="panel-overlay" id="panel-overlay" onclick="closeAllPanels()"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getHeader, getLeftPanel, getRightPanel, getPanelOverlay };
|
@ -2,45 +2,60 @@ function getMain() {
|
|||||||
return `
|
return `
|
||||||
<main>
|
<main>
|
||||||
<section id="table-of-contents">
|
<section id="table-of-contents">
|
||||||
<h2>Table des matières</h2>
|
<h2>📑 Table des matières</h2>
|
||||||
<nav id="toc-nav">
|
<nav id="toc-nav">
|
||||||
<!--
|
<div class="toc-placeholder">
|
||||||
Exemple de structure hiérarchique :
|
<p>La table des matières sera générée automatiquement à partir des titres de votre journal.</p>
|
||||||
<ul>
|
<button id="refresh-toc" class="btn secondary">🔄 Actualiser</button>
|
||||||
<li><a href="#section1">1. Introduction</a>
|
</div>
|
||||||
<ul>
|
|
||||||
<li><a href="#subsection1-1">1.1 Objectifs</a></li>
|
|
||||||
<li><a href="#subsection1-2">1.2 Contexte</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
-->
|
|
||||||
</nav>
|
</nav>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="design-journal">
|
<section id="design-journal">
|
||||||
<h2>Journal de conception</h2>
|
<h2>📝 Journal de conception</h2>
|
||||||
|
<div id="journal-controls" style="padding: 1rem; border-bottom: 1px solid var(--border-color); display: flex; gap: 1rem; align-items: center;">
|
||||||
|
<button id="save-journal" class="btn success">💾 Sauvegarder</button>
|
||||||
|
<button id="load-journal" class="btn">📂 Charger</button>
|
||||||
|
<span id="save-status" class="text-light"></span>
|
||||||
|
</div>
|
||||||
<div id="journal-editor" contenteditable="true">
|
<div id="journal-editor" contenteditable="true">
|
||||||
<!-- Zone d’écriture principale -->
|
<!-- Zone d'écriture principale -->
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="ai-assistant">
|
<section id="ai-assistant">
|
||||||
<h2>Assistant IA</h2>
|
<h2>🤖 Assistant IA</h2>
|
||||||
<button id="activate-rephrase">Reformuler un passage</button>
|
<div class="ai-coming-soon">
|
||||||
<button id="check-inconsistencies">Vérifier incohérences</button>
|
<div class="coming-soon-badge">
|
||||||
<button id="check-duplications">Vérifier duplications</button>
|
<span class="badge-icon">🚧</span>
|
||||||
<button id="give-advice">Conseil sur le contenu</button>
|
<span class="badge-text">Fonctionnalités à venir</span>
|
||||||
<div>
|
</div>
|
||||||
<input type="number" id="liberty-repeat-count" min="1" placeholder="Combien d’itérations ?" />
|
<div class="ai-preview">
|
||||||
<button id="liberty-mode">Mode Liberté : auto-suites</button>
|
<h3>Prochainement disponible avec Mistral AI :</h3>
|
||||||
|
<ul class="ai-features-list">
|
||||||
|
<li>✨ <strong>Reformulation intelligente</strong> - Amélioration du style et de la clarté</li>
|
||||||
|
<li>🔍 <strong>Détection d'incohérences</strong> - Analyse sémantique du contenu</li>
|
||||||
|
<li>📋 <strong>Vérification des doublons</strong> - Identification des répétitions</li>
|
||||||
|
<li>💡 <strong>Conseils personnalisés</strong> - Suggestions d'amélioration</li>
|
||||||
|
<li>🚀 <strong>Mode Liberté</strong> - Génération automatique de suites</li>
|
||||||
|
</ul>
|
||||||
|
<div class="ai-tech-stack">
|
||||||
|
<p><strong>Technologie :</strong> Powered by Mistral AI</p>
|
||||||
|
<p><strong>Modèle :</strong> Mistral Large (multilingue)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ai-assistant-feedback">
|
<div id="ai-assistant-feedback">
|
||||||
<!-- Résultats et retours IA affichés ici -->
|
<div class="feedback-message coming-soon-message">
|
||||||
|
<strong>🔮 Intelligence Artificielle en développement</strong><br>
|
||||||
|
Ces fonctionnalités seront bientôt disponibles pour améliorer votre expérience de rédaction.
|
||||||
|
<br><br>
|
||||||
|
<small>💡 En attendant, utilisez les templates pour structurer efficacement vos journaux de conception.</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getMain };
|
module.exports = { getMain };
|
@ -1,4 +1,4 @@
|
|||||||
const { getHeader } = require('./header');
|
const { getHeader, getLeftPanel, getRightPanel, getPanelOverlay } = require('./header');
|
||||||
const { getMain } = require('./main');
|
const { getMain } = require('./main');
|
||||||
const { getFooter } = require('./footer');
|
const { getFooter } = require('./footer');
|
||||||
|
|
||||||
@ -11,6 +11,7 @@ function getHead(){
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Conception-Assistant</title>
|
<title>Conception-Assistant</title>
|
||||||
<link rel="stylesheet" href="/assets/css/style.css">
|
<link rel="stylesheet" href="/assets/css/style.css">
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📝</text></svg>">
|
||||||
</head>
|
</head>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -19,9 +20,14 @@ function getBody(){
|
|||||||
return `
|
return `
|
||||||
<body>`
|
<body>`
|
||||||
+ getHeader()
|
+ getHeader()
|
||||||
|
+ getLeftPanel()
|
||||||
|
+ getRightPanel()
|
||||||
|
+ getPanelOverlay()
|
||||||
+ getMain()
|
+ getMain()
|
||||||
+ getFooter() +
|
+ getFooter() +
|
||||||
`</body>
|
`
|
||||||
|
<script src="/assets/js/app.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user