From 7a8307f4b46e9f1ed9836f2e38c36276d62b9b5a Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 24 May 2026 19:02:54 +0000 Subject: [PATCH] fix(graph): resolution des bugs du graphe globale, support RTL, dates localisees et simulation D3 ultra-stable --- memento-note/app/api/graph/route.ts | 2 +- memento-note/components/note-graph-view.tsx | 78 +++++++++++++++------ 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/memento-note/app/api/graph/route.ts b/memento-note/app/api/graph/route.ts index a74087a..7663052 100644 --- a/memento-note/app/api/graph/route.ts +++ b/memento-note/app/api/graph/route.ts @@ -127,7 +127,7 @@ export async function GET(request: NextRequest) { const title = (noteA.title ?? '').trim().toLowerCase() if (title.length < 3) continue const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - const re = new RegExp(`\\b${escaped}\\b`, 'i') + const re = new RegExp(`(?(null) const graphRef = useRef(null) + const existingNodesRef = useRef>(new Map()) const [dimensions, setDimensions] = useState({ width: 800, height: 600 }) const [rawData, setRawData] = useState(null) const [loading, setLoading] = useState(true) @@ -81,7 +82,7 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) { const [focusNodeId, setFocusNodeId] = useState(null) const [controlsOpen, setControlsOpen] = useState(!embedded) - const { t } = useLanguage() + const { t, language } = useLanguage() const plainText = useCallback((html: string | null | undefined) => (html ?? '') @@ -114,6 +115,19 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) { return plainText(notePreview.content).length }, [notePreview, plainText]) + const isRtl = useMemo(() => { + if (!notePreview?.content) return false + const sample = plainText(notePreview.content).replace(/\s+/g, '').slice(0, 400) + const rtlChars = /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/ + let rtl = 0 + let ltr = 0 + for (const ch of sample) { + if (rtlChars.test(ch)) rtl++ + else if (/[A-Za-z]/.test(ch)) ltr++ + } + return rtl > 0 && rtl >= ltr + }, [notePreview, plainText]) + // ─── Resize ─────────────────────────────────────────────────────────────── useEffect(() => { const el = containerRef.current @@ -210,14 +224,27 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) { }) return { - nodes: filtered.map(n => ({ - id: n.id, - name: n.title, - val: 1 + Math.min(n.degree, 8) * 0.5, - color: colorMap.get(n.notebookId) ?? '#94a3b8', - notebookId: n.notebookId, - degree: n.degree, - })), + nodes: filtered.map(n => { + const existing = existingNodesRef.current.get(n.id) + if (existing) { + existing.name = n.title + existing.val = 1 + Math.min(n.degree, 8) * 0.5 + existing.color = colorMap.get(n.notebookId) ?? '#94a3b8' + existing.notebookId = n.notebookId + existing.degree = n.degree + return existing + } + const newNode = { + id: n.id, + name: n.title, + val: 1 + Math.min(n.degree, 8) * 0.5, + color: colorMap.get(n.notebookId) ?? '#94a3b8', + notebookId: n.notebookId, + degree: n.degree, + } + existingNodesRef.current.set(n.id, newNode) + return newNode + }), links: visibleEdges.map(e => { let color = '#cbd5e1' let width = 2.5 @@ -420,24 +447,25 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) { onRenderFramePre={paintClusters} nodeCanvasObjectMode={() => 'after'} nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, globalScale: number) => { + const n = node as any if (globalScale < 0.7) return - const name: string = node.name ?? '' + const name: string = n.name ?? '' const label = name.length > 20 ? name.slice(0, 18) + '…' : name const fontSize = 11 / globalScale if (fontSize > 18) return ctx.font = `${fontSize}px -apple-system, sans-serif` ctx.textAlign = 'center' ctx.textBaseline = 'top' - const r = Math.sqrt(node.val ?? 1) * 5 + const r = Math.sqrt(n.val ?? 1) * 5 // White background behind label const tw = ctx.measureText(label).width - const lx = node.x - tw / 2 - 2 - const ly = node.y + r + 2 + const lx = n.x - tw / 2 - 2 + const ly = n.y + r + 2 ctx.fillStyle = 'rgba(250,250,249,0.85)' ctx.fillRect(lx, ly, tw + 4, fontSize + 2) // Label text ctx.fillStyle = '#334155' - ctx.fillText(label, node.x, ly + 1) + ctx.fillText(label, n.x, ly + 1) }} cooldownTicks={80} d3AlphaDecay={0.03} @@ -597,7 +625,10 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) { {selectedNotebookName} )} -

+

{selectedNode.title || {t('notes.untitled')}}

@@ -613,8 +644,8 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) {
- - {new Date(selectedNode.createdAt).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' })} + + {new Date(selectedNode.createdAt).toLocaleDateString(language === 'fa' ? 'fa-IR' : language, { day: '2-digit', month: 'short', year: 'numeric' })}
@@ -634,7 +665,7 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) { {t('graphView.preview.updated')}{' '} - {new Date(notePreview.updatedAt).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })} + {new Date(notePreview.updatedAt).toLocaleDateString(language === 'fa' ? 'fa-IR' : language, { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}
)} @@ -667,22 +698,23 @@ export function NoteGraphView({ embedded = false }: { embedded?: boolean }) { <> {/* Note Content Renderer */} {notePreview.type === 'checklist' && notePreview.checkItems && notePreview.checkItems.length > 0 ? ( -
+
{}} />
) : notePreview.type === 'markdown' || notePreview.isMarkdown ? ( -
+
) : (
)}