@x12i/connectix-docs 0.1.0 → 0.1.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/dist/generated/bundle.d.ts.map +1 -1
- package/dist/generated/bundle.js +50 -8
- package/dist/generated/bundle.js.map +1 -1
- package/dist/web/INDEX.md +38 -0
- package/dist/web/SEARCH.json +157 -0
- package/dist/web/agent/INDEX.md +38 -0
- package/dist/web/agent/SEARCH.json +157 -0
- package/dist/web/agent/agent-manifest.json +120 -0
- package/dist/web/agent/indexes/books.json +17 -0
- package/dist/web/agent/indexes/books.md +4 -0
- package/dist/web/agent/indexes/concepts.json +4 -0
- package/dist/web/agent/indexes/concepts.md +3 -0
- package/dist/web/agent/indexes/diagrams.json +4 -0
- package/dist/web/agent/indexes/diagrams.md +3 -0
- package/dist/web/agent/indexes/guides.json +4 -0
- package/dist/web/agent/indexes/guides.md +3 -0
- package/dist/web/agent/indexes/scenarios.json +4 -0
- package/dist/web/agent/indexes/scenarios.md +3 -0
- package/dist/web/agent/indexes/tutorials.json +4 -0
- package/dist/web/agent/indexes/tutorials.md +3 -0
- package/dist/web/agent/indexes/use-cases.json +29 -0
- package/dist/web/agent/indexes/use-cases.md +6 -0
- package/dist/web/agent/use-cases/call-connect-tools.md +78 -0
- package/dist/web/agent/use-cases/catalog-extension-overview.md +42 -0
- package/dist/web/agent/use-cases/orient-connectix.md +95 -0
- package/dist/web/agent/use-cases/run-package-simulator.md +48 -0
- package/dist/web/agent-manifest.json +20 -2
- package/dist/web/agents.html +38 -0
- package/dist/web/app.js +449 -0
- package/dist/web/assets/extras.css +302 -0
- package/dist/web/assets/site.css +21 -0
- package/dist/web/books/00-orient-connectix.html +35 -2
- package/dist/web/books/01-tools-and-simulator.html +35 -2
- package/dist/web/catalog.html +38 -0
- package/dist/web/concepts.html +65 -0
- package/dist/web/diagrams.html +65 -0
- package/dist/web/docs.html +38 -0
- package/dist/web/explore.html +76 -0
- package/dist/web/getting-started.html +38 -0
- package/dist/web/guides.html +65 -0
- package/dist/web/index.html +39 -1
- package/dist/web/indexes/books.json +17 -0
- package/dist/web/indexes/books.md +4 -0
- package/dist/web/indexes/concepts.json +4 -0
- package/dist/web/indexes/concepts.md +3 -0
- package/dist/web/indexes/diagrams.json +4 -0
- package/dist/web/indexes/diagrams.md +3 -0
- package/dist/web/indexes/guides.json +4 -0
- package/dist/web/indexes/guides.md +3 -0
- package/dist/web/indexes/scenarios.json +4 -0
- package/dist/web/indexes/scenarios.md +3 -0
- package/dist/web/indexes/tutorials.json +4 -0
- package/dist/web/indexes/tutorials.md +3 -0
- package/dist/web/indexes/use-cases.json +29 -0
- package/dist/web/indexes/use-cases.md +6 -0
- package/dist/web/knowledge.json +15 -0
- package/dist/web/read.html +161 -74
- package/dist/web/scenarios.html +65 -0
- package/dist/web/search.html +71 -0
- package/dist/web/site.json +1 -1
- package/dist/web/use-cases/call-connect-tools.md +78 -0
- package/dist/web/use-cases/catalog-extension-overview.md +42 -0
- package/dist/web/use-cases/orient-connectix.md +95 -0
- package/dist/web/use-cases/run-package-simulator.md +48 -0
- package/dist/web/use-cases.html +38 -0
- package/dist/web/use-cases.json +28 -4
- package/package.json +7 -7
package/dist/web/app.js
CHANGED
|
@@ -22,6 +22,20 @@ function loadSite() {
|
|
|
22
22
|
return loadJson("/site.json");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function loadSequences() {
|
|
26
|
+
return loadJson("/sequences.json").then((d) => d.sequences || d);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function loadChapters(bookId, audience) {
|
|
30
|
+
return loadJson(
|
|
31
|
+
"/downloads/books/" +
|
|
32
|
+
encodeURIComponent(bookId) +
|
|
33
|
+
"/" +
|
|
34
|
+
encodeURIComponent(audience) +
|
|
35
|
+
".chapters.json",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
function escapeHtml(s) {
|
|
26
40
|
return String(s).replace(/[&<>"']/g, (c) =>
|
|
27
41
|
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c],
|
|
@@ -149,6 +163,135 @@ function hydrateUseCases(root) {
|
|
|
149
163
|
});
|
|
150
164
|
}
|
|
151
165
|
|
|
166
|
+
function readHref(bookId, audience, chapter) {
|
|
167
|
+
return (
|
|
168
|
+
"/read?book=" +
|
|
169
|
+
encodeURIComponent(bookId) +
|
|
170
|
+
"&role=" +
|
|
171
|
+
encodeURIComponent(audience) +
|
|
172
|
+
(chapter ? "#" + encodeURIComponent(chapter) : "")
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Compact chapter index into one book (tutorials section). */
|
|
177
|
+
function hydrateTutorials(section) {
|
|
178
|
+
const bookId = section.getAttribute("data-book");
|
|
179
|
+
const audience = section.getAttribute("data-audience") || "developers";
|
|
180
|
+
const root = section.querySelector(".tutorials-root");
|
|
181
|
+
if (!bookId || !root) return;
|
|
182
|
+
loadChapters(bookId, audience)
|
|
183
|
+
.then((chapters) => {
|
|
184
|
+
const items = (chapters || [])
|
|
185
|
+
.filter((c) => c.slug !== "preamble")
|
|
186
|
+
.map(
|
|
187
|
+
(c, i) =>
|
|
188
|
+
`<li><span class="step-num">${i + 1}</span><span class="step-text"><a href="${readHref(bookId, audience, c.slug)}">${escapeHtml(c.title)}</a></span></li>`,
|
|
189
|
+
)
|
|
190
|
+
.join("");
|
|
191
|
+
root.innerHTML = `<ol class="step-list tutorials-list">${items}</ol>`;
|
|
192
|
+
})
|
|
193
|
+
.catch(() => {
|
|
194
|
+
/* keep the static "Start reading" fallback */
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Interactive journey: ordered steps (required vs optional) → book chapters. */
|
|
199
|
+
function hydrateJourney(root) {
|
|
200
|
+
Promise.all([loadSequences(), loadCatalog(), loadSite()]).then(
|
|
201
|
+
([sequences, catalog, site]) => {
|
|
202
|
+
if (site.accent) document.body.style.setProperty("--accent", site.accent);
|
|
203
|
+
if (site.accentAlt)
|
|
204
|
+
document.body.style.setProperty("--accent-alt", site.accentAlt);
|
|
205
|
+
|
|
206
|
+
const bookTitle = (id) => {
|
|
207
|
+
const b = catalog.books.find((x) => x.id === id);
|
|
208
|
+
return b ? b.title : id;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
let audienceFilter = "all";
|
|
212
|
+
|
|
213
|
+
const stepLinks = (step) =>
|
|
214
|
+
(step.links || [])
|
|
215
|
+
.filter(
|
|
216
|
+
(l) => audienceFilter === "all" || l.audience === audienceFilter,
|
|
217
|
+
)
|
|
218
|
+
.map((l) => {
|
|
219
|
+
const alt = l.audience === "builders";
|
|
220
|
+
const label =
|
|
221
|
+
l.label ||
|
|
222
|
+
bookTitle(l.bookId) + " — " + roleLabel(catalog, l.audience);
|
|
223
|
+
return `<a class="journey-link ${alt ? "alt" : ""}" href="${readHref(l.bookId, l.audience, (l.chapters || [])[0])}">${escapeHtml(label)} →</a>`;
|
|
224
|
+
})
|
|
225
|
+
.join("");
|
|
226
|
+
|
|
227
|
+
const render = () => {
|
|
228
|
+
const html = sequences
|
|
229
|
+
.map((seq) => {
|
|
230
|
+
const steps = (seq.steps || [])
|
|
231
|
+
.filter(
|
|
232
|
+
(s) =>
|
|
233
|
+
audienceFilter === "all" ||
|
|
234
|
+
!s.audience ||
|
|
235
|
+
s.audience === audienceFilter,
|
|
236
|
+
)
|
|
237
|
+
.map((s, i) => {
|
|
238
|
+
const links = stepLinks(s);
|
|
239
|
+
const cls = s.required === false ? "journey-step optional" : "journey-step";
|
|
240
|
+
return `<li class="${cls}">
|
|
241
|
+
<button type="button" class="journey-step-head" data-journey-toggle aria-expanded="false">
|
|
242
|
+
<span class="journey-num">${i + 1}</span>
|
|
243
|
+
<span class="journey-label">${escapeHtml(s.label)}</span>
|
|
244
|
+
<span class="journey-flag">${s.required === false ? "optional" : "required"}</span>
|
|
245
|
+
</button>
|
|
246
|
+
<div class="journey-panel" hidden>
|
|
247
|
+
${s.summary ? `<p class="journey-summary">${escapeHtml(s.summary)}</p>` : ""}
|
|
248
|
+
<div class="journey-links">${links || `<span class="journey-nolinks">No ${escapeHtml(audienceFilter)} chapter for this step — switch the filter.</span>`}</div>
|
|
249
|
+
</div>
|
|
250
|
+
</li>`;
|
|
251
|
+
})
|
|
252
|
+
.join("");
|
|
253
|
+
return `<div class="journey-seq">
|
|
254
|
+
<h2 class="journey-title">${escapeHtml(seq.title)}</h2>
|
|
255
|
+
${seq.lead ? `<p class="journey-lead">${escapeHtml(seq.lead)}</p>` : ""}
|
|
256
|
+
<ol class="journey-steps">${steps}</ol>
|
|
257
|
+
</div>`;
|
|
258
|
+
})
|
|
259
|
+
.join("");
|
|
260
|
+
|
|
261
|
+
const filters = ["all", "developers", "builders"]
|
|
262
|
+
.map(
|
|
263
|
+
(f) =>
|
|
264
|
+
`<button type="button" class="journey-filter ${f === audienceFilter ? "active" : ""} ${f === "builders" ? "alt" : ""}" data-journey-filter="${f}">${f === "all" ? "All" : escapeHtml(roleLabel(catalog, f))}</button>`,
|
|
265
|
+
)
|
|
266
|
+
.join("");
|
|
267
|
+
|
|
268
|
+
root.innerHTML = `<div class="journey-toolbar"><span class="journey-toolbar-label">Show steps for</span>${filters}</div>${html}`;
|
|
269
|
+
|
|
270
|
+
root.querySelectorAll("[data-journey-toggle]").forEach((btn) => {
|
|
271
|
+
btn.addEventListener("click", () => {
|
|
272
|
+
const panel = btn.parentElement.querySelector(".journey-panel");
|
|
273
|
+
const open = !panel.hidden;
|
|
274
|
+
panel.hidden = open;
|
|
275
|
+
btn.setAttribute("aria-expanded", String(!open));
|
|
276
|
+
btn.parentElement.classList.toggle("open", !open);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
root.querySelectorAll("[data-journey-filter]").forEach((btn) => {
|
|
280
|
+
btn.addEventListener("click", () => {
|
|
281
|
+
audienceFilter = btn.getAttribute("data-journey-filter");
|
|
282
|
+
render();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
render();
|
|
288
|
+
},
|
|
289
|
+
).catch(() => {
|
|
290
|
+
root.innerHTML =
|
|
291
|
+
'<p class="journey-loading">No journey published for this library yet.</p>';
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
152
295
|
function hydratePage(kind) {
|
|
153
296
|
bindNavToggle();
|
|
154
297
|
if (kind === "catalog") {
|
|
@@ -159,6 +302,312 @@ function hydratePage(kind) {
|
|
|
159
302
|
const root = document.getElementById("use-cases-root");
|
|
160
303
|
if (root) hydrateUseCases(root);
|
|
161
304
|
}
|
|
305
|
+
if (kind === "journey") {
|
|
306
|
+
const root = document.getElementById("journey-root");
|
|
307
|
+
if (root) hydrateJourney(root);
|
|
308
|
+
}
|
|
309
|
+
document
|
|
310
|
+
.querySelectorAll('[data-hydrate="tutorials"]')
|
|
311
|
+
.forEach((section) => hydrateTutorials(section));
|
|
312
|
+
document
|
|
313
|
+
.querySelectorAll("[data-diagram-id]")
|
|
314
|
+
.forEach((el) => hydrateDiagram(el));
|
|
315
|
+
const searchRoot = document.getElementById("search-root");
|
|
316
|
+
if (searchRoot) hydrateSearch(searchRoot);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function hydrateSearch(root) {
|
|
320
|
+
root.innerHTML = `
|
|
321
|
+
<label class="search-label" for="docify-search">Search knowledge</label>
|
|
322
|
+
<input id="docify-search" type="search" class="search-input" placeholder="Concept, guide, diagram, API…" autocomplete="off" />
|
|
323
|
+
<div class="search-filters" role="group" aria-label="Filter by type">
|
|
324
|
+
<button type="button" class="journey-filter active" data-search-type="">All</button>
|
|
325
|
+
<button type="button" class="journey-filter" data-search-type="concept">Concepts</button>
|
|
326
|
+
<button type="button" class="journey-filter" data-search-type="guide">Guides</button>
|
|
327
|
+
<button type="button" class="journey-filter" data-search-type="scenario">Scenarios</button>
|
|
328
|
+
<button type="button" class="journey-filter" data-search-type="diagram">Diagrams</button>
|
|
329
|
+
<button type="button" class="journey-filter" data-search-type="use-case">Use cases</button>
|
|
330
|
+
</div>
|
|
331
|
+
<ul class="search-results" id="search-results" aria-live="polite"></ul>`;
|
|
332
|
+
let type = "";
|
|
333
|
+
let index = { records: [] };
|
|
334
|
+
loadJson("/SEARCH.json")
|
|
335
|
+
.catch(() => loadJson("/agent/SEARCH.json"))
|
|
336
|
+
.then((data) => {
|
|
337
|
+
index = data || { records: [] };
|
|
338
|
+
})
|
|
339
|
+
.catch(() => {});
|
|
340
|
+
|
|
341
|
+
const render = () => {
|
|
342
|
+
const q = (document.getElementById("docify-search").value || "").trim().toLowerCase();
|
|
343
|
+
let records = index.records || [];
|
|
344
|
+
if (type) records = records.filter((r) => r.type === type);
|
|
345
|
+
if (q) {
|
|
346
|
+
records = records.filter((r) =>
|
|
347
|
+
`${r.id} ${r.title} ${r.summary} ${(r.aliases || []).join(" ")} ${(r.tags || []).join(" ")}`
|
|
348
|
+
.toLowerCase()
|
|
349
|
+
.includes(q),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const ul = document.getElementById("search-results");
|
|
353
|
+
if (!records.length) {
|
|
354
|
+
ul.innerHTML = "<li class='search-empty'>No matches.</li>";
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
ul.innerHTML = records
|
|
358
|
+
.slice(0, 40)
|
|
359
|
+
.map((r) => {
|
|
360
|
+
const href = r.webPath || ("/" + String(r.path || "").replace(/\.md$/, ""));
|
|
361
|
+
return `<li><a href="${escapeHtml(href)}"><span class="search-type">${escapeHtml(r.type)}</span> <strong>${escapeHtml(r.title)}</strong> — ${escapeHtml(r.summary)}</a></li>`;
|
|
362
|
+
})
|
|
363
|
+
.join("");
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
document.getElementById("docify-search").addEventListener("input", render);
|
|
367
|
+
root.querySelectorAll("[data-search-type]").forEach((btn) => {
|
|
368
|
+
btn.addEventListener("click", () => {
|
|
369
|
+
type = btn.getAttribute("data-search-type") || "";
|
|
370
|
+
root.querySelectorAll("[data-search-type]").forEach((b) => b.classList.toggle("active", b === btn));
|
|
371
|
+
render();
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Accessible diagram: list + table views always; spatial SVG when nodes/edges exist.
|
|
378
|
+
* Never relies on color alone; keyboard-focusable nodes.
|
|
379
|
+
*/
|
|
380
|
+
function hydrateDiagram(el) {
|
|
381
|
+
const id = el.getAttribute("data-diagram-id");
|
|
382
|
+
const load = () =>
|
|
383
|
+
loadJson("/knowledge.json")
|
|
384
|
+
.then((k) => (k.diagrams || []).find((d) => d.id === id))
|
|
385
|
+
.catch(() => null);
|
|
386
|
+
|
|
387
|
+
load().then((diagram) => {
|
|
388
|
+
if (!diagram) {
|
|
389
|
+
el.innerHTML = "<p>Diagram not found.</p>";
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
let view = "diagram";
|
|
393
|
+
const render = () => {
|
|
394
|
+
const tabs = ["diagram", "list", "table", "steps"]
|
|
395
|
+
.map(
|
|
396
|
+
(v) =>
|
|
397
|
+
`<button type="button" class="journey-filter ${v === view ? "active" : ""}" data-diagram-view="${v}">${v}</button>`,
|
|
398
|
+
)
|
|
399
|
+
.join("");
|
|
400
|
+
let body = "";
|
|
401
|
+
if (view === "list" || view === "steps") {
|
|
402
|
+
body =
|
|
403
|
+
`<ol class="diagram-list">` +
|
|
404
|
+
(diagram.nodes || [])
|
|
405
|
+
.map(
|
|
406
|
+
(n) =>
|
|
407
|
+
`<li tabindex="0"><strong>${escapeHtml(n.label)}</strong>${n.description ? " — " + escapeHtml(n.description) : ""}${n.conceptId ? ` · concept <code>${escapeHtml(n.conceptId)}</code>` : ""}</li>`,
|
|
408
|
+
)
|
|
409
|
+
.join("") +
|
|
410
|
+
`</ol>`;
|
|
411
|
+
} else if (view === "table") {
|
|
412
|
+
body =
|
|
413
|
+
`<table class="diagram-table"><caption>${escapeHtml(diagram.title)} relationships</caption><thead><tr><th>From</th><th>To</th><th>Label</th><th>Meaning</th></tr></thead><tbody>` +
|
|
414
|
+
(diagram.edges || [])
|
|
415
|
+
.map(
|
|
416
|
+
(e) =>
|
|
417
|
+
`<tr><td>${escapeHtml(e.from)}</td><td>${escapeHtml(e.to)}</td><td>${escapeHtml(e.label || "")}</td><td>${escapeHtml(e.description || "")}</td></tr>`,
|
|
418
|
+
)
|
|
419
|
+
.join("") +
|
|
420
|
+
`</tbody></table>`;
|
|
421
|
+
} else {
|
|
422
|
+
const nodes = diagram.nodes || [];
|
|
423
|
+
const w = Math.max(320, nodes.length * 140);
|
|
424
|
+
const h = 180;
|
|
425
|
+
const circles = nodes
|
|
426
|
+
.map((n, i) => {
|
|
427
|
+
const x = 60 + i * 140;
|
|
428
|
+
const y = 90;
|
|
429
|
+
return `<g tabindex="0" role="img" aria-label="${escapeHtml(n.label)}. ${escapeHtml(n.description || "")}">
|
|
430
|
+
<circle cx="${x}" cy="${y}" r="28" fill="var(--surface, #fff)" stroke="currentColor" stroke-width="2" />
|
|
431
|
+
<text x="${x}" y="${y + 4}" text-anchor="middle" font-size="11">${escapeHtml(n.label).slice(0, 12)}</text>
|
|
432
|
+
</g>`;
|
|
433
|
+
})
|
|
434
|
+
.join("");
|
|
435
|
+
const lines = (diagram.edges || [])
|
|
436
|
+
.map((e) => {
|
|
437
|
+
const fi = nodes.findIndex((n) => n.id === e.from);
|
|
438
|
+
const ti = nodes.findIndex((n) => n.id === e.to);
|
|
439
|
+
if (fi < 0 || ti < 0) return "";
|
|
440
|
+
const x1 = 60 + fi * 140;
|
|
441
|
+
const x2 = 60 + ti * 140;
|
|
442
|
+
return `<line x1="${x1 + 28}" y1="90" x2="${x2 - 28}" y2="90" stroke="currentColor" stroke-width="2" marker-end="url(#arrow)" />`;
|
|
443
|
+
})
|
|
444
|
+
.join("");
|
|
445
|
+
body = `<div class="diagram-spatial" role="region" aria-label="${escapeHtml(diagram.title)}">
|
|
446
|
+
<svg viewBox="0 0 ${w} ${h}" width="100%" height="${h}" role="img" aria-describedby="diagram-alt-${escapeHtml(diagram.id)}">
|
|
447
|
+
<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="currentColor"/></marker></defs>
|
|
448
|
+
${lines}${circles}
|
|
449
|
+
</svg>
|
|
450
|
+
</div>`;
|
|
451
|
+
}
|
|
452
|
+
el.innerHTML = `
|
|
453
|
+
<section class="diagram-block">
|
|
454
|
+
<header>
|
|
455
|
+
<h2>${escapeHtml(diagram.title)}</h2>
|
|
456
|
+
<p>${escapeHtml(diagram.summary)}</p>
|
|
457
|
+
<p><strong>Why:</strong> ${escapeHtml(diagram.why)}</p>
|
|
458
|
+
<p><strong>How to read:</strong> ${escapeHtml(diagram.howToRead)}</p>
|
|
459
|
+
</header>
|
|
460
|
+
<div class="diagram-toolbar" role="toolbar" aria-label="Diagram views">${tabs}</div>
|
|
461
|
+
${body}
|
|
462
|
+
<p id="diagram-alt-${escapeHtml(diagram.id)}" class="diagram-text-alt"><strong>Text alternative:</strong> ${escapeHtml(diagram.textAlternative)}</p>
|
|
463
|
+
${(diagram.keyTakeaways || []).length ? `<ul>${diagram.keyTakeaways.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>` : ""}
|
|
464
|
+
${diagram.mermaid ? `<details><summary>Mermaid source</summary><pre><code>${escapeHtml(diagram.mermaid)}</code></pre></details>` : ""}
|
|
465
|
+
</section>`;
|
|
466
|
+
el.querySelectorAll("[data-diagram-view]").forEach((btn) => {
|
|
467
|
+
btn.addEventListener("click", () => {
|
|
468
|
+
view = btn.getAttribute("data-diagram-view");
|
|
469
|
+
render();
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
};
|
|
473
|
+
render();
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function hydrateRelationshipExplorer(root, panel) {
|
|
478
|
+
if (!root || !panel) return;
|
|
479
|
+
let view = "list";
|
|
480
|
+
let focusId = new URLSearchParams(location.search).get("id") || "";
|
|
481
|
+
let knowledge = { relationships: [] };
|
|
482
|
+
|
|
483
|
+
const render = () => {
|
|
484
|
+
const rels = knowledge.relationships || [];
|
|
485
|
+
const filtered = focusId
|
|
486
|
+
? rels.filter((r) => r.from === focusId || r.to === focusId)
|
|
487
|
+
: rels;
|
|
488
|
+
root.innerHTML =
|
|
489
|
+
'<label for="explore-focus">Focus id</label>' +
|
|
490
|
+
'<input id="explore-focus" type="search" class="search-input" value="' +
|
|
491
|
+
escapeHtml(focusId) +
|
|
492
|
+
'" placeholder="concept or guide id" />' +
|
|
493
|
+
'<p class="muted">' +
|
|
494
|
+
filtered.length +
|
|
495
|
+
" declared relationship(s)</p>";
|
|
496
|
+
const input = document.getElementById("explore-focus");
|
|
497
|
+
input.addEventListener("change", (e) => {
|
|
498
|
+
focusId = e.target.value.trim();
|
|
499
|
+
const u = new URL(location.href);
|
|
500
|
+
if (focusId) u.searchParams.set("id", focusId);
|
|
501
|
+
else u.searchParams.delete("id");
|
|
502
|
+
history.replaceState(null, "", u);
|
|
503
|
+
render();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
if (view === "table") {
|
|
507
|
+
panel.innerHTML =
|
|
508
|
+
'<table class="diagram-table"><caption>Declared relationships</caption><thead><tr><th>Type</th><th>From</th><th>To</th><th>Label</th></tr></thead><tbody>' +
|
|
509
|
+
filtered
|
|
510
|
+
.map(
|
|
511
|
+
(r) =>
|
|
512
|
+
"<tr><td>" +
|
|
513
|
+
escapeHtml(String(r.type)) +
|
|
514
|
+
'</td><td><a href="/explore?id=' +
|
|
515
|
+
encodeURIComponent(r.from) +
|
|
516
|
+
'">' +
|
|
517
|
+
escapeHtml(r.from) +
|
|
518
|
+
'</a></td><td><a href="/explore?id=' +
|
|
519
|
+
encodeURIComponent(r.to) +
|
|
520
|
+
'">' +
|
|
521
|
+
escapeHtml(r.to) +
|
|
522
|
+
"</a></td><td>" +
|
|
523
|
+
escapeHtml(r.label || r.description || "") +
|
|
524
|
+
"</td></tr>",
|
|
525
|
+
)
|
|
526
|
+
.join("") +
|
|
527
|
+
"</tbody></table>";
|
|
528
|
+
} else if (view === "diagram") {
|
|
529
|
+
const nodes = Array.from(new Set(filtered.flatMap((r) => [r.from, r.to])));
|
|
530
|
+
panel.innerHTML =
|
|
531
|
+
'<div class="diagram-spatial" role="region" aria-label="Relationship map"><svg viewBox="0 0 ' +
|
|
532
|
+
Math.max(320, nodes.length * 120) +
|
|
533
|
+
' 160" width="100%" height="160">' +
|
|
534
|
+
nodes
|
|
535
|
+
.map((id, i) => {
|
|
536
|
+
const x = 50 + i * 120;
|
|
537
|
+
return (
|
|
538
|
+
'<g tabindex="0" role="img" aria-label="' +
|
|
539
|
+
escapeHtml(id) +
|
|
540
|
+
'"><circle cx="' +
|
|
541
|
+
x +
|
|
542
|
+
'" cy="80" r="24" fill="var(--surface,#fff)" stroke="currentColor" stroke-width="2"/><text x="' +
|
|
543
|
+
x +
|
|
544
|
+
'" y="84" text-anchor="middle" font-size="10">' +
|
|
545
|
+
escapeHtml(id).slice(0, 10) +
|
|
546
|
+
"</text></g>"
|
|
547
|
+
);
|
|
548
|
+
})
|
|
549
|
+
.join("") +
|
|
550
|
+
filtered
|
|
551
|
+
.map((r) => {
|
|
552
|
+
const a = nodes.indexOf(r.from);
|
|
553
|
+
const b = nodes.indexOf(r.to);
|
|
554
|
+
if (a < 0 || b < 0) return "";
|
|
555
|
+
return (
|
|
556
|
+
'<line x1="' +
|
|
557
|
+
(50 + a * 120) +
|
|
558
|
+
'" y1="80" x2="' +
|
|
559
|
+
(50 + b * 120) +
|
|
560
|
+
'" y2="80" stroke="currentColor" stroke-width="2"/>'
|
|
561
|
+
);
|
|
562
|
+
})
|
|
563
|
+
.join("") +
|
|
564
|
+
'</svg><p class="diagram-text-alt">Text alternative: ' +
|
|
565
|
+
(filtered.map((r) => r.from + " " + r.type + " " + r.to).join("; ") ||
|
|
566
|
+
"No relationships") +
|
|
567
|
+
".</p></div>";
|
|
568
|
+
} else {
|
|
569
|
+
panel.innerHTML =
|
|
570
|
+
'<ul class="search-results">' +
|
|
571
|
+
(filtered
|
|
572
|
+
.map(
|
|
573
|
+
(r) =>
|
|
574
|
+
"<li><code>" +
|
|
575
|
+
escapeHtml(String(r.type)) +
|
|
576
|
+
'</code> <a href="/explore?id=' +
|
|
577
|
+
encodeURIComponent(r.from) +
|
|
578
|
+
'">' +
|
|
579
|
+
escapeHtml(r.from) +
|
|
580
|
+
'</a> → <a href="/explore?id=' +
|
|
581
|
+
encodeURIComponent(r.to) +
|
|
582
|
+
'">' +
|
|
583
|
+
escapeHtml(r.to) +
|
|
584
|
+
"</a>" +
|
|
585
|
+
(r.description ? " — " + escapeHtml(r.description) : "") +
|
|
586
|
+
"</li>",
|
|
587
|
+
)
|
|
588
|
+
.join("") || "<li>No declared relationships.</li>") +
|
|
589
|
+
"</ul>";
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
document.querySelectorAll("[data-explore-view]").forEach((btn) => {
|
|
594
|
+
btn.addEventListener("click", () => {
|
|
595
|
+
view = btn.getAttribute("data-explore-view") || "list";
|
|
596
|
+
document
|
|
597
|
+
.querySelectorAll("[data-explore-view]")
|
|
598
|
+
.forEach((b) => b.classList.toggle("active", b === btn));
|
|
599
|
+
render();
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
loadJson("/knowledge.json")
|
|
604
|
+
.then((k) => {
|
|
605
|
+
knowledge = k || knowledge;
|
|
606
|
+
render();
|
|
607
|
+
})
|
|
608
|
+
.catch(() => {
|
|
609
|
+
panel.innerHTML = "<p>No knowledge.json published yet.</p>";
|
|
610
|
+
});
|
|
162
611
|
}
|
|
163
612
|
|
|
164
613
|
bindNavToggle();
|