()
| 465 | } |
| 466 | |
| 467 | async function buildGraphData() { |
| 468 | await ensureAllNotesLoaded(); |
| 469 | |
| 470 | // De-duplicate NOTES by id (manifest may have duplicates) |
| 471 | const seen = new Set(); |
| 472 | const uniqueNotes = NOTES.filter(n => { |
| 473 | if (seen.has(n.id)) return false; |
| 474 | seen.add(n.id); |
| 475 | return true; |
| 476 | }); |
| 477 | |
| 478 | const nodes = uniqueNotes.map(n => ({ id: n.id, title: n.title, note: n })); |
| 479 | const linkSet = new Set(); |
| 480 | const links = []; |
| 481 | |
| 482 | const wikiRegex = /\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|[^\]]+)?\]\]/g; |
| 483 | |
| 484 | for (const note of uniqueNotes) { |
| 485 | const md = noteCache.get(note.path) || ""; |
| 486 | let match; |
| 487 | wikiRegex.lastIndex = 0; |
| 488 | while ((match = wikiRegex.exec(md)) !== null) { |
| 489 | const target = resolveWikiNote(match[1].trim()); |
| 490 | if (!target || target.id === note.id) continue; |
| 491 | const key = [note.id, target.id].sort().join("|||"); |
| 492 | if (linkSet.has(key)) continue; |
| 493 | linkSet.add(key); |
| 494 | links.push({ source: note.id, target: target.id }); |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | // Count connections per node |
| 499 | const connCount = {}; |
| 500 | nodes.forEach(n => { connCount[n.id] = 0; }); |
| 501 | links.forEach(l => { |
| 502 | connCount[l.source] = (connCount[l.source] || 0) + 1; |
| 503 | connCount[l.target] = (connCount[l.target] || 0) + 1; |
| 504 | }); |
| 505 | |
| 506 | return { nodes, links, connCount }; |
| 507 | } |
| 508 | |
| 509 | function renderGraph({ nodes, links, connCount }) { |
| 510 | const svgEl = document.getElementById("graph-svg"); |
no test coverage detected