correction du nixpack pour supprimer les fichiers sources

This commit is contained in:
2026-03-05 08:09:28 +01:00
parent ad606f15fa
commit e12fb751ca
5 changed files with 65 additions and 21 deletions

View File

@@ -168,18 +168,20 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
useEffect(() => {
if (!contentRef.current || initialContent === undefined) return;
// Ignore exact loopbacks from our own saves
if (initialContent === syncRef.current) return;
// 1. Si le contenu entrant est identique à ce qu'on a déjà, on ne touche à rien
if (initialContent === contentRef.current.innerHTML) return;
// Safety: never overwrite real content with an empty string from a stale/placeholder source
const hasRealContent = latestContentRef.current && latestContentRef.current.trim().length > 0;
if (!initialContent && hasRealContent) return;
// 2. LOGIQUE CRUCIALE : On ne met à jour le DOM que si :
// - L'éditeur est vide (premier chargement)
// - OU le document a changé (si vous gérez des IDs de documents)
// - OU si l'utilisateur n'est PAS en train de focus l'éditeur
const isUserEditing = document.activeElement === contentRef.current;
// We reached here, so initialContent is genuinely NEW data we didn't know about.
// E.g. clicked another chapter, or data was modified in another tab/device.
contentRef.current.innerHTML = initialContent;
syncRef.current = initialContent;
latestContentRef.current = initialContent;
if (!isUserEditing || (contentRef.current.innerHTML === "" && initialContent !== "")) {
contentRef.current.innerHTML = initialContent;
syncRef.current = initialContent;
latestContentRef.current = initialContent;
}
}, [initialContent]);
// Flush pending save on unmount
@@ -187,9 +189,11 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
return () => {
if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current);
if (latestContentRef.current !== syncRef.current && onSave) {
onSave(latestContentRef.current);
}
}
// Always save if there are unsaved changes, regardless of timer
if (latestContentRef.current !== syncRef.current && onSave) {
syncRef.current = latestContentRef.current;
onSave(latestContentRef.current);
}
};
}, [onSave]);
@@ -217,8 +221,15 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
saveTimeoutRef.current = setTimeout(async () => {
setSaveStatus('saving');
const htmlToSave = latestContentRef.current;
await onSave(htmlToSave);
syncRef.current = htmlToSave; // Record that we've synced this exact string to the server
// Update syncRef BEFORE calling onSave, because onSave triggers setProjects
// which causes a re-render. The useEffect must see the updated syncRef
// to avoid re-writing innerHTML unnecessarily.
syncRef.current = htmlToSave;
try {
await onSave(htmlToSave);
} catch (err) {
console.error('Auto-save failed:', err);
}
setSaveStatus('saved');
}, 2000); // 2 seconds
}

View File

@@ -4,7 +4,7 @@
import React, { useState, useEffect } from 'react';
import { BookProject, UserProfile, ViewMode, ChatMessage } from '@/lib/types';
import AIPanel from '@/components/AIPanel';
import { Book, FileText, Globe, GitGraph, Lightbulb, Settings, Menu, ChevronRight, ChevronLeft, Share2, HelpCircle, LogOut, LayoutDashboard, User, Plus, Trash2 } from 'lucide-react';
import { Book, FileText, Globe, GitGraph, Lightbulb, Settings, Menu, ChevronRight, ChevronLeft, Share2, HelpCircle, LogOut, LayoutDashboard, User, Plus, Trash2, Wand2 } from 'lucide-react';
import { useLanguage } from '@/providers/LanguageProvider';
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
@@ -157,6 +157,18 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
<div className={`${isAiPanelOpen ? 'w-80 lg:w-96' : 'w-0'} transition-all duration-300 flex-shrink-0 h-full border-l border-theme-border flex flex-col bg-theme-panel absolute right-0 lg:relative z-40 shadow-2xl lg:shadow-none`}>
{isAiPanelOpen && <AIPanel chatHistory={props.chatHistory} onSendMessage={props.onSendMessage} onInsertText={props.onInsertText} selectedText="" isGenerating={props.isGenerating} usage={user.usage} />}
</div>
{/* Floating Mobile Button for AI Panel */}
{!isAiPanelOpen && (
<button
onClick={() => setIsAiPanelOpen(true)}
className="lg:hidden fixed bottom-6 right-6 z-40 bg-indigo-600 text-white p-3 rounded-full shadow-lg hover:bg-indigo-700 transition-colors flex items-center justify-center pointer-events-auto"
aria-label="Ouvrir le panneau IA"
title="Ouvrir le panneau IA"
>
<Wand2 size={24} />
</button>
)}
</div>
);
};