feat(notes): liens internes, onglet Réseau, living blocks et consentement IA
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m19s
CI / Deploy production (on server) (push) Has been skipped

Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-05-24 14:27:29 +00:00
parent 077e665dfc
commit e2672cd2c2
323 changed files with 20670 additions and 42431 deletions

View File

@@ -7,16 +7,17 @@ import { fr } from 'date-fns/locale/fr'
import { enUS } from 'date-fns/locale/en-US'
import { faIR } from 'date-fns/locale/fa-IR'
import { formatAbsoluteDateLocalized } from '@/lib/utils/format-localized-date'
import { X, Info, Clock, Hash, Book, FileText, Calendar, Tag, ChevronRight, Trash2, RotateCcw, Loader2, Check, History as HistoryIcon } from 'lucide-react'
import { X, Info, Clock, Hash, Book, FileText, Calendar, Tag, ChevronRight, Trash2, RotateCcw, Loader2, Check, History as HistoryIcon, Network, Copy } from 'lucide-react'
import { cn } from '@/lib/utils'
import { useLanguage } from '@/lib/i18n'
import { useNotebooks } from '@/context/notebooks-context'
import { LabelBadge } from './label-badge'
import { NoteHistoryModal } from './note-history-modal'
import { NoteNetworkTab } from './note-network-tab'
import { enableNoteHistory, commitNoteHistory, getNoteHistory, deleteNoteHistoryEntry, restoreNoteVersion } from '@/app/actions/notes'
import { useEffect } from 'react'
type Tab = 'info' | 'versions'
type Tab = 'info' | 'versions' | 'network'
interface NoteDocumentInfoPanelProps {
note: Note
@@ -39,6 +40,23 @@ function charCount(text: string) {
return text.replace(/<[^>]+>/g, '').length
}
function lineCount(text: string) {
const plain = text.replace(/<[^>]+>/g, '\n')
return plain.trim() ? plain.split('\n').length : 0
}
function equationCount(text: string) {
const block = (text.match(/\$\$[\s\S]+?\$\$/g) || []).length
const inline = (text.match(/\$[^$\n]+?\$/g) || []).length
return block + inline
}
function imageCount(text: string) {
const md = (text.match(/!\[[^\]]*\]\([^)]+\)/g) || []).length
const html = (text.match(/<img\s/gi) || []).length
return md + html
}
export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }: NoteDocumentInfoPanelProps) {
const { t, language } = useLanguage()
const { notebooks } = useNotebooks()
@@ -51,6 +69,7 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
const [isLoadingHistory, setIsLoadingHistory] = useState(false)
const [isDeleting, setIsDeleting] = useState<string | null>(null)
const [isRestoring, setIsRestoring] = useState<string | null>(null)
const [copiedId, setCopiedId] = useState(false)
const locale = getLocale(language)
const displayNoteType = useMemo(() => {
@@ -114,6 +133,9 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
const words = useMemo(() => wordCount(content), [content])
const chars = useMemo(() => charCount(content), [content])
const lines = useMemo(() => lineCount(content), [content])
const equations = useMemo(() => equationCount(content), [content])
const images = useMemo(() => imageCount(content), [content])
const createdAt = note.createdAt ? new Date(note.createdAt as unknown as string) : null
const updatedAt = note.contentUpdatedAt ? new Date(note.contentUpdatedAt as unknown as string) : null
@@ -125,7 +147,7 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
{/* Header tabs */}
<div className="flex items-center justify-between px-5 py-4 border-b border-border/40">
<div className="flex gap-1">
{(['info', 'versions'] as Tab[]).map(tab => (
{(['info', 'versions', 'network'] as Tab[]).map(tab => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
@@ -138,7 +160,12 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
>
{tab === 'info' && <Info className="h-3 w-3" />}
{tab === 'versions' && <Clock className="h-3 w-3" />}
{tab === 'info' ? t('documentInfo.tabInfo') : t('documentInfo.tabVersions')}
{tab === 'network' && <Network className="h-3 w-3" />}
{tab === 'info'
? t('documentInfo.tabInfo')
: tab === 'versions'
? t('documentInfo.tabVersions')
: t('documentInfo.tabNetwork')}
</button>
))}
</div>
@@ -168,6 +195,19 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
</div>
</div>
<div className="grid grid-cols-3 border-b border-border/30 divide-x divide-border/30">
{[
{ value: lines, label: t('documentInfo.linesLabel') },
{ value: equations, label: t('documentInfo.equationsLabel') },
{ value: images, label: t('documentInfo.imagesLabel') },
].map(({ value, label }) => (
<div key={label} className="flex flex-col items-center gap-0.5 py-3">
<span className="text-lg font-bold font-memento-serif tabular-nums">{value}</span>
<span className="text-[8px] uppercase tracking-widest text-muted-foreground font-semibold">{label}</span>
</div>
))}
</div>
<div className="divide-y divide-border/30">
{notebook && (
<div className="flex items-start gap-3 px-4 py-3">
@@ -229,9 +269,24 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
<div className="flex items-start gap-3 px-4 py-3">
<Hash className="h-3.5 w-3.5 text-muted-foreground mt-0.5 shrink-0" />
<div className="min-w-0">
<div className="min-w-0 flex-1">
<p className="text-[10px] uppercase tracking-widest text-muted-foreground mb-0.5">{t('documentInfo.idLabel')}</p>
<p className="text-[11px] text-muted-foreground font-mono truncate">{note.id}</p>
<div className="flex items-center gap-1.5 min-w-0">
<p className="text-[11px] text-muted-foreground font-mono truncate">{note.id}</p>
<button
type="button"
onClick={() => {
navigator.clipboard.writeText(note.id).then(() => {
setCopiedId(true)
setTimeout(() => setCopiedId(false), 2000)
})
}}
className="p-1 rounded hover:bg-muted text-muted-foreground shrink-0"
title={t('documentInfo.copyId')}
>
{copiedId ? <Check className="h-3 w-3 text-emerald-500" /> : <Copy className="h-3 w-3" />}
</button>
</div>
</div>
</div>
</div>
@@ -382,6 +437,11 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
)}
</div>
)}
{/* ── NETWORK TAB ── */}
{activeTab === 'network' && (
<NoteNetworkTab noteId={note.id} noteTitle={note.title || ''} />
)}
</div>
</div>