ernestmindres commited on
Commit
b7335d6
·
verified ·
1 Parent(s): 27a627a

Update templates/create_project.html

Browse files
Files changed (1) hide show
  1. templates/create_project.html +874 -859
templates/create_project.html CHANGED
@@ -1,860 +1,875 @@
1
- <!DOCTYPE html>
2
- <html lang="fr" class="dark">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Mailix | Éditeur de Code - Pro</title>
7
- <link rel="icon" type="image/png" href="https://i.imgur.com/7Gn3toV.png">
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <link rel="stylesheet"
10
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
11
-
12
-
13
-
14
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
15
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css">
16
-
17
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
18
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/xml/xml.min.js"></script>
19
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
20
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/css/css.min.js"></script>
21
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/htmlmixed/htmlmixed.min.js"></script>
22
-
23
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/python/python.min.js"></script>
24
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/php/php.min.js"></script>
25
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/clike/clike.min.js"></script>
26
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js"></script>
27
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/markdown/markdown.min.js"></script>
28
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/shell/shell.min.js"></script>
29
-
30
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/typescript/typescript.min.js"></script>
31
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/go/go.min.js"></script>
32
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/yaml/yaml.min.js"></script>
33
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/rust/rust.min.js"></script>
34
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/stex/stex.min.js"></script>
35
- <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/meta.js"></script> </head>
36
-
37
- <style>
38
- /* Configuration de la police Inter */
39
- html, body {
40
- font-family: 'Inter', sans-serif;
41
- height: 100vh; /* Utiliser 100vh pour garantir la hauteur de la fenêtre */
42
- overflow: hidden; /* Supprimer le défilement de l'écran principal */
43
- }
44
-
45
- /* Transition pour un effet doux des couleurs */
46
- .transition-colors-theme { transition: background-color 0.3s, color 0.3s, border-color 0.3s; }
47
-
48
- /* Définir les Variables de Thème - UNIQUEMENT Mode Sombre (Défaut) */
49
- :root, .dark {
50
- --bg-page: #121212;
51
- --bg-card: #1E1E1E;
52
- --bg-sidebar: #1E1E1E;
53
- --bg-sidebar-active: #2C2C2C;
54
- --border-sidebar: #383838;
55
- --text-color: #FFFFFF;
56
- --text-secondary: #D1D5DB;
57
- --text-link: #3B82F6;
58
- --text-link-hover: #60A5FA;
59
- --input-bg: #2C2C2C;
60
- --input-border: #4B5563;
61
- --input-text-key: #6EE7B7;
62
- --primary-color: #3B82F6;
63
- --primary-hover: #2563EB;
64
- --warning-bg: #451A03;
65
- --warning-border: #78350F;
66
- --warning-text: #FBBF24;
67
- --editor-tab-bg: #1E1E1E; /* Fond des onglets de l'éditeur */
68
- --editor-tab-active-bg: #2C2C2C; /* Fond de l'onglet actif */
69
- --status-bar-bg: #007ACC; /* Bleu VS Code pour la barre de statut */
70
- }
71
- /* Mode Clair (Supprimé selon le plan) */
72
-
73
- /* Appliquer les variables au body et aux éléments (Conservées) */
74
- .bg-page-var { background-color: var(--bg-page); }
75
- .bg-card-var { background-color: var(--bg-card); }
76
- .bg-sidebar-var { background-color: var(--bg-sidebar); }
77
- .border-sidebar-var { border-color: var(--border-sidebar); }
78
- .text-color-var { color: var(--text-color); }
79
- .text-secondary-var { color: var(--text-secondary); }
80
- .text-link-var { color: var(--text-link); }
81
- .hover\:text-link-hover-var:hover { color: var(--text-link-hover); }
82
- .bg-input-var { background-color: var(--input-bg); }
83
- .border-input-var { border-color: var(--input-border); }
84
- .text-input-key-var { color: var(--input-text-key); }
85
- .bg-primary-color-var { background-color: var(--primary-color); }
86
- .hover\:bg-primary-hover-var:hover { background-color: var(--primary-hover); }
87
- .focus\:ring-primary-color-var:focus { --tw-ring-color: var(--primary-color); }
88
- .focus\:border-primary-color-var:focus { border-color: var(--primary-color); }
89
- .bg-sidebar-active-var { background-color: var(--bg-sidebar-active); }
90
- .bg-warning-var { background-color: var(--warning-bg); }
91
- .border-warning-var { border-color: var(--warning-border); }
92
- .text-warning-var { color: var(--warning-text); }
93
-
94
-
95
- /* Styles pour les sidebars - FIXES */
96
- .sidebar {
97
- transition: transform 0.3s ease-in-out;
98
- position: fixed;
99
- top: 0;
100
- z-index: 30;
101
- height: 100vh;
102
- }
103
-
104
- /* Sidebar Gauche - Masquée par défaut sur toutes les tailles */
105
- .sidebar-left {
106
- transform: translateX(-100%);
107
- left: 0;
108
- }
109
-
110
- .sidebar-left.open {
111
- transform: translateX(0);
112
- }
113
-
114
- /* FIX 1.1: Sidebar Droite - Masquée par défaut, positionnée à droite */
115
- .sidebar-right {
116
- transform: translateX(100%);
117
- right: 0;
118
- }
119
-
120
- .sidebar-right.open {
121
- transform: translateX(0);
122
- }
123
-
124
- /* Conteneur principal pour décaler le contenu sur les grands écrans (lg) */
125
- @media (min-width: 1024px) {
126
- .main-content {
127
- margin-left: 0;
128
- margin-right: 0;
129
- }
130
- /* Décalage si sidebar gauche ouverte */
131
- .main-content.sidebar-left-open {
132
- margin-left: 256px;
133
- }
134
- /* Décalage si sidebar droite ouverte */
135
- .main-content.sidebar-right-open {
136
- margin-right: 256px;
137
- }
138
- /* Décalage si les deux sidebars sont ouvertes */
139
- .main-content.sidebar-left-open.sidebar-right-open {
140
- margin-left: 256px;
141
- margin-right: 256px;
142
- }
143
- }
144
-
145
- /* Ajustement du bouton de Thème (Supprimé) */
146
- .theme-switch {
147
- display: none;
148
- }
149
-
150
- /* Styles Spécifiques à l'Éditeur */
151
- .editor-container {
152
- /* Maintient la disposition en colonne pour header, tabs, editor, footer */
153
- display: flex;
154
- flex-direction: column;
155
- /* Le conteneur s'étire maintenant grâce au body flex-column */
156
- height: 100%;
157
- flex-grow: 1; /* Permet au main-content de prendre l'espace restant dans la ligne flex body */
158
- padding-top: 0;
159
- }
160
-
161
- /* FIX 3: Réduction du padding du header */
162
- #main-header {
163
- padding-top: 0.5rem; /* p-2 */
164
- padding-bottom: 0.5rem; /* p-2 */
165
- padding-left: 1rem; /* px-4 */
166
- padding-right: 1rem; /* px-4 */
167
- }
168
-
169
- /* FIX 2.2: Le wrapper de l'éditeur prend l'espace restant dans la colonne flex */
170
- #code-editor-wrapper {
171
- flex-grow: 1;
172
- overflow: hidden; /* Empêche le CodeMirror de faire défiler le parent */
173
- }
174
-
175
- /* CodeMirror Styling - Pour un look sombre VS Code (Monokai) */
176
- .CodeMirror {
177
- flex-grow: 1;
178
- height: 100% !important; /* Essentiel pour que le défilement CodeMirror fonctionne */
179
- font-size: 14px;
180
- line-height: 1.5;
181
- background: #272822; /* Fond Monokai */
182
- color: #f8f8f2;
183
- border-top: 1px solid var(--border-sidebar);
184
- border-bottom: 1px solid var(--border-sidebar);
185
- }
186
-
187
- /* Style de la barre d'onglets */
188
- #tab-bar {
189
- background-color: var(--editor-tab-bg);
190
- border-bottom: 1px solid var(--border-sidebar);
191
- min-height: 40px;
192
- padding-left: 10px;
193
- /* MODIFICATION 2.1 - 2.3: Permet le défilement horizontal */
194
- overflow-x: auto;
195
- white-space: nowrap; /* Empêche le wrapping des onglets */
196
- }
197
-
198
- .tab-item {
199
- /* MODIFICATION 2.2: S'assure que les onglets ne se rétrécissent pas pour le défilement */
200
- flex-shrink: 0;
201
- display: inline-flex; /* Utilisation de inline-flex au lieu de flex pour un meilleur comportement avec white-space: nowrap */
202
- align-items: center;
203
- padding: 8px 15px;
204
- border-right: 1px solid var(--border-sidebar);
205
- cursor: pointer;
206
- color: var(--text-secondary);
207
- font-size: 0.875rem;
208
- transition: background-color 0.2s;
209
- }
210
-
211
- .tab-item.active {
212
- background-color: var(--editor-tab-active-bg);
213
- color: var(--text-color);
214
- position: relative;
215
- }
216
-
217
- .tab-item:hover:not(.active) {
218
- background-color: var(--bg-sidebar-active);
219
- }
220
-
221
- .tab-close {
222
- margin-left: 8px;
223
- font-size: 14px;
224
- color: var(--text-secondary);
225
- }
226
-
227
- /* Style de la barre de statut */
228
- #status-bar {
229
- background-color: var(--status-bar-bg);
230
- color: white;
231
- min-height: 25px;
232
- font-size: 0.75rem;
233
- }
234
-
235
- /* Modale */
236
- .modal {
237
- position: fixed;
238
- top: 0;
239
- left: 0;
240
- width: 100%;
241
- height: 100%;
242
- background-color: rgba(0, 0, 0, 0.7);
243
- display: none;
244
- justify-content: center;
245
- align-items: center;
246
- z-index: 50;
247
- }
248
- .modal.open {
249
- display: flex;
250
- }
251
- .modal-content {
252
- padding: 1.5rem;
253
- border-radius: 0.5rem;
254
- width: 90%;
255
- max-width: 400px;
256
- }
257
- .input-text {
258
- border-width: 1px;
259
- padding: 0.75rem;
260
- border-radius: 0.375rem;
261
- width: 100%;
262
- font-size: 1rem;
263
- }
264
-
265
-
266
- /* Masquer la marge du main-content sur mobile pour l'éditeur */
267
- @media (max-width: 1023px) {
268
- .main-content {
269
- padding: 0;
270
- }
271
- .editor-container {
272
- /* Calcule la hauteur totale - (Header + barre de statut + barre d'onglets) pour l'éditeur */
273
- /* Cette règle est moins critique si flex-grow est appliqué correctement */
274
- height: calc(100vh - 40px - 40px - 25px); /* Environ */
275
- }
276
- }
277
- </style>
278
- </head>
279
-
280
- <body class="bg-page-var text-color-var min-h-screen transition-colors-theme flex flex-col">
281
-
282
- <aside id="sidebar-left" class="sidebar sidebar-left w-64 bg-sidebar-var border-r border-sidebar-var p-6 flex flex-col transition-colors-theme">
283
-
284
- <div class="flex items-center mb-6 shrink-0">
285
- <img src="https://i.imgur.com/7Gn3toV.png" alt="Nexus Pro Logo" class="h-8">
286
- <span class="text-xl font-bold text-color-var ml-2 transition-colors-theme">Mailix</span>
287
- </div>
288
-
289
- <div class="flex-grow overflow-y-auto pr-2" id="scrollable-nav-container">
290
- <h3 class="text-xs font-semibold uppercase tracking-wider text-secondary-var mb-2">Fichiers Ouverts</h3>
291
- <nav id="file-explorer" class="space-y-1 pb-4 border-b border-sidebar-var/50 mb-4">
292
- </nav>
293
-
294
- <h3 class="text-xs font-semibold uppercase tracking-wider text-secondary-var mb-2">Navigation</h3>
295
- <nav class="space-y-3 pb-4">
296
- <a href="{{ url_for('user_bp.dashboard') }}" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
297
- <span class="material-symbols-rounded">dashboard</span>
298
- <span class="ml-3">Dashboard</span>
299
- </a>
300
-
301
- <a href="{{ url_for('user_bp.profile') }}" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
302
- <span class="material-symbols-rounded">person</span>
303
- <span class="ml-3">Profil</span>
304
- </a>
305
- <a href="/parametres" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
306
- <span class="material-symbols-rounded">settings</span> <span class="ml-3">Paramètres</span>
307
- </a>
308
-
309
- <a href="/aide" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
310
- <span class="material-symbols-rounded">help</span>
311
- <span class="ml-3">Aide & Support</span>
312
- </a>
313
- <a href="{{ url_for('user_bp.profile') }}" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
314
- <span class="material-symbols-rounded">person</span>
315
- <span class="ml-3">Profil</span>
316
- </a>
317
-
318
- <a href="/tarifs" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
319
- <span class="material-symbols-rounded">payments</span>
320
- <span class="ml-3">Tarifs</span>
321
- </a>
322
- <a href="/statut" class="flex items-center p-3 bg-sidebar-active-var text-color-var rounded-lg transition duration-150 transition-colors-theme">
323
- <span class="material-symbols-rounded">monitor_heart</span>
324
- <span class="ml-3">Statut de l'API</span>
325
- </a>
326
- <a href="{{ url_for('user_bp.deconnexion') }}" id="logout-button-sidebar" class="flex items-center p-3 text-red-500 hover:text-red-400 hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
327
- <span class="material-symbols-rounded">deconnexion</span>
328
- <span class="ml-3">Déconnexion</span>
329
- </a>
330
- </nav>
331
- </div>
332
-
333
- <div class="mt-auto pt-4 text-xs text-secondary-var transition-colors-theme border-t border-sidebar-var/50 shrink-0">
334
- <p>Connecté en tant que: <span class="font-semibold text-color-var transition-colors-theme">{{ user.email if user and user.email else 'Chargement...' }}</span></p>
335
- <p>Plan: <span class="font-semibold text-color-var transition-colors-theme">{{ (user.plan | upper) if user and user.plan else 'GRATUIT' }}</span></p>
336
- </div>
337
-
338
- </aside>
339
-
340
-
341
- <aside id="sidebar-right" class="sidebar sidebar-right w-64 bg-sidebar-var border-l border-sidebar-var p-4 flex flex-col transition-colors-theme overflow-y-auto">
342
- <div class="flex items-center mb-4 shrink-0 border-b border-sidebar-var/50 pb-2">
343
- <span class="material-symbols-rounded text-xl mr-2">settings</span>
344
- <span class="text-lg font-semibold text-color-var">Outils & Paramètres</span>
345
- </div>
346
-
347
- <div id="editor-settings-section" class="mb-6">
348
- <h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
349
- <span class="material-symbols-rounded text-base mr-1">settings</span>
350
- Paramètres de l'Éditeur
351
- </h4>
352
- <div class="space-y-3 p-2 bg-sidebar-active-var rounded-lg">
353
- <div>
354
- <label class="block text-xs font-medium text-secondary-var mb-1">Thème du Code</label>
355
- <select class="w-full bg-input-var text-color-var p-2 rounded-md border border-input-var text-sm">
356
- <option selected>Monokai (Défaut)</option>
357
- <option>VS Light</option>
358
- <option>Dracula</option>
359
- </select>
360
- </div>
361
- <div>
362
- <label class="block text-xs font-medium text-secondary-var mb-1">Taille de Police</label>
363
- <input type="range" min="10" max="20" value="14" class="w-full h-1 bg-primary-color-var rounded-lg appearance-none cursor-pointer">
364
- <p class="text-xs text-secondary-var mt-1">Taille actuelle: 14px</p>
365
- </div>
366
- <div>
367
- <label class="block text-xs font-medium text-secondary-var mb-1">Indentation</label>
368
- <select class="w-full bg-input-var text-color-var p-2 rounded-md border border-input-var text-sm">
369
- <option selected>Espaces (4)</option>
370
- <option>Espaces (2)</option>
371
- <option>Tabulations</option>
372
- </select>
373
- </div>
374
- </div>
375
- </div>
376
-
377
- <div id="minimap-section" class="mb-6">
378
- <h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
379
- <span class="material-symbols-rounded text-base mr-1">map</span>
380
- Aperçu du Code (Minimap)
381
- </h4>
382
- <div class="h-20 bg-input-var border border-input-border rounded-lg flex items-center justify-center text-xs text-secondary-var/70">
383
- [Zone de Minimap Simulé]
384
- </div>
385
- </div>
386
-
387
- <div id="inspection-section" class="flex-grow">
388
- <h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
389
- <span class="material-symbols-rounded text-base mr-1">search</span>
390
- Inspection/Propriétés
391
- </h4>
392
- <div class="p-3 bg-sidebar-active-var rounded-lg h-full overflow-y-auto text-xs space-y-2">
393
- <p class="font-semibold text-color-var">Élément sélectionné: &lt;body&gt;</p>
394
- <p><span class="text-input-key-var">background-color:</span> var(--bg-page);</p>
395
- <p><span class="text-input-key-var">color:</span> var(--text-color);</p>
396
- <p class="text-secondary-var mt-4">[Aide Contextuelle pour la balise HTML 'body']</p>
397
- </div>
398
- </div>
399
- </aside>
400
-
401
-
402
- <div id="main-content" class="main-content flex-grow transition-colors-theme editor-container">
403
-
404
- <header id="main-header" class="flex justify-between items-center bg-page-var p-2 border-b border-sidebar-var shrink-0">
405
- <button id="sidebar-left-toggle" class="mobile-toggle-button p-2 rounded-lg bg-sidebar-active-var hover:bg-sidebar-active-var/80 transition-colors-theme">
406
- <span class="material-symbols-rounded text-color-var">menu</span>
407
- </button>
408
- <div class="flex items-center">
409
- <img src="https://i.imgur.com/7Gn3toV.png" alt="Nexus Pro Logo" class="h-8">
410
- <span class="text-xl font-bold text-color-var ml-2 transition-colors-theme">Mailix Editor</span>
411
- </div>
412
- <button id="sidebar-right-toggle" class="mobile-toggle-button p-2 rounded-lg bg-sidebar-active-var hover:bg-sidebar-active-var/80 transition-colors-theme" title="Outils et Paramètres">
413
- <span class="material-symbols-rounded text-color-var">more_vert</span>
414
- </button>
415
- </header>
416
-
417
- <div id="tab-bar" class="flex items-center overflow-x-auto shrink-0">
418
- <button id="add-tab-button" class="flex items-center p-2 text-secondary-var hover:text-color-var transition-colors-theme" title="Ajouter un nouveau fichier">
419
- <span class="material-symbols-rounded text-xl">add</span>
420
- </button>
421
- <button id="import-button" class="flex items-center p-2 text-secondary-var hover:text-color-var transition-colors-theme" title="Importer un fichier local">
422
- <span class="material-symbols-rounded text-xl">file_upload</span>
423
- </button>
424
- <input type="file" id="file-input" class="hidden" />
425
-
426
- <button id="save-button" class="ml-auto flex items-center p-2 text-link-var hover:text-link-hover-var transition-colors-theme font-semibold border-l border-sidebar-var" title="Sauvegarder localement">
427
- <span class="material-symbols-rounded text-xl mr-1">save</span>
428
- Sauvegarder
429
- </button>
430
- </div>
431
-
432
- <div id="code-editor-wrapper">
433
- <textarea id="code-editor-textarea" style="display: none;">
434
- &lt;!DOCTYPE html&gt;
435
- &lt;html lang="fr"&gt;
436
- &lt;head&gt;
437
- &lt;meta charset="UTF-8"&gt;
438
- &lt;title&gt;Mon Nouveau Projet&lt;/title&gt;
439
- &lt;link rel="stylesheet" href="style.css"&gt;
440
- &lt;/head&gt;
441
- &lt;body&gt;
442
- &lt;h1&gt;Bienvenue dans l'Éditeur Mailix&lt;/h1&gt;
443
- &lt;p&gt;Ce code est mis en évidence par CodeMirror.&lt;/p&gt;
444
- &lt;/body&gt;
445
- &lt;/html&gt;
446
- </textarea>
447
- </div>
448
-
449
- <footer id="status-bar" class="flex items-center justify-between px-4 py-0.5 shrink-0">
450
- <div class="flex items-center space-x-4">
451
- <span class="material-symbols-rounded text-sm">code</span>
452
- <span id="status-language">Langage: HTML</span>
453
- </div>
454
- <div class="flex items-center space-x-4">
455
- <span id="status-encoding">UTF-8</span>
456
- <span id="status-line-col">Ligne: 1, Col: 1</span>
457
- </div>
458
- </footer>
459
-
460
- </div>
461
-
462
- <div id="create-file-modal" class="modal">
463
- <div class="modal-content bg-card-var text-color-var">
464
- <h2 class="text-xl font-bold mb-4">Créer un Nouveau Fichier</h2>
465
- <input type="text" id="new-file-name-input" class="input-text bg-input-var border-input-var text-color-var focus:border-primary-color-var focus:ring-1 focus:ring-primary-color-var outline-none mb-4" placeholder="Nom du fichier (ex: index.html, style.css, script.js)" required>
466
- <div class="flex justify-end space-x-3">
467
- <button id="cancel-create-file" class="px-4 py-2 text-secondary-var hover:text-color-var rounded-lg transition-colors-theme">Annuler</button>
468
- <button id="confirm-create-file" class="px-4 py-2 bg-primary-color-var hover:bg-primary-hover-var text-white font-semibold rounded-lg transition-colors-theme">Créer</button>
469
- </div>
470
- </div>
471
- </div>
472
-
473
- <script>
474
- // --- PARTIE 1: GESTION DE LA SIDEBAR (Gauche et Droite) ---
475
-
476
- // Éléments de la Sidebar Gauche
477
- const sidebarLeftToggle = document.getElementById('sidebar-left-toggle');
478
- const sidebarLeft = document.getElementById('sidebar-left'); // ID mis à jour
479
- const mainContent = document.getElementById('main-content');
480
-
481
- // Éléments de la Sidebar Droite (NOUVEAU)
482
- const sidebarRightToggle = document.getElementById('sidebar-right-toggle');
483
- const sidebarRight = document.getElementById('sidebar-right');
484
-
485
- // Logique pour la Sidebar Gauche
486
- function toggleSidebarLeft() {
487
- sidebarLeft.classList.toggle('open');
488
- // Gérer le décalage du contenu principal sur grand écran (lg)
489
- if (window.innerWidth >= 1024) {
490
- mainContent.classList.toggle('sidebar-left-open');
491
- }
492
- }
493
-
494
- // Logique pour la Sidebar Droite (NOUVEAU)
495
- function toggleSidebarRight() {
496
- sidebarRight.classList.toggle('open');
497
- // Gérer le décalage du contenu principal sur grand écran (lg)
498
- if (window.innerWidth >= 1024) {
499
- mainContent.classList.toggle('sidebar-right-open');
500
- }
501
- }
502
-
503
- sidebarLeftToggle.addEventListener('click', toggleSidebarLeft);
504
- sidebarRightToggle.addEventListener('click', toggleSidebarRight); // Lier le nouveau bouton
505
-
506
-
507
- // Logique de Déconnexion (Conservée)
508
- document.getElementById('logout-button-sidebar').addEventListener('click', (e) => {
509
- e.preventDefault();
510
- window.location.href = e.currentTarget.href;
511
- });
512
-
513
-
514
- // --- PARTIE 2: GESTION DE L'ÉDITEUR DE CODE (CodeMirror + Onglets) ---
515
-
516
- // Structure de données pour les fichiers (V1: Stockage local)
517
- let files = [
518
- { id: 1, name: 'index.html', content: document.getElementById('code-editor-textarea').value, mode: 'htmlmixed', isActive: true },
519
- { id: 2, name: 'style.css', content: "body {\n font-family: sans-serif;\n color: #4CAF50; /* Green */\n}", mode: 'css', isActive: false },
520
- { id: 3, name: 'script.js', content: "console.log('Editor Ready!');", mode: 'javascript', isActive: false }
521
- ];
522
- let fileIdCounter = files.length + 1;
523
- let currentEditor = null; // L'instance CodeMirror
524
-
525
- const tabBar = document.getElementById('tab-bar');
526
- const editorTextarea = document.getElementById('code-editor-textarea');
527
- const statusBarLanguage = document.getElementById('status-language');
528
- const statusBarLineCol = document.getElementById('status-line-col');
529
- const fileExplorer = document.getElementById('file-explorer');
530
-
531
- // Éléments de la modale
532
- const createFileModal = document.getElementById('create-file-modal');
533
- const newFileNameInput = document.getElementById('new-file-name-input');
534
- const confirmCreateFileButton = document.getElementById('confirm-create-file');
535
- const cancelCreateFileButton = document.getElementById('cancel-create-file');
536
-
537
- /**
538
- * Détermine le mode CodeMirror à partir de l'extension du nom de fichier.
539
- * @param {string} fileName Le nom du fichier.
540
- * @returns {string} Le mode CodeMirror.
541
- */
542
-
543
- function getModeFromFileExtension(fileName) {
544
- const ext = fileName.split('.').pop().toLowerCase();
545
- switch (ext) {
546
- // Web
547
- case 'html':
548
- case 'htm':
549
- return 'htmlmixed';
550
- case 'css':
551
- return 'css';
552
- case 'js':
553
- return 'javascript';
554
- case 'ts':
555
- return 'typescript'; // Nouveau
556
- case 'json':
557
- return 'application/json'; // Nouveau (mode spécifique pour JSON)
558
-
559
- // Backend / Systèmes
560
- case 'py':
561
- return 'python';
562
- case 'php':
563
- case 'phtml':
564
- return 'php';
565
- case 'go':
566
- return 'go'; // Nouveau
567
- case 'rs':
568
- return 'rust'; // Nouveau
569
- case 'sh':
570
- return 'shell';
571
-
572
- // Langages C-like (Java, C, C++, C#)
573
- case 'java':
574
- case 'c':
575
- case 'cpp':
576
- case 'cs':
577
- return 'clike';
578
-
579
- // Données et Bases de données
580
- case 'sql':
581
- return 'sql';
582
- case 'yaml':
583
- case 'yml':
584
- return 'yaml'; // Nouveau
585
-
586
- // Documentation et autres
587
- case 'md':
588
- case 'markdown':
589
- return 'markdown';
590
- case 'tex':
591
- case 'latex':
592
- return 'stex'; // Nouveau (pour LaTeX)
593
-
594
- default:
595
- return 'text/plain'; // Mode texte brut pour les inconnus ou le texte simple
596
- }
597
- }
598
-
599
-
600
- /**
601
- * Initialise CodeMirror sur le textarea.
602
- */
603
- function initializeEditor(initialContent, mode) {
604
- if (currentEditor) {
605
- currentEditor.toTextArea(); // Détruit l'ancienne instance
606
- currentEditor = null;
607
- }
608
-
609
- editorTextarea.value = initialContent;
610
-
611
- currentEditor = CodeMirror.fromTextArea(editorTextarea, {
612
- lineNumbers: true,
613
- mode: mode,
614
- theme: 'monokai',
615
- indentUnit: 4,
616
- tabSize: 4,
617
- indentWithTabs: false,
618
- autofocus: true,
619
- // MODIFICATION 1.2: Désactiver le retour à la ligne pour forcer le défilement horizontal
620
- lineWrapping: false,
621
- });
622
-
623
- // Événement de mise à jour du contenu et de la barre de statut
624
- currentEditor.on("change", handleEditorChange);
625
- currentEditor.on("cursorActivity", updateStatusBar);
626
-
627
- // S'assurer que CodeMirror utilise 100% de la hauteur disponible dans son conteneur parent flexible
628
- const cmElement = currentEditor.getWrapperElement();
629
- cmElement.style.height = '100%';
630
-
631
- updateStatusBar();
632
- }
633
-
634
- /**
635
- * Met à jour le contenu de l'objet fichier actif dans le tableau `files`.
636
- */
637
- function handleEditorChange() {
638
- if (currentEditor) {
639
- const activeFile = files.find(f => f.isActive);
640
- if (activeFile) {
641
- activeFile.content = currentEditor.getValue();
642
- }
643
- }
644
- }
645
-
646
- /**
647
- * Met à jour les indicateurs Ligne/Col dans la barre de statut.
648
- */
649
- function updateStatusBar() {
650
- if (currentEditor) {
651
- const cursor = currentEditor.getCursor();
652
- statusBarLineCol.textContent = `Ligne: ${cursor.line + 1}, Col: ${cursor.ch + 1}`;
653
- }
654
- }
655
-
656
- /**
657
- * Affiche l'éditeur avec le contenu du fichier sélectionné.
658
- * @param {number} fileId L'ID du fichier à activer.
659
- */
660
- function setActiveFile(fileId) {
661
- files.forEach(f => f.isActive = f.id === fileId);
662
-
663
- const activeFile = files.find(f => f.isActive);
664
- if (activeFile) {
665
- // Mise à jour de CodeMirror
666
- if (currentEditor) {
667
- currentEditor.setValue(activeFile.content);
668
- currentEditor.setOption("mode", activeFile.mode);
669
- currentEditor.focus();
670
- } else {
671
- // Initialisation au premier chargement
672
- initializeEditor(activeFile.content, activeFile.mode);
673
- }
674
-
675
- // Mise à jour de la barre de statut
676
- const modeName = activeFile.mode.includes('html') ? 'HTML' : (activeFile.mode === 'css' ? 'CSS' : 'JavaScript');
677
- statusBarLanguage.textContent = `Langage: ${modeName}`;
678
- }
679
- renderTabs();
680
- renderFileExplorer(); // Mise à jour de l'explorateur
681
- }
682
-
683
- /**
684
- * Génère et affiche les onglets dans la barre d'onglets.
685
- */
686
- function renderTabs() {
687
- // Enlever tous les onglets existants sauf le bouton '+' et 'Sauvegarder' et 'Importer'
688
- // On cible spécifiquement les éléments avec la classe 'tab-item'
689
- const existingTabs = tabBar.querySelectorAll('.tab-item');
690
- existingTabs.forEach(tab => tab.remove());
691
-
692
- const saveButton = document.getElementById('save-button');
693
-
694
- files.forEach(file => {
695
- const tab = document.createElement('div');
696
- // 'flex items-center' est conservé mais 'inline-flex' est dans le CSS pour le white-space: nowrap
697
- tab.className = `tab-item flex items-center ${file.isActive ? 'active' : ''}`;
698
- tab.dataset.fileId = file.id;
699
- tab.innerHTML = `
700
- <span>${file.name}</span>
701
- <span class="tab-close material-symbols-rounded text-sm hover:text-color-var transition-colors-theme ml-2">close</span>
702
- `;
703
-
704
- // Attacher l'onglet avant le bouton Sauvegarder (qui est 'ml-auto')
705
- // Remarque: La structure des boutons dans le HTML implique que les onglets sont insérés avant le saveButton (qui a ml-auto)
706
- tabBar.insertBefore(tab, saveButton);
707
-
708
- // Gérer le clic pour activer l'onglet
709
- tab.querySelector('span').addEventListener('click', (e) => {
710
- // Évite de déclencher l'activation si on clique sur l'icône de fermeture
711
- if (!e.target.classList.contains('tab-close')) {
712
- setActiveFile(file.id);
713
- }
714
- });
715
-
716
- // Gérer le clic pour fermer l'onglet
717
- tab.querySelector('.tab-close').addEventListener('click', () => closeFile(file.id));
718
- });
719
- }
720
-
721
- /**
722
- * Génère et affiche la liste des fichiers dans l'explorateur de fichiers.
723
- */
724
- function renderFileExplorer() {
725
- fileExplorer.innerHTML = ''; // Nettoyer l'explorateur
726
-
727
- files.forEach(file => {
728
- const item = document.createElement('a');
729
- const isActiveClass = file.isActive ? 'bg-sidebar-active-var text-color-var' : 'text-secondary-var hover:text-color-var hover:bg-sidebar-active-var';
730
-
731
- item.href = '#';
732
- item.className = `flex items-center p-2 text-sm rounded-lg transition duration-150 transition-colors-theme ${isActiveClass}`;
733
- item.dataset.fileId = file.id;
734
- item.innerHTML = `
735
- <span class="material-symbols-rounded text-base">insert_drive_file</span>
736
- <span class="ml-2 truncate">${file.name}</span>
737
- `;
738
-
739
- item.addEventListener('click', (e) => {
740
- e.preventDefault();
741
- setActiveFile(file.id);
742
- });
743
-
744
- fileExplorer.appendChild(item);
745
- });
746
- }
747
-
748
- /**
749
- * Ajoute un nouveau fichier/onglet.
750
- * @param {string} fileName Le nom du fichier.
751
- * @param {string} fileContent Le contenu du fichier (par défaut vide).
752
- */
753
- function createNewFile(fileName, fileContent = "// Nouveau fichier") {
754
- const mode = getModeFromFileExtension(fileName);
755
- const newFile = {
756
- id: fileIdCounter++,
757
- name: fileName,
758
- content: fileContent,
759
- mode: mode,
760
- isActive: false // Sera activé par setActiveFile
761
- };
762
- files.push(newFile);
763
- setActiveFile(newFile.id);
764
- }
765
-
766
- /**
767
- * Ferme un fichier/onglet.
768
- * @param {number} fileId L'ID du fichier à fermer.
769
- */
770
- function closeFile(fileId) {
771
- const index = files.findIndex(f => f.id === fileId);
772
- if (index === -1) return;
773
-
774
- const wasActive = files[index].isActive;
775
-
776
- files.splice(index, 1); // Retire le fichier
777
-
778
- if (files.length === 0) {
779
- // Si plus de fichiers, ajoute un fichier vierge
780
- createNewFile(`untitled-${fileIdCounter}.js`);
781
- return;
782
- }
783
-
784
- if (wasActive) {
785
- // Active le fichier précédent ou le premier s'il n'y a pas de précédent
786
- const newActiveFile = files[Math.max(0, index - 1)];
787
- setActiveFile(newActiveFile.id);
788
- } else {
789
- // Si ce n'était pas l'actif, on rafraîchit juste les onglets et l'explorateur
790
- renderTabs();
791
- renderFileExplorer();
792
- }
793
- }
794
-
795
- // --- Logique de la Modale de Création de Fichier ---
796
- document.getElementById('add-tab-button').addEventListener('click', () => {
797
- newFileNameInput.value = ''; // Réinitialiser l'input
798
- createFileModal.classList.add('open');
799
- newFileNameInput.focus();
800
- });
801
-
802
- cancelCreateFileButton.addEventListener('click', () => {
803
- createFileModal.classList.remove('open');
804
- });
805
-
806
- confirmCreateFileButton.addEventListener('click', () => {
807
- const fileName = newFileNameInput.value.trim();
808
- if (fileName) {
809
- createNewFile(fileName);
810
- createFileModal.classList.remove('open');
811
- } else {
812
- alert("Veuillez entrer un nom de fichier.");
813
- }
814
- });
815
-
816
- // --- Logique d'Importation de Fichier ---
817
- const importButton = document.getElementById('import-button');
818
- const fileInput = document.getElementById('file-input');
819
-
820
- importButton.addEventListener('click', () => {
821
- fileInput.click(); // Simuler le clic sur l'input file caché
822
- });
823
-
824
- fileInput.addEventListener('change', (event) => {
825
- const file = event.target.files[0];
826
- if (file) {
827
- const reader = new FileReader();
828
- reader.onload = (e) => {
829
- const content = e.target.result;
830
- // Créer le nouveau fichier avec le nom et le contenu du fichier importé
831
- createNewFile(file.name, content);
832
- };
833
- reader.readAsText(file); // Lire le fichier en tant que texte
834
- }
835
- // Réinitialiser l'input file pour permettre l'importation du même fichier à nouveau
836
- fileInput.value = null;
837
- });
838
-
839
-
840
- // 3. Événements et initialisation
841
- document.addEventListener('DOMContentLoaded', () => {
842
- // Initialiser CodeMirror avec le fichier actif par défaut
843
- const activeFile = files.find(f => f.isActive);
844
- if (activeFile) {
845
- initializeEditor(activeFile.content, activeFile.mode);
846
- }
847
-
848
- // Événement factice pour le bouton Sauvegarder
849
- document.getElementById('save-button').addEventListener('click', () => {
850
- const active = files.find(f => f.isActive);
851
- alert(`Fichier "${active.name}" sauvegardé localement!\nContenu: ${active.content.substring(0, 50)}...`);
852
- });
853
-
854
- // Rendu initial des onglets et de l'explorateur
855
- renderTabs();
856
- renderFileExplorer();
857
- });
858
- </script>
859
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mailix | Éditeur de Code - Pro</title>
7
+ <link rel="icon" type="image/png" href="https://i.imgur.com/7Gn3toV.png">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <link rel="stylesheet"
10
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
11
+
12
+
13
+
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
15
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css">
16
+
17
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
18
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/xml/xml.min.js"></script>
19
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
20
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/css/css.min.js"></script>
21
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/htmlmixed/htmlmixed.min.js"></script>
22
+
23
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/python/python.min.js"></script>
24
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/php/php.min.js"></script>
25
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/clike/clike.min.js"></script>
26
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js"></script>
27
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/markdown/markdown.min.js"></script>
28
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/shell/shell.min.js"></script>
29
+
30
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/typescript/typescript.min.js"></script>
31
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/go/go.min.js"></script>
32
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/yaml/yaml.min.js"></script>
33
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/rust/rust.min.js"></script>
34
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/stex/stex.min.js"></script>
35
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/meta.js"></script> </head>
36
+
37
+ <style>
38
+ /* Configuration de la police Inter */
39
+ html, body {
40
+ font-family: 'Inter', sans-serif;
41
+ height: 100vh; /* Utiliser 100vh pour garantir la hauteur de la fenêtre */
42
+ overflow: hidden; /* Supprimer le défilement de l'écran principal */
43
+ }
44
+
45
+ /* Transition pour un effet doux des couleurs */
46
+ .transition-colors-theme { transition: background-color 0.3s, color 0.3s, border-color 0.3s; }
47
+
48
+ /* Définir les Variables de Thème - UNIQUEMENT Mode Sombre (Défaut) */
49
+ :root, .dark {
50
+ --bg-page: #121212;
51
+ --bg-card: #1E1E1E;
52
+ --bg-sidebar: #1E1E1E;
53
+ --bg-sidebar-active: #2C2C2C;
54
+ --border-sidebar: #383838;
55
+ --text-color: #FFFFFF;
56
+ --text-secondary: #D1D5DB;
57
+ --text-link: #3B82F6;
58
+ --text-link-hover: #60A5FA;
59
+ --input-bg: #2C2C2C;
60
+ --input-border: #4B5563;
61
+ --input-text-key: #6EE7B7;
62
+ --primary-color: #3B82F6;
63
+ --primary-hover: #2563EB;
64
+ --warning-bg: #451A03;
65
+ --warning-border: #78350F;
66
+ --warning-text: #FBBF24;
67
+ --editor-tab-bg: #1E1E1E; /* Fond des onglets de l'éditeur */
68
+ --editor-tab-active-bg: #2C2C2C; /* Fond de l'onglet actif */
69
+ --status-bar-bg: #007ACC; /* Bleu VS Code pour la barre de statut */
70
+ }
71
+ /* Mode Clair (Supprimé selon le plan) */
72
+
73
+ /* Appliquer les variables au body et aux éléments (Conservées) */
74
+ .bg-page-var { background-color: var(--bg-page); }
75
+ .bg-card-var { background-color: var(--bg-card); }
76
+ .bg-sidebar-var { background-color: var(--bg-sidebar); }
77
+ .border-sidebar-var { border-color: var(--border-sidebar); }
78
+ .text-color-var { color: var(--text-color); }
79
+ .text-secondary-var { color: var(--text-secondary); }
80
+ .text-link-var { color: var(--text-link); }
81
+ .hover\:text-link-hover-var:hover { color: var(--text-link-hover); }
82
+ .bg-input-var { background-color: var(--input-bg); }
83
+ .border-input-var { border-color: var(--input-border); }
84
+ .text-input-key-var { color: var(--input-text-key); }
85
+ .bg-primary-color-var { background-color: var(--primary-color); }
86
+ .hover\:bg-primary-hover-var:hover { background-color: var(--primary-hover); }
87
+ .focus\:ring-primary-color-var:focus { --tw-ring-color: var(--primary-color); }
88
+ .focus\:border-primary-color-var:focus { border-color: var(--primary-color); }
89
+ .bg-sidebar-active-var { background-color: var(--bg-sidebar-active); }
90
+ .bg-warning-var { background-color: var(--warning-bg); }
91
+ .border-warning-var { border-color: var(--warning-border); }
92
+ .text-warning-var { color: var(--warning-text); }
93
+
94
+
95
+ /* Styles pour les sidebars - FIXES */
96
+ .sidebar {
97
+ transition: transform 0.3s ease-in-out;
98
+ position: fixed;
99
+ top: 0;
100
+ z-index: 30;
101
+ height: 100vh;
102
+ }
103
+
104
+ /* Sidebar Gauche - Masquée par défaut sur toutes les tailles */
105
+ .sidebar-left {
106
+ transform: translateX(-100%);
107
+ left: 0;
108
+ }
109
+
110
+ .sidebar-left.open {
111
+ transform: translateX(0);
112
+ }
113
+
114
+ /* FIX 1.1: Sidebar Droite - Masquée par défaut, positionnée à droite */
115
+ .sidebar-right {
116
+ transform: translateX(100%);
117
+ right: 0;
118
+ }
119
+
120
+ .sidebar-right.open {
121
+ transform: translateX(0);
122
+ }
123
+
124
+ /* Conteneur principal pour décaler le contenu sur les grands écrans (lg) */
125
+ @media (min-width: 1024px) {
126
+ .main-content {
127
+ margin-left: 0;
128
+ margin-right: 0;
129
+ }
130
+ /* Décalage si sidebar gauche ouverte */
131
+ .main-content.sidebar-left-open {
132
+ margin-left: 256px;
133
+ }
134
+ /* Décalage si sidebar droite ouverte */
135
+ .main-content.sidebar-right-open {
136
+ margin-right: 256px;
137
+ }
138
+ /* Décalage si les deux sidebars sont ouvertes */
139
+ .main-content.sidebar-left-open.sidebar-right-open {
140
+ margin-left: 256px;
141
+ margin-right: 256px;
142
+ }
143
+ }
144
+
145
+ /* Ajustement du bouton de Thème (Supprimé) */
146
+ .theme-switch {
147
+ display: none;
148
+ }
149
+
150
+ /* Styles Spécifiques à l'Éditeur */
151
+ .editor-container {
152
+ /* Maintient la disposition en colonne pour header, tabs, editor, footer */
153
+ display: flex;
154
+ flex-direction: column;
155
+ /* Le conteneur s'étire maintenant grâce au body flex-column */
156
+ height: 100%;
157
+ flex-grow: 1; /* Permet au main-content de prendre l'espace restant dans la ligne flex body */
158
+ padding-top: 0;
159
+ }
160
+
161
+ /* FIX 3: Réduction du padding du header */
162
+ #main-header {
163
+ padding-top: 0.5rem; /* p-2 */
164
+ padding-bottom: 0.5rem; /* p-2 */
165
+ padding-left: 1rem; /* px-4 */
166
+ padding-right: 1rem; /* px-4 */
167
+ }
168
+
169
+ /* FIX 2.2: Le wrapper de l'éditeur prend l'espace restant dans la colonne flex */
170
+ #code-editor-wrapper {
171
+ flex-grow: 1;
172
+ overflow: hidden; /* Empêche le CodeMirror de faire défiler le parent */
173
+ }
174
+
175
+ /* CodeMirror Styling - Pour un look sombre VS Code (Monokai) */
176
+ .CodeMirror {
177
+ flex-grow: 1;
178
+ height: 100% !important; /* Essentiel pour que le défilement CodeMirror fonctionne */
179
+ font-size: 14px;
180
+ line-height: 1.5;
181
+ background: #272822; /* Fond Monokai */
182
+ color: #f8f8f2;
183
+ border-top: 1px solid var(--border-sidebar);
184
+ border-bottom: 1px solid var(--border-sidebar);
185
+ }
186
+
187
+ /* Style de la barre d'onglets */
188
+ #tab-bar {
189
+ background-color: var(--editor-tab-bg);
190
+ border-bottom: 1px solid var(--border-sidebar);
191
+ min-height: 40px;
192
+ padding-left: 10px;
193
+ /* MODIFICATION 2.1 - 2.3: Permet le défilement horizontal */
194
+ overflow-x: auto;
195
+ white-space: nowrap; /* Empêche le wrapping des onglets */
196
+ }
197
+
198
+ .tab-item {
199
+ /* MODIFICATION 2.2: S'assure que les onglets ne se rétrécissent pas pour le défilement */
200
+ flex-shrink: 0;
201
+ display: inline-flex; /* Utilisation de inline-flex au lieu de flex pour un meilleur comportement avec white-space: nowrap */
202
+ align-items: center;
203
+ padding: 8px 15px;
204
+ border-right: 1px solid var(--border-sidebar);
205
+ cursor: pointer;
206
+ color: var(--text-secondary);
207
+ font-size: 0.875rem;
208
+ transition: background-color 0.2s;
209
+ }
210
+
211
+ .tab-item.active {
212
+ background-color: var(--editor-tab-active-bg);
213
+ color: var(--text-color);
214
+ position: relative;
215
+ }
216
+
217
+ .tab-item:hover:not(.active) {
218
+ background-color: var(--bg-sidebar-active);
219
+ }
220
+
221
+ .tab-close {
222
+ margin-left: 8px;
223
+ font-size: 14px;
224
+ color: var(--text-secondary);
225
+ }
226
+
227
+ /* Style de la barre de statut */
228
+ #status-bar {
229
+ background-color: var(--status-bar-bg);
230
+ color: white;
231
+ min-height: 25px;
232
+ font-size: 0.75rem;
233
+ }
234
+
235
+ /* Modale */
236
+ .modal {
237
+ position: fixed;
238
+ top: 0;
239
+ left: 0;
240
+ width: 100%;
241
+ height: 100%;
242
+ background-color: rgba(0, 0, 0, 0.7);
243
+ display: none;
244
+ justify-content: center;
245
+ align-items: center;
246
+ z-index: 50;
247
+ }
248
+ .modal.open {
249
+ display: flex;
250
+ }
251
+ .modal-content {
252
+ padding: 1.5rem;
253
+ border-radius: 0.5rem;
254
+ width: 90%;
255
+ max-width: 400px;
256
+ }
257
+ .input-text {
258
+ border-width: 1px;
259
+ padding: 0.75rem;
260
+ border-radius: 0.375rem;
261
+ width: 100%;
262
+ font-size: 1rem;
263
+ }
264
+
265
+
266
+ /* Masquer la marge du main-content sur mobile pour l'éditeur */
267
+ @media (max-width: 1023px) {
268
+ .main-content {
269
+ padding: 0;
270
+ }
271
+ .editor-container {
272
+ /* Calcule la hauteur totale - (Header + barre de statut + barre d'onglets) pour l'éditeur */
273
+ /* Cette règle est moins critique si flex-grow est appliqué correctement */
274
+ height: calc(100vh - 40px - 40px - 25px); /* Environ */
275
+ }
276
+ }
277
+ </style>
278
+ </head>
279
+
280
+ <body class="bg-page-var text-color-var min-h-screen transition-colors-theme flex flex-col">
281
+
282
+ <aside id="sidebar-left" class="sidebar sidebar-left w-64 bg-sidebar-var border-r border-sidebar-var p-6 flex flex-col transition-colors-theme">
283
+
284
+ <div class="flex items-center mb-6 shrink-0">
285
+ <img src="https://i.imgur.com/7Gn3toV.png" alt="Nexus Pro Logo" class="h-8">
286
+ <span class="text-xl font-bold text-color-var ml-2 transition-colors-theme">Mailix</span>
287
+ </div>
288
+
289
+ <div class="flex-grow overflow-y-auto pr-2" id="scrollable-nav-container">
290
+ <h3 class="text-xs font-semibold uppercase tracking-wider text-secondary-var mb-2">Fichiers Ouverts</h3>
291
+ <nav id="file-explorer" class="space-y-1 pb-4 border-b border-sidebar-var/50 mb-4">
292
+ </nav>
293
+
294
+ <h3 class="text-xs font-semibold uppercase tracking-wider text-secondary-var mb-2">Navigation</h3>
295
+ <nav class="space-y-3 pb-4">
296
+ <a href="{{ url_for('user_bp.dashboard') }}" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
297
+ <span class="material-symbols-rounded">dashboard</span>
298
+ <span class="ml-3">Dashboard</span>
299
+ </a>
300
+
301
+ <a href="{{ url_for('user_bp.profile') }}" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
302
+ <span class="material-symbols-rounded">person</span>
303
+ <span class="ml-3">Profil</span>
304
+ </a>
305
+ <a href="/parametres" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
306
+ <span class="material-symbols-rounded">settings</span> <span class="ml-3">Paramètres</span>
307
+ </a>
308
+
309
+ <a href="/aide" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
310
+ <span class="material-symbols-rounded">help</span>
311
+ <span class="ml-3">Aide & Support</span>
312
+ </a>
313
+ <a href="{{ url_for('user_bp.profile') }}" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
314
+ <span class="material-symbols-rounded">person</span>
315
+ <span class="ml-3">Profil</span>
316
+ </a>
317
+
318
+ <a href="/tarifs" class="flex items-center p-3 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
319
+ <span class="material-symbols-rounded">payments</span>
320
+ <span class="ml-3">Tarifs</span>
321
+ </a>
322
+ <a href="/statut" class="flex items-center p-3 bg-sidebar-active-var text-color-var rounded-lg transition duration-150 transition-colors-theme">
323
+ <span class="material-symbols-rounded">monitor_heart</span>
324
+ <span class="ml-3">Statut de l'API</span>
325
+ </a>
326
+ <a href="{{ url_for('user_bp.deconnexion') }}" id="logout-button-sidebar" class="flex items-center p-3 text-red-500 hover:text-red-400 hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme">
327
+ <span class="material-symbols-rounded">deconnexion</span>
328
+ <span class="ml-3">Déconnexion</span>
329
+ </a>
330
+ </nav>
331
+ </div>
332
+
333
+ <div class="mt-auto pt-4 text-xs text-secondary-var transition-colors-theme border-t border-sidebar-var/50 shrink-0">
334
+ <p>Connecté en tant que: <span class="font-semibold text-color-var transition-colors-theme">{{ user.email if user and user.email else 'Chargement...' }}</span></p>
335
+ <p>Plan: <span class="font-semibold text-color-var transition-colors-theme">{{ (user.plan | upper) if user and user.plan else 'GRATUIT' }}</span></p>
336
+ </div>
337
+
338
+ </aside>
339
+
340
+
341
+ <aside id="sidebar-right" class="sidebar sidebar-right w-64 bg-sidebar-var border-l border-sidebar-var p-4 flex flex-col transition-colors-theme overflow-y-auto">
342
+ <div class="flex items-center mb-4 shrink-0 border-b border-sidebar-var/50 pb-2">
343
+ <span class="material-symbols-rounded text-xl mr-2">settings</span>
344
+ <span class="text-lg font-semibold text-color-var">Outils & Paramètres</span>
345
+ </div>
346
+
347
+ <div id="editor-settings-section" class="mb-6">
348
+ <h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
349
+ <span class="material-symbols-rounded text-base mr-1">settings</span>
350
+ Paramètres de l'Éditeur
351
+ </h4>
352
+ <div class="space-y-3 p-2 bg-sidebar-active-var rounded-lg">
353
+ <div>
354
+ <label class="block text-xs font-medium text-secondary-var mb-1">Thème du Code</label>
355
+ <select class="w-full bg-input-var text-color-var p-2 rounded-md border border-input-var text-sm">
356
+ <option selected>Monokai (Défaut)</option>
357
+ <option>VS Light</option>
358
+ <option>Dracula</option>
359
+ </select>
360
+ </div>
361
+ <div>
362
+ <label class="block text-xs font-medium text-secondary-var mb-1">Taille de Police</label>
363
+ <input type="range" min="10" max="20" value="14" class="w-full h-1 bg-primary-color-var rounded-lg appearance-none cursor-pointer">
364
+ <p class="text-xs text-secondary-var mt-1">Taille actuelle: 14px</p>
365
+ </div>
366
+ <div>
367
+ <label class="block text-xs font-medium text-secondary-var mb-1">Indentation</label>
368
+ <select class="w-full bg-input-var text-color-var p-2 rounded-md border border-input-var text-sm">
369
+ <option selected>Espaces (4)</option>
370
+ <option>Espaces (2)</option>
371
+ <option>Tabulations</option>
372
+ </select>
373
+ </div>
374
+ </div>
375
+ </div>
376
+
377
+ <div id="minimap-section" class="mb-6">
378
+ <h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
379
+ <span class="material-symbols-rounded text-base mr-1">map</span>
380
+ Aperçu du Code (Minimap)
381
+ </h4>
382
+ <div class="h-20 bg-input-var border border-input-border rounded-lg flex items-center justify-center text-xs text-secondary-var/70">
383
+ [Zone de Minimap Simulé]
384
+ </div>
385
+ </div>
386
+
387
+ <div id="inspection-section" class="flex-grow">
388
+ <h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
389
+ <span class="material-symbols-rounded text-base mr-1">search</span>
390
+ Inspection/Propriétés
391
+ </h4>
392
+ <div class="p-3 bg-sidebar-active-var rounded-lg h-full overflow-y-auto text-xs space-y-2">
393
+ <p class="font-semibold text-color-var">Élément sélectionné: &lt;body&gt;</p>
394
+ <p><span class="text-input-key-var">background-color:</span> var(--bg-page);</p>
395
+ <p><span class="text-input-key-var">color:</span> var(--text-color);</p>
396
+ <p class="text-secondary-var mt-4">[Aide Contextuelle pour la balise HTML 'body']</p>
397
+ </div>
398
+ </div>
399
+ </aside>
400
+
401
+
402
+ <div id="main-content" class="main-content flex-grow transition-colors-theme editor-container">
403
+
404
+ <header id="main-header" class="flex justify-between items-center bg-page-var p-2 border-b border-sidebar-var shrink-0">
405
+
406
+ <div class="flex items-center">
407
+ <button id="sidebar-left-toggle" class="mobile-toggle-button p-2 rounded-lg bg-sidebar-active-var hover:bg-sidebar-active-var/80 transition-colors-theme mr-3" title="Afficher/Masquer le menu latéral">
408
+ <span class="material-symbols-rounded text-color-var">menu</span>
409
+ </button>
410
+
411
+ <a href="/" class="flex items-center"> <img src="https://i.imgur.com/7Gn3toV.png" alt="Nexus Pro Logo" class="h-8">
412
+ <span class="text-xl font-bold text-color-var ml-2 transition-colors-theme hidden sm:inline">Mailix Editor</span>
413
+ </a>
414
+ </div>
415
+
416
+
417
+ <div class="flex items-center">
418
+
419
+ <a href="/html-run"
420
+ class="flex items-center p-2 text-secondary-var hover:text-color-var hover:bg-sidebar-active-var rounded-lg transition duration-150 transition-colors-theme mr-2"
421
+ title="Lancer le Lanceur HTML">
422
+ <span class="material-symbols-rounded">play_arrow</span>
423
+ <span class="ml-3 hidden sm:inline">Run</span>
424
+ </a>
425
+
426
+ <button id="sidebar-right-toggle" class="mobile-toggle-button p-2 rounded-lg bg-sidebar-active-var hover:bg-sidebar-active-var/80 transition-colors-theme" title="Outils et Paramètres">
427
+ <span class="material-symbols-rounded text-color-var">more_vert</span>
428
+ </button>
429
+ </div>
430
+ </header>
431
+
432
+ <div id="tab-bar" class="flex items-center overflow-x-auto shrink-0">
433
+ <button id="add-tab-button" class="flex items-center p-2 text-secondary-var hover:text-color-var transition-colors-theme" title="Ajouter un nouveau fichier">
434
+ <span class="material-symbols-rounded text-xl">add</span>
435
+ </button>
436
+ <button id="import-button" class="flex items-center p-2 text-secondary-var hover:text-color-var transition-colors-theme" title="Importer un fichier local">
437
+ <span class="material-symbols-rounded text-xl">file_upload</span>
438
+ </button>
439
+ <input type="file" id="file-input" class="hidden" />
440
+
441
+ <button id="save-button" class="ml-auto flex items-center p-2 text-link-var hover:text-link-hover-var transition-colors-theme font-semibold border-l border-sidebar-var" title="Sauvegarder localement">
442
+ <span class="material-symbols-rounded text-xl mr-1">save</span>
443
+ Sauvegarder
444
+ </button>
445
+ </div>
446
+
447
+ <div id="code-editor-wrapper">
448
+ <textarea id="code-editor-textarea" style="display: none;">
449
+ &lt;!DOCTYPE html&gt;
450
+ &lt;html lang="fr"&gt;
451
+ &lt;head&gt;
452
+ &lt;meta charset="UTF-8"&gt;
453
+ &lt;title&gt;Mon Nouveau Projet&lt;/title&gt;
454
+ &lt;link rel="stylesheet" href="style.css"&gt;
455
+ &lt;/head&gt;
456
+ &lt;body&gt;
457
+ &lt;h1&gt;Bienvenue dans l'Éditeur Mailix&lt;/h1&gt;
458
+ &lt;p&gt;Ce code est mis en évidence par CodeMirror.&lt;/p&gt;
459
+ &lt;/body&gt;
460
+ &lt;/html&gt;
461
+ </textarea>
462
+ </div>
463
+
464
+ <footer id="status-bar" class="flex items-center justify-between px-4 py-0.5 shrink-0">
465
+ <div class="flex items-center space-x-4">
466
+ <span class="material-symbols-rounded text-sm">code</span>
467
+ <span id="status-language">Langage: HTML</span>
468
+ </div>
469
+ <div class="flex items-center space-x-4">
470
+ <span id="status-encoding">UTF-8</span>
471
+ <span id="status-line-col">Ligne: 1, Col: 1</span>
472
+ </div>
473
+ </footer>
474
+
475
+ </div>
476
+
477
+ <div id="create-file-modal" class="modal">
478
+ <div class="modal-content bg-card-var text-color-var">
479
+ <h2 class="text-xl font-bold mb-4">Créer un Nouveau Fichier</h2>
480
+ <input type="text" id="new-file-name-input" class="input-text bg-input-var border-input-var text-color-var focus:border-primary-color-var focus:ring-1 focus:ring-primary-color-var outline-none mb-4" placeholder="Nom du fichier (ex: index.html, style.css, script.js)" required>
481
+ <div class="flex justify-end space-x-3">
482
+ <button id="cancel-create-file" class="px-4 py-2 text-secondary-var hover:text-color-var rounded-lg transition-colors-theme">Annuler</button>
483
+ <button id="confirm-create-file" class="px-4 py-2 bg-primary-color-var hover:bg-primary-hover-var text-white font-semibold rounded-lg transition-colors-theme">Créer</button>
484
+ </div>
485
+ </div>
486
+ </div>
487
+
488
+ <script>
489
+ // --- PARTIE 1: GESTION DE LA SIDEBAR (Gauche et Droite) ---
490
+
491
+ // Éléments de la Sidebar Gauche
492
+ const sidebarLeftToggle = document.getElementById('sidebar-left-toggle');
493
+ const sidebarLeft = document.getElementById('sidebar-left'); // ID mis à jour
494
+ const mainContent = document.getElementById('main-content');
495
+
496
+ // Éléments de la Sidebar Droite (NOUVEAU)
497
+ const sidebarRightToggle = document.getElementById('sidebar-right-toggle');
498
+ const sidebarRight = document.getElementById('sidebar-right');
499
+
500
+ // Logique pour la Sidebar Gauche
501
+ function toggleSidebarLeft() {
502
+ sidebarLeft.classList.toggle('open');
503
+ // Gérer le décalage du contenu principal sur grand écran (lg)
504
+ if (window.innerWidth >= 1024) {
505
+ mainContent.classList.toggle('sidebar-left-open');
506
+ }
507
+ }
508
+
509
+ // Logique pour la Sidebar Droite (NOUVEAU)
510
+ function toggleSidebarRight() {
511
+ sidebarRight.classList.toggle('open');
512
+ // Gérer le décalage du contenu principal sur grand écran (lg)
513
+ if (window.innerWidth >= 1024) {
514
+ mainContent.classList.toggle('sidebar-right-open');
515
+ }
516
+ }
517
+
518
+ sidebarLeftToggle.addEventListener('click', toggleSidebarLeft);
519
+ sidebarRightToggle.addEventListener('click', toggleSidebarRight); // Lier le nouveau bouton
520
+
521
+
522
+ // Logique de Déconnexion (Conservée)
523
+ document.getElementById('logout-button-sidebar').addEventListener('click', (e) => {
524
+ e.preventDefault();
525
+ window.location.href = e.currentTarget.href;
526
+ });
527
+
528
+
529
+ // --- PARTIE 2: GESTION DE L'ÉDITEUR DE CODE (CodeMirror + Onglets) ---
530
+
531
+ // Structure de données pour les fichiers (V1: Stockage local)
532
+ let files = [
533
+ { id: 1, name: 'index.html', content: document.getElementById('code-editor-textarea').value, mode: 'htmlmixed', isActive: true },
534
+ { id: 2, name: 'style.css', content: "body {\n font-family: sans-serif;\n color: #4CAF50; /* Green */\n}", mode: 'css', isActive: false },
535
+ { id: 3, name: 'script.js', content: "console.log('Editor Ready!');", mode: 'javascript', isActive: false }
536
+ ];
537
+ let fileIdCounter = files.length + 1;
538
+ let currentEditor = null; // L'instance CodeMirror
539
+
540
+ const tabBar = document.getElementById('tab-bar');
541
+ const editorTextarea = document.getElementById('code-editor-textarea');
542
+ const statusBarLanguage = document.getElementById('status-language');
543
+ const statusBarLineCol = document.getElementById('status-line-col');
544
+ const fileExplorer = document.getElementById('file-explorer');
545
+
546
+ // Éléments de la modale
547
+ const createFileModal = document.getElementById('create-file-modal');
548
+ const newFileNameInput = document.getElementById('new-file-name-input');
549
+ const confirmCreateFileButton = document.getElementById('confirm-create-file');
550
+ const cancelCreateFileButton = document.getElementById('cancel-create-file');
551
+
552
+ /**
553
+ * Détermine le mode CodeMirror à partir de l'extension du nom de fichier.
554
+ * @param {string} fileName Le nom du fichier.
555
+ * @returns {string} Le mode CodeMirror.
556
+ */
557
+
558
+ function getModeFromFileExtension(fileName) {
559
+ const ext = fileName.split('.').pop().toLowerCase();
560
+ switch (ext) {
561
+ // Web
562
+ case 'html':
563
+ case 'htm':
564
+ return 'htmlmixed';
565
+ case 'css':
566
+ return 'css';
567
+ case 'js':
568
+ return 'javascript';
569
+ case 'ts':
570
+ return 'typescript'; // Nouveau
571
+ case 'json':
572
+ return 'application/json'; // Nouveau (mode spécifique pour JSON)
573
+
574
+ // Backend / Systèmes
575
+ case 'py':
576
+ return 'python';
577
+ case 'php':
578
+ case 'phtml':
579
+ return 'php';
580
+ case 'go':
581
+ return 'go'; // Nouveau
582
+ case 'rs':
583
+ return 'rust'; // Nouveau
584
+ case 'sh':
585
+ return 'shell';
586
+
587
+ // Langages C-like (Java, C, C++, C#)
588
+ case 'java':
589
+ case 'c':
590
+ case 'cpp':
591
+ case 'cs':
592
+ return 'clike';
593
+
594
+ // Données et Bases de données
595
+ case 'sql':
596
+ return 'sql';
597
+ case 'yaml':
598
+ case 'yml':
599
+ return 'yaml'; // Nouveau
600
+
601
+ // Documentation et autres
602
+ case 'md':
603
+ case 'markdown':
604
+ return 'markdown';
605
+ case 'tex':
606
+ case 'latex':
607
+ return 'stex'; // Nouveau (pour LaTeX)
608
+
609
+ default:
610
+ return 'text/plain'; // Mode texte brut pour les inconnus ou le texte simple
611
+ }
612
+ }
613
+
614
+
615
+ /**
616
+ * Initialise CodeMirror sur le textarea.
617
+ */
618
+ function initializeEditor(initialContent, mode) {
619
+ if (currentEditor) {
620
+ currentEditor.toTextArea(); // Détruit l'ancienne instance
621
+ currentEditor = null;
622
+ }
623
+
624
+ editorTextarea.value = initialContent;
625
+
626
+ currentEditor = CodeMirror.fromTextArea(editorTextarea, {
627
+ lineNumbers: true,
628
+ mode: mode,
629
+ theme: 'monokai',
630
+ indentUnit: 4,
631
+ tabSize: 4,
632
+ indentWithTabs: false,
633
+ autofocus: true,
634
+ // MODIFICATION 1.2: Désactiver le retour à la ligne pour forcer le défilement horizontal
635
+ lineWrapping: false,
636
+ });
637
+
638
+ // Événement de mise à jour du contenu et de la barre de statut
639
+ currentEditor.on("change", handleEditorChange);
640
+ currentEditor.on("cursorActivity", updateStatusBar);
641
+
642
+ // S'assurer que CodeMirror utilise 100% de la hauteur disponible dans son conteneur parent flexible
643
+ const cmElement = currentEditor.getWrapperElement();
644
+ cmElement.style.height = '100%';
645
+
646
+ updateStatusBar();
647
+ }
648
+
649
+ /**
650
+ * Met à jour le contenu de l'objet fichier actif dans le tableau `files`.
651
+ */
652
+ function handleEditorChange() {
653
+ if (currentEditor) {
654
+ const activeFile = files.find(f => f.isActive);
655
+ if (activeFile) {
656
+ activeFile.content = currentEditor.getValue();
657
+ }
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Met à jour les indicateurs Ligne/Col dans la barre de statut.
663
+ */
664
+ function updateStatusBar() {
665
+ if (currentEditor) {
666
+ const cursor = currentEditor.getCursor();
667
+ statusBarLineCol.textContent = `Ligne: ${cursor.line + 1}, Col: ${cursor.ch + 1}`;
668
+ }
669
+ }
670
+
671
+ /**
672
+ * Affiche l'éditeur avec le contenu du fichier sélectionné.
673
+ * @param {number} fileId L'ID du fichier à activer.
674
+ */
675
+ function setActiveFile(fileId) {
676
+ files.forEach(f => f.isActive = f.id === fileId);
677
+
678
+ const activeFile = files.find(f => f.isActive);
679
+ if (activeFile) {
680
+ // Mise à jour de CodeMirror
681
+ if (currentEditor) {
682
+ currentEditor.setValue(activeFile.content);
683
+ currentEditor.setOption("mode", activeFile.mode);
684
+ currentEditor.focus();
685
+ } else {
686
+ // Initialisation au premier chargement
687
+ initializeEditor(activeFile.content, activeFile.mode);
688
+ }
689
+
690
+ // Mise à jour de la barre de statut
691
+ const modeName = activeFile.mode.includes('html') ? 'HTML' : (activeFile.mode === 'css' ? 'CSS' : 'JavaScript');
692
+ statusBarLanguage.textContent = `Langage: ${modeName}`;
693
+ }
694
+ renderTabs();
695
+ renderFileExplorer(); // Mise à jour de l'explorateur
696
+ }
697
+
698
+ /**
699
+ * Génère et affiche les onglets dans la barre d'onglets.
700
+ */
701
+ function renderTabs() {
702
+ // Enlever tous les onglets existants sauf le bouton '+' et 'Sauvegarder' et 'Importer'
703
+ // On cible spécifiquement les éléments avec la classe 'tab-item'
704
+ const existingTabs = tabBar.querySelectorAll('.tab-item');
705
+ existingTabs.forEach(tab => tab.remove());
706
+
707
+ const saveButton = document.getElementById('save-button');
708
+
709
+ files.forEach(file => {
710
+ const tab = document.createElement('div');
711
+ // 'flex items-center' est conservé mais 'inline-flex' est dans le CSS pour le white-space: nowrap
712
+ tab.className = `tab-item flex items-center ${file.isActive ? 'active' : ''}`;
713
+ tab.dataset.fileId = file.id;
714
+ tab.innerHTML = `
715
+ <span>${file.name}</span>
716
+ <span class="tab-close material-symbols-rounded text-sm hover:text-color-var transition-colors-theme ml-2">close</span>
717
+ `;
718
+
719
+ // Attacher l'onglet avant le bouton Sauvegarder (qui est 'ml-auto')
720
+ // Remarque: La structure des boutons dans le HTML implique que les onglets sont insérés avant le saveButton (qui a ml-auto)
721
+ tabBar.insertBefore(tab, saveButton);
722
+
723
+ // Gérer le clic pour activer l'onglet
724
+ tab.querySelector('span').addEventListener('click', (e) => {
725
+ // Évite de déclencher l'activation si on clique sur l'icône de fermeture
726
+ if (!e.target.classList.contains('tab-close')) {
727
+ setActiveFile(file.id);
728
+ }
729
+ });
730
+
731
+ // Gérer le clic pour fermer l'onglet
732
+ tab.querySelector('.tab-close').addEventListener('click', () => closeFile(file.id));
733
+ });
734
+ }
735
+
736
+ /**
737
+ * Génère et affiche la liste des fichiers dans l'explorateur de fichiers.
738
+ */
739
+ function renderFileExplorer() {
740
+ fileExplorer.innerHTML = ''; // Nettoyer l'explorateur
741
+
742
+ files.forEach(file => {
743
+ const item = document.createElement('a');
744
+ const isActiveClass = file.isActive ? 'bg-sidebar-active-var text-color-var' : 'text-secondary-var hover:text-color-var hover:bg-sidebar-active-var';
745
+
746
+ item.href = '#';
747
+ item.className = `flex items-center p-2 text-sm rounded-lg transition duration-150 transition-colors-theme ${isActiveClass}`;
748
+ item.dataset.fileId = file.id;
749
+ item.innerHTML = `
750
+ <span class="material-symbols-rounded text-base">insert_drive_file</span>
751
+ <span class="ml-2 truncate">${file.name}</span>
752
+ `;
753
+
754
+ item.addEventListener('click', (e) => {
755
+ e.preventDefault();
756
+ setActiveFile(file.id);
757
+ });
758
+
759
+ fileExplorer.appendChild(item);
760
+ });
761
+ }
762
+
763
+ /**
764
+ * Ajoute un nouveau fichier/onglet.
765
+ * @param {string} fileName Le nom du fichier.
766
+ * @param {string} fileContent Le contenu du fichier (par défaut vide).
767
+ */
768
+ function createNewFile(fileName, fileContent = "// Nouveau fichier") {
769
+ const mode = getModeFromFileExtension(fileName);
770
+ const newFile = {
771
+ id: fileIdCounter++,
772
+ name: fileName,
773
+ content: fileContent,
774
+ mode: mode,
775
+ isActive: false // Sera activé par setActiveFile
776
+ };
777
+ files.push(newFile);
778
+ setActiveFile(newFile.id);
779
+ }
780
+
781
+ /**
782
+ * Ferme un fichier/onglet.
783
+ * @param {number} fileId L'ID du fichier à fermer.
784
+ */
785
+ function closeFile(fileId) {
786
+ const index = files.findIndex(f => f.id === fileId);
787
+ if (index === -1) return;
788
+
789
+ const wasActive = files[index].isActive;
790
+
791
+ files.splice(index, 1); // Retire le fichier
792
+
793
+ if (files.length === 0) {
794
+ // Si plus de fichiers, ajoute un fichier vierge
795
+ createNewFile(`untitled-${fileIdCounter}.js`);
796
+ return;
797
+ }
798
+
799
+ if (wasActive) {
800
+ // Active le fichier précédent ou le premier s'il n'y a pas de précédent
801
+ const newActiveFile = files[Math.max(0, index - 1)];
802
+ setActiveFile(newActiveFile.id);
803
+ } else {
804
+ // Si ce n'était pas l'actif, on rafraîchit juste les onglets et l'explorateur
805
+ renderTabs();
806
+ renderFileExplorer();
807
+ }
808
+ }
809
+
810
+ // --- Logique de la Modale de Création de Fichier ---
811
+ document.getElementById('add-tab-button').addEventListener('click', () => {
812
+ newFileNameInput.value = ''; // Réinitialiser l'input
813
+ createFileModal.classList.add('open');
814
+ newFileNameInput.focus();
815
+ });
816
+
817
+ cancelCreateFileButton.addEventListener('click', () => {
818
+ createFileModal.classList.remove('open');
819
+ });
820
+
821
+ confirmCreateFileButton.addEventListener('click', () => {
822
+ const fileName = newFileNameInput.value.trim();
823
+ if (fileName) {
824
+ createNewFile(fileName);
825
+ createFileModal.classList.remove('open');
826
+ } else {
827
+ alert("Veuillez entrer un nom de fichier.");
828
+ }
829
+ });
830
+
831
+ // --- Logique d'Importation de Fichier ---
832
+ const importButton = document.getElementById('import-button');
833
+ const fileInput = document.getElementById('file-input');
834
+
835
+ importButton.addEventListener('click', () => {
836
+ fileInput.click(); // Simuler le clic sur l'input file caché
837
+ });
838
+
839
+ fileInput.addEventListener('change', (event) => {
840
+ const file = event.target.files[0];
841
+ if (file) {
842
+ const reader = new FileReader();
843
+ reader.onload = (e) => {
844
+ const content = e.target.result;
845
+ // Créer le nouveau fichier avec le nom et le contenu du fichier importé
846
+ createNewFile(file.name, content);
847
+ };
848
+ reader.readAsText(file); // Lire le fichier en tant que texte
849
+ }
850
+ // Réinitialiser l'input file pour permettre l'importation du même fichier à nouveau
851
+ fileInput.value = null;
852
+ });
853
+
854
+
855
+ // 3. Événements et initialisation
856
+ document.addEventListener('DOMContentLoaded', () => {
857
+ // Initialiser CodeMirror avec le fichier actif par défaut
858
+ const activeFile = files.find(f => f.isActive);
859
+ if (activeFile) {
860
+ initializeEditor(activeFile.content, activeFile.mode);
861
+ }
862
+
863
+ // Événement factice pour le bouton Sauvegarder
864
+ document.getElementById('save-button').addEventListener('click', () => {
865
+ const active = files.find(f => f.isActive);
866
+ alert(`Fichier "${active.name}" sauvegardé localement!\nContenu: ${active.content.substring(0, 50)}...`);
867
+ });
868
+
869
+ // Rendu initial des onglets et de l'explorateur
870
+ renderTabs();
871
+ renderFileExplorer();
872
+ });
873
+ </script>
874
+ </body>
875
  </html>