ma-agents 3.15.0 → 3.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +1 -1
- package/lib/knowledge-atlas/dist/atlas.cjs +86 -14
- package/lib/knowledge-atlas/src/render/design-system.ts +7 -3
- package/lib/knowledge-atlas/src/render/document.ts +26 -6
- package/lib/knowledge-atlas/src/render/graph.ts +63 -4
- package/package.json +1 -1
|
@@ -5697,10 +5697,14 @@ var DESIGN_SYSTEM_CSS = `/* Knowledge Atlas \u2014 design system (Story 29.3) */
|
|
|
5697
5697
|
top: 0;
|
|
5698
5698
|
}
|
|
5699
5699
|
|
|
5700
|
-
/*
|
|
5701
|
-
|
|
5700
|
+
/* Scroll behaviour: use auto (instant) so that landing on a page via a
|
|
5701
|
+
#anchor \u2014 e.g. clicking a graph node or an entity link into a long reading
|
|
5702
|
+
view \u2014 reliably jumps to the target. A global smooth scroll-behavior
|
|
5703
|
+
made the browser's on-load hash navigation unreliable on long documents (it
|
|
5704
|
+
could stay at the top \u2014 the "click goes to the start of the document" bug).
|
|
5705
|
+
In-page TOC/backlink clicks still jump correctly, just without the animation. */
|
|
5702
5706
|
html {
|
|
5703
|
-
scroll-behavior:
|
|
5707
|
+
scroll-behavior: auto;
|
|
5704
5708
|
}
|
|
5705
5709
|
|
|
5706
5710
|
body {
|
|
@@ -12150,14 +12154,23 @@ function renderDocumentPage(fileRec, manifest, projectRoot, navFlags, resolveAbs
|
|
|
12150
12154
|
const statusBadgesHtml = renderStatusBadges(fileRec.statuses);
|
|
12151
12155
|
const existingIds = /* @__PURE__ */ new Set();
|
|
12152
12156
|
for (const m of renderedHtml.matchAll(/\sid="([^"]+)"/g)) existingIds.add(m[1]);
|
|
12153
|
-
const
|
|
12157
|
+
const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12158
|
+
const inlinePlacements = [];
|
|
12159
|
+
const fallbackAnchors = [];
|
|
12154
12160
|
for (const node of fileNodes) {
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
+
const a = node.anchor;
|
|
12162
|
+
if (!a || existingIds.has(a)) continue;
|
|
12163
|
+
existingIds.add(a);
|
|
12164
|
+
const m = new RegExp('<a\\b[^>]*href="[^"]*#' + escRe(a) + '"', "i").exec(renderedHtml);
|
|
12165
|
+
if (m) inlinePlacements.push({ idx: m.index, anchor: a });
|
|
12166
|
+
else fallbackAnchors.push(a);
|
|
12167
|
+
}
|
|
12168
|
+
inlinePlacements.sort((x, y) => y.idx - x.idx);
|
|
12169
|
+
for (const p of inlinePlacements) {
|
|
12170
|
+
const span = `<span class="entity-anchor" id="${esc4(p.anchor)}"></span>`;
|
|
12171
|
+
renderedHtml = renderedHtml.slice(0, p.idx) + span + renderedHtml.slice(p.idx);
|
|
12172
|
+
}
|
|
12173
|
+
const anchorTargetsHtml = fallbackAnchors.map((a) => `<span class="entity-anchor" id="${esc4(a)}"></span>`).join("\n");
|
|
12161
12174
|
const backLinkHtml = '<a class="back-link" href="../index.html">← Back to Home</a>';
|
|
12162
12175
|
const pageBody = [
|
|
12163
12176
|
'<div class="content-wrap">',
|
|
@@ -13057,6 +13070,7 @@ function buildAppScript() {
|
|
|
13057
13070
|
// mouse (finding #8: tooltip was shown with display:block but no left/top,
|
|
13058
13071
|
// so it rendered at the bottom of <main>).
|
|
13059
13072
|
var lastHoverNode = null;
|
|
13073
|
+
var selectedLink = null; // edge highlighted on click: drawn bold + its two endpoints ringed
|
|
13060
13074
|
|
|
13061
13075
|
function getFilteredData() {
|
|
13062
13076
|
var nodes = allNodes;
|
|
@@ -13096,6 +13110,20 @@ function buildAppScript() {
|
|
|
13096
13110
|
return;
|
|
13097
13111
|
}
|
|
13098
13112
|
|
|
13113
|
+
// Layout forces (fixes the overlapping-blob): stronger charge repulsion + a
|
|
13114
|
+
// larger link distance push nodes apart; a gentler velocity decay lets a
|
|
13115
|
+
// dragged node's neighbourhood follow it and settle rather than everything
|
|
13116
|
+
// snapping. The vendored force-graph exposes charge/link via d3Force but no
|
|
13117
|
+
// collide, so spacing comes from charge+distance. All best-effort.
|
|
13118
|
+
try {
|
|
13119
|
+
var _charge = graph.d3Force && graph.d3Force('charge');
|
|
13120
|
+
if (_charge && _charge.strength) _charge.strength(-160);
|
|
13121
|
+
var _link = graph.d3Force && graph.d3Force('link');
|
|
13122
|
+
if (_link && _link.distance) _link.distance(48);
|
|
13123
|
+
if (graph.d3VelocityDecay) graph.d3VelocityDecay(0.3);
|
|
13124
|
+
if (graph.warmupTicks) graph.warmupTicks(80); // pre-spread before first paint
|
|
13125
|
+
} catch (_e) { /* forces best-effort; graph still renders without them */ }
|
|
13126
|
+
|
|
13099
13127
|
// FR222: distinct shape per entity type (previously color+size only \u2014 the
|
|
13100
13128
|
// canvas never read NODE_STYLES' shape/data-shape, so every node was a
|
|
13101
13129
|
// circle regardless of kind). Traces the path for the node's shape
|
|
@@ -13161,29 +13189,59 @@ function buildAppScript() {
|
|
|
13161
13189
|
// FR222: paint the node as its per-kind shape (circle/square/diamond/
|
|
13162
13190
|
// triangle) instead of force-graph's default circle-only rendering.
|
|
13163
13191
|
.nodeCanvasObject(function(n, ctx) {
|
|
13164
|
-
var size = n.size || 6;
|
|
13192
|
+
var size = (n.size || 6) * 0.5; // smaller nodes (were visually oversized/overlapping)
|
|
13165
13193
|
ctx.fillStyle = n.color || '#888';
|
|
13166
13194
|
traceNodeShapePath(ctx, n.shape, n.x, n.y, size);
|
|
13167
13195
|
ctx.fill();
|
|
13196
|
+
// Ring the two endpoints of the selected edge so both ends are easy to
|
|
13197
|
+
// find, however far apart they sit in the layout.
|
|
13198
|
+
if (selectedLink) {
|
|
13199
|
+
var s = selectedLink.source, t = selectedLink.target;
|
|
13200
|
+
if (n === s || n === t) {
|
|
13201
|
+
ctx.beginPath();
|
|
13202
|
+
ctx.arc(n.x, n.y, size + 3.5, 0, 2 * Math.PI);
|
|
13203
|
+
ctx.strokeStyle = '#111827';
|
|
13204
|
+
ctx.lineWidth = 1.8;
|
|
13205
|
+
ctx.stroke();
|
|
13206
|
+
}
|
|
13207
|
+
}
|
|
13168
13208
|
})
|
|
13169
13209
|
// Matching hit-test paint: without this, force-graph's click/hover
|
|
13170
13210
|
// detection falls back to a generic area that no longer matches the
|
|
13171
13211
|
// custom shape drawn above (non-circle nodes would get unreliable
|
|
13172
13212
|
// hover/click hit-boxes).
|
|
13173
13213
|
.nodePointerAreaPaint(function(n, color, ctx) {
|
|
13174
|
-
var size = (n.size || 6) + 2; //
|
|
13214
|
+
var size = (n.size || 6) * 0.5 + 2; // match the smaller draw size, +2 to stay easy to hit
|
|
13175
13215
|
ctx.fillStyle = color;
|
|
13176
13216
|
traceNodeShapePath(ctx, n.shape, n.x, n.y, size);
|
|
13177
13217
|
ctx.fill();
|
|
13178
13218
|
})
|
|
13179
13219
|
.linkSource('source')
|
|
13180
13220
|
.linkTarget('target')
|
|
13181
|
-
|
|
13221
|
+
// Selected edge is drawn dark + thick so you can trace it end to end.
|
|
13222
|
+
.linkColor(function(e) { return e === selectedLink ? '#111827' : (e.color || '#aaa'); })
|
|
13223
|
+
.linkWidth(function(e) { return e === selectedLink ? 3.5 : (e.dashed ? 0.5 : 1); })
|
|
13182
13224
|
// Story 29.8 - dashed stroke for weak references edges; solid otherwise.
|
|
13183
13225
|
.linkLineDash(function(e) { return e.dashed ? [4, 3] : null; })
|
|
13184
|
-
.linkDirectionalArrowLength(4)
|
|
13226
|
+
.linkDirectionalArrowLength(function(e) { return e === selectedLink ? 7 : 4; })
|
|
13185
13227
|
.linkDirectionalArrowRelPos(1)
|
|
13186
13228
|
.linkCurvature(0.1)
|
|
13229
|
+
// Click an edge to highlight it (bold + ringed endpoints); click it again
|
|
13230
|
+
// or click empty space to clear. Re-applying the accessors forces an
|
|
13231
|
+
// immediate repaint of the new selection state.
|
|
13232
|
+
.onLinkClick(function(e) {
|
|
13233
|
+
selectedLink = (selectedLink === e) ? null : e;
|
|
13234
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
13235
|
+
})
|
|
13236
|
+
.onLinkHover(function(e) {
|
|
13237
|
+
if (container) container.style.cursor = e ? 'pointer' : 'default';
|
|
13238
|
+
if (!tooltip) return;
|
|
13239
|
+
if (!e) { tooltip.style.display = 'none'; return; }
|
|
13240
|
+
var s = (e.source && e.source.label) || e.source;
|
|
13241
|
+
var t = (e.target && e.target.label) || e.target;
|
|
13242
|
+
tooltip.style.display = 'block';
|
|
13243
|
+
tooltip.textContent = s + ' \u2192 ' + t + (e.kind ? ' [' + e.kind + ']' : '');
|
|
13244
|
+
})
|
|
13187
13245
|
.onNodeClick(function(n) {
|
|
13188
13246
|
// Navigate to the reading view via the server-resolved doc page (the
|
|
13189
13247
|
// 'page' field, e.g. "docs/<slug>.html"), NOT the raw fileId \u2014 the
|
|
@@ -13200,6 +13258,16 @@ function buildAppScript() {
|
|
|
13200
13258
|
var href = page + (n.anchor ? '#' + n.anchor : '');
|
|
13201
13259
|
window.location.href = href;
|
|
13202
13260
|
})
|
|
13261
|
+
// Drag-to-pin (fixes the spring-back): keep the node exactly where it was
|
|
13262
|
+
// dropped (fx/fy). Because the simulation stays warm, its linked neighbours
|
|
13263
|
+
// follow the drag and nodes farther along the graph move progressively less.
|
|
13264
|
+
// Right-click a node to release its pin back into the layout.
|
|
13265
|
+
.onNodeDrag(function(n) { n.fx = n.x; n.fy = n.y; })
|
|
13266
|
+
.onNodeDragEnd(function(n) { n.fx = n.x; n.fy = n.y; })
|
|
13267
|
+
.onNodeRightClick(function(n) {
|
|
13268
|
+
n.fx = undefined; n.fy = undefined;
|
|
13269
|
+
if (graph.d3ReheatSimulation) graph.d3ReheatSimulation();
|
|
13270
|
+
})
|
|
13203
13271
|
.onNodeHover(function(n) {
|
|
13204
13272
|
// Distinct cursor hint: pointer only over nodes that actually
|
|
13205
13273
|
// navigate somewhere (have a resolved page); default cursor otherwise
|
|
@@ -13214,6 +13282,10 @@ function buildAppScript() {
|
|
|
13214
13282
|
.onBackgroundClick(function() {
|
|
13215
13283
|
if (tooltip) tooltip.style.display = 'none';
|
|
13216
13284
|
lastHoverNode = null;
|
|
13285
|
+
if (selectedLink) {
|
|
13286
|
+
selectedLink = null;
|
|
13287
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
13288
|
+
}
|
|
13217
13289
|
});
|
|
13218
13290
|
}
|
|
13219
13291
|
|
|
@@ -108,10 +108,14 @@ export const DESIGN_SYSTEM_CSS: string = `\
|
|
|
108
108
|
top: 0;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/*
|
|
112
|
-
|
|
111
|
+
/* Scroll behaviour: use auto (instant) so that landing on a page via a
|
|
112
|
+
#anchor — e.g. clicking a graph node or an entity link into a long reading
|
|
113
|
+
view — reliably jumps to the target. A global smooth scroll-behavior
|
|
114
|
+
made the browser's on-load hash navigation unreliable on long documents (it
|
|
115
|
+
could stay at the top — the "click goes to the start of the document" bug).
|
|
116
|
+
In-page TOC/backlink clicks still jump correctly, just without the animation. */
|
|
113
117
|
html {
|
|
114
|
-
scroll-behavior:
|
|
118
|
+
scroll-behavior: auto;
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
body {
|
|
@@ -299,16 +299,36 @@ export function renderDocumentPage(
|
|
|
299
299
|
// to a heading slug (`story-11-oauth2-integration`). We emit an invisible
|
|
300
300
|
// anchor target for every file node whose anchor is not already a heading id,
|
|
301
301
|
// so every cross-link `…#anchor` resolves to a real location on the page.
|
|
302
|
+
// Place each entity's anchor at the FIRST place that entity is mentioned in
|
|
303
|
+
// the body (its first linkified occurrence: an <a … href="…#anchor">), so a
|
|
304
|
+
// cross-link or graph-node click lands where the entity is actually discussed.
|
|
305
|
+
// Previously every anchor was emitted as one clump at the top of the article,
|
|
306
|
+
// so every #anchor (e.g. all 9 epics) resolved to the same spot. Anchors with
|
|
307
|
+
// no linked mention fall back to the top clump.
|
|
302
308
|
const existingIds = new Set<string>();
|
|
303
309
|
for (const m of renderedHtml.matchAll(/\sid="([^"]+)"/g)) existingIds.add(m[1]!);
|
|
304
|
-
const
|
|
310
|
+
const escRe = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
311
|
+
const inlinePlacements: { idx: number; anchor: string }[] = [];
|
|
312
|
+
const fallbackAnchors: string[] = [];
|
|
305
313
|
for (const node of fileNodes) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
314
|
+
const a = node.anchor;
|
|
315
|
+
if (!a || existingIds.has(a)) continue;
|
|
316
|
+
existingIds.add(a);
|
|
317
|
+
// First <a> tag whose href ends with this exact anchor fragment.
|
|
318
|
+
const m = new RegExp('<a\\b[^>]*href="[^"]*#' + escRe(a) + '"', 'i').exec(renderedHtml);
|
|
319
|
+
if (m) inlinePlacements.push({ idx: m.index, anchor: a });
|
|
320
|
+
else fallbackAnchors.push(a);
|
|
321
|
+
}
|
|
322
|
+
// Insert the invisible anchor spans just before their target links. Apply from
|
|
323
|
+
// the last position to the first so earlier indices stay valid.
|
|
324
|
+
inlinePlacements.sort((x, y) => y.idx - x.idx);
|
|
325
|
+
for (const p of inlinePlacements) {
|
|
326
|
+
const span = `<span class="entity-anchor" id="${esc(p.anchor)}"></span>`;
|
|
327
|
+
renderedHtml = renderedHtml.slice(0, p.idx) + span + renderedHtml.slice(p.idx);
|
|
310
328
|
}
|
|
311
|
-
const anchorTargetsHtml =
|
|
329
|
+
const anchorTargetsHtml = fallbackAnchors
|
|
330
|
+
.map(a => `<span class="entity-anchor" id="${esc(a)}"></span>`)
|
|
331
|
+
.join('\n');
|
|
312
332
|
|
|
313
333
|
// Back link
|
|
314
334
|
const backLinkHtml = '<a class="back-link" href="../index.html">← Back to Home</a>';
|
|
@@ -648,6 +648,7 @@ function buildAppScript(): string {
|
|
|
648
648
|
// mouse (finding #8: tooltip was shown with display:block but no left/top,
|
|
649
649
|
// so it rendered at the bottom of <main>).
|
|
650
650
|
var lastHoverNode = null;
|
|
651
|
+
var selectedLink = null; // edge highlighted on click: drawn bold + its two endpoints ringed
|
|
651
652
|
|
|
652
653
|
function getFilteredData() {
|
|
653
654
|
var nodes = allNodes;
|
|
@@ -687,6 +688,20 @@ function buildAppScript(): string {
|
|
|
687
688
|
return;
|
|
688
689
|
}
|
|
689
690
|
|
|
691
|
+
// Layout forces (fixes the overlapping-blob): stronger charge repulsion + a
|
|
692
|
+
// larger link distance push nodes apart; a gentler velocity decay lets a
|
|
693
|
+
// dragged node's neighbourhood follow it and settle rather than everything
|
|
694
|
+
// snapping. The vendored force-graph exposes charge/link via d3Force but no
|
|
695
|
+
// collide, so spacing comes from charge+distance. All best-effort.
|
|
696
|
+
try {
|
|
697
|
+
var _charge = graph.d3Force && graph.d3Force('charge');
|
|
698
|
+
if (_charge && _charge.strength) _charge.strength(-160);
|
|
699
|
+
var _link = graph.d3Force && graph.d3Force('link');
|
|
700
|
+
if (_link && _link.distance) _link.distance(48);
|
|
701
|
+
if (graph.d3VelocityDecay) graph.d3VelocityDecay(0.3);
|
|
702
|
+
if (graph.warmupTicks) graph.warmupTicks(80); // pre-spread before first paint
|
|
703
|
+
} catch (_e) { /* forces best-effort; graph still renders without them */ }
|
|
704
|
+
|
|
690
705
|
// FR222: distinct shape per entity type (previously color+size only — the
|
|
691
706
|
// canvas never read NODE_STYLES' shape/data-shape, so every node was a
|
|
692
707
|
// circle regardless of kind). Traces the path for the node's shape
|
|
@@ -752,29 +767,59 @@ function buildAppScript(): string {
|
|
|
752
767
|
// FR222: paint the node as its per-kind shape (circle/square/diamond/
|
|
753
768
|
// triangle) instead of force-graph's default circle-only rendering.
|
|
754
769
|
.nodeCanvasObject(function(n, ctx) {
|
|
755
|
-
var size = n.size || 6;
|
|
770
|
+
var size = (n.size || 6) * 0.5; // smaller nodes (were visually oversized/overlapping)
|
|
756
771
|
ctx.fillStyle = n.color || '#888';
|
|
757
772
|
traceNodeShapePath(ctx, n.shape, n.x, n.y, size);
|
|
758
773
|
ctx.fill();
|
|
774
|
+
// Ring the two endpoints of the selected edge so both ends are easy to
|
|
775
|
+
// find, however far apart they sit in the layout.
|
|
776
|
+
if (selectedLink) {
|
|
777
|
+
var s = selectedLink.source, t = selectedLink.target;
|
|
778
|
+
if (n === s || n === t) {
|
|
779
|
+
ctx.beginPath();
|
|
780
|
+
ctx.arc(n.x, n.y, size + 3.5, 0, 2 * Math.PI);
|
|
781
|
+
ctx.strokeStyle = '#111827';
|
|
782
|
+
ctx.lineWidth = 1.8;
|
|
783
|
+
ctx.stroke();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
759
786
|
})
|
|
760
787
|
// Matching hit-test paint: without this, force-graph's click/hover
|
|
761
788
|
// detection falls back to a generic area that no longer matches the
|
|
762
789
|
// custom shape drawn above (non-circle nodes would get unreliable
|
|
763
790
|
// hover/click hit-boxes).
|
|
764
791
|
.nodePointerAreaPaint(function(n, color, ctx) {
|
|
765
|
-
var size = (n.size || 6) + 2; //
|
|
792
|
+
var size = (n.size || 6) * 0.5 + 2; // match the smaller draw size, +2 to stay easy to hit
|
|
766
793
|
ctx.fillStyle = color;
|
|
767
794
|
traceNodeShapePath(ctx, n.shape, n.x, n.y, size);
|
|
768
795
|
ctx.fill();
|
|
769
796
|
})
|
|
770
797
|
.linkSource('source')
|
|
771
798
|
.linkTarget('target')
|
|
772
|
-
|
|
799
|
+
// Selected edge is drawn dark + thick so you can trace it end to end.
|
|
800
|
+
.linkColor(function(e) { return e === selectedLink ? '#111827' : (e.color || '#aaa'); })
|
|
801
|
+
.linkWidth(function(e) { return e === selectedLink ? 3.5 : (e.dashed ? 0.5 : 1); })
|
|
773
802
|
// Story 29.8 - dashed stroke for weak references edges; solid otherwise.
|
|
774
803
|
.linkLineDash(function(e) { return e.dashed ? [4, 3] : null; })
|
|
775
|
-
.linkDirectionalArrowLength(4)
|
|
804
|
+
.linkDirectionalArrowLength(function(e) { return e === selectedLink ? 7 : 4; })
|
|
776
805
|
.linkDirectionalArrowRelPos(1)
|
|
777
806
|
.linkCurvature(0.1)
|
|
807
|
+
// Click an edge to highlight it (bold + ringed endpoints); click it again
|
|
808
|
+
// or click empty space to clear. Re-applying the accessors forces an
|
|
809
|
+
// immediate repaint of the new selection state.
|
|
810
|
+
.onLinkClick(function(e) {
|
|
811
|
+
selectedLink = (selectedLink === e) ? null : e;
|
|
812
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
813
|
+
})
|
|
814
|
+
.onLinkHover(function(e) {
|
|
815
|
+
if (container) container.style.cursor = e ? 'pointer' : 'default';
|
|
816
|
+
if (!tooltip) return;
|
|
817
|
+
if (!e) { tooltip.style.display = 'none'; return; }
|
|
818
|
+
var s = (e.source && e.source.label) || e.source;
|
|
819
|
+
var t = (e.target && e.target.label) || e.target;
|
|
820
|
+
tooltip.style.display = 'block';
|
|
821
|
+
tooltip.textContent = s + ' → ' + t + (e.kind ? ' [' + e.kind + ']' : '');
|
|
822
|
+
})
|
|
778
823
|
.onNodeClick(function(n) {
|
|
779
824
|
// Navigate to the reading view via the server-resolved doc page (the
|
|
780
825
|
// 'page' field, e.g. "docs/<slug>.html"), NOT the raw fileId — the
|
|
@@ -791,6 +836,16 @@ function buildAppScript(): string {
|
|
|
791
836
|
var href = page + (n.anchor ? '#' + n.anchor : '');
|
|
792
837
|
window.location.href = href;
|
|
793
838
|
})
|
|
839
|
+
// Drag-to-pin (fixes the spring-back): keep the node exactly where it was
|
|
840
|
+
// dropped (fx/fy). Because the simulation stays warm, its linked neighbours
|
|
841
|
+
// follow the drag and nodes farther along the graph move progressively less.
|
|
842
|
+
// Right-click a node to release its pin back into the layout.
|
|
843
|
+
.onNodeDrag(function(n) { n.fx = n.x; n.fy = n.y; })
|
|
844
|
+
.onNodeDragEnd(function(n) { n.fx = n.x; n.fy = n.y; })
|
|
845
|
+
.onNodeRightClick(function(n) {
|
|
846
|
+
n.fx = undefined; n.fy = undefined;
|
|
847
|
+
if (graph.d3ReheatSimulation) graph.d3ReheatSimulation();
|
|
848
|
+
})
|
|
794
849
|
.onNodeHover(function(n) {
|
|
795
850
|
// Distinct cursor hint: pointer only over nodes that actually
|
|
796
851
|
// navigate somewhere (have a resolved page); default cursor otherwise
|
|
@@ -805,6 +860,10 @@ function buildAppScript(): string {
|
|
|
805
860
|
.onBackgroundClick(function() {
|
|
806
861
|
if (tooltip) tooltip.style.display = 'none';
|
|
807
862
|
lastHoverNode = null;
|
|
863
|
+
if (selectedLink) {
|
|
864
|
+
selectedLink = null;
|
|
865
|
+
graph.linkWidth(graph.linkWidth()).linkColor(graph.linkColor());
|
|
866
|
+
}
|
|
808
867
|
});
|
|
809
868
|
}
|
|
810
869
|
|