@andespindola/brainlink 0.1.0-beta.5 → 0.1.0-beta.7
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.
|
@@ -140,7 +140,7 @@ select {
|
|
|
140
140
|
|
|
141
141
|
.inspector {
|
|
142
142
|
display: grid;
|
|
143
|
-
grid-template-rows: auto auto auto
|
|
143
|
+
grid-template-rows: auto auto auto minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
|
144
144
|
gap: 22px;
|
|
145
145
|
min-width: 0;
|
|
146
146
|
height: 100%;
|
|
@@ -253,7 +253,7 @@ li small {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
.note-content {
|
|
256
|
-
max-height:
|
|
256
|
+
max-height: min(68svh, 760px);
|
|
257
257
|
margin: 0;
|
|
258
258
|
padding: 12px;
|
|
259
259
|
border: 1px solid var(--line);
|
|
@@ -267,6 +267,80 @@ li small {
|
|
|
267
267
|
line-height: 1.5;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
.content-dialog {
|
|
271
|
+
width: min(920px, calc(100vw - 32px));
|
|
272
|
+
max-height: calc(100svh - 32px);
|
|
273
|
+
padding: 0;
|
|
274
|
+
border: 1px solid var(--line);
|
|
275
|
+
border-radius: 8px;
|
|
276
|
+
background: var(--panel);
|
|
277
|
+
color: var(--text);
|
|
278
|
+
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.48);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.content-dialog::backdrop {
|
|
282
|
+
background: rgba(4, 7, 10, 0.72);
|
|
283
|
+
backdrop-filter: blur(4px);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.content-dialog article {
|
|
287
|
+
display: grid;
|
|
288
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
289
|
+
max-height: calc(100svh - 34px);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.content-dialog header {
|
|
293
|
+
display: flex;
|
|
294
|
+
align-items: flex-start;
|
|
295
|
+
justify-content: space-between;
|
|
296
|
+
gap: 18px;
|
|
297
|
+
padding: 22px;
|
|
298
|
+
border-bottom: 1px solid var(--line);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.content-dialog h2,
|
|
302
|
+
.content-dialog p {
|
|
303
|
+
margin: 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.content-dialog h2 {
|
|
307
|
+
margin-top: 6px;
|
|
308
|
+
font-size: 24px;
|
|
309
|
+
line-height: 1.15;
|
|
310
|
+
overflow-wrap: anywhere;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.content-dialog p {
|
|
314
|
+
margin-top: 8px;
|
|
315
|
+
color: var(--muted);
|
|
316
|
+
overflow-wrap: anywhere;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.content-dialog button {
|
|
320
|
+
flex: 0 0 auto;
|
|
321
|
+
height: 38px;
|
|
322
|
+
padding: 0 14px;
|
|
323
|
+
border: 1px solid var(--line);
|
|
324
|
+
border-radius: 8px;
|
|
325
|
+
background: var(--panel-strong);
|
|
326
|
+
color: var(--text);
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.content-dialog button:hover,
|
|
331
|
+
.content-dialog button:focus {
|
|
332
|
+
border-color: var(--accent);
|
|
333
|
+
color: var(--accent);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.content-dialog .note-content {
|
|
337
|
+
max-height: none;
|
|
338
|
+
min-height: 0;
|
|
339
|
+
border: 0;
|
|
340
|
+
border-radius: 0;
|
|
341
|
+
padding: 22px;
|
|
342
|
+
}
|
|
343
|
+
|
|
270
344
|
@media (max-width: 860px) {
|
|
271
345
|
.shell {
|
|
272
346
|
grid-template-columns: 1fr;
|
|
@@ -291,4 +365,9 @@ li small {
|
|
|
291
365
|
.agent-filter {
|
|
292
366
|
width: 100%;
|
|
293
367
|
}
|
|
368
|
+
|
|
369
|
+
.content-dialog header {
|
|
370
|
+
align-items: stretch;
|
|
371
|
+
flex-direction: column;
|
|
372
|
+
}
|
|
294
373
|
}`;
|
|
@@ -47,10 +47,6 @@ export const createClientHtml = () => `<!doctype html>
|
|
|
47
47
|
<h2>Notes</h2>
|
|
48
48
|
<ul id="notes"></ul>
|
|
49
49
|
</section>
|
|
50
|
-
<section>
|
|
51
|
-
<h2>Content</h2>
|
|
52
|
-
<pre id="content" class="note-content"></pre>
|
|
53
|
-
</section>
|
|
54
50
|
<section>
|
|
55
51
|
<h2>Outgoing</h2>
|
|
56
52
|
<ul id="outgoing"></ul>
|
|
@@ -61,6 +57,19 @@ export const createClientHtml = () => `<!doctype html>
|
|
|
61
57
|
</section>
|
|
62
58
|
</aside>
|
|
63
59
|
</main>
|
|
60
|
+
<dialog id="contentDialog" class="content-dialog" aria-labelledby="contentTitle">
|
|
61
|
+
<article>
|
|
62
|
+
<header>
|
|
63
|
+
<div>
|
|
64
|
+
<span class="eyebrow">Markdown content</span>
|
|
65
|
+
<h2 id="contentTitle">Selected note</h2>
|
|
66
|
+
<p id="contentPath"></p>
|
|
67
|
+
</div>
|
|
68
|
+
<button id="contentClose" type="button">Close</button>
|
|
69
|
+
</header>
|
|
70
|
+
<pre id="contentBody" class="note-content"></pre>
|
|
71
|
+
</article>
|
|
72
|
+
</dialog>
|
|
64
73
|
<script src="/app.js"></script>
|
|
65
74
|
</body>
|
|
66
75
|
</html>`;
|
|
@@ -12,6 +12,7 @@ const state = {
|
|
|
12
12
|
transform: { x: 0, y: 0, scale: 1 },
|
|
13
13
|
pointer: { x: 0, y: 0, down: false, dragNode: null, moved: false },
|
|
14
14
|
graphSignature: '',
|
|
15
|
+
graphStatus: '',
|
|
15
16
|
last: performance.now()
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -30,7 +31,6 @@ const elements = {
|
|
|
30
31
|
path: byId('path'),
|
|
31
32
|
tags: byId('tags'),
|
|
32
33
|
notes: byId('notes'),
|
|
33
|
-
content: byId('content'),
|
|
34
34
|
outgoing: byId('outgoing'),
|
|
35
35
|
incoming: byId('incoming'),
|
|
36
36
|
nodeCount: byId('nodeCount'),
|
|
@@ -38,11 +38,32 @@ const elements = {
|
|
|
38
38
|
tagCount: byId('tagCount'),
|
|
39
39
|
zoomIn: byId('zoomIn'),
|
|
40
40
|
zoomOut: byId('zoomOut'),
|
|
41
|
-
reset: byId('reset')
|
|
41
|
+
reset: byId('reset'),
|
|
42
|
+
contentDialog: byId('contentDialog'),
|
|
43
|
+
contentTitle: byId('contentTitle'),
|
|
44
|
+
contentPath: byId('contentPath'),
|
|
45
|
+
contentBody: byId('contentBody'),
|
|
46
|
+
contentClose: byId('contentClose')
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
const agentQuery = () => state.agentId ? '?agent=' + encodeURIComponent(state.agentId) : ''
|
|
45
50
|
|
|
51
|
+
const setGraphStatus = text => {
|
|
52
|
+
state.graphStatus = text
|
|
53
|
+
elements.stats.textContent = text
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleGraphRefreshError = error => {
|
|
57
|
+
if (state.graphSignature) {
|
|
58
|
+
elements.stats.textContent = state.graphStatus
|
|
59
|
+
console.error(error)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
elements.stats.textContent = 'Failed to load graph'
|
|
64
|
+
console.error(error)
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
const graphTheme = {
|
|
47
68
|
node: '#aeb8c5',
|
|
48
69
|
nodeSelected: '#f3f7fb',
|
|
@@ -111,7 +132,7 @@ const encodeEntityTag = (value) => {
|
|
|
111
132
|
binary += String.fromCharCode(utf8[index])
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
return btoa(binary).
|
|
135
|
+
return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/g, '')
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
const graphSignature = graph => JSON.stringify({
|
|
@@ -261,14 +282,23 @@ const allNotesList = () => state.nodes.length
|
|
|
261
282
|
? state.nodes.map(node => '<li><button type="button" data-node-id="' + escapeHtml(node.id) + '">' + escapeHtml(node.title) + '</button><small>' + escapeHtml(node.path) + '</small></li>').join('')
|
|
262
283
|
: '<li><small>No notes indexed.</small></li>'
|
|
263
284
|
|
|
264
|
-
const
|
|
285
|
+
const openContentDialog = node => {
|
|
286
|
+
if (!node) return
|
|
287
|
+
elements.contentTitle.textContent = node.title
|
|
288
|
+
elements.contentPath.textContent = node.path
|
|
289
|
+
elements.contentBody.textContent = node.content
|
|
290
|
+
if (!elements.contentDialog.open) {
|
|
291
|
+
elements.contentDialog.showModal()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const selectNode = (node, options = { openContent: false }) => {
|
|
265
296
|
state.selected = node
|
|
266
297
|
if (!node) {
|
|
267
298
|
elements.title.textContent = 'Graph Overview'
|
|
268
299
|
elements.path.textContent = state.nodes.length + ' notes and ' + state.graph.edges.length + ' links indexed.'
|
|
269
300
|
elements.tags.innerHTML = ''
|
|
270
301
|
elements.notes.innerHTML = allNotesList()
|
|
271
|
-
elements.content.textContent = 'Selecione uma nota no grafo ou na lista para ver o Markdown completo, backlinks e links de saida.'
|
|
272
302
|
elements.outgoing.innerHTML = '<li><small>Select a note to inspect outgoing links.</small></li>'
|
|
273
303
|
elements.incoming.innerHTML = '<li><small>Select a note to inspect backlinks.</small></li>'
|
|
274
304
|
return
|
|
@@ -294,14 +324,14 @@ const selectNode = node => {
|
|
|
294
324
|
? node.tags.map(tag => '<span>#' + escapeHtml(tag) + '</span>').join('')
|
|
295
325
|
: '<span>No tags</span>'
|
|
296
326
|
elements.notes.innerHTML = allNotesList()
|
|
297
|
-
elements.content.textContent = node.content
|
|
298
327
|
elements.outgoing.innerHTML = list(outgoing)
|
|
299
328
|
elements.incoming.innerHTML = list(incoming)
|
|
329
|
+
if (options.openContent) openContentDialog(node)
|
|
300
330
|
}
|
|
301
331
|
|
|
302
332
|
const selectNodeById = id => {
|
|
303
333
|
const node = state.nodes.find(item => item.id === id)
|
|
304
|
-
if (node) selectNode(node)
|
|
334
|
+
if (node) selectNode(node, { openContent: true })
|
|
305
335
|
}
|
|
306
336
|
|
|
307
337
|
const zoom = factor => {
|
|
@@ -327,6 +357,10 @@ const bindEvents = () => {
|
|
|
327
357
|
elements.zoomIn.addEventListener('click', () => zoom(1.18))
|
|
328
358
|
elements.zoomOut.addEventListener('click', () => zoom(0.84))
|
|
329
359
|
elements.reset.addEventListener('click', resetView)
|
|
360
|
+
elements.contentClose.addEventListener('click', () => elements.contentDialog.close())
|
|
361
|
+
elements.contentDialog.addEventListener('click', event => {
|
|
362
|
+
if (event.target === elements.contentDialog) elements.contentDialog.close()
|
|
363
|
+
})
|
|
330
364
|
;[elements.notes, elements.outgoing, elements.incoming].forEach(element => {
|
|
331
365
|
element.addEventListener('click', event => {
|
|
332
366
|
const target = event.target
|
|
@@ -367,8 +401,8 @@ const bindEvents = () => {
|
|
|
367
401
|
state.transform.y += dy
|
|
368
402
|
})
|
|
369
403
|
canvas.addEventListener('pointerup', event => {
|
|
370
|
-
if (state.pointer.dragNode && !state.pointer.moved) selectNode(state.pointer.dragNode)
|
|
371
|
-
if (!state.pointer.dragNode && !state.pointer.moved) selectNode(state.hovered)
|
|
404
|
+
if (state.pointer.dragNode && !state.pointer.moved) selectNode(state.pointer.dragNode, { openContent: true })
|
|
405
|
+
if (!state.pointer.dragNode && !state.pointer.moved) selectNode(state.hovered, { openContent: true })
|
|
372
406
|
state.pointer = { x: 0, y: 0, down: false, dragNode: null, moved: false }
|
|
373
407
|
canvas.releasePointerCapture(event.pointerId)
|
|
374
408
|
})
|
|
@@ -418,7 +452,7 @@ const loadGraph = async (options = { reset: false }) => {
|
|
|
418
452
|
state.nodes = layout.nodes
|
|
419
453
|
state.edges = layout.edges
|
|
420
454
|
const tags = new Set(graph.nodes.flatMap(node => node.tags))
|
|
421
|
-
|
|
455
|
+
setGraphStatus(state.agentId + ' · ' + graph.nodes.length + ' notes · ' + graph.edges.length + ' links · live')
|
|
422
456
|
elements.nodeCount.textContent = graph.nodes.length
|
|
423
457
|
elements.edgeCount.textContent = graph.edges.length
|
|
424
458
|
elements.tagCount.textContent = tags.size
|
|
@@ -441,10 +475,7 @@ const refreshGraphLoop = () => {
|
|
|
441
475
|
return
|
|
442
476
|
}
|
|
443
477
|
|
|
444
|
-
loadGraph().catch(
|
|
445
|
-
elements.stats.textContent = 'Failed to refresh graph'
|
|
446
|
-
console.error(error)
|
|
447
|
-
})
|
|
478
|
+
loadGraph().catch(handleGraphRefreshError)
|
|
448
479
|
|
|
449
480
|
tickCounter += 1
|
|
450
481
|
if (tickCounter % 3 === 0) {
|
|
@@ -470,9 +501,6 @@ document.addEventListener('visibilitychange', () => {
|
|
|
470
501
|
return
|
|
471
502
|
}
|
|
472
503
|
|
|
473
|
-
loadGraph({ reset: true }).catch(
|
|
474
|
-
elements.stats.textContent = 'Failed to refresh graph'
|
|
475
|
-
console.error(error)
|
|
476
|
-
})
|
|
504
|
+
loadGraph({ reset: true }).catch(handleGraphRefreshError)
|
|
477
505
|
})
|
|
478
506
|
`;
|
package/package.json
CHANGED