jekyll-theme-zer0 1.2.0 → 1.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/README.md +28 -4
- data/_includes/components/cookie-consent.html +5 -1
- data/_includes/components/dev-shortcuts.html +13 -0
- data/_includes/components/info-section.html +18 -2
- data/_includes/components/js-cdn.html +5 -1
- data/_includes/content/backlinks.html +103 -0
- data/_includes/content/transclude.html +62 -0
- data/_includes/core/footer.html +35 -9
- data/_includes/navigation/local-graph.html +44 -0
- data/_includes/navigation/sidebar-left.html +6 -0
- data/_layouts/default.html +9 -0
- data/_layouts/note.html +7 -1
- data/_layouts/welcome.html +2 -2
- data/_plugins/obsidian_links.rb +427 -0
- data/_sass/core/_obsidian.scss +169 -0
- data/_sass/custom.scss +3 -0
- data/assets/data/wiki-index.json +74 -0
- data/assets/js/obsidian-graph.js +475 -0
- data/assets/js/obsidian-local-graph.js +382 -0
- data/assets/js/obsidian-wiki-links.js +341 -0
- data/scripts/lint-pages +6 -0
- metadata +11 -2
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* obsidian-graph.js
|
|
3
|
+
*
|
|
4
|
+
* Renders an Obsidian-style interactive knowledge graph from
|
|
5
|
+
* /assets/data/wiki-index.json into the element with id `obsidian-graph`.
|
|
6
|
+
*
|
|
7
|
+
* Loaded only on the graph page (pages/_docs/obsidian/graph.md), which
|
|
8
|
+
* also pulls in cytoscape.js from a CDN. No runtime dependencies are
|
|
9
|
+
* added to the rest of the site.
|
|
10
|
+
*
|
|
11
|
+
* Nodes: one per indexed entry (collection doc or standalone page)
|
|
12
|
+
* Edges: directed, source -> target, derived from `entry.outgoing`
|
|
13
|
+
* (normalized [[wiki-link]] targets emitted by
|
|
14
|
+
* assets/data/wiki-index.json — see the Liquid template there).
|
|
15
|
+
*
|
|
16
|
+
* Targets are matched against the same lookup table the client-side
|
|
17
|
+
* resolver uses (title / basename / aliases, all normalized). Unresolved
|
|
18
|
+
* targets become floating "broken" nodes drawn in red so the graph also
|
|
19
|
+
* surfaces dangling links.
|
|
20
|
+
*
|
|
21
|
+
* Public hooks (read-only):
|
|
22
|
+
* window.ObsidianGraph.cy — cytoscape instance once initialized
|
|
23
|
+
* window.ObsidianGraph.byKey — normalized lookup table
|
|
24
|
+
* window.ObsidianGraph.entries — raw entries array
|
|
25
|
+
*/
|
|
26
|
+
(function () {
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
var CONTAINER_ID = 'obsidian-graph';
|
|
30
|
+
var INDEX_URL = (window.OBSIDIAN_WIKI_INDEX_URL ||
|
|
31
|
+
((document.querySelector('base') || {}).href || '/') + 'assets/data/wiki-index.json');
|
|
32
|
+
|
|
33
|
+
function normalize(value) {
|
|
34
|
+
return String(value || '').toLowerCase().trim().replace(/\s+/g, ' ');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildLookup(entries) {
|
|
38
|
+
var byKey = Object.create(null);
|
|
39
|
+
entries.forEach(function (entry) {
|
|
40
|
+
if (!entry || !entry.url) return;
|
|
41
|
+
var keys = [];
|
|
42
|
+
if (entry.title) keys.push(entry.title);
|
|
43
|
+
if (entry.basename) keys.push(entry.basename);
|
|
44
|
+
(entry.aliases || []).forEach(function (a) { if (a) keys.push(a); });
|
|
45
|
+
keys.forEach(function (k) {
|
|
46
|
+
var nk = normalize(k);
|
|
47
|
+
if (!nk || byKey[nk]) return; // first wins, mirrors plugin/resolver
|
|
48
|
+
byKey[nk] = entry;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
return byKey;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Detect Bootstrap's color-mode at init time so cytoscape gets concrete
|
|
55
|
+
// hex values (it rejects `var(--bs-…)`). We re-read on toggle so the
|
|
56
|
+
// graph stays legible when users flip light/dark.
|
|
57
|
+
function readTheme() {
|
|
58
|
+
var attr = (document.documentElement.getAttribute('data-bs-theme') ||
|
|
59
|
+
document.body.getAttribute('data-bs-theme') || '').toLowerCase();
|
|
60
|
+
var dark = attr === 'dark' || (!attr &&
|
|
61
|
+
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
62
|
+
return dark ? {
|
|
63
|
+
label: '#e9ecef',
|
|
64
|
+
labelOutline: '#1b1f23',
|
|
65
|
+
edge: 'rgba(173,181,189,0.45)',
|
|
66
|
+
edgeArrow: 'rgba(173,181,189,0.65)',
|
|
67
|
+
canvasBg: '#1b1f23',
|
|
68
|
+
nodeBorder: 'rgba(255,255,255,0.22)'
|
|
69
|
+
} : {
|
|
70
|
+
label: '#1b1f23',
|
|
71
|
+
labelOutline: '#f8f9fa',
|
|
72
|
+
edge: 'rgba(73,80,87,0.40)',
|
|
73
|
+
edgeArrow: 'rgba(73,80,87,0.60)',
|
|
74
|
+
canvasBg: '#f8f9fa',
|
|
75
|
+
nodeBorder: 'rgba(0,0,0,0.20)'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function collectionColor(name) {
|
|
80
|
+
// Stable, distinguishable palette per collection. Falls back for pages.
|
|
81
|
+
var palette = {
|
|
82
|
+
posts: '#0d6efd', // primary blue
|
|
83
|
+
docs: '#198754', // success green
|
|
84
|
+
notes: '#6f42c1', // purple
|
|
85
|
+
notebooks: '#d63384', // pink
|
|
86
|
+
quickstart: '#fd7e14', // orange
|
|
87
|
+
about: '#20c997', // teal
|
|
88
|
+
hobbies: '#ffc107', // amber
|
|
89
|
+
news: '#6610f2', // indigo
|
|
90
|
+
services: '#0dcaf0' // cyan
|
|
91
|
+
};
|
|
92
|
+
return palette[name] || '#6c757d'; // gray for standalone pages
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildElements(entries, byKey) {
|
|
96
|
+
var elements = [];
|
|
97
|
+
var seenTargets = Object.create(null);
|
|
98
|
+
var brokenIds = Object.create(null);
|
|
99
|
+
|
|
100
|
+
entries.forEach(function (entry) {
|
|
101
|
+
var nid = entry.url;
|
|
102
|
+
elements.push({
|
|
103
|
+
group: 'nodes',
|
|
104
|
+
data: {
|
|
105
|
+
id: nid,
|
|
106
|
+
label: entry.title || entry.basename || nid,
|
|
107
|
+
url: entry.url,
|
|
108
|
+
collection: entry.collection || 'page',
|
|
109
|
+
color: collectionColor(entry.collection),
|
|
110
|
+
excerpt: entry.excerpt || '',
|
|
111
|
+
broken: false
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
seenTargets[nid] = true;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
entries.forEach(function (entry) {
|
|
118
|
+
(entry.outgoing || []).forEach(function (target) {
|
|
119
|
+
var nk = normalize(target);
|
|
120
|
+
var resolved = byKey[nk];
|
|
121
|
+
var targetId;
|
|
122
|
+
if (resolved) {
|
|
123
|
+
targetId = resolved.url;
|
|
124
|
+
if (targetId === entry.url) return; // skip self-loops, no insight
|
|
125
|
+
} else {
|
|
126
|
+
targetId = '__broken__:' + nk;
|
|
127
|
+
if (!brokenIds[targetId]) {
|
|
128
|
+
brokenIds[targetId] = true;
|
|
129
|
+
elements.push({
|
|
130
|
+
group: 'nodes',
|
|
131
|
+
data: {
|
|
132
|
+
id: targetId,
|
|
133
|
+
label: target,
|
|
134
|
+
url: null,
|
|
135
|
+
collection: 'broken',
|
|
136
|
+
color: '#dc3545',
|
|
137
|
+
excerpt: 'Unresolved wiki-link',
|
|
138
|
+
broken: true
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
elements.push({
|
|
144
|
+
group: 'edges',
|
|
145
|
+
data: {
|
|
146
|
+
id: 'e:' + entry.url + '->' + targetId,
|
|
147
|
+
source: entry.url,
|
|
148
|
+
target: targetId,
|
|
149
|
+
broken: !resolved
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return elements;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function computeNodeDegree(elements) {
|
|
159
|
+
var degree = Object.create(null);
|
|
160
|
+
elements.forEach(function (el) {
|
|
161
|
+
if (el.group !== 'edges') return;
|
|
162
|
+
degree[el.data.source] = (degree[el.data.source] || 0) + 1;
|
|
163
|
+
degree[el.data.target] = (degree[el.data.target] || 0) + 1;
|
|
164
|
+
});
|
|
165
|
+
elements.forEach(function (el) {
|
|
166
|
+
if (el.group !== 'nodes') return;
|
|
167
|
+
el.data.degree = degree[el.data.id] || 0;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function renderGraph(container, elements) {
|
|
172
|
+
if (typeof window.cytoscape !== 'function') {
|
|
173
|
+
container.innerHTML =
|
|
174
|
+
'<div class="alert alert-danger" role="alert">' +
|
|
175
|
+
'Graph view failed to load: <code>cytoscape</code> library is not ' +
|
|
176
|
+
'available. Check your network connection or content security policy.' +
|
|
177
|
+
'</div>';
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
var theme = readTheme();
|
|
182
|
+
container.style.backgroundColor = theme.canvasBg;
|
|
183
|
+
|
|
184
|
+
var cy = window.cytoscape({
|
|
185
|
+
container: container,
|
|
186
|
+
elements: elements,
|
|
187
|
+
wheelSensitivity: 0.2,
|
|
188
|
+
minZoom: 0.1,
|
|
189
|
+
maxZoom: 4,
|
|
190
|
+
style: [
|
|
191
|
+
{
|
|
192
|
+
selector: 'node',
|
|
193
|
+
style: {
|
|
194
|
+
'background-color': 'data(color)',
|
|
195
|
+
'label': 'data(label)',
|
|
196
|
+
'font-size': '11px',
|
|
197
|
+
'font-weight': 500,
|
|
198
|
+
'color': theme.label,
|
|
199
|
+
// Halo-only labels (outline matches canvas) — no white pill,
|
|
200
|
+
// no obstruction. Mirrors how Obsidian draws labels.
|
|
201
|
+
'text-outline-color': theme.labelOutline,
|
|
202
|
+
'text-outline-width': 2.5,
|
|
203
|
+
'text-outline-opacity': 0.95,
|
|
204
|
+
'text-background-opacity': 0,
|
|
205
|
+
'text-border-opacity': 0,
|
|
206
|
+
'text-valign': 'bottom',
|
|
207
|
+
'text-margin-y': 5,
|
|
208
|
+
'text-wrap': 'ellipsis',
|
|
209
|
+
'text-max-width': '140px',
|
|
210
|
+
// Labels appear when zoomed-in or hovered — keeps the
|
|
211
|
+
// overview clean instead of being a wall of text.
|
|
212
|
+
'min-zoomed-font-size': 9,
|
|
213
|
+
'text-opacity': 0,
|
|
214
|
+
'width': 'mapData(degree, 0, 20, 12, 50)',
|
|
215
|
+
'height': 'mapData(degree, 0, 20, 12, 50)',
|
|
216
|
+
'border-width': 1.5,
|
|
217
|
+
'border-color': theme.nodeBorder,
|
|
218
|
+
'transition-property': 'background-color, border-color, width, height, text-opacity',
|
|
219
|
+
'transition-duration': '160ms'
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
// Always show labels for hub nodes (degree >= 6) so the user
|
|
224
|
+
// has anchor landmarks even when zoomed all the way out.
|
|
225
|
+
selector: 'node[degree >= 6]',
|
|
226
|
+
style: {
|
|
227
|
+
'text-opacity': 1,
|
|
228
|
+
'font-size': '12px',
|
|
229
|
+
'font-weight': 600,
|
|
230
|
+
'min-zoomed-font-size': 0
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
selector: 'node[?broken]',
|
|
235
|
+
style: {
|
|
236
|
+
'border-style': 'dashed',
|
|
237
|
+
'border-color': '#dc3545',
|
|
238
|
+
'border-width': 2
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
selector: 'edge',
|
|
243
|
+
style: {
|
|
244
|
+
'curve-style': 'bezier',
|
|
245
|
+
'width': 1.5,
|
|
246
|
+
'line-color': theme.edge,
|
|
247
|
+
'target-arrow-color': theme.edgeArrow,
|
|
248
|
+
'target-arrow-shape': 'triangle',
|
|
249
|
+
'arrow-scale': 0.9,
|
|
250
|
+
'transition-property': 'line-color, width',
|
|
251
|
+
'transition-duration': '150ms'
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
selector: 'edge[?broken]',
|
|
256
|
+
style: {
|
|
257
|
+
'line-color': 'rgba(220,53,69,0.45)',
|
|
258
|
+
'target-arrow-color': 'rgba(220,53,69,0.55)',
|
|
259
|
+
'line-style': 'dashed'
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
selector: '.faded',
|
|
264
|
+
style: {
|
|
265
|
+
'opacity': 0.15,
|
|
266
|
+
'text-opacity': 0.15
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
selector: 'node.highlighted',
|
|
271
|
+
style: {
|
|
272
|
+
'border-color': '#fd7e14',
|
|
273
|
+
'border-width': 3,
|
|
274
|
+
'opacity': 1,
|
|
275
|
+
'text-opacity': 1,
|
|
276
|
+
'font-size': '12px',
|
|
277
|
+
'font-weight': 700,
|
|
278
|
+
'min-zoomed-font-size': 0,
|
|
279
|
+
'z-index': 9999
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
selector: 'edge.highlighted',
|
|
284
|
+
style: {
|
|
285
|
+
'line-color': '#fd7e14',
|
|
286
|
+
'target-arrow-color': '#fd7e14',
|
|
287
|
+
'width': 2.5,
|
|
288
|
+
'opacity': 1,
|
|
289
|
+
'z-index': 9999
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
],
|
|
293
|
+
layout: {
|
|
294
|
+
name: 'cose',
|
|
295
|
+
animate: false,
|
|
296
|
+
randomize: true,
|
|
297
|
+
// Looser packing so clusters have breathing room and labels
|
|
298
|
+
// don't pile on top of each other.
|
|
299
|
+
nodeRepulsion: function () { return 18000; },
|
|
300
|
+
idealEdgeLength: function () { return 130; },
|
|
301
|
+
edgeElasticity: function () { return 80; },
|
|
302
|
+
nodeOverlap: 24,
|
|
303
|
+
gravity: 0.18,
|
|
304
|
+
nestingFactor: 1.2,
|
|
305
|
+
numIter: 2500,
|
|
306
|
+
padding: 40,
|
|
307
|
+
componentSpacing: 80
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
cy.on('tap', 'node', function (evt) {
|
|
312
|
+
var node = evt.target;
|
|
313
|
+
var url = node.data('url');
|
|
314
|
+
if (url) {
|
|
315
|
+
// Same-tab navigation; use Cmd/Ctrl-click for new tab via the
|
|
316
|
+
// standard handler below.
|
|
317
|
+
var native = evt.originalEvent;
|
|
318
|
+
if (native && (native.metaKey || native.ctrlKey)) {
|
|
319
|
+
window.open(url, '_blank', 'noopener');
|
|
320
|
+
} else {
|
|
321
|
+
window.location.href = url;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Hover: highlight neighborhood, fade everything else.
|
|
327
|
+
cy.on('mouseover', 'node', function (evt) {
|
|
328
|
+
var node = evt.target;
|
|
329
|
+
var nhood = node.closedNeighborhood();
|
|
330
|
+
cy.elements().not(nhood).addClass('faded');
|
|
331
|
+
nhood.addClass('highlighted');
|
|
332
|
+
});
|
|
333
|
+
cy.on('mouseout', 'node', function () {
|
|
334
|
+
cy.elements().removeClass('faded highlighted');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
return cy;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function wireSearch(cy, byKey) {
|
|
341
|
+
var input = document.getElementById('obsidian-graph-search');
|
|
342
|
+
var status = document.getElementById('obsidian-graph-status');
|
|
343
|
+
if (!input || !cy) return;
|
|
344
|
+
|
|
345
|
+
function run() {
|
|
346
|
+
var q = normalize(input.value);
|
|
347
|
+
cy.elements().removeClass('faded highlighted');
|
|
348
|
+
if (!q) {
|
|
349
|
+
if (status) status.textContent = '';
|
|
350
|
+
cy.fit(undefined, 70);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
var matches = cy.nodes().filter(function (n) {
|
|
354
|
+
return normalize(n.data('label')).indexOf(q) !== -1;
|
|
355
|
+
});
|
|
356
|
+
if (matches.length === 0) {
|
|
357
|
+
if (status) status.textContent = 'No nodes match “' + input.value + '”.';
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
var nhood = matches.closedNeighborhood();
|
|
361
|
+
cy.elements().not(nhood).addClass('faded');
|
|
362
|
+
matches.addClass('highlighted');
|
|
363
|
+
if (status) status.textContent = matches.length + ' node' +
|
|
364
|
+
(matches.length === 1 ? '' : 's') + ' matched.';
|
|
365
|
+
cy.fit(matches, 100);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
input.addEventListener('input', run);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function wireFitButton(cy) {
|
|
372
|
+
var btn = document.getElementById('obsidian-graph-fit');
|
|
373
|
+
if (!btn || !cy) return;
|
|
374
|
+
btn.addEventListener('click', function () {
|
|
375
|
+
cy.elements().removeClass('faded highlighted');
|
|
376
|
+
var input = document.getElementById('obsidian-graph-search');
|
|
377
|
+
if (input) input.value = '';
|
|
378
|
+
var status = document.getElementById('obsidian-graph-status');
|
|
379
|
+
if (status) status.textContent = '';
|
|
380
|
+
cy.fit(undefined, 70);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function applyOrphansVisibility(cy, show) {
|
|
385
|
+
if (!cy) return;
|
|
386
|
+
var orphans = cy.nodes().filter(function (n) { return n.degree(false) === 0; });
|
|
387
|
+
if (show) {
|
|
388
|
+
orphans.style('display', 'element');
|
|
389
|
+
} else {
|
|
390
|
+
orphans.style('display', 'none');
|
|
391
|
+
}
|
|
392
|
+
// Re-run a quick layout pass on visible elements so the connected
|
|
393
|
+
// cluster expands into the freed space.
|
|
394
|
+
cy.layout({
|
|
395
|
+
name: 'cose',
|
|
396
|
+
animate: false,
|
|
397
|
+
randomize: false,
|
|
398
|
+
nodeRepulsion: function () { return 18000; },
|
|
399
|
+
idealEdgeLength: function () { return 130; },
|
|
400
|
+
edgeElasticity: function () { return 80; },
|
|
401
|
+
nodeOverlap: 24,
|
|
402
|
+
gravity: 0.18,
|
|
403
|
+
numIter: 1200,
|
|
404
|
+
padding: 40,
|
|
405
|
+
componentSpacing: 80,
|
|
406
|
+
eles: cy.elements(':visible')
|
|
407
|
+
}).run();
|
|
408
|
+
cy.fit(cy.elements(':visible'), 70);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function wireOrphansToggle(cy) {
|
|
412
|
+
var toggle = document.getElementById('obsidian-graph-orphans');
|
|
413
|
+
if (!toggle || !cy) return;
|
|
414
|
+
// Hide orphans by default to match Obsidian's "Show orphans" off state.
|
|
415
|
+
applyOrphansVisibility(cy, toggle.checked);
|
|
416
|
+
toggle.addEventListener('change', function () {
|
|
417
|
+
applyOrphansVisibility(cy, toggle.checked);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function setStats(entries, elements) {
|
|
422
|
+
var nodes = elements.filter(function (e) { return e.group === 'nodes'; });
|
|
423
|
+
var edges = elements.filter(function (e) { return e.group === 'edges'; });
|
|
424
|
+
var broken = nodes.filter(function (e) { return e.data.broken; }).length;
|
|
425
|
+
var el = document.getElementById('obsidian-graph-stats');
|
|
426
|
+
if (!el) return;
|
|
427
|
+
el.innerHTML =
|
|
428
|
+
'<span class="badge text-bg-secondary me-2">' + entries.length + ' pages</span>' +
|
|
429
|
+
'<span class="badge text-bg-info me-2">' + edges.length + ' links</span>' +
|
|
430
|
+
(broken > 0 ?
|
|
431
|
+
'<span class="badge text-bg-danger">' + broken + ' broken</span>' :
|
|
432
|
+
'<span class="badge text-bg-success">0 broken</span>');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function init() {
|
|
436
|
+
var container = document.getElementById(CONTAINER_ID);
|
|
437
|
+
if (!container) return;
|
|
438
|
+
|
|
439
|
+
container.innerHTML =
|
|
440
|
+
'<div class="d-flex align-items-center justify-content-center h-100 text-muted">' +
|
|
441
|
+
'<div class="spinner-border me-2" role="status" aria-hidden="true"></div>' +
|
|
442
|
+
'Loading graph data…</div>';
|
|
443
|
+
|
|
444
|
+
fetch(INDEX_URL, { credentials: 'same-origin' })
|
|
445
|
+
.then(function (r) {
|
|
446
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
447
|
+
return r.json();
|
|
448
|
+
})
|
|
449
|
+
.then(function (payload) {
|
|
450
|
+
var entries = (payload && payload.entries) || [];
|
|
451
|
+
var byKey = buildLookup(entries);
|
|
452
|
+
var elements = buildElements(entries, byKey);
|
|
453
|
+
computeNodeDegree(elements);
|
|
454
|
+
container.innerHTML = '';
|
|
455
|
+
setStats(entries, elements);
|
|
456
|
+
var cy = renderGraph(container, elements);
|
|
457
|
+
wireSearch(cy, byKey);
|
|
458
|
+
wireFitButton(cy);
|
|
459
|
+
wireOrphansToggle(cy);
|
|
460
|
+
window.ObsidianGraph = { cy: cy, byKey: byKey, entries: entries };
|
|
461
|
+
})
|
|
462
|
+
.catch(function (err) {
|
|
463
|
+
container.innerHTML =
|
|
464
|
+
'<div class="alert alert-danger" role="alert">' +
|
|
465
|
+
'Failed to load graph data: ' + (err && err.message ? err.message : err) +
|
|
466
|
+
'</div>';
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (document.readyState === 'loading') {
|
|
471
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
472
|
+
} else {
|
|
473
|
+
init();
|
|
474
|
+
}
|
|
475
|
+
})();
|