Mailix / templates /create_project.html
ernestmindres's picture
Update templates/create_project.html
b7335d6 verified
<!DOCTYPE html>
<html lang="fr" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mailix | Éditeur de Code - Pro</title>
<link rel="icon" type="image/png" href="https://i.imgur.com/7Gn3toV.png">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/css/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/htmlmixed/htmlmixed.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/python/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/php/php.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/clike/clike.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/markdown/markdown.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/shell/shell.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/typescript/typescript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/go/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/yaml/yaml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/rust/rust.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/stex/stex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/meta.js"></script> </head>
<style>
/* Configuration de la police Inter */
html, body {
font-family: 'Inter', sans-serif;
height: 100vh; /* Utiliser 100vh pour garantir la hauteur de la fenêtre */
overflow: hidden; /* Supprimer le défilement de l'écran principal */
}
/* Transition pour un effet doux des couleurs */
.transition-colors-theme { transition: background-color 0.3s, color 0.3s, border-color 0.3s; }
/* Définir les Variables de Thème - UNIQUEMENT Mode Sombre (Défaut) */
:root, .dark {
--bg-page: #121212;
--bg-card: #1E1E1E;
--bg-sidebar: #1E1E1E;
--bg-sidebar-active: #2C2C2C;
--border-sidebar: #383838;
--text-color: #FFFFFF;
--text-secondary: #D1D5DB;
--text-link: #3B82F6;
--text-link-hover: #60A5FA;
--input-bg: #2C2C2C;
--input-border: #4B5563;
--input-text-key: #6EE7B7;
--primary-color: #3B82F6;
--primary-hover: #2563EB;
--warning-bg: #451A03;
--warning-border: #78350F;
--warning-text: #FBBF24;
--editor-tab-bg: #1E1E1E; /* Fond des onglets de l'éditeur */
--editor-tab-active-bg: #2C2C2C; /* Fond de l'onglet actif */
--status-bar-bg: #007ACC; /* Bleu VS Code pour la barre de statut */
}
/* Mode Clair (Supprimé selon le plan) */
/* Appliquer les variables au body et aux éléments (Conservées) */
.bg-page-var { background-color: var(--bg-page); }
.bg-card-var { background-color: var(--bg-card); }
.bg-sidebar-var { background-color: var(--bg-sidebar); }
.border-sidebar-var { border-color: var(--border-sidebar); }
.text-color-var { color: var(--text-color); }
.text-secondary-var { color: var(--text-secondary); }
.text-link-var { color: var(--text-link); }
.hover\:text-link-hover-var:hover { color: var(--text-link-hover); }
.bg-input-var { background-color: var(--input-bg); }
.border-input-var { border-color: var(--input-border); }
.text-input-key-var { color: var(--input-text-key); }
.bg-primary-color-var { background-color: var(--primary-color); }
.hover\:bg-primary-hover-var:hover { background-color: var(--primary-hover); }
.focus\:ring-primary-color-var:focus { --tw-ring-color: var(--primary-color); }
.focus\:border-primary-color-var:focus { border-color: var(--primary-color); }
.bg-sidebar-active-var { background-color: var(--bg-sidebar-active); }
.bg-warning-var { background-color: var(--warning-bg); }
.border-warning-var { border-color: var(--warning-border); }
.text-warning-var { color: var(--warning-text); }
/* Styles pour les sidebars - FIXES */
.sidebar {
transition: transform 0.3s ease-in-out;
position: fixed;
top: 0;
z-index: 30;
height: 100vh;
}
/* Sidebar Gauche - Masquée par défaut sur toutes les tailles */
.sidebar-left {
transform: translateX(-100%);
left: 0;
}
.sidebar-left.open {
transform: translateX(0);
}
/* FIX 1.1: Sidebar Droite - Masquée par défaut, positionnée à droite */
.sidebar-right {
transform: translateX(100%);
right: 0;
}
.sidebar-right.open {
transform: translateX(0);
}
/* Conteneur principal pour décaler le contenu sur les grands écrans (lg) */
@media (min-width: 1024px) {
.main-content {
margin-left: 0;
margin-right: 0;
}
/* Décalage si sidebar gauche ouverte */
.main-content.sidebar-left-open {
margin-left: 256px;
}
/* Décalage si sidebar droite ouverte */
.main-content.sidebar-right-open {
margin-right: 256px;
}
/* Décalage si les deux sidebars sont ouvertes */
.main-content.sidebar-left-open.sidebar-right-open {
margin-left: 256px;
margin-right: 256px;
}
}
/* Ajustement du bouton de Thème (Supprimé) */
.theme-switch {
display: none;
}
/* Styles Spécifiques à l'Éditeur */
.editor-container {
/* Maintient la disposition en colonne pour header, tabs, editor, footer */
display: flex;
flex-direction: column;
/* Le conteneur s'étire maintenant grâce au body flex-column */
height: 100%;
flex-grow: 1; /* Permet au main-content de prendre l'espace restant dans la ligne flex body */
padding-top: 0;
}
/* FIX 3: Réduction du padding du header */
#main-header {
padding-top: 0.5rem; /* p-2 */
padding-bottom: 0.5rem; /* p-2 */
padding-left: 1rem; /* px-4 */
padding-right: 1rem; /* px-4 */
}
/* FIX 2.2: Le wrapper de l'éditeur prend l'espace restant dans la colonne flex */
#code-editor-wrapper {
flex-grow: 1;
overflow: hidden; /* Empêche le CodeMirror de faire défiler le parent */
}
/* CodeMirror Styling - Pour un look sombre VS Code (Monokai) */
.CodeMirror {
flex-grow: 1;
height: 100% !important; /* Essentiel pour que le défilement CodeMirror fonctionne */
font-size: 14px;
line-height: 1.5;
background: #272822; /* Fond Monokai */
color: #f8f8f2;
border-top: 1px solid var(--border-sidebar);
border-bottom: 1px solid var(--border-sidebar);
}
/* Style de la barre d'onglets */
#tab-bar {
background-color: var(--editor-tab-bg);
border-bottom: 1px solid var(--border-sidebar);
min-height: 40px;
padding-left: 10px;
/* MODIFICATION 2.1 - 2.3: Permet le défilement horizontal */
overflow-x: auto;
white-space: nowrap; /* Empêche le wrapping des onglets */
}
.tab-item {
/* MODIFICATION 2.2: S'assure que les onglets ne se rétrécissent pas pour le défilement */
flex-shrink: 0;
display: inline-flex; /* Utilisation de inline-flex au lieu de flex pour un meilleur comportement avec white-space: nowrap */
align-items: center;
padding: 8px 15px;
border-right: 1px solid var(--border-sidebar);
cursor: pointer;
color: var(--text-secondary);
font-size: 0.875rem;
transition: background-color 0.2s;
}
.tab-item.active {
background-color: var(--editor-tab-active-bg);
color: var(--text-color);
position: relative;
}
.tab-item:hover:not(.active) {
background-color: var(--bg-sidebar-active);
}
.tab-close {
margin-left: 8px;
font-size: 14px;
color: var(--text-secondary);
}
/* Style de la barre de statut */
#status-bar {
background-color: var(--status-bar-bg);
color: white;
min-height: 25px;
font-size: 0.75rem;
}
/* Modale */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: none;
justify-content: center;
align-items: center;
z-index: 50;
}
.modal.open {
display: flex;
}
.modal-content {
padding: 1.5rem;
border-radius: 0.5rem;
width: 90%;
max-width: 400px;
}
.input-text {
border-width: 1px;
padding: 0.75rem;
border-radius: 0.375rem;
width: 100%;
font-size: 1rem;
}
/* Masquer la marge du main-content sur mobile pour l'éditeur */
@media (max-width: 1023px) {
.main-content {
padding: 0;
}
.editor-container {
/* Calcule la hauteur totale - (Header + barre de statut + barre d'onglets) pour l'éditeur */
/* Cette règle est moins critique si flex-grow est appliqué correctement */
height: calc(100vh - 40px - 40px - 25px); /* Environ */
}
}
</style>
</head>
<body class="bg-page-var text-color-var min-h-screen transition-colors-theme flex flex-col">
<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">
<div class="flex items-center mb-6 shrink-0">
<img src="https://i.imgur.com/7Gn3toV.png" alt="Nexus Pro Logo" class="h-8">
<span class="text-xl font-bold text-color-var ml-2 transition-colors-theme">Mailix</span>
</div>
<div class="flex-grow overflow-y-auto pr-2" id="scrollable-nav-container">
<h3 class="text-xs font-semibold uppercase tracking-wider text-secondary-var mb-2">Fichiers Ouverts</h3>
<nav id="file-explorer" class="space-y-1 pb-4 border-b border-sidebar-var/50 mb-4">
</nav>
<h3 class="text-xs font-semibold uppercase tracking-wider text-secondary-var mb-2">Navigation</h3>
<nav class="space-y-3 pb-4">
<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">
<span class="material-symbols-rounded">dashboard</span>
<span class="ml-3">Dashboard</span>
</a>
<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">
<span class="material-symbols-rounded">person</span>
<span class="ml-3">Profil</span>
</a>
<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">
<span class="material-symbols-rounded">settings</span> <span class="ml-3">Paramètres</span>
</a>
<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">
<span class="material-symbols-rounded">help</span>
<span class="ml-3">Aide & Support</span>
</a>
<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">
<span class="material-symbols-rounded">person</span>
<span class="ml-3">Profil</span>
</a>
<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">
<span class="material-symbols-rounded">payments</span>
<span class="ml-3">Tarifs</span>
</a>
<a href="/statut" class="flex items-center p-3 bg-sidebar-active-var text-color-var rounded-lg transition duration-150 transition-colors-theme">
<span class="material-symbols-rounded">monitor_heart</span>
<span class="ml-3">Statut de l'API</span>
</a>
<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">
<span class="material-symbols-rounded">deconnexion</span>
<span class="ml-3">Déconnexion</span>
</a>
</nav>
</div>
<div class="mt-auto pt-4 text-xs text-secondary-var transition-colors-theme border-t border-sidebar-var/50 shrink-0">
<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>
<p>Plan: <span class="font-semibold text-color-var transition-colors-theme">{{ (user.plan | upper) if user and user.plan else 'GRATUIT' }}</span></p>
</div>
</aside>
<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">
<div class="flex items-center mb-4 shrink-0 border-b border-sidebar-var/50 pb-2">
<span class="material-symbols-rounded text-xl mr-2">settings</span>
<span class="text-lg font-semibold text-color-var">Outils & Paramètres</span>
</div>
<div id="editor-settings-section" class="mb-6">
<h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
<span class="material-symbols-rounded text-base mr-1">settings</span>
Paramètres de l'Éditeur
</h4>
<div class="space-y-3 p-2 bg-sidebar-active-var rounded-lg">
<div>
<label class="block text-xs font-medium text-secondary-var mb-1">Thème du Code</label>
<select class="w-full bg-input-var text-color-var p-2 rounded-md border border-input-var text-sm">
<option selected>Monokai (Défaut)</option>
<option>VS Light</option>
<option>Dracula</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-secondary-var mb-1">Taille de Police</label>
<input type="range" min="10" max="20" value="14" class="w-full h-1 bg-primary-color-var rounded-lg appearance-none cursor-pointer">
<p class="text-xs text-secondary-var mt-1">Taille actuelle: 14px</p>
</div>
<div>
<label class="block text-xs font-medium text-secondary-var mb-1">Indentation</label>
<select class="w-full bg-input-var text-color-var p-2 rounded-md border border-input-var text-sm">
<option selected>Espaces (4)</option>
<option>Espaces (2)</option>
<option>Tabulations</option>
</select>
</div>
</div>
</div>
<div id="minimap-section" class="mb-6">
<h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
<span class="material-symbols-rounded text-base mr-1">map</span>
Aperçu du Code (Minimap)
</h4>
<div class="h-20 bg-input-var border border-input-border rounded-lg flex items-center justify-center text-xs text-secondary-var/70">
[Zone de Minimap Simulé]
</div>
</div>
<div id="inspection-section" class="flex-grow">
<h4 class="flex items-center text-sm font-bold uppercase tracking-wider text-secondary-var mb-2">
<span class="material-symbols-rounded text-base mr-1">search</span>
Inspection/Propriétés
</h4>
<div class="p-3 bg-sidebar-active-var rounded-lg h-full overflow-y-auto text-xs space-y-2">
<p class="font-semibold text-color-var">Élément sélectionné: &lt;body&gt;</p>
<p><span class="text-input-key-var">background-color:</span> var(--bg-page);</p>
<p><span class="text-input-key-var">color:</span> var(--text-color);</p>
<p class="text-secondary-var mt-4">[Aide Contextuelle pour la balise HTML 'body']</p>
</div>
</div>
</aside>
<div id="main-content" class="main-content flex-grow transition-colors-theme editor-container">
<header id="main-header" class="flex justify-between items-center bg-page-var p-2 border-b border-sidebar-var shrink-0">
<div class="flex items-center">
<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">
<span class="material-symbols-rounded text-color-var">menu</span>
</button>
<a href="/" class="flex items-center"> <img src="https://i.imgur.com/7Gn3toV.png" alt="Nexus Pro Logo" class="h-8">
<span class="text-xl font-bold text-color-var ml-2 transition-colors-theme hidden sm:inline">Mailix Editor</span>
</a>
</div>
<div class="flex items-center">
<a href="/html-run"
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"
title="Lancer le Lanceur HTML">
<span class="material-symbols-rounded">play_arrow</span>
<span class="ml-3 hidden sm:inline">Run</span>
</a>
<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">
<span class="material-symbols-rounded text-color-var">more_vert</span>
</button>
</div>
</header>
<div id="tab-bar" class="flex items-center overflow-x-auto shrink-0">
<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">
<span class="material-symbols-rounded text-xl">add</span>
</button>
<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">
<span class="material-symbols-rounded text-xl">file_upload</span>
</button>
<input type="file" id="file-input" class="hidden" />
<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">
<span class="material-symbols-rounded text-xl mr-1">save</span>
Sauvegarder
</button>
</div>
<div id="code-editor-wrapper">
<textarea id="code-editor-textarea" style="display: none;">
&lt;!DOCTYPE html&gt;
&lt;html lang="fr"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;title&gt;Mon Nouveau Projet&lt;/title&gt;
&lt;link rel="stylesheet" href="style.css"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Bienvenue dans l'Éditeur Mailix&lt;/h1&gt;
&lt;p&gt;Ce code est mis en évidence par CodeMirror.&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</textarea>
</div>
<footer id="status-bar" class="flex items-center justify-between px-4 py-0.5 shrink-0">
<div class="flex items-center space-x-4">
<span class="material-symbols-rounded text-sm">code</span>
<span id="status-language">Langage: HTML</span>
</div>
<div class="flex items-center space-x-4">
<span id="status-encoding">UTF-8</span>
<span id="status-line-col">Ligne: 1, Col: 1</span>
</div>
</footer>
</div>
<div id="create-file-modal" class="modal">
<div class="modal-content bg-card-var text-color-var">
<h2 class="text-xl font-bold mb-4">Créer un Nouveau Fichier</h2>
<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>
<div class="flex justify-end space-x-3">
<button id="cancel-create-file" class="px-4 py-2 text-secondary-var hover:text-color-var rounded-lg transition-colors-theme">Annuler</button>
<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>
</div>
</div>
</div>
<script>
// --- PARTIE 1: GESTION DE LA SIDEBAR (Gauche et Droite) ---
// Éléments de la Sidebar Gauche
const sidebarLeftToggle = document.getElementById('sidebar-left-toggle');
const sidebarLeft = document.getElementById('sidebar-left'); // ID mis à jour
const mainContent = document.getElementById('main-content');
// Éléments de la Sidebar Droite (NOUVEAU)
const sidebarRightToggle = document.getElementById('sidebar-right-toggle');
const sidebarRight = document.getElementById('sidebar-right');
// Logique pour la Sidebar Gauche
function toggleSidebarLeft() {
sidebarLeft.classList.toggle('open');
// Gérer le décalage du contenu principal sur grand écran (lg)
if (window.innerWidth >= 1024) {
mainContent.classList.toggle('sidebar-left-open');
}
}
// Logique pour la Sidebar Droite (NOUVEAU)
function toggleSidebarRight() {
sidebarRight.classList.toggle('open');
// Gérer le décalage du contenu principal sur grand écran (lg)
if (window.innerWidth >= 1024) {
mainContent.classList.toggle('sidebar-right-open');
}
}
sidebarLeftToggle.addEventListener('click', toggleSidebarLeft);
sidebarRightToggle.addEventListener('click', toggleSidebarRight); // Lier le nouveau bouton
// Logique de Déconnexion (Conservée)
document.getElementById('logout-button-sidebar').addEventListener('click', (e) => {
e.preventDefault();
window.location.href = e.currentTarget.href;
});
// --- PARTIE 2: GESTION DE L'ÉDITEUR DE CODE (CodeMirror + Onglets) ---
// Structure de données pour les fichiers (V1: Stockage local)
let files = [
{ id: 1, name: 'index.html', content: document.getElementById('code-editor-textarea').value, mode: 'htmlmixed', isActive: true },
{ id: 2, name: 'style.css', content: "body {\n font-family: sans-serif;\n color: #4CAF50; /* Green */\n}", mode: 'css', isActive: false },
{ id: 3, name: 'script.js', content: "console.log('Editor Ready!');", mode: 'javascript', isActive: false }
];
let fileIdCounter = files.length + 1;
let currentEditor = null; // L'instance CodeMirror
const tabBar = document.getElementById('tab-bar');
const editorTextarea = document.getElementById('code-editor-textarea');
const statusBarLanguage = document.getElementById('status-language');
const statusBarLineCol = document.getElementById('status-line-col');
const fileExplorer = document.getElementById('file-explorer');
// Éléments de la modale
const createFileModal = document.getElementById('create-file-modal');
const newFileNameInput = document.getElementById('new-file-name-input');
const confirmCreateFileButton = document.getElementById('confirm-create-file');
const cancelCreateFileButton = document.getElementById('cancel-create-file');
/**
* Détermine le mode CodeMirror à partir de l'extension du nom de fichier.
* @param {string} fileName Le nom du fichier.
* @returns {string} Le mode CodeMirror.
*/
function getModeFromFileExtension(fileName) {
const ext = fileName.split('.').pop().toLowerCase();
switch (ext) {
// Web
case 'html':
case 'htm':
return 'htmlmixed';
case 'css':
return 'css';
case 'js':
return 'javascript';
case 'ts':
return 'typescript'; // Nouveau
case 'json':
return 'application/json'; // Nouveau (mode spécifique pour JSON)
// Backend / Systèmes
case 'py':
return 'python';
case 'php':
case 'phtml':
return 'php';
case 'go':
return 'go'; // Nouveau
case 'rs':
return 'rust'; // Nouveau
case 'sh':
return 'shell';
// Langages C-like (Java, C, C++, C#)
case 'java':
case 'c':
case 'cpp':
case 'cs':
return 'clike';
// Données et Bases de données
case 'sql':
return 'sql';
case 'yaml':
case 'yml':
return 'yaml'; // Nouveau
// Documentation et autres
case 'md':
case 'markdown':
return 'markdown';
case 'tex':
case 'latex':
return 'stex'; // Nouveau (pour LaTeX)
default:
return 'text/plain'; // Mode texte brut pour les inconnus ou le texte simple
}
}
/**
* Initialise CodeMirror sur le textarea.
*/
function initializeEditor(initialContent, mode) {
if (currentEditor) {
currentEditor.toTextArea(); // Détruit l'ancienne instance
currentEditor = null;
}
editorTextarea.value = initialContent;
currentEditor = CodeMirror.fromTextArea(editorTextarea, {
lineNumbers: true,
mode: mode,
theme: 'monokai',
indentUnit: 4,
tabSize: 4,
indentWithTabs: false,
autofocus: true,
// MODIFICATION 1.2: Désactiver le retour à la ligne pour forcer le défilement horizontal
lineWrapping: false,
});
// Événement de mise à jour du contenu et de la barre de statut
currentEditor.on("change", handleEditorChange);
currentEditor.on("cursorActivity", updateStatusBar);
// S'assurer que CodeMirror utilise 100% de la hauteur disponible dans son conteneur parent flexible
const cmElement = currentEditor.getWrapperElement();
cmElement.style.height = '100%';
updateStatusBar();
}
/**
* Met à jour le contenu de l'objet fichier actif dans le tableau `files`.
*/
function handleEditorChange() {
if (currentEditor) {
const activeFile = files.find(f => f.isActive);
if (activeFile) {
activeFile.content = currentEditor.getValue();
}
}
}
/**
* Met à jour les indicateurs Ligne/Col dans la barre de statut.
*/
function updateStatusBar() {
if (currentEditor) {
const cursor = currentEditor.getCursor();
statusBarLineCol.textContent = `Ligne: ${cursor.line + 1}, Col: ${cursor.ch + 1}`;
}
}
/**
* Affiche l'éditeur avec le contenu du fichier sélectionné.
* @param {number} fileId L'ID du fichier à activer.
*/
function setActiveFile(fileId) {
files.forEach(f => f.isActive = f.id === fileId);
const activeFile = files.find(f => f.isActive);
if (activeFile) {
// Mise à jour de CodeMirror
if (currentEditor) {
currentEditor.setValue(activeFile.content);
currentEditor.setOption("mode", activeFile.mode);
currentEditor.focus();
} else {
// Initialisation au premier chargement
initializeEditor(activeFile.content, activeFile.mode);
}
// Mise à jour de la barre de statut
const modeName = activeFile.mode.includes('html') ? 'HTML' : (activeFile.mode === 'css' ? 'CSS' : 'JavaScript');
statusBarLanguage.textContent = `Langage: ${modeName}`;
}
renderTabs();
renderFileExplorer(); // Mise à jour de l'explorateur
}
/**
* Génère et affiche les onglets dans la barre d'onglets.
*/
function renderTabs() {
// Enlever tous les onglets existants sauf le bouton '+' et 'Sauvegarder' et 'Importer'
// On cible spécifiquement les éléments avec la classe 'tab-item'
const existingTabs = tabBar.querySelectorAll('.tab-item');
existingTabs.forEach(tab => tab.remove());
const saveButton = document.getElementById('save-button');
files.forEach(file => {
const tab = document.createElement('div');
// 'flex items-center' est conservé mais 'inline-flex' est dans le CSS pour le white-space: nowrap
tab.className = `tab-item flex items-center ${file.isActive ? 'active' : ''}`;
tab.dataset.fileId = file.id;
tab.innerHTML = `
<span>${file.name}</span>
<span class="tab-close material-symbols-rounded text-sm hover:text-color-var transition-colors-theme ml-2">close</span>
`;
// Attacher l'onglet avant le bouton Sauvegarder (qui est 'ml-auto')
// Remarque: La structure des boutons dans le HTML implique que les onglets sont insérés avant le saveButton (qui a ml-auto)
tabBar.insertBefore(tab, saveButton);
// Gérer le clic pour activer l'onglet
tab.querySelector('span').addEventListener('click', (e) => {
// Évite de déclencher l'activation si on clique sur l'icône de fermeture
if (!e.target.classList.contains('tab-close')) {
setActiveFile(file.id);
}
});
// Gérer le clic pour fermer l'onglet
tab.querySelector('.tab-close').addEventListener('click', () => closeFile(file.id));
});
}
/**
* Génère et affiche la liste des fichiers dans l'explorateur de fichiers.
*/
function renderFileExplorer() {
fileExplorer.innerHTML = ''; // Nettoyer l'explorateur
files.forEach(file => {
const item = document.createElement('a');
const isActiveClass = file.isActive ? 'bg-sidebar-active-var text-color-var' : 'text-secondary-var hover:text-color-var hover:bg-sidebar-active-var';
item.href = '#';
item.className = `flex items-center p-2 text-sm rounded-lg transition duration-150 transition-colors-theme ${isActiveClass}`;
item.dataset.fileId = file.id;
item.innerHTML = `
<span class="material-symbols-rounded text-base">insert_drive_file</span>
<span class="ml-2 truncate">${file.name}</span>
`;
item.addEventListener('click', (e) => {
e.preventDefault();
setActiveFile(file.id);
});
fileExplorer.appendChild(item);
});
}
/**
* Ajoute un nouveau fichier/onglet.
* @param {string} fileName Le nom du fichier.
* @param {string} fileContent Le contenu du fichier (par défaut vide).
*/
function createNewFile(fileName, fileContent = "// Nouveau fichier") {
const mode = getModeFromFileExtension(fileName);
const newFile = {
id: fileIdCounter++,
name: fileName,
content: fileContent,
mode: mode,
isActive: false // Sera activé par setActiveFile
};
files.push(newFile);
setActiveFile(newFile.id);
}
/**
* Ferme un fichier/onglet.
* @param {number} fileId L'ID du fichier à fermer.
*/
function closeFile(fileId) {
const index = files.findIndex(f => f.id === fileId);
if (index === -1) return;
const wasActive = files[index].isActive;
files.splice(index, 1); // Retire le fichier
if (files.length === 0) {
// Si plus de fichiers, ajoute un fichier vierge
createNewFile(`untitled-${fileIdCounter}.js`);
return;
}
if (wasActive) {
// Active le fichier précédent ou le premier s'il n'y a pas de précédent
const newActiveFile = files[Math.max(0, index - 1)];
setActiveFile(newActiveFile.id);
} else {
// Si ce n'était pas l'actif, on rafraîchit juste les onglets et l'explorateur
renderTabs();
renderFileExplorer();
}
}
// --- Logique de la Modale de Création de Fichier ---
document.getElementById('add-tab-button').addEventListener('click', () => {
newFileNameInput.value = ''; // Réinitialiser l'input
createFileModal.classList.add('open');
newFileNameInput.focus();
});
cancelCreateFileButton.addEventListener('click', () => {
createFileModal.classList.remove('open');
});
confirmCreateFileButton.addEventListener('click', () => {
const fileName = newFileNameInput.value.trim();
if (fileName) {
createNewFile(fileName);
createFileModal.classList.remove('open');
} else {
alert("Veuillez entrer un nom de fichier.");
}
});
// --- Logique d'Importation de Fichier ---
const importButton = document.getElementById('import-button');
const fileInput = document.getElementById('file-input');
importButton.addEventListener('click', () => {
fileInput.click(); // Simuler le clic sur l'input file caché
});
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
// Créer le nouveau fichier avec le nom et le contenu du fichier importé
createNewFile(file.name, content);
};
reader.readAsText(file); // Lire le fichier en tant que texte
}
// Réinitialiser l'input file pour permettre l'importation du même fichier à nouveau
fileInput.value = null;
});
// 3. Événements et initialisation
document.addEventListener('DOMContentLoaded', () => {
// Initialiser CodeMirror avec le fichier actif par défaut
const activeFile = files.find(f => f.isActive);
if (activeFile) {
initializeEditor(activeFile.content, activeFile.mode);
}
// Événement factice pour le bouton Sauvegarder
document.getElementById('save-button').addEventListener('click', () => {
const active = files.find(f => f.isActive);
alert(`Fichier "${active.name}" sauvegardé localement!\nContenu: ${active.content.substring(0, 50)}...`);
});
// Rendu initial des onglets et de l'explorateur
renderTabs();
renderFileExplorer();
});
</script>
</body>
</html>