import React from 'react'; import { X, Clock, Folder, Calendar, FileText, Hash, Network, CheckCircle2, AlertCircle, Copy, Check, History, Info, ChevronRight } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { Note, Carnet } from '../types'; interface NotebookInfoSidebarProps { isOpen: boolean; onClose: () => void; activeNote: Note | undefined; notes: Note[]; carnets: Carnet[]; onOpenNote: (id: string) => void; onUpdateNote?: (note: Note) => void; } export const NotebookInfoSidebar: React.FC = ({ isOpen, onClose, activeNote, notes = [], carnets, onOpenNote, onUpdateNote }) => { const [activeTab, setActiveTab] = React.useState<'infos' | 'versions' | 'relations'>('infos'); const [copiedId, setCopiedId] = React.useState(false); const [hoveredOrbitNode, setHoveredOrbitNode] = React.useState(null); // For ID copy action const handleCopyId = (id: string) => { navigator.clipboard.writeText(id).then(() => { setCopiedId(true); setTimeout(() => setCopiedId(false), 2000); }); }; // Explicit links for Network const explicitWikiLinks = React.useMemo(() => [ { source: 'n1', target: 'n1-b' }, { source: 'n3', target: 'n3-b' }, { source: 'bridge-1', target: 'n1' }, { source: 'bridge-1', target: 'n2' }, ], []); const CARNET_COLOR_PALETTE: { [key: string]: string } = { '1': '#D97706', // Daily Notes - Warm Amber '2': '#059669', // Project: Neo - Soft Emerald '3': '#4F46E5', // Shared Docs - Rich Indigo '4': '#0891B2', // Architecture Research - Clean Cyan '5': '#EA580C', // History of Architecture - Deep Orange '6': '#DB2777', // Modernism - Vibrant Rose '7': '#65A30D', // Sustainable Design - Cool Lime }; const DEFAULT_CARNET_COLOR = '#71717A'; // Network calculation values const backlinks = React.useMemo(() => { if (!activeNote || !notes) return []; return notes.filter(n => { if (n.id === activeNote.id || n.isDeleted) return false; const isExplicit = explicitWikiLinks.some(link => (link.source === n.id && link.target === activeNote.id) ); const isContentLink = n.content.toLowerCase().includes(`[[${activeNote.title.toLowerCase()}]]`); return isExplicit || isContentLink; }); }, [activeNote, notes, explicitWikiLinks]); const outboundLinks = React.useMemo(() => { if (!activeNote || !notes) return []; return notes.filter(n => { if (n.id === activeNote.id || n.isDeleted) return false; const isExplicit = explicitWikiLinks.some(link => (link.source === activeNote.id && link.target === n.id) ); const isContentLink = activeNote.content.toLowerCase().includes(`[[${n.title.toLowerCase()}]]`); return isExplicit || isContentLink; }); }, [activeNote, notes, explicitWikiLinks]); const unlinkedMentions = React.useMemo(() => { if (!activeNote || !notes) return []; return notes.filter(n => { if (n.id === activeNote.id || n.isDeleted) return false; const isLinked = [...backlinks, ...outboundLinks].some(link => link.id === n.id); if (isLinked) return false; return n.content.toLowerCase().includes(activeNote.title.toLowerCase()); }); }, [activeNote, notes, backlinks, outboundLinks]); const orbitNodes = React.useMemo(() => { const list: { id: string; title: string; color: string; carnetName: string; relationship: 'backlink' | 'outbound' | 'mention' }[] = []; backlinks.forEach(n => { const carnet = carnets.find(c => c.id === n.carnetId); list.push({ id: n.id, title: n.title, color: CARNET_COLOR_PALETTE[n.carnetId] || DEFAULT_CARNET_COLOR, carnetName: carnet?.name || 'Carnet', relationship: 'backlink' }); }); outboundLinks.forEach(n => { const carnet = carnets.find(c => c.id === n.carnetId); list.push({ id: n.id, title: n.title, color: CARNET_COLOR_PALETTE[n.carnetId] || DEFAULT_CARNET_COLOR, carnetName: carnet?.name || 'Carnet', relationship: 'outbound' }); }); unlinkedMentions.forEach(n => { const carnet = carnets.find(c => c.id === n.carnetId); list.push({ id: n.id, title: n.title, color: CARNET_COLOR_PALETTE[n.carnetId] || DEFAULT_CARNET_COLOR, carnetName: carnet?.name || 'Carnet', relationship: 'mention' }); }); return list.slice(0, 8); }, [backlinks, outboundLinks, unlinkedMentions, carnets]); const getSnippetWithHighlight = (content: string, term: string) => { const index = content.toLowerCase().indexOf(term.toLowerCase()); if (index === -1) { return {content.substring(0, 80)}...; } const start = Math.max(0, index - 40); const end = Math.min(content.length, index + term.length + 40); const before = content.substring(start, index); const match = content.substring(index, index + term.length); const after = content.substring(index + term.length, end); return ( {start > 0 && "..."} {before} {match} {after} {end < content.length && "..."} ); }; // Safe time calculation helper (mocked cleanly to match image's 'il y a 12 jours' or standard dynamic calculations) const getRelativeCreatedStr = (dateStr: string) => { if (dateStr.includes('12 mai 2026')) return 'il y a 12 jours'; if (dateStr.includes('Oct 26')) return 'il y a 2h'; if (dateStr.includes('Oct 27')) return 'il y a 1j'; if (dateStr.includes('Oct 24')) return 'il y a 3j'; if (dateStr.includes('Oct 25')) return 'il y a 2j'; if (dateStr.includes('Oct 22')) return 'il y a 5j'; if (dateStr.includes('Oct 23')) return 'il y a 4j'; if (dateStr.includes('Oct 28')) return 'il y a 10 min'; return 'il y a quelques jours'; }; return ( {isOpen && ( {/* Header tabs row matching image style */}
{/* Infos tab */} {/* Versions tab */} {/* Network / Relations tab */}
{/* Core scrollable content area */}
{/* TABS - INFOS */} {activeTab === 'infos' && ( {activeNote ? (
{/* Calculated Stats */} {(() => { const wordCount = activeNote.content.trim() ? activeNote.content.trim().split(/\s+/).filter(Boolean).length : 0; const charCount = activeNote.content.length; const lineCount = activeNote.content.trim() ? activeNote.content.split('\n').length : 0; // Count math equations const matchesBigMath = (activeNote.content.match(/\$\$[\s\S]*?\$\$/g) || []).length; const matchesInlineMath = (activeNote.content.match(/\$[^\$\n]+?\$/g) || []).length; const equationCount = matchesBigMath + matchesInlineMath; // Count graph relations or internal visual blocks const matchesLivingBlocks = (activeNote.content.match(/\[\[living-block:.*?\]\]/g) || []).length; const graphCount = orbitNodes.length + matchesLivingBlocks; // Count images const matchesMarkdownImages = (activeNote.content.match(/!\[.*?\]\(.*?\)/g) || []).length; const matchesHtmlImages = (activeNote.content.match(/ {/* Grid Stats */}
{wordCount} Mots
{charCount} Caractères
{/* Secondary Detailed Counts Widget */}
{lineCount} Lignes
{equationCount} Équations
{graphCount} Graphes
{imageCount} Images
); })()} {/* Attribute Detail rows styled to 100% exact layout matching the attached image */}
{/* Carnet attribute */}
Carnet {carnets.find(c => c.id === activeNote.carnetId)?.name || "Général"}
{/* Type attribute */}
Type {activeNote.isClipped ? 'Source Web' : 'Texte enrichi'}
{/* Créé le attribute */}
Créée le {activeNote.date || "12 mai 2026"} {getRelativeCreatedStr(activeNote.date || "12 mai 2026")}
{/* Modifiée attribute */}
Modifiée {activeNote.date || "12 mai 2026"} • 15:58 {getRelativeCreatedStr(activeNote.date || "12 mai 2026")}
{/* ID attribute */}
ID
{activeNote.id}
{/* Snapshots Toggle */}

Snapshots Actifs

Suivi d'historique automatique

) : (

Veuillez sélectionner une note pour inspecter ses informations.

)}
)} {/* TABS - VERSIONS */} {activeTab === 'versions' && (

Snapshots & Versions

{(activeNote?.versionHistory || []).length} Snapshots
{activeNote ? (
{activeNote.isVersioningEnabled !== false ? ( <> {/* Banner to snap manual version */}
Garnir l'historique

Figer manuellement l'état actuel de la note.

{/* Snapshot list */}
{(activeNote.versionHistory || []).length > 0 ? (
{(activeNote.versionHistory || []).map((v) => (
{v.title} {v.timestamp}
{v.size >= 1024 ? (v.size / 1024).toFixed(1) + ' KB' : v.size + ' B'}
))}
) : (

Aucun snapshot enregistré pour le moment. Modifiez la note pour démarrer le suivi ou figez-en un manuellement.

)}
) : (
Suivi d'historique inactif

L'historique des versions est actuellement désactivé pour cette note spécifique. Pour l'activer, cochez l'option dans l'onglet "Infos".

)}
) : (

Veuillez sélectionner une note pour voir son historique de versions.

)}
)} {/* TABS - RELATIONS (RESEAU) */} {activeTab === 'relations' && (

Vue Graphe Locale

{activeNote ? ( <> {/* Interactive Local Graph representation */}
{/* Dotted boundary */} {/* Links */} {orbitNodes.map((node, i) => { const angle = i * (orbitNodes.length > 0 ? (2 * Math.PI) / orbitNodes.length : 0); const nx = 160 + 70 * Math.cos(angle); const ny = 110 + 62 * Math.sin(angle); return ( {node.relationship === 'outbound' && ( )} {node.relationship === 'backlink' && ( )} ); })} {/* Center Node: Active Note */} {/* Orbit nodes */} {orbitNodes.map((node, i) => { const angle = i * (orbitNodes.length > 0 ? (2 * Math.PI) / orbitNodes.length : 0); const nx = 160 + 70 * Math.cos(angle); const ny = 110 + 62 * Math.sin(angle); const isHovered = hoveredOrbitNode?.id === node.id; return ( onOpenNote(node.id)} onMouseEnter={() => setHoveredOrbitNode(node)} onMouseLeave={() => setHoveredOrbitNode(null)} > {node.title.substring(0, 10)} ); })}
{hoveredOrbitNode ? (
{hoveredOrbitNode.carnetName} {hoveredOrbitNode.relationship === 'backlink' ? 'Lien Entrant' : hoveredOrbitNode.relationship === 'outbound' ? 'Lien Sortant' : 'Mention Simple'}

{hoveredOrbitNode.title}

Cliquez pour ouvrir la note

) : (
Survolez un nœud, cliquez pour ouvrir
)}
{/* Explicit links listings with highlighting */}
{/* 1. Backlinks */}
Liens Entrants ({backlinks.length})
{backlinks.length > 0 ? (
{backlinks.map(n => (
onOpenNote(n.id)} className="p-3 bg-white dark:bg-zinc-950 border border-border hover:border-accent/40 rounded-xl cursor-pointer transition-all space-y-1.5 hover:shadow-sm" >
{n.title} Réf

{getSnippetWithHighlight(n.content, activeNote.title)}

))}
) : (

Aucun lien entrant de type wiki [[lien]] pointant vers cette note.

)}
{/* 2. Outbound Links */}
Liens Sortants ({outboundLinks.length})
{outboundLinks.length > 0 ? (
{outboundLinks.map(n => (
onOpenNote(n.id)} className="p-3 bg-white dark:bg-zinc-950 border border-border hover:border-accent/40 rounded-xl cursor-pointer transition-all space-y-1.5 hover:shadow-sm" >
{n.title} Vers

{getSnippetWithHighlight(activeNote.content, n.title)}

))}
) : (

Cette note ne pointe vers aucune autre note de type [[lien]].

)}
) : (

Sélectionnez une note pour analyser son graphe local.

)} )}
)} ); };