@webship.co/workspace-graph 0.0.0-stage → 1.0.0-beta1
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/README.md +24 -2
- package/commands/cmd-tools-graphify.sh +27 -0
- package/commands/cmd-tools-graphs.sh +18 -0
- package/commands/cmd-tools-obsidian.sh +31 -0
- package/config/workspace.graphs.settings.yml +25 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +309 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/graphs.d.ts +1 -0
- package/dist/src/lib/graphs.js +141 -0
- package/dist/src/lib/graphs.js.map +1 -0
- package/dist/src/lib/obsidian.d.ts +1 -0
- package/dist/src/lib/obsidian.js +414 -0
- package/dist/src/lib/obsidian.js.map +1 -0
- package/docs/AGENTS.md +29 -0
- package/docs/CLAUDE.md +7 -0
- package/docs/README.md +11 -0
- package/features/graph.feature +13 -0
- package/package.json +80 -4
- package/scripts/args/arg-graphify.sh +36 -0
- package/scripts/args/arg-graphs.sh +12 -0
- package/scripts/args/arg-obsidian.sh +17 -0
- package/scripts/functions/fun-graphify.sh +587 -0
- package/scripts/functions/fun-graphs.sh +205 -0
- package/scripts/functions/fun-obsidian.sh +330 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphs.js","sourceRoot":"","sources":["../../../src/lib/graphs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* The Obsidian vault a graphed project can have, and the menu entries that make and read it.
|
|
5
|
+
*
|
|
6
|
+
* A vault is made FROM the graph — `cmd-tools-obsidian.sh` reads `graph.json` and writes notes,
|
|
7
|
+
* wikilinks and a JSON Canvas — so it lives inside the graph store at
|
|
8
|
+
* `graphs/<workspace>/<project>/obsidian/` and is thrown away with it. It also means the entries
|
|
9
|
+
* belong in the Graph menu rather than a menu of their own: without a graph there is nothing to
|
|
10
|
+
* convert, and a second button that is disabled most of the time only adds noise to the row.
|
|
11
|
+
*
|
|
12
|
+
* `views.js` builds the row's menu from this and `server.js` serves what the menu points at, so
|
|
13
|
+
* the menu cannot offer a file the server will not find.
|
|
14
|
+
*/
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { ROOT, graphStoreDir } = require('./graphs');
|
|
18
|
+
const esc = (v) => String(v == null ? '' : v).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
19
|
+
// Where a project's vault lives. Inside its graph, never inside the project.
|
|
20
|
+
function vaultDir(wsKey, project) {
|
|
21
|
+
return path.join(graphStoreDir(wsKey, project), 'obsidian');
|
|
22
|
+
}
|
|
23
|
+
// The index note is named after the project, with the same sanitising the builder applies, so the
|
|
24
|
+
// server and the builder agree on one filename.
|
|
25
|
+
function indexNote(project) {
|
|
26
|
+
return `${String(project).replace(/[\\/:*?"<>|[\]#^]+/g, '-').replace(/^[ .-]+|[ .-]+$/g, '').slice(0, 120) || 'unnamed'}.md`;
|
|
27
|
+
}
|
|
28
|
+
// Same memo window as the graph's: a row re-renders once a second while a job runs, and asking the
|
|
29
|
+
// filesystem per render turns a list of twenty projects into a stream of stat calls.
|
|
30
|
+
let _cache = new Map();
|
|
31
|
+
let _cacheAt = 0;
|
|
32
|
+
function hasVault(wsKey, project) {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
if (now - _cacheAt > 2000) {
|
|
35
|
+
_cache = new Map();
|
|
36
|
+
_cacheAt = now;
|
|
37
|
+
}
|
|
38
|
+
const k = `${wsKey}/${project}`;
|
|
39
|
+
if (!_cache.has(k))
|
|
40
|
+
_cache.set(k, fs.existsSync(path.join(vaultDir(wsKey, project), '.obsidian')));
|
|
41
|
+
return _cache.get(k);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The Obsidian entries of the Graph menu.
|
|
45
|
+
*
|
|
46
|
+
* Returned as a list of `<li>` strings rather than a menu, because they hang off the Graph button:
|
|
47
|
+
* `graphMenuHtml` decides where they sit and only asks for them once a graph exists.
|
|
48
|
+
*/
|
|
49
|
+
function obsidianMenuItems(key, project, vals, busy, post = (n) => `/actions/${n}`) {
|
|
50
|
+
const built = hasVault(key, project);
|
|
51
|
+
const items = ['<li class="uk-nav-divider"></li>', '<li class="uk-nav-header">Obsidian</li>'];
|
|
52
|
+
items.push(`<li><a href hx-post="${post('obsidian')}" ${vals()} class="${busy ? 'uk-disabled' : ''}"><span uk-icon="icon: file-edit; ratio: .7"></span> ${built ? 'Rebuild the vault' : 'Build the vault'}${busy ? ' (running…)' : ''}</a></li>`);
|
|
53
|
+
if (built) {
|
|
54
|
+
// A desktop protocol link, not an HTTP one: it hands the absolute path to the Obsidian app on
|
|
55
|
+
// the machine running the browser. Every reserved character has to be encoded — an unescaped
|
|
56
|
+
// slash makes Obsidian read the path as a vault name and open the wrong thing, or nothing.
|
|
57
|
+
const uri = `obsidian://open?path=${encodeURIComponent(vaultDir(key, project))}`;
|
|
58
|
+
items.push(`<li><a href="${esc(uri)}" title="Opens the Obsidian app on this machine"><span uk-icon="icon: link-external; ratio: .7"></span> Open in Obsidian</a></li>`);
|
|
59
|
+
// The reader first: the vault lives on the machine running the dashboard, which is not
|
|
60
|
+
// necessarily the machine looking at it, and this needs nothing installed.
|
|
61
|
+
items.push(`<li><a href hx-get="/obsidian/${esc(key)}/${esc(project)}/frame?what=view" hx-target="#editor-modal-body" hx-swap="innerHTML"><span uk-icon="icon: file-text; ratio: .7"></span> Read the notes</a></li>`);
|
|
62
|
+
items.push(`<li><a href hx-get="/obsidian/${esc(key)}/${esc(project)}/frame?what=canvas" hx-target="#editor-modal-body" hx-swap="innerHTML"><span uk-icon="icon: git-fork; ratio: .7"></span> Open the map</a></li>`);
|
|
63
|
+
items.push(`<li><a href="/obsidian/${esc(key)}/${esc(project)}/download" title="The vault as a .tar.gz, to open on another machine"><span uk-icon="icon: download; ratio: .7"></span> Download the vault</a></li>`);
|
|
64
|
+
items.push(`<li><a href class="uk-text-danger arm-step" data-armed="0" hx-post="${post('obsidian-remove')}" ${vals(',"confirm":"yes"')} hx-trigger="confirmed-remove"><span uk-icon="icon: trash; ratio: .7"></span> Delete the vault</a></li>`);
|
|
65
|
+
}
|
|
66
|
+
return items.join('');
|
|
67
|
+
}
|
|
68
|
+
module.exports = { vaultDir, hasVault, obsidianMenuItems, indexNote };
|
|
69
|
+
/* ---------------------------------------------------------------------------
|
|
70
|
+
* The web reader.
|
|
71
|
+
*
|
|
72
|
+
* Obsidian is a desktop app, and the vault lives on the machine running the
|
|
73
|
+
* dashboard — which is not necessarily the machine looking at it. So the vault is
|
|
74
|
+
* also readable in the browser: the notes with their wikilinks working, and the
|
|
75
|
+
* canvas drawn as the map it describes.
|
|
76
|
+
*
|
|
77
|
+
* Everything is served from this app. No CDN, because the graph viewer already
|
|
78
|
+
* needs the internet for vis-network and having one thing in the dashboard that
|
|
79
|
+
* stops working on a train is enough.
|
|
80
|
+
* ------------------------------------------------------------------------- */
|
|
81
|
+
// The notes, by name. One pass over the vault, because every view needs the full
|
|
82
|
+
// set: the list, and the link resolver that decides which [[targets]] are real.
|
|
83
|
+
function vaultNotes(wsKey, project) {
|
|
84
|
+
const dir = vaultDir(wsKey, project);
|
|
85
|
+
const found = new Map();
|
|
86
|
+
const walk = (d, rel) => {
|
|
87
|
+
let entries;
|
|
88
|
+
try {
|
|
89
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
90
|
+
}
|
|
91
|
+
catch (_) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const e of entries) {
|
|
95
|
+
if (e.name === '.obsidian')
|
|
96
|
+
continue;
|
|
97
|
+
const full = path.join(d, e.name);
|
|
98
|
+
if (e.isDirectory())
|
|
99
|
+
walk(full, path.join(rel, e.name));
|
|
100
|
+
else if (e.name.endsWith('.md'))
|
|
101
|
+
found.set(e.name.slice(0, -3), path.join(rel, e.name));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
walk(dir, '');
|
|
105
|
+
return found;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The note shape this workspace writes, rendered.
|
|
109
|
+
*
|
|
110
|
+
* Not a general markdown parser — a parser that handles every construct would be a
|
|
111
|
+
* dependency and a much larger surface, and these notes are written by
|
|
112
|
+
* fun-obsidian.sh, so their shape is known: frontmatter, a heading, a source line,
|
|
113
|
+
* two link lists, a rule and a footer.
|
|
114
|
+
*
|
|
115
|
+
* Everything is escaped first and marked up second, so a note whose content happens
|
|
116
|
+
* to contain markup is displayed rather than obeyed.
|
|
117
|
+
*/
|
|
118
|
+
function renderNote(md, linkHref, exists) {
|
|
119
|
+
let meta = {};
|
|
120
|
+
let body = md;
|
|
121
|
+
const fm = md.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
122
|
+
if (fm) {
|
|
123
|
+
body = md.slice(fm[0].length);
|
|
124
|
+
for (const line of fm[1].split('\n')) {
|
|
125
|
+
const m = line.match(/^([a-z_]+):\s*(.*)$/i);
|
|
126
|
+
if (m)
|
|
127
|
+
meta[m[1]] = m[2].replace(/^["']|["']$/g, '');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const inline = (t) => esc(t)
|
|
131
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
132
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
133
|
+
.replace(/\[\[([^\]|#]+)(\|[^\]]+)?\]\]/g, (_, name) => {
|
|
134
|
+
const n = name.trim();
|
|
135
|
+
return exists(n)
|
|
136
|
+
? `<a class="wl" href="${linkHref(n)}">${esc(n)}</a>`
|
|
137
|
+
: `<span class="wl wl-missing" title="No note of that name in this vault">${esc(n)}</span>`;
|
|
138
|
+
});
|
|
139
|
+
const out = [];
|
|
140
|
+
let inList = false;
|
|
141
|
+
for (const raw of body.split('\n')) {
|
|
142
|
+
const line = raw.trimEnd();
|
|
143
|
+
if (/^-{3,}$/.test(line)) {
|
|
144
|
+
if (inList) {
|
|
145
|
+
out.push('</ul>');
|
|
146
|
+
inList = false;
|
|
147
|
+
}
|
|
148
|
+
out.push('<hr>');
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const li = line.match(/^-\s+(.*)$/);
|
|
152
|
+
if (li) {
|
|
153
|
+
if (!inList) {
|
|
154
|
+
out.push('<ul class="links">');
|
|
155
|
+
inList = true;
|
|
156
|
+
}
|
|
157
|
+
out.push(`<li>${inline(li[1])}</li>`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (inList) {
|
|
161
|
+
out.push('</ul>');
|
|
162
|
+
inList = false;
|
|
163
|
+
}
|
|
164
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
165
|
+
if (h) {
|
|
166
|
+
out.push(`<h${h[1].length}>${inline(h[2])}</h${h[1].length}>`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (!line)
|
|
170
|
+
continue;
|
|
171
|
+
out.push(`<p>${inline(line)}</p>`);
|
|
172
|
+
}
|
|
173
|
+
if (inList)
|
|
174
|
+
out.push('</ul>');
|
|
175
|
+
return { meta, html: out.join('\n') };
|
|
176
|
+
}
|
|
177
|
+
// One stylesheet for both views, using the dashboard's own theme tokens so the reader
|
|
178
|
+
// follows whatever theme is set instead of picking its own colours.
|
|
179
|
+
const VAULT_CSS = `
|
|
180
|
+
body { background: var(--surface); color: var(--ink); margin: 0; }
|
|
181
|
+
.vault { display: grid; grid-template-columns: 16rem 1fr; gap: 0; height: calc(100vh - 3.5rem); }
|
|
182
|
+
.vault-list { border-right: 1px solid var(--line); overflow-y: auto; padding: .5rem; }
|
|
183
|
+
.vault-list input { width: 100%; margin-bottom: .5rem; padding: .35rem .5rem;
|
|
184
|
+
border: 1px solid var(--line); border-radius: 4px; background: var(--surface);
|
|
185
|
+
color: var(--ink); }
|
|
186
|
+
.vault-list a { display: block; padding: .25rem .4rem; border-radius: 4px;
|
|
187
|
+
color: var(--ink); text-decoration: none; font-size: .82rem; overflow: hidden;
|
|
188
|
+
text-overflow: ellipsis; white-space: nowrap; }
|
|
189
|
+
.vault-list a:hover { background: var(--surface-sunken); }
|
|
190
|
+
.vault-list a.on { background: var(--link); color: var(--danger-ink); }
|
|
191
|
+
.vault-body { overflow-y: auto; padding: 1.25rem 1.75rem; }
|
|
192
|
+
.vault-body h1 { font-size: 1.5rem; margin: 0 0 .75rem; }
|
|
193
|
+
.vault-body h2 { font-size: 1rem; margin: 1.25rem 0 .4rem; color: var(--text-subtle); }
|
|
194
|
+
.vault-body ul.links { list-style: none; padding-left: 0; }
|
|
195
|
+
.vault-body ul.links li { padding: .15rem 0; }
|
|
196
|
+
.wl { color: var(--link); text-decoration: none; border-bottom: 1px solid transparent; }
|
|
197
|
+
.wl:hover { border-bottom-color: var(--link); }
|
|
198
|
+
.wl-missing { color: var(--text-subtle); border-bottom: 1px dotted var(--text-subtle); }
|
|
199
|
+
.vault-meta { font-size: .78rem; color: var(--text-subtle); margin-bottom: 1rem; }
|
|
200
|
+
.vault-meta code { background: var(--surface-sunken); padding: .05rem .3rem; border-radius: 3px; }
|
|
201
|
+
.vault-bar { display: flex; gap: .5rem; align-items: center; padding: .5rem .75rem;
|
|
202
|
+
border-bottom: 1px solid var(--line); background: var(--surface-raised); }
|
|
203
|
+
.vault-bar a, .vault-bar span { font-size: .82rem; }
|
|
204
|
+
#cv { width: 100%; height: calc(100vh - 7rem); background: var(--surface); cursor: grab; }
|
|
205
|
+
#cv:active { cursor: grabbing; }
|
|
206
|
+
#cv text { font: 15px system-ui, sans-serif; fill: var(--ink); pointer-events: none; }
|
|
207
|
+
#cv .n rect { fill: var(--surface-raised); stroke: var(--line); stroke-width: 1.5; }
|
|
208
|
+
#cv .n:hover rect { stroke: var(--link); stroke-width: 3; }
|
|
209
|
+
#cv .e { stroke: var(--text-subtle); stroke-width: 1.5; fill: none; opacity: .55; }
|
|
210
|
+
.vault-bar button { font-size: .8rem; padding: .15rem .55rem; border: 1px solid var(--line);
|
|
211
|
+
border-radius: 4px; background: var(--surface-raised); color: var(--ink); cursor: pointer; }
|
|
212
|
+
.vault-bar button:hover { border-color: var(--link); }
|
|
213
|
+
`;
|
|
214
|
+
// A page frame that inherits the dashboard's assembled theme sheet.
|
|
215
|
+
function vaultPage(title, cssVersion, bodyHtml) {
|
|
216
|
+
return `<!doctype html><html><head><meta charset="utf-8">
|
|
217
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
218
|
+
<title>${esc(title)}</title>
|
|
219
|
+
<link rel="icon" href="/favicon.ico">
|
|
220
|
+
<link rel="stylesheet" href="/style.css?v=${esc(String(cssVersion))}">
|
|
221
|
+
<style>${VAULT_CSS}</style>
|
|
222
|
+
</head><body>${bodyHtml}</body></html>`;
|
|
223
|
+
}
|
|
224
|
+
/** The note browser: the list, and one note with its links live. */
|
|
225
|
+
function vaultView(wsKey, project, note, cssVersion) {
|
|
226
|
+
const notes = vaultNotes(wsKey, project);
|
|
227
|
+
const names = [...notes.keys()].sort((a, b) => a.localeCompare(b));
|
|
228
|
+
const current = notes.has(note) ? note : (names.includes(project) ? project : names[0]);
|
|
229
|
+
const base = `/obsidian/${encodeURIComponent(wsKey)}/${encodeURIComponent(project)}/view`;
|
|
230
|
+
const href = (n) => `${base}?note=${encodeURIComponent(n)}`;
|
|
231
|
+
let rendered = { meta: {}, html: '<p class="uk-text-meta">This vault has no notes.</p>' };
|
|
232
|
+
if (current) {
|
|
233
|
+
try {
|
|
234
|
+
rendered = renderNote(fs.readFileSync(path.join(vaultDir(wsKey, project), notes.get(current)), 'utf8'), href, (n) => notes.has(n));
|
|
235
|
+
}
|
|
236
|
+
catch (_) {
|
|
237
|
+
rendered = { meta: {}, html: '<p class="msg error">That note could not be read.</p>' };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const meta = rendered.meta;
|
|
241
|
+
const metaLine = [
|
|
242
|
+
meta.source_file ? `<code>${esc(meta.source_file)}</code>` : '',
|
|
243
|
+
meta.source_location ? esc(meta.source_location) : '',
|
|
244
|
+
meta.cluster !== undefined ? `cluster ${esc(meta.cluster)}` : '',
|
|
245
|
+
meta.tags ? esc(meta.tags) : '',
|
|
246
|
+
].filter(Boolean).join(' · ');
|
|
247
|
+
return vaultPage(`${current || project} · ${project}`, cssVersion, `
|
|
248
|
+
<div class="vault-bar">
|
|
249
|
+
<strong>${esc(project)}</strong>
|
|
250
|
+
<span class="uk-text-meta">${names.length} notes</span>
|
|
251
|
+
<a href="/obsidian/${esc(wsKey)}/${esc(project)}/canvas">Map</a>
|
|
252
|
+
<a href="/obsidian/${esc(wsKey)}/${esc(project)}/download">Download</a>
|
|
253
|
+
<a href="obsidian://open?path=${encodeURIComponent(vaultDir(wsKey, project))}">Open in the Obsidian app</a>
|
|
254
|
+
<span style="margin-left:auto"><a href="/">${esc(wsKey)} workspace</a></span>
|
|
255
|
+
</div>
|
|
256
|
+
<div class="vault">
|
|
257
|
+
<nav class="vault-list">
|
|
258
|
+
<input id="q" type="search" placeholder="Filter ${names.length} notes…" autocomplete="off">
|
|
259
|
+
<div id="ls">
|
|
260
|
+
${names.map((n) => `<a href="${href(n)}" class="${n === current ? 'on' : ''}" data-n="${esc(n.toLowerCase())}">${esc(n)}</a>`).join('')}
|
|
261
|
+
</div>
|
|
262
|
+
</nav>
|
|
263
|
+
<article class="vault-body">
|
|
264
|
+
${metaLine ? `<div class="vault-meta">${metaLine}</div>` : ''}
|
|
265
|
+
${rendered.html}
|
|
266
|
+
</article>
|
|
267
|
+
</div>
|
|
268
|
+
<script>
|
|
269
|
+
// Filtering is the one thing worth scripting here: every link is a real URL, so
|
|
270
|
+
// the reader works with scripting off apart from this.
|
|
271
|
+
var q = document.getElementById('q'), ls = document.getElementById('ls');
|
|
272
|
+
q.addEventListener('input', function () {
|
|
273
|
+
var v = q.value.toLowerCase();
|
|
274
|
+
for (var i = 0; i < ls.children.length; i++) {
|
|
275
|
+
var a = ls.children[i];
|
|
276
|
+
a.style.display = a.dataset.n.indexOf(v) === -1 ? 'none' : '';
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
</script>`);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The canvas, drawn.
|
|
283
|
+
*
|
|
284
|
+
* The .canvas file already carries a position for every node — the builder laid them
|
|
285
|
+
* out cluster by cluster — so this is a straight projection into SVG rather than a
|
|
286
|
+
* layout algorithm. That also means the web map and the map Obsidian's Canvas shows
|
|
287
|
+
* are the same picture, which is the point of storing coordinates at all.
|
|
288
|
+
*/
|
|
289
|
+
function vaultCanvas(wsKey, project, cssVersion) {
|
|
290
|
+
const file = path.join(vaultDir(wsKey, project), `${indexNote(project).slice(0, -3)}.canvas`);
|
|
291
|
+
let canvas;
|
|
292
|
+
try {
|
|
293
|
+
canvas = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
294
|
+
}
|
|
295
|
+
catch (_) {
|
|
296
|
+
return vaultPage(`${project} map`, cssVersion, '<div class="vault-bar"><span class="msg error">This vault has no canvas. Rebuild it.</span></div>');
|
|
297
|
+
}
|
|
298
|
+
const nodes = canvas.nodes || [];
|
|
299
|
+
const edges = canvas.edges || [];
|
|
300
|
+
const at = new Map(nodes.map((n) => [n.id, n]));
|
|
301
|
+
const pad = 60;
|
|
302
|
+
const xs = nodes.map((n) => n.x);
|
|
303
|
+
const ys = nodes.map((n) => n.y);
|
|
304
|
+
const minX = Math.min(...xs, 0) - pad;
|
|
305
|
+
const minY = Math.min(...ys, 0) - pad;
|
|
306
|
+
const w = Math.max(...xs.map((x, i) => x + nodes[i].width), 100) - minX + pad;
|
|
307
|
+
const h = Math.max(...ys.map((y, i) => y + nodes[i].height), 100) - minY + pad;
|
|
308
|
+
// The colour a node carries is a JSON Canvas preset number, not a CSS colour.
|
|
309
|
+
const PRESET = { 1: '#e05252', 2: '#e08c52', 3: '#d9c04a', 4: '#4fa373', 5: '#4f9fa3', 6: '#8b6fc4' };
|
|
310
|
+
const view = `/obsidian/${encodeURIComponent(wsKey)}/${encodeURIComponent(project)}/view`;
|
|
311
|
+
const noteOf = (n) => (n.file || '').replace(/^notes\//, '').replace(/\.md$/, '');
|
|
312
|
+
const edgeSvg = edges.map((e) => {
|
|
313
|
+
const a = at.get(e.fromNode);
|
|
314
|
+
const b = at.get(e.toNode);
|
|
315
|
+
if (!a || !b)
|
|
316
|
+
return '';
|
|
317
|
+
const x1 = a.x + a.width;
|
|
318
|
+
const y1 = a.y + a.height / 2;
|
|
319
|
+
const x2 = b.x;
|
|
320
|
+
const y2 = b.y + b.height / 2;
|
|
321
|
+
const mid = (x1 + x2) / 2;
|
|
322
|
+
return `<path class="e" d="M${x1},${y1} C${mid},${y1} ${mid},${y2} ${x2},${y2}"/>`;
|
|
323
|
+
}).join('');
|
|
324
|
+
const nodeSvg = nodes.map((n) => {
|
|
325
|
+
const name = noteOf(n);
|
|
326
|
+
const label = name.length > 30 ? `${name.slice(0, 29)}…` : name;
|
|
327
|
+
const stroke = PRESET[n.color] || '';
|
|
328
|
+
return `<a class="n" href="${view}?note=${encodeURIComponent(name)}"><title>${esc(name)}</title>
|
|
329
|
+
<rect x="${n.x}" y="${n.y}" width="${n.width}" height="${n.height}" rx="6"${stroke ? ` style="stroke:${stroke}"` : ''}/>
|
|
330
|
+
<text x="${n.x + 12}" y="${n.y + n.height / 2 + 4}">${esc(label)}</text></a>`;
|
|
331
|
+
}).join('');
|
|
332
|
+
return vaultPage(`${project} map`, cssVersion, `
|
|
333
|
+
<div class="vault-bar">
|
|
334
|
+
<strong>${esc(project)}</strong>
|
|
335
|
+
<span class="uk-text-meta">${nodes.length} on the map · ${edges.length} links</span>
|
|
336
|
+
<a href="${view}">Notes</a>
|
|
337
|
+
<a href="obsidian://open?path=${encodeURIComponent(vaultDir(wsKey, project))}">Open in the Obsidian app</a>
|
|
338
|
+
<button id="fit" type="button">Fit</button>
|
|
339
|
+
<button id="one" type="button">100%</button>
|
|
340
|
+
<span class="uk-text-meta" style="margin-left:auto">drag to pan · scroll to zoom · click a node to read it</span>
|
|
341
|
+
</div>
|
|
342
|
+
<svg id="cv" viewBox="${minX} ${minY} ${w} ${h}">
|
|
343
|
+
<g id="vp">${edgeSvg}${nodeSvg}</g>
|
|
344
|
+
</svg>
|
|
345
|
+
<script>
|
|
346
|
+
// Pan and zoom by moving the viewBox, so the drawing stays vector-sharp and the
|
|
347
|
+
// links keep working — a CSS transform would break hit testing on the anchors.
|
|
348
|
+
(function () {
|
|
349
|
+
var svg = document.getElementById('cv');
|
|
350
|
+
var full = { x: ${minX}, y: ${minY}, w: ${w}, h: ${h} };
|
|
351
|
+
var vb = { x: full.x, y: full.y, w: full.w, h: full.h };
|
|
352
|
+
function apply() { svg.setAttribute('viewBox', vb.x + ' ' + vb.y + ' ' + vb.w + ' ' + vb.h); }
|
|
353
|
+
function set(o) { vb.x = o.x; vb.y = o.y; vb.w = o.w; vb.h = o.h;
|
|
354
|
+
svg.setAttribute('viewBox', vb.x + ' ' + vb.y + ' ' + vb.w + ' ' + vb.h); }
|
|
355
|
+
function fit() { set(full); }
|
|
356
|
+
// 100%: one user unit per CSS pixel, anchored at the top-left of the map.
|
|
357
|
+
function oneToOne() {
|
|
358
|
+
set({ x: full.x, y: full.y, w: svg.clientWidth, h: svg.clientHeight });
|
|
359
|
+
}
|
|
360
|
+
document.getElementById('fit').addEventListener('click', fit);
|
|
361
|
+
document.getElementById('one').addEventListener('click', oneToOne);
|
|
362
|
+
// Fit only when the whole map already fits comfortably; otherwise open readable.
|
|
363
|
+
if (full.w <= svg.clientWidth * 1.4 && full.h <= svg.clientHeight * 1.4) { fit(); } else { oneToOne(); }
|
|
364
|
+
var drag = null, moved = false;
|
|
365
|
+
svg.addEventListener('pointerdown', function (e) { drag = { x: e.clientX, y: e.clientY }; moved = false; });
|
|
366
|
+
window.addEventListener('pointerup', function () { drag = null; });
|
|
367
|
+
window.addEventListener('pointermove', function (e) {
|
|
368
|
+
if (!drag) return;
|
|
369
|
+
var k = vb.w / svg.clientWidth;
|
|
370
|
+
vb.x -= (e.clientX - drag.x) * k; vb.y -= (e.clientY - drag.y) * k;
|
|
371
|
+
drag = { x: e.clientX, y: e.clientY };
|
|
372
|
+
if (Math.abs(e.movementX) + Math.abs(e.movementY) > 2) moved = true;
|
|
373
|
+
apply();
|
|
374
|
+
});
|
|
375
|
+
// A drag that ends over a node must not also open it.
|
|
376
|
+
svg.addEventListener('click', function (e) { if (moved) { e.preventDefault(); moved = false; } }, true);
|
|
377
|
+
svg.addEventListener('wheel', function (e) {
|
|
378
|
+
e.preventDefault();
|
|
379
|
+
var f = e.deltaY > 0 ? 1.12 : 0.89;
|
|
380
|
+
var r = svg.getBoundingClientRect();
|
|
381
|
+
var mx = vb.x + (e.clientX - r.left) / r.width * vb.w;
|
|
382
|
+
var my = vb.y + (e.clientY - r.top) / r.height * vb.h;
|
|
383
|
+
vb.x = mx - (mx - vb.x) * f; vb.y = my - (my - vb.y) * f;
|
|
384
|
+
vb.w *= f; vb.h *= f;
|
|
385
|
+
apply();
|
|
386
|
+
}, { passive: false });
|
|
387
|
+
})();
|
|
388
|
+
</script>`);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The modal wrapper: one of the two views in an iframe, filling the dialog.
|
|
392
|
+
*
|
|
393
|
+
* An iframe rather than the page's markup inlined, because both views are whole documents — own
|
|
394
|
+
* stylesheet link, own script, own pan/zoom state — and splicing that into the dashboard would
|
|
395
|
+
* put two competing sets of handlers on one document.
|
|
396
|
+
*/
|
|
397
|
+
function vaultFrame(wsKey, project, what) {
|
|
398
|
+
const src = `/obsidian/${encodeURIComponent(wsKey)}/${encodeURIComponent(project)}/${what === 'canvas' ? 'canvas' : 'view'}`;
|
|
399
|
+
const other = what === 'canvas' ? 'view' : 'canvas';
|
|
400
|
+
return `<div class="uk-flex uk-flex-between uk-flex-middle uk-margin-small-bottom">
|
|
401
|
+
<h3 class="uk-margin-remove">${esc(project)} <span class="uk-text-meta">${what === 'canvas' ? 'map' : 'notes'}</span></h3>
|
|
402
|
+
<div>
|
|
403
|
+
<a class="uk-button uk-button-default uk-button-small" href hx-get="/obsidian/${esc(wsKey)}/${esc(project)}/frame?what=${other}" hx-target="#editor-modal-body" hx-swap="innerHTML">${other === 'canvas' ? 'Map' : 'Notes'}</a>
|
|
404
|
+
<a class="uk-button uk-button-default uk-button-small" href="${esc(src)}" target="_blank">Open full screen</a>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
<iframe src="${esc(src)}" title="${esc(project)}" style="width:100%;height:74vh;border:1px solid var(--line);border-radius:4px;background:var(--surface)"></iframe>`;
|
|
408
|
+
}
|
|
409
|
+
module.exports.vaultFrame = vaultFrame;
|
|
410
|
+
module.exports.vaultNotes = vaultNotes;
|
|
411
|
+
module.exports.renderNote = renderNote;
|
|
412
|
+
module.exports.vaultView = vaultView;
|
|
413
|
+
module.exports.vaultCanvas = vaultCanvas;
|
|
414
|
+
//# sourceMappingURL=obsidian.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"obsidian.js","sourceRoot":"","sources":["../../../src/lib/obsidian.ts"],"names":[],"mappings":"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"}
|
package/docs/AGENTS.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Graphs
|
|
2
|
+
|
|
3
|
+
Every project's knowledge graph, one directory per project:
|
|
4
|
+
`graphs/<workspace>/<project>/` with `graph.json`, `GRAPH_REPORT.md` and `graph.html`.
|
|
5
|
+
|
|
6
|
+
Part of the **Webship Workspace** (`~/workspace`), a DDEV-only harness. The rules in the root
|
|
7
|
+
`CLAUDE.md` apply here too; what follows is what is specific to this folder.
|
|
8
|
+
|
|
9
|
+
## Why the graph is not in the project
|
|
10
|
+
|
|
11
|
+
A graph is tens of megabytes, its clustering is not bit-stable so every rebuild re-diffs in full,
|
|
12
|
+
and many of the projects worth mapping are repositories we ship — none should gain a generated
|
|
13
|
+
directory because somebody mapped it. `cmd-tools-graphify.sh` builds it in the project's own
|
|
14
|
+
workspace folder and the result is moved here.
|
|
15
|
+
|
|
16
|
+
**Build one from the project's workspace, never from here:**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd ~/workspace/dev && bash cmd-tools-graphify.sh <project>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands here
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bash cmd-tools-graphs.sh # Graphs (list, collect, serve over MCP)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Every one locates the workspace from its own path, so there is nothing to export and nothing to
|
|
29
|
+
install. `--help` on any of them lists its arguments.
|
package/docs/CLAUDE.md
ADDED
package/docs/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Graphs
|
|
2
|
+
|
|
3
|
+
Knowledge graphs of a project's code, one directory per project:
|
|
4
|
+
`graphs/<workspace>/<project>/`.
|
|
5
|
+
|
|
6
|
+
They live here rather than inside the project on purpose. A graph is tens of megabytes, its
|
|
7
|
+
clustering is not bit-stable so every rebuild diffs in full, and half the projects worth mapping
|
|
8
|
+
are repositories we ship — none of them should gain a graph directory because somebody mapped it.
|
|
9
|
+
|
|
10
|
+
A graph answers *what depends on what*, deterministically, from the code. For *where is this
|
|
11
|
+
explained or configured*, see `rag/` — the two are complements, not alternatives.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Feature: The Graphs workspace plugin
|
|
2
|
+
Once the plugin is activated and installed, the graphs workspace is on the dashboard, and the
|
|
3
|
+
Plugins page lists the plugin with its four steps.
|
|
4
|
+
|
|
5
|
+
Scenario: The workspace page is there
|
|
6
|
+
Given I am on "/graphs"
|
|
7
|
+
Then the element "h1" should be displayed
|
|
8
|
+
And the element "#webship-workspace-projects, .items-list, .workspace-page" should be attached
|
|
9
|
+
|
|
10
|
+
Scenario: The plugin is listed on the Plugins page
|
|
11
|
+
Given I am on "/settings/plugins"
|
|
12
|
+
Then I should see "Graphs"
|
|
13
|
+
And I should see "@webship.co/workspace-graph"
|
package/package.json
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webship.co/workspace-graph",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0-beta1",
|
|
4
|
+
"description": "The knowledge graphs for Webship Workspace: the graphs/ workspace as a plugin, the Graph menu on every project row (build with graphify, open, report, download, MCP, delete) and the Obsidian vault made from a graph (build, read, map, download), with the /graph and /obsidian pages.",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/webship/workspace-graph.git"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"README.md",
|
|
14
|
+
"commands",
|
|
15
|
+
"config",
|
|
16
|
+
"dist/src",
|
|
17
|
+
"docs",
|
|
18
|
+
"features",
|
|
19
|
+
"scripts"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json",
|
|
23
|
+
"prepare": "npm run build",
|
|
24
|
+
"test": "npm run build && node --test dist/tests/*.test.js",
|
|
25
|
+
"lint": "tsc -p tsconfig.json --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"workspacePlugin": {
|
|
28
|
+
"name": "graph",
|
|
29
|
+
"label": "Graphs",
|
|
30
|
+
"workspace": {
|
|
31
|
+
"key": "graphs",
|
|
32
|
+
"settingsTemplate": "config/workspace.graphs.settings.yml",
|
|
33
|
+
"files": [
|
|
34
|
+
"commands/cmd-tools-graphs.sh"
|
|
35
|
+
],
|
|
36
|
+
"docs": [
|
|
37
|
+
"docs/AGENTS.md",
|
|
38
|
+
"docs/CLAUDE.md",
|
|
39
|
+
"docs/README.md"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"features": [
|
|
43
|
+
"features/graph.feature"
|
|
44
|
+
],
|
|
45
|
+
"typeActions": {
|
|
46
|
+
"graphify": [
|
|
47
|
+
"sites",
|
|
48
|
+
"code"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"commands": {
|
|
52
|
+
"types": [
|
|
53
|
+
"sites",
|
|
54
|
+
"code"
|
|
55
|
+
],
|
|
56
|
+
"files": [
|
|
57
|
+
"commands/cmd-tools-graphify.sh",
|
|
58
|
+
"commands/cmd-tools-obsidian.sh"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"functions": [
|
|
63
|
+
"scripts/functions/fun-graphify.sh",
|
|
64
|
+
"scripts/functions/fun-graphs.sh",
|
|
65
|
+
"scripts/functions/fun-obsidian.sh"
|
|
66
|
+
],
|
|
67
|
+
"args": [
|
|
68
|
+
"scripts/args/arg-graphify.sh",
|
|
69
|
+
"scripts/args/arg-graphs.sh",
|
|
70
|
+
"scripts/args/arg-obsidian.sh"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@types/node": "^24.0.0",
|
|
76
|
+
"@webship.co/workspace-plugin-api": "^1.0.0-beta1",
|
|
77
|
+
"typescript": "^5.9.0"
|
|
78
|
+
},
|
|
79
|
+
"publishConfig": {
|
|
80
|
+
"access": "public"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
argparse "$@" <<ARGEOF || exit 1
|
|
4
|
+
parser.add_argument('PROJECT_NAME',
|
|
5
|
+
help='The name of the project to map.')
|
|
6
|
+
parser.add_argument('-q', '--query',
|
|
7
|
+
default="",
|
|
8
|
+
help='Ask the existing graph a question instead of rebuilding it. Example: --query "what does this controller depend on?"')
|
|
9
|
+
parser.add_argument('-d', '--deep',
|
|
10
|
+
action='store_true',
|
|
11
|
+
default=False,
|
|
12
|
+
help='Add the semantic pass over docs, PDFs and images. Needs an LLM backend and sends file contents to it. It does not link Drupal YAML to PHP classes.')
|
|
13
|
+
parser.add_argument('-f', '--force',
|
|
14
|
+
action='store_true',
|
|
15
|
+
default=False,
|
|
16
|
+
help='Skip the incremental manifest gate and the semantic cache. Unchanged files are still replayed from graphify-out/cache/ast — delete that directory for a true re-parse.')
|
|
17
|
+
parser.add_argument('-S', '--scope',
|
|
18
|
+
default="contrib",
|
|
19
|
+
choices=['custom', 'contrib', 'all'],
|
|
20
|
+
help='How much of the tree to map: custom code only, custom + contrib modules/themes/profiles, or everything including core and vendor [default %(default)s].')
|
|
21
|
+
parser.add_argument('-a', '--all',
|
|
22
|
+
action='store_true',
|
|
23
|
+
default=False,
|
|
24
|
+
help='Same as --scope all: do not skip vendor/, core/, contrib or libraries. Slow, and the graph ends up dominated by third-party code.')
|
|
25
|
+
parser.add_argument('-Y', '--no-yaml',
|
|
26
|
+
action='store_true',
|
|
27
|
+
default=False,
|
|
28
|
+
help='Do not mirror the YAML as JSON. graphify parses no YAML of its own, so on a recipe or a config-heavy module this leaves the graph empty.')
|
|
29
|
+
parser.add_argument('-s', '--store',
|
|
30
|
+
default='_none_',
|
|
31
|
+
help='Fetch a URL into the project\'s ./raw and fold it into the graph, so the AI can read it later. Example: --store https://www.drupal.org/node/3586390')
|
|
32
|
+
parser.add_argument('-x', '--exclude',
|
|
33
|
+
default="_none_",
|
|
34
|
+
nargs='+',
|
|
35
|
+
help='Extra paths to skip, on top of the defaults. Example: --exclude "web/modules/custom/legacy tests"')
|
|
36
|
+
ARGEOF
|