jekyll-theme-zer0 1.2.1 → 1.4.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 +67 -0
- data/README.md +28 -4
- data/_includes/components/js-cdn.html +12 -1
- data/_includes/content/backlinks.html +107 -0
- data/_includes/content/transclude.html +62 -0
- data/_includes/navigation/local-graph.html +79 -0
- data/_includes/navigation/sidebar-left.html +6 -0
- data/_layouts/default.html +104 -91
- data/_layouts/note.html +7 -1
- data/_plugins/obsidian_links.rb +427 -0
- data/_sass/core/_obsidian.scss +242 -0
- data/_sass/core/_offcanvas-panels.scss +8 -4
- 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 +434 -0
- data/assets/js/obsidian-wiki-links.js +358 -0
- data/scripts/README.md +22 -0
- data/scripts/bin/test +62 -0
- data/scripts/bin/validate +596 -0
- data/scripts/lib/README.md +16 -0
- data/scripts/lint-pages +6 -0
- data/scripts/validate +11 -0
- metadata +13 -2
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* obsidian-local-graph.js
|
|
3
|
+
*
|
|
4
|
+
* Renders a focused "local graph" (current page + immediate neighbors) into
|
|
5
|
+
* the element with id `obsidian-local-graph`. Mirrors Obsidian's local
|
|
6
|
+
* graph view: a focused, page-scoped subgraph instead of the full site map.
|
|
7
|
+
*
|
|
8
|
+
* Loaded by _includes/navigation/local-graph.html inside a dedicated
|
|
9
|
+
* collapsible side panel. Cytoscape.js is loaded lazily (and only once) from
|
|
10
|
+
* the same CDN as the full graph page.
|
|
11
|
+
*
|
|
12
|
+
* Subgraph:
|
|
13
|
+
* - center = current page (matched against entry.url, falling back to
|
|
14
|
+
* normalized title/basename/aliases for permalink quirks)
|
|
15
|
+
* - depth = configurable via data-depth attribute (default 1)
|
|
16
|
+
* - direction = both incoming and outgoing wiki-links
|
|
17
|
+
*
|
|
18
|
+
* If the current page is in the wiki-index but has no local links, the panel
|
|
19
|
+
* stays available and renders a single-node graph for the current page.
|
|
20
|
+
* Pages outside the wiki-index still hide the panel.
|
|
21
|
+
*/
|
|
22
|
+
(function () {
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
var CONTAINER_ID = 'obsidian-local-graph';
|
|
26
|
+
var PANEL_SELECTOR = '[data-obsidian-local-graph-panel]';
|
|
27
|
+
var TOGGLE_SELECTOR = '[data-obsidian-local-graph-toggle]';
|
|
28
|
+
var STATUS_SELECTOR = '[data-obsidian-local-graph-status]';
|
|
29
|
+
var CYTOSCAPE_URL = 'https://cdn.jsdelivr.net/npm/cytoscape@3.30.0/dist/cytoscape.min.js';
|
|
30
|
+
var CYTOSCAPE_SRI = 'sha384-kpMsYllYzyaWU69Piok08rPNktpnjqAoDMdB00fjqUkEk3lkuUbSuwJ+oXrjvN6B';
|
|
31
|
+
|
|
32
|
+
function companionElements(container) {
|
|
33
|
+
return {
|
|
34
|
+
panel: container.closest(PANEL_SELECTOR),
|
|
35
|
+
toggle: document.querySelector(TOGGLE_SELECTOR),
|
|
36
|
+
status: document.querySelector(STATUS_SELECTOR)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setStatus(container, message, isError) {
|
|
41
|
+
var status = companionElements(container).status;
|
|
42
|
+
if (!status) return;
|
|
43
|
+
status.textContent = message || '';
|
|
44
|
+
status.hidden = !message;
|
|
45
|
+
status.classList.toggle('text-danger', !!isError);
|
|
46
|
+
status.classList.toggle('text-secondary', !isError);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setPanelAvailable(container, available) {
|
|
50
|
+
var companions = companionElements(container);
|
|
51
|
+
[companions.panel, companions.toggle].forEach(function (element) {
|
|
52
|
+
if (!element) return;
|
|
53
|
+
element.hidden = !available;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resizeGraph(container) {
|
|
58
|
+
var cy = container.__obsidianLocalGraph;
|
|
59
|
+
if (!cy) return;
|
|
60
|
+
cy.resize();
|
|
61
|
+
cy.fit(cy.elements(), 24);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalize(value) {
|
|
65
|
+
return String(value || '').toLowerCase().trim().replace(/\s+/g, ' ');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Strip trailing slash and any leading site baseurl so we can match
|
|
69
|
+
// entry.url (which is always relative, with trailing slash).
|
|
70
|
+
function normalizePath(p) {
|
|
71
|
+
if (!p) return '';
|
|
72
|
+
try { p = decodeURIComponent(p); } catch (_) {}
|
|
73
|
+
p = p.split('#')[0].split('?')[0];
|
|
74
|
+
if (p.length > 1 && p.charAt(p.length - 1) !== '/') p += '/';
|
|
75
|
+
return p;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildLookup(entries) {
|
|
79
|
+
var byKey = Object.create(null);
|
|
80
|
+
var byUrl = Object.create(null);
|
|
81
|
+
entries.forEach(function (entry) {
|
|
82
|
+
if (!entry || !entry.url) return;
|
|
83
|
+
byUrl[normalizePath(entry.url)] = entry;
|
|
84
|
+
var keys = [];
|
|
85
|
+
if (entry.title) keys.push(entry.title);
|
|
86
|
+
if (entry.basename) keys.push(entry.basename);
|
|
87
|
+
(entry.aliases || []).forEach(function (a) { if (a) keys.push(a); });
|
|
88
|
+
keys.forEach(function (k) {
|
|
89
|
+
var nk = normalize(k);
|
|
90
|
+
if (!nk || byKey[nk]) return;
|
|
91
|
+
byKey[nk] = entry;
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
return { byKey: byKey, byUrl: byUrl };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function findCurrentEntry(lookup) {
|
|
98
|
+
var path = normalizePath(window.location.pathname);
|
|
99
|
+
if (lookup.byUrl[path]) return lookup.byUrl[path];
|
|
100
|
+
// Fallback: match by last path segment (handles baseurl mismatches).
|
|
101
|
+
var parts = path.split('/').filter(Boolean);
|
|
102
|
+
var last = parts[parts.length - 1];
|
|
103
|
+
if (last && lookup.byKey[normalize(last)]) {
|
|
104
|
+
return lookup.byKey[normalize(last)];
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function collectionColor(name) {
|
|
110
|
+
var palette = {
|
|
111
|
+
posts: '#0d6efd', docs: '#198754', notes: '#6f42c1',
|
|
112
|
+
notebooks: '#d63384', quickstart: '#fd7e14', about: '#20c997',
|
|
113
|
+
hobbies: '#ffc107', news: '#6610f2', services: '#0dcaf0'
|
|
114
|
+
};
|
|
115
|
+
return palette[name] || '#6c757d';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// BFS from the current entry up to `depth` hops, following both
|
|
119
|
+
// outgoing edges and incoming edges (any other entry whose `outgoing`
|
|
120
|
+
// includes one of our keys).
|
|
121
|
+
function buildSubgraph(entries, lookup, current, depth) {
|
|
122
|
+
var visited = Object.create(null);
|
|
123
|
+
var queue = [{ entry: current, dist: 0 }];
|
|
124
|
+
var nodes = [];
|
|
125
|
+
var edges = [];
|
|
126
|
+
var seenEdge = Object.create(null);
|
|
127
|
+
|
|
128
|
+
// Pre-compute reverse adjacency so we can find incoming neighbors
|
|
129
|
+
// without scanning all entries each hop.
|
|
130
|
+
var reverse = Object.create(null);
|
|
131
|
+
entries.forEach(function (entry) {
|
|
132
|
+
(entry.outgoing || []).forEach(function (target) {
|
|
133
|
+
var nk = normalize(target);
|
|
134
|
+
if (!reverse[nk]) reverse[nk] = [];
|
|
135
|
+
reverse[nk].push(entry);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
function keysFor(entry) {
|
|
140
|
+
var keys = [];
|
|
141
|
+
if (entry.title) keys.push(normalize(entry.title));
|
|
142
|
+
if (entry.basename) keys.push(normalize(entry.basename));
|
|
143
|
+
(entry.aliases || []).forEach(function (a) {
|
|
144
|
+
if (a) keys.push(normalize(a));
|
|
145
|
+
});
|
|
146
|
+
return keys;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function addEdge(srcId, tgtId, broken) {
|
|
150
|
+
var k = srcId + '|' + tgtId;
|
|
151
|
+
if (seenEdge[k]) return;
|
|
152
|
+
seenEdge[k] = true;
|
|
153
|
+
edges.push({
|
|
154
|
+
group: 'edges',
|
|
155
|
+
data: { id: 'le:' + k, source: srcId, target: tgtId, broken: !!broken }
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
while (queue.length) {
|
|
160
|
+
var item = queue.shift();
|
|
161
|
+
var entry = item.entry;
|
|
162
|
+
var nid = entry.url;
|
|
163
|
+
if (visited[nid]) continue;
|
|
164
|
+
visited[nid] = true;
|
|
165
|
+
|
|
166
|
+
nodes.push({
|
|
167
|
+
group: 'nodes',
|
|
168
|
+
data: {
|
|
169
|
+
id: nid,
|
|
170
|
+
label: entry.title || entry.basename || nid,
|
|
171
|
+
url: entry.url,
|
|
172
|
+
collection: entry.collection || 'page',
|
|
173
|
+
color: collectionColor(entry.collection),
|
|
174
|
+
isCurrent: entry.url === current.url
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (item.dist >= depth) continue;
|
|
179
|
+
|
|
180
|
+
// Outgoing edges
|
|
181
|
+
(entry.outgoing || []).forEach(function (target) {
|
|
182
|
+
var nk = normalize(target);
|
|
183
|
+
var resolved = lookup.byKey[nk];
|
|
184
|
+
if (resolved) {
|
|
185
|
+
if (resolved.url === entry.url) return;
|
|
186
|
+
addEdge(entry.url, resolved.url, false);
|
|
187
|
+
if (!visited[resolved.url]) {
|
|
188
|
+
queue.push({ entry: resolved, dist: item.dist + 1 });
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
var brokenId = '__broken__:' + nk;
|
|
192
|
+
if (!visited[brokenId]) {
|
|
193
|
+
visited[brokenId] = true;
|
|
194
|
+
nodes.push({
|
|
195
|
+
group: 'nodes',
|
|
196
|
+
data: {
|
|
197
|
+
id: brokenId,
|
|
198
|
+
label: target,
|
|
199
|
+
url: null,
|
|
200
|
+
collection: 'broken',
|
|
201
|
+
color: '#dc3545',
|
|
202
|
+
broken: true
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
addEdge(entry.url, brokenId, true);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Incoming edges (anyone whose outgoing matches one of our keys)
|
|
211
|
+
keysFor(entry).forEach(function (k) {
|
|
212
|
+
(reverse[k] || []).forEach(function (src) {
|
|
213
|
+
if (src.url === entry.url) return;
|
|
214
|
+
addEdge(src.url, entry.url, false);
|
|
215
|
+
if (!visited[src.url]) {
|
|
216
|
+
queue.push({ entry: src, dist: item.dist + 1 });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return nodes.concat(edges);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function readTheme() {
|
|
226
|
+
var attr = (document.documentElement.getAttribute('data-bs-theme') ||
|
|
227
|
+
document.body.getAttribute('data-bs-theme') || '').toLowerCase();
|
|
228
|
+
var dark = attr === 'dark' || (!attr && window.matchMedia &&
|
|
229
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
230
|
+
return dark ? {
|
|
231
|
+
label: '#e9ecef', labelOutline: '#1b1f23',
|
|
232
|
+
edge: 'rgba(173,181,189,0.45)', edgeArrow: 'rgba(173,181,189,0.65)',
|
|
233
|
+
canvasBg: '#1b1f23', nodeBorder: 'rgba(255,255,255,0.22)'
|
|
234
|
+
} : {
|
|
235
|
+
label: '#1b1f23', labelOutline: '#f8f9fa',
|
|
236
|
+
edge: 'rgba(73,80,87,0.40)', edgeArrow: 'rgba(73,80,87,0.60)',
|
|
237
|
+
canvasBg: '#f8f9fa', nodeBorder: 'rgba(0,0,0,0.20)'
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function loadCytoscape(cb) {
|
|
242
|
+
if (typeof window.cytoscape === 'function') return cb();
|
|
243
|
+
// Re-use any in-flight load (e.g. when the full graph page also loads it).
|
|
244
|
+
if (window.__obsidianCytoscapeLoading) {
|
|
245
|
+
window.__obsidianCytoscapeLoading.push(cb);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
window.__obsidianCytoscapeLoading = [cb];
|
|
249
|
+
var existing = document.querySelector('script[src*="cytoscape"]');
|
|
250
|
+
if (existing) {
|
|
251
|
+
existing.addEventListener('load', function () {
|
|
252
|
+
window.__obsidianCytoscapeLoading.forEach(function (fn) { fn(); });
|
|
253
|
+
window.__obsidianCytoscapeLoading = null;
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
var s = document.createElement('script');
|
|
258
|
+
s.src = CYTOSCAPE_URL;
|
|
259
|
+
s.integrity = CYTOSCAPE_SRI;
|
|
260
|
+
s.crossOrigin = 'anonymous';
|
|
261
|
+
s.defer = true;
|
|
262
|
+
s.onload = function () {
|
|
263
|
+
window.__obsidianCytoscapeLoading.forEach(function (fn) { fn(); });
|
|
264
|
+
window.__obsidianCytoscapeLoading = null;
|
|
265
|
+
};
|
|
266
|
+
s.onerror = function () {
|
|
267
|
+
console.warn('[obsidian-local-graph] failed to load cytoscape');
|
|
268
|
+
window.__obsidianCytoscapeLoading = null;
|
|
269
|
+
};
|
|
270
|
+
document.head.appendChild(s);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function render(container, elements, currentUrl) {
|
|
274
|
+
var theme = readTheme();
|
|
275
|
+
container.style.backgroundColor = theme.canvasBg;
|
|
276
|
+
|
|
277
|
+
var cy = window.cytoscape({
|
|
278
|
+
container: container,
|
|
279
|
+
elements: elements,
|
|
280
|
+
wheelSensitivity: 0.2,
|
|
281
|
+
minZoom: 0.3,
|
|
282
|
+
maxZoom: 3,
|
|
283
|
+
autoungrabify: false,
|
|
284
|
+
style: [
|
|
285
|
+
{
|
|
286
|
+
selector: 'node',
|
|
287
|
+
style: {
|
|
288
|
+
'background-color': 'data(color)',
|
|
289
|
+
'label': 'data(label)',
|
|
290
|
+
'font-size': '11px',
|
|
291
|
+
'font-weight': 500,
|
|
292
|
+
'color': theme.label,
|
|
293
|
+
'text-outline-color': theme.labelOutline,
|
|
294
|
+
'text-outline-width': 2,
|
|
295
|
+
'text-outline-opacity': 0.95,
|
|
296
|
+
'text-background-opacity': 0,
|
|
297
|
+
'text-valign': 'bottom',
|
|
298
|
+
'text-margin-y': 4,
|
|
299
|
+
'text-wrap': 'ellipsis',
|
|
300
|
+
'text-max-width': '140px',
|
|
301
|
+
'width': 16, 'height': 16,
|
|
302
|
+
'border-width': 1.5,
|
|
303
|
+
'border-color': theme.nodeBorder,
|
|
304
|
+
'transition-property': 'background-color, border-color, width, height',
|
|
305
|
+
'transition-duration': '160ms'
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
// Highlight the current page so users always know "you are here".
|
|
310
|
+
selector: 'node[?isCurrent]',
|
|
311
|
+
style: {
|
|
312
|
+
'width': 22, 'height': 22,
|
|
313
|
+
'border-width': 3,
|
|
314
|
+
'border-color': '#fd7e14',
|
|
315
|
+
'font-size': '11px',
|
|
316
|
+
'font-weight': 700
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
selector: 'node[broken]',
|
|
321
|
+
style: {
|
|
322
|
+
'background-color': '#dc3545',
|
|
323
|
+
'border-style': 'dashed',
|
|
324
|
+
'border-color': '#dc3545'
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
selector: 'edge',
|
|
329
|
+
style: {
|
|
330
|
+
'width': 1,
|
|
331
|
+
'line-color': theme.edge,
|
|
332
|
+
'target-arrow-color': theme.edgeArrow,
|
|
333
|
+
'target-arrow-shape': 'triangle',
|
|
334
|
+
'arrow-scale': 0.7,
|
|
335
|
+
'curve-style': 'bezier'
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
selector: 'edge[broken]',
|
|
340
|
+
style: { 'line-style': 'dashed', 'line-color': '#dc3545' }
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
layout: {
|
|
344
|
+
name: 'cose',
|
|
345
|
+
animate: false,
|
|
346
|
+
padding: 14,
|
|
347
|
+
nodeRepulsion: function () { return 6000; },
|
|
348
|
+
idealEdgeLength: function () { return 60; },
|
|
349
|
+
edgeElasticity: function () { return 60; },
|
|
350
|
+
nodeOverlap: 12,
|
|
351
|
+
gravity: 0.3,
|
|
352
|
+
numIter: 1200,
|
|
353
|
+
fit: true
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
container.__obsidianLocalGraph = cy;
|
|
358
|
+
|
|
359
|
+
cy.on('tap', 'node', function (evt) {
|
|
360
|
+
var d = evt.target.data();
|
|
361
|
+
if (!d.url || d.broken) return;
|
|
362
|
+
if (d.url === currentUrl) return;
|
|
363
|
+
// ⌘/Ctrl-click opens in a new tab, mirroring the full graph page.
|
|
364
|
+
var oe = evt.originalEvent;
|
|
365
|
+
if (oe && (oe.metaKey || oe.ctrlKey)) {
|
|
366
|
+
window.open(d.url, '_blank', 'noopener');
|
|
367
|
+
} else {
|
|
368
|
+
window.location.href = d.url;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
cy.on('mouseover', 'node', function (evt) {
|
|
373
|
+
evt.target.style('z-index', 99);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
requestAnimationFrame(function () {
|
|
377
|
+
resizeGraph(container);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
return cy;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function init() {
|
|
384
|
+
var container = document.getElementById(CONTAINER_ID);
|
|
385
|
+
if (!container) return;
|
|
386
|
+
setPanelAvailable(container, true);
|
|
387
|
+
setStatus(container, 'Loading graph...', false);
|
|
388
|
+
|
|
389
|
+
var panel = container.closest(PANEL_SELECTOR);
|
|
390
|
+
if (panel) {
|
|
391
|
+
panel.addEventListener('shown.bs.offcanvas', function () {
|
|
392
|
+
resizeGraph(container);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
window.addEventListener('resize', function () {
|
|
397
|
+
resizeGraph(container);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
var depth = parseInt(container.getAttribute('data-depth') || '1', 10);
|
|
401
|
+
if (!isFinite(depth) || depth < 1) depth = 1;
|
|
402
|
+
var indexUrl = container.getAttribute('data-index-url') ||
|
|
403
|
+
((document.querySelector('base') || {}).href || '/') +
|
|
404
|
+
'assets/data/wiki-index.json';
|
|
405
|
+
|
|
406
|
+
fetch(indexUrl, { credentials: 'same-origin' })
|
|
407
|
+
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
|
|
408
|
+
.then(function (data) {
|
|
409
|
+
var entries = Array.isArray(data && data.entries) ? data.entries : [];
|
|
410
|
+
if (!entries.length) { setPanelAvailable(container, false); return; }
|
|
411
|
+
var lookup = buildLookup(entries);
|
|
412
|
+
var current = findCurrentEntry(lookup);
|
|
413
|
+
if (!current) { setPanelAvailable(container, false); return; }
|
|
414
|
+
var elements = buildSubgraph(entries, lookup, current, depth);
|
|
415
|
+
loadCytoscape(function () {
|
|
416
|
+
render(container, elements, current.url);
|
|
417
|
+
var nodeCount = elements.filter(function (element) { return element.group === 'nodes'; }).length;
|
|
418
|
+
var edgeCount = elements.filter(function (element) { return element.group === 'edges'; }).length;
|
|
419
|
+
setStatus(container, nodeCount + ' pages · ' + edgeCount + ' links', false);
|
|
420
|
+
});
|
|
421
|
+
})
|
|
422
|
+
.catch(function (err) {
|
|
423
|
+
// Sidebar panel failing is non-fatal — hide and stay quiet.
|
|
424
|
+
console.warn('[obsidian-local-graph] init failed:', err);
|
|
425
|
+
setPanelAvailable(container, false);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (document.readyState === 'loading') {
|
|
430
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
431
|
+
} else {
|
|
432
|
+
init();
|
|
433
|
+
}
|
|
434
|
+
})();
|