import { EditorState } from '@codemirror/state'; import { EditorView } from '@codemirror/view'; import { basicSetup } from '@codemirror/basic-setup'; import { markdown } from '@codemirror/lang-markdown'; import { oneDark } from '@codemirror/theme-one-dark'; import { keymap } from '@codemirror/view'; import { indentWithTab } from '@codemirror/commands'; import { LinkInserter } from './link-inserter.js'; // Import du mode Vim let vimExtension = null; (async () => { try { const { vim } = await import('@replit/codemirror-vim'); vimExtension = vim; console.log('✅ Vim extension loaded and ready'); } catch (error) { console.warn('⚠️ Vim extension not available:', error.message); } })(); /** * MarkdownEditor - Éditeur Markdown avec preview en temps réel */ class MarkdownEditor { constructor(textareaElement, previewElement) { this.textarea = textareaElement; this.preview = previewElement; this._updateTimeout = null; this.editorView = null; // CodeMirror 6 uses EditorView this._isSyncing = false; this._autoSaveTimeout = null; if (!this.textarea || !this.preview) { console.error('MarkdownEditor: textarea or preview element not found'); return; } this.init(); } init() { // Configuration de marked.js pour le preview if (typeof marked !== 'undefined') { marked.setOptions({ gfm: true, breaks: true, highlight: (code, lang) => { if (typeof hljs !== 'undefined') { try { if (lang && hljs.getLanguage(lang)) { return hljs.highlight(code, { language: lang }).value; } return hljs.highlightAuto(code).value; } catch (err) { console.warn('Highlight.js error:', err); } } return code; } }); } // Initialiser l'éditeur (avec ou sans Vim) this.initEditor(); } getExtensions() { const extensions = [ basicSetup, markdown(), oneDark, keymap.of([indentWithTab]), EditorView.updateListener.of((update) => { if (update.docChanged) { // Debounce la mise à jour du preview if (this._updateTimeout) { clearTimeout(this._updateTimeout); } this._updateTimeout = setTimeout(() => { this.updatePreview(); }, 150); // Auto-save logic if (this._autoSaveTimeout) { clearTimeout(this._autoSaveTimeout); } this._autoSaveTimeout = setTimeout(() => { const form = this.textarea.closest('form'); if (form) { const saveStatus = document.getElementById('auto-save-status'); if (saveStatus) { saveStatus.textContent = 'Sauvegarde...'; } // Synchroniser le contenu de CodeMirror vers le textarea this.syncToTextarea(); form.requestSubmit(); } }, 2000); // Auto-save after 2 seconds of inactivity } }), // Keymap for Ctrl/Cmd+S keymap.of([{ key: "Mod-s", run: () => { const form = this.textarea.closest('form'); if (form) { // Synchroniser le contenu de CodeMirror vers le textarea this.syncToTextarea(); form.requestSubmit(); } return true; } }]) ]; // Ajouter l'extension Vim si activée et disponible if (window.vimModeManager && window.vimModeManager.isEnabled()) { if (vimExtension) { extensions.push(vimExtension()); console.log('✅ Vim mode enabled in editor'); } else { console.warn('⚠️ Vim mode requested but extension not loaded yet'); } } return extensions; } initEditor() { const currentContent = this.editorView ? this.editorView.state.doc.toString() : this.textarea.value; const extensions = this.getExtensions(); // Détruire l'ancien éditeur si il existe if (this.editorView) { this.editorView.destroy(); } // Initialiser CodeMirror 6 const startState = EditorState.create({ doc: currentContent, extensions }); this.editorView = new EditorView({ state: startState, parent: this.textarea.parentElement }); // Hide the original textarea this.textarea.style.display = 'none'; // Adjust height (similar to CM5, but targeting the CM6 editor) const adjustHeight = () => { const editorDom = this.editorView.dom; if (!editorDom) return; const height = window.innerHeight - 180; // Adjust as needed editorDom.style.height = `${height}px`; editorDom.style.maxHeight = `${height}px`; // Ensure it doesn't grow beyond this editorDom.style.overflowY = 'auto'; }; adjustHeight(); window.addEventListener('resize', adjustHeight); // Scroll syncing (simplified for now, might need more complex logic) this.editorView.dom.addEventListener('scroll', () => { if (this._isSyncing) return; this._isSyncing = true; const editorScrollTop = this.editorView.scrollDOM.scrollTop; const editorScrollHeight = this.editorView.scrollDOM.scrollHeight - this.editorView.scrollDOM.clientHeight; if (editorScrollHeight > 0 && this.preview) { const scrollPercent = editorScrollTop / editorScrollHeight; const previewScrollHeight = this.preview.scrollHeight - this.preview.clientHeight; if (previewScrollHeight > 0) { this.preview.scrollTop = scrollPercent * previewScrollHeight; } } setTimeout(() => { this._isSyncing = false; }, 50); }); this.preview.addEventListener('scroll', () => { if (this._isSyncing) return; this._isSyncing = true; const previewScrollTop = this.preview.scrollTop; const previewScrollHeight = this.preview.scrollHeight - this.preview.clientHeight; if (previewScrollHeight > 0 && this.editorView) { const scrollPercent = previewScrollTop / previewScrollHeight; const editorScrollHeight = this.editorView.scrollDOM.scrollHeight - this.editorView.scrollDOM.clientHeight; if (editorScrollHeight > 0) { this.editorView.scrollDOM.scrollTop = scrollPercent * editorScrollHeight; } } setTimeout(() => { this._isSyncing = false; }, 50); }); // Initial preview update this.updatePreview(); // Initialiser les SlashCommands si ce n'est pas déjà fait if (this.editorView && !window.currentSlashCommands) { window.currentSlashCommands = new SlashCommands({ editorView: this.editorView }); } } stripFrontMatter(markdownContent) { const lines = markdownContent.split('\n'); if (lines.length > 0 && lines[0].trim() === '---') { let firstDelimiter = -1; let secondDelimiter = -1; for (let i = 0; i < lines.length; i++) { if (lines[i].trim() === '---') { if (firstDelimiter === -1) { firstDelimiter = i; } else { secondDelimiter = i; break; } } } if (firstDelimiter !== -1 && secondDelimiter !== -1) { return lines.slice(secondDelimiter + 1).join('\n'); } } return markdownContent; } updatePreview() { const content = this.editorView ? this.editorView.state.doc.toString() : this.textarea.value; const contentWithoutFrontMatter = this.stripFrontMatter(content); if (typeof marked !== 'undefined' && typeof DOMPurify !== 'undefined') { const html = marked.parse(contentWithoutFrontMatter); // Permettre les attributs HTMX et onclick dans DOMPurify const cleanHtml = DOMPurify.sanitize(html, { ADD_ATTR: ['hx-get', 'hx-target', 'hx-swap', 'onclick'] }); this.preview.innerHTML = cleanHtml; // Traiter les nouveaux éléments HTMX if (typeof htmx !== 'undefined') { htmx.process(this.preview); } // Intercepter les clics sur les liens internes (avec hx-get) this.setupInternalLinkHandlers(); if (typeof hljs !== 'undefined') { this.preview.querySelectorAll('pre code').forEach(block => { hljs.highlightElement(block); }); } } else { this.preview.textContent = contentWithoutFrontMatter; } } setupInternalLinkHandlers() { // Trouver tous les liens avec hx-get (liens internes) const internalLinks = this.preview.querySelectorAll('a[hx-get]'); internalLinks.forEach(link => { // Retirer les anciens listeners pour éviter les doublons link.replaceWith(link.cloneNode(true)); }); // Ré-sélectionner après clonage const freshLinks = this.preview.querySelectorAll('a[hx-get]'); freshLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const target = link.getAttribute('hx-get'); const targetElement = link.getAttribute('hx-target') || '#editor-container'; const swapMethod = link.getAttribute('hx-swap') || 'innerHTML'; console.log('[InternalLink] Clicked:', target); if (target && typeof htmx !== 'undefined') { htmx.ajax('GET', target, { target: targetElement, swap: swapMethod }); } }); }); console.log('[Preview] Setup', freshLinks.length, 'internal link handlers'); } syncToTextarea() { if (this.editorView && this.textarea) { this.textarea.value = this.editorView.state.doc.toString(); } } destroy() { if (this._updateTimeout) { clearTimeout(this._updateTimeout); this._updateTimeout = null; } if (this._autoSaveTimeout) { clearTimeout(this._autoSaveTimeout); this._autoSaveTimeout = null; } if (this.editorView) { this.editorView.destroy(); // Destroy CM6 instance this.editorView = null; } // Restore the textarea if (this.textarea) { this.textarea.style.display = ''; } this.textarea = null; this.preview = null; } async reloadWithVimMode() { console.log('Reloading editor with Vim mode...'); await this.initEditor(); } } // Global instances let currentMarkdownEditor = null; let currentSlashCommands = null; // SlashCommands will need significant refactoring for CM6 /** * SlashCommands - Système de commandes slash pour l'éditeur CodeMirror 6 * Utilise l'API native de CodeMirror 6 pour une meilleure fiabilité */ class SlashCommands { constructor({ editorView }) { this.editorView = editorView; if (!this.editorView) { console.error('SlashCommands: EditorView instance required'); return; } this.active = false; this.query = ''; this.selectedIndex = 0; this.palette = null; this.slashPos = null; this._updateListener = null; this._keydownHandler = null; this.commands = [ { name: 'h1', snippet: '# ' }, { name: 'h2', snippet: '## ' }, { name: 'h3', snippet: '### ' }, { name: 'list', snippet: '- ' }, { name: 'date', snippet: () => new Date().toLocaleDateString('fr-FR') }, { name: 'link', snippet: '[texte](url)' }, { name: 'ilink', isModal: true, handler: () => this.openLinkInserter() }, { name: 'bold', snippet: '**texte**' }, { name: 'italic', snippet: '*texte*' }, { name: 'code', snippet: '`code`' }, { name: 'codeblock', snippet: '```\ncode\n```' }, { name: 'quote', snippet: '> ' }, { name: 'hr', snippet: '---' }, { name: 'table', snippet: '| Colonne 1 | Colonne 2 | Colonne 3 |\n|-----------|-----------|-----------|\n| Ligne 1 | Données | Données |\n| Ligne 2 | Données | Données |' }, ]; this.init(); } init() { this.createPalette(); // Écouter les événements input pour détecter les changements de texte this._inputHandler = () => { Promise.resolve().then(() => { this.checkForSlashCommand(); }); }; // Écouter les changements de sélection this._selectionHandler = () => { Promise.resolve().then(() => { this.checkForSlashCommand(); }); }; this.editorView.dom.addEventListener('input', this._inputHandler); this.editorView.dom.addEventListener('selectionchange', this._selectionHandler); // Gérer uniquement les touches de navigation quand la palette est active this._keydownHandler = (event) => { if (!this.active) return; const filteredCommands = this.getFilteredCommands(); if (filteredCommands.length === 0) { this.hidePalette(); return; } let handled = false; switch (event.key) { case 'ArrowDown': this.selectedIndex = (this.selectedIndex + 1) % filteredCommands.length; this.updatePalette(); handled = true; break; case 'ArrowUp': this.selectedIndex = (this.selectedIndex - 1 + filteredCommands.length) % filteredCommands.length; this.updatePalette(); handled = true; break; case 'Enter': case 'Tab': this.executeCommand(filteredCommands[this.selectedIndex]); handled = true; break; case 'Escape': this.hidePalette(); handled = true; break; } if (handled) { event.preventDefault(); event.stopPropagation(); } }; // Utiliser la phase de capture pour intercepter avant CodeMirror this.editorView.dom.addEventListener('keydown', this._keydownHandler, true); } createPalette() { this.palette = document.createElement('ul'); this.palette.id = 'slash-commands-palette'; this.palette.style.cssText = ` position: fixed; background: var(--bg-secondary); background-color: var(--bg-secondary) !important; border: 1px solid var(--border-primary); list-style: none; padding: 0.5rem; margin: 0; border-radius: 8px; z-index: 10000; display: none; min-width: 220px; max-height: 320px; overflow-y: auto; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.3); opacity: 1 !important; `; document.body.appendChild(this.palette); } checkForSlashCommand() { if (!this.editorView) return; const { state } = this.editorView; const { from, to } = state.selection.main; // Ne pas afficher si une sélection est active if (from !== to) { this.hidePalette(); return; } const line = state.doc.lineAt(from); const textBeforeCursor = state.doc.sliceString(line.from, from); // Chercher le dernier "/" dans la ligne avant le curseur const slashIndex = textBeforeCursor.lastIndexOf('/'); if (slashIndex === -1) { this.hidePalette(); return; } // Vérifier que le "/" est au début de la ligne ou après un espace/newline const charBeforeSlash = slashIndex > 0 ? textBeforeCursor[slashIndex - 1] : ''; const isValidPosition = slashIndex === 0 || /\s/.test(charBeforeSlash); if (!isValidPosition) { this.hidePalette(); return; } // Extraire la query après le "/" const potentialQuery = textBeforeCursor.substring(slashIndex + 1); // Si la query contient un espace ou un newline, cacher la palette if (potentialQuery.includes(' ') || potentialQuery.includes('\n')) { this.hidePalette(); return; } // Mettre à jour l'état et afficher la palette this.query = potentialQuery; this.slashPos = { lineNumber: line.number, offset: slashIndex, absolutePos: line.from + slashIndex }; // Réinitialiser l'index sélectionné si la query change if (!this.active) { this.selectedIndex = 0; } this.showPalette(); } getFilteredCommands() { if (!this.query) { return this.commands; } return this.commands.filter(cmd => cmd.name.toLowerCase().includes(this.query.toLowerCase()) ); } showPalette() { const filteredCommands = this.getFilteredCommands(); if (filteredCommands.length === 0) { this.hidePalette(); return; } this.active = true; this.updatePalette(); this.positionPalette(); if (this.palette) { this.palette.style.display = 'block'; } } hidePalette() { this.active = false; this.query = ''; this.selectedIndex = 0; this.slashPos = null; if (this.palette) { this.palette.style.display = 'none'; } } updatePalette() { if (!this.palette) return; const filteredCommands = this.getFilteredCommands(); this.palette.innerHTML = ''; filteredCommands.forEach((cmd, index) => { const li = document.createElement('li'); li.innerHTML = `/${cmd.name}`; const isSelected = index === this.selectedIndex; li.style.cssText = ` padding: 0.5rem 0.75rem; cursor: pointer; color: ${isSelected ? 'var(--text-primary)' : 'var(--text-secondary)'}; background: ${isSelected ? 'var(--accent-primary)' : 'transparent'}; border-radius: 4px; margin: 4px 0; transition: all 150ms ease; font-family: 'Fira Code', 'Cascadia Code', 'Consolas', monospace; font-size: 0.9rem; font-weight: ${isSelected ? '500' : '400'}; display: flex; align-items: center; `; li.addEventListener('mouseenter', () => { this.selectedIndex = index; this.updatePalette(); }); li.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); this.executeCommand(cmd); }); this.palette.appendChild(li); if (isSelected) { li.scrollIntoView({ block: 'nearest', behavior: 'auto' }); } }); } positionPalette() { if (!this.palette || !this.editorView || !this.slashPos) return; // Utiliser requestAnimationFrame pour s'assurer que le DOM est à jour requestAnimationFrame(() => { const { state } = this.editorView; const { from } = state.selection.main; // Obtenir les coordonnées de la position du curseur const coords = this.editorView.coordsAtPos(from); if (coords) { const { left, bottom } = coords; const paletteHeight = 320; // max-height de la palette const windowHeight = window.innerHeight; // Si pas assez de place en bas, afficher au-dessus const spaceBelow = windowHeight - bottom; const showAbove = spaceBelow < paletteHeight && bottom > paletteHeight; this.palette.style.left = `${left}px`; if (showAbove) { this.palette.style.top = `${coords.top - paletteHeight - 5}px`; this.palette.style.bottom = 'auto'; } else { this.palette.style.top = `${bottom + 5}px`; this.palette.style.bottom = 'auto'; } } }); } executeCommand(command) { if (!command || !this.slashPos) { this.hidePalette(); return; } // Commande spéciale avec modal (comme /ilink) if (command.isModal && command.handler) { console.log('Executing modal command:', command.name); // NE PAS cacher la palette tout de suite car le handler a besoin de slashPos // La palette sera cachée par le handler lui-même command.handler(); return; } let snippet = command.snippet; if (typeof snippet === 'function') { snippet = snippet(); } const { state, dispatch } = this.editorView; const { from } = state.selection.main; // Remplacer depuis le "/" jusqu'au curseur const replaceFrom = this.slashPos.absolutePos; dispatch(state.update({ changes: { from: replaceFrom, to: from, insert: snippet }, selection: { anchor: replaceFrom + snippet.length } })); this.editorView.focus(); this.hidePalette(); } openLinkInserter() { // Sauvegarder la position du slash IMMÉDIATEMENT avant toute autre opération const savedSlashPos = this.slashPos; console.log('[SlashCommands] openLinkInserter - savedSlashPos:', savedSlashPos); if (!savedSlashPos) { console.error('[SlashCommands] No slash position available!'); this.hidePalette(); return; } // Maintenant on peut cacher la palette en toute sécurité this.hidePalette(); // S'assurer que le LinkInserter global existe, le créer si nécessaire if (!window.linkInserter) { console.log('Initializing LinkInserter...'); window.linkInserter = new LinkInserter(); } // Ouvrir le modal de sélection de lien window.linkInserter.open({ editorView: this.editorView, onSelect: ({ title, path }) => { console.log('[SlashCommands] onSelect callback received:', { title, path }); console.log('[SlashCommands] savedSlashPos:', savedSlashPos); // Créer un lien HTMX cliquable dans le preview // Format : Title // Le onclick="return false;" empêche le comportement par défaut du # qui pourrait rediriger const linkHtml = `${title}`; console.log('[SlashCommands] Inserting:', linkHtml); const { state, dispatch } = this.editorView; const { from } = state.selection.main; // Remplacer depuis le "/" jusqu'au curseur actuel const replaceFrom = savedSlashPos.absolutePos; console.log('[SlashCommands] Replacing from', replaceFrom, 'to', from); dispatch(state.update({ changes: { from: replaceFrom, to: from, insert: linkHtml }, selection: { anchor: replaceFrom + linkHtml.length } })); this.editorView.focus(); console.log('[SlashCommands] Link inserted successfully'); } }); } destroy() { // Retirer tous les listeners d'événements if (this.editorView) { if (this._keydownHandler) { this.editorView.dom.removeEventListener('keydown', this._keydownHandler, true); this._keydownHandler = null; } if (this._inputHandler) { this.editorView.dom.removeEventListener('input', this._inputHandler); this._inputHandler = null; } if (this._selectionHandler) { this.editorView.dom.removeEventListener('selectionchange', this._selectionHandler); this._selectionHandler = null; } } this.hidePalette(); if (this.palette && this.palette.parentNode) { this.palette.parentNode.removeChild(this.palette); } this.palette = null; this.editorView = null; } } // Global instances window.currentMarkdownEditor = null; window.currentSlashCommands = null; // SlashCommands will need significant refactoring for CM6 function initializeMarkdownEditor(context) { const scope = context && typeof context.querySelector === 'function' ? context : document; const textarea = scope.querySelector('#editor') || document.getElementById('editor'); const preview = scope.querySelector('#preview') || document.getElementById('preview'); if (!textarea || !preview) { console.error('initializeMarkdownEditor: Missing textarea or preview elements'); return; } if (window.currentMarkdownEditor) { window.currentMarkdownEditor.destroy(); window.currentMarkdownEditor = null; } if (window.currentSlashCommands) { window.currentSlashCommands.destroy(); window.currentSlashCommands = null; } const markdownEditor = new MarkdownEditor(textarea, preview); window.currentMarkdownEditor = markdownEditor; // Note: SlashCommands sera créé automatiquement dans initEditor() qui est async } /** * Toggle between editor-only, split, and preview-only modes */ window.togglePreview = function() { const grid = document.getElementById('editor-grid'); const editorPanel = document.querySelector('.editor-panel'); const preview = document.getElementById('preview'); const btn = document.getElementById('toggle-preview-btn'); if (!grid || !editorPanel || !preview || !btn) { console.error('togglePreview: Missing elements', { grid, editorPanel, preview, btn }); return; } // Détecter si on est sur mobile const isMobile = window.innerWidth <= 768; // Récupérer le mode actuel let currentMode = localStorage.getItem('viewMode') || 'split'; let newMode; if (isMobile) { // Sur mobile: seulement 2 modes (editor-only <-> preview-only) if (currentMode === 'editor-only') { newMode = 'preview-only'; } else { // Depuis preview-only ou split, aller vers editor-only newMode = 'editor-only'; } } else { // Desktop: 3 modes (split -> editor-only -> preview-only -> split) if (currentMode === 'split') { newMode = 'editor-only'; } else if (currentMode === 'editor-only') { newMode = 'preview-only'; } else { newMode = 'split'; } } // Appliquer le nouveau mode window.applyViewMode(newMode, document); } /** * Apply view mode (editor-only, split, preview-only) */ window.applyViewMode = function(mode, context) { const scope = context && typeof context.querySelector === 'function' ? context : document; const grid = scope.querySelector('#editor-grid'); const editorPanel = scope.querySelector('.editor-panel'); const preview = scope.querySelector('#preview'); const btn = scope.querySelector('#toggle-preview-btn'); if (!grid || !editorPanel || !preview) { console.error('applyViewMode: Missing essential elements', { grid, editorPanel, preview }); return; } // Le bouton peut ne pas exister (page d'accueil par exemple) // Ce n'est pas une erreur critique // Détecter si on est sur mobile const isMobile = window.innerWidth <= 768; // Retirer toutes les classes de mode grid.classList.remove('preview-hidden', 'editor-hidden', 'split-view'); editorPanel.classList.remove('hidden'); preview.classList.remove('hidden'); switch (mode) { case 'editor-only': grid.classList.add('preview-hidden'); preview.classList.add('hidden'); if (btn) { btn.textContent = '◧ Éditeur'; btn.title = isMobile ? 'Mode: Éditeur → Cliquer pour Preview' : 'Mode: Éditeur seul → Cliquer pour Preview seule'; } break; case 'preview-only': grid.classList.add('editor-hidden'); editorPanel.classList.add('hidden'); if (btn) { btn.textContent = '◨ Preview'; btn.title = isMobile ? 'Mode: Preview → Cliquer pour Éditeur' : 'Mode: Preview seule → Cliquer pour Split'; } break; case 'split': default: grid.classList.add('split-view'); if (btn) { btn.textContent = '◫ Split'; btn.title = 'Mode: Split → Cliquer pour Éditeur seul'; } break; } localStorage.setItem('viewMode', mode); } /** * Restore view mode from localStorage */ window.restorePreviewState = function(context) { const viewMode = localStorage.getItem('viewMode') || 'split'; window.applyViewMode(viewMode, context); } /** * Initialisation automatique quand le DOM est prêt */ document.addEventListener('DOMContentLoaded', () => { // Check if editor elements are present on initial load const initialTextarea = document.querySelector('#editor'); const initialPreview = document.querySelector('#preview'); if (initialTextarea && initialPreview) { initializeMarkdownEditor(); window.restorePreviewState(document); } // Appliquer le mode d'affichage AVANT le swap pour éviter le flash document.body.addEventListener('htmx:beforeSwap', (event) => { const target = event.detail?.target; if (target && target.id === 'editor-container') { // Récupérer le contenu qui va être inséré const serverResponse = event.detail.serverResponse; if (serverResponse) { // Créer un élément temporaire pour parser la réponse const temp = document.createElement('div'); temp.innerHTML = serverResponse; // Appliquer les classes au grid AVANT l'insertion const grid = temp.querySelector('#editor-grid'); const editorPanel = temp.querySelector('.editor-panel'); const preview = temp.querySelector('#preview'); const toggleBtn = temp.querySelector('#toggle-preview-btn'); if (grid && editorPanel && preview) { // Vérifier si c'est la page d'accueil (pas de bouton toggle) const isHomePage = !toggleBtn; if (isHomePage) { // Page d'accueil : toujours en mode preview-only, ne rien changer // Le template a déjà les bonnes classes (editor-hidden) return; } const viewMode = localStorage.getItem('viewMode') || 'split'; const isMobile = window.innerWidth <= 768; // Sur mobile, convertir split en preview-only const effectiveMode = (isMobile && viewMode === 'split') ? 'preview-only' : viewMode; // Retirer toutes les classes grid.classList.remove('preview-hidden', 'editor-hidden', 'split-view'); editorPanel.classList.remove('hidden'); preview.classList.remove('hidden'); // Appliquer le bon mode switch (effectiveMode) { case 'editor-only': grid.classList.add('preview-hidden'); preview.classList.add('hidden'); break; case 'preview-only': grid.classList.add('editor-hidden'); editorPanel.classList.add('hidden'); break; case 'split': default: grid.classList.add('split-view'); break; } // Mettre à jour la réponse serveur avec les bonnes classes event.detail.serverResponse = temp.innerHTML; } } } }); document.body.addEventListener('htmx:afterSwap', (event) => { const target = event.detail?.target; // The target is 'editor-container', check if it contains editor elements if (target && target.id === 'editor-container') { // Supprimer le trigger "load" pour éviter qu'il se redéclenche target.removeAttribute('hx-trigger'); target.removeAttribute('hx-get'); const textarea = target.querySelector('#editor'); const preview = target.querySelector('#preview'); const toggleBtn = target.querySelector('#toggle-preview-btn'); // Vérifier si c'est la page d'accueil (pas de bouton toggle) const isHomePage = !toggleBtn; if (textarea && preview) { initializeMarkdownEditor(target); // Ne pas restaurer le mode pour la page d'accueil (toujours preview-only) if (!isHomePage) { window.restorePreviewState(target); } } else { // If the editor was previously active, destroy it if (window.currentMarkdownEditor) { window.currentMarkdownEditor.destroy(); window.currentMarkdownEditor = null; } if (window.currentSlashCommands) { window.currentSlashCommands.destroy(); window.currentSlashCommands = null; } } } }); });