correction du nixpack pour supprimer les fichiers sources
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user