jekyll-theme-zer0 1.3.0 → 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 +29 -0
- data/README.md +4 -4
- data/_includes/components/js-cdn.html +8 -1
- data/_includes/content/backlinks.html +4 -0
- data/_includes/navigation/local-graph.html +55 -20
- data/_layouts/default.html +104 -100
- data/_sass/core/_obsidian.scss +77 -4
- data/_sass/core/_offcanvas-panels.scss +8 -4
- data/assets/js/obsidian-local-graph.js +72 -20
- data/assets/js/obsidian-wiki-links.js +21 -4
- 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/validate +11 -0
- metadata +4 -2
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* obsidian-local-graph.js
|
|
3
3
|
*
|
|
4
|
-
* Renders a
|
|
4
|
+
* Renders a focused "local graph" (current page + immediate neighbors) into
|
|
5
5
|
* the element with id `obsidian-local-graph`. Mirrors Obsidian's local
|
|
6
6
|
* graph view: a focused, page-scoped subgraph instead of the full site map.
|
|
7
7
|
*
|
|
8
|
-
* Loaded by _includes/navigation/local-graph.html
|
|
9
|
-
*
|
|
10
|
-
* same CDN as the full graph page.
|
|
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
11
|
*
|
|
12
12
|
* Subgraph:
|
|
13
13
|
* - center = current page (matched against entry.url, falling back to
|
|
@@ -15,17 +15,52 @@
|
|
|
15
15
|
* - depth = configurable via data-depth attribute (default 1)
|
|
16
16
|
* - direction = both incoming and outgoing wiki-links
|
|
17
17
|
*
|
|
18
|
-
* If the current page is
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
21
|
*/
|
|
22
22
|
(function () {
|
|
23
23
|
'use strict';
|
|
24
24
|
|
|
25
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]';
|
|
26
29
|
var CYTOSCAPE_URL = 'https://cdn.jsdelivr.net/npm/cytoscape@3.30.0/dist/cytoscape.min.js';
|
|
27
30
|
var CYTOSCAPE_SRI = 'sha384-kpMsYllYzyaWU69Piok08rPNktpnjqAoDMdB00fjqUkEk3lkuUbSuwJ+oXrjvN6B';
|
|
28
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
|
+
|
|
29
64
|
function normalize(value) {
|
|
30
65
|
return String(value || '').toLowerCase().trim().replace(/\s+/g, ' ');
|
|
31
66
|
}
|
|
@@ -252,7 +287,7 @@
|
|
|
252
287
|
style: {
|
|
253
288
|
'background-color': 'data(color)',
|
|
254
289
|
'label': 'data(label)',
|
|
255
|
-
'font-size': '
|
|
290
|
+
'font-size': '11px',
|
|
256
291
|
'font-weight': 500,
|
|
257
292
|
'color': theme.label,
|
|
258
293
|
'text-outline-color': theme.labelOutline,
|
|
@@ -262,8 +297,8 @@
|
|
|
262
297
|
'text-valign': 'bottom',
|
|
263
298
|
'text-margin-y': 4,
|
|
264
299
|
'text-wrap': 'ellipsis',
|
|
265
|
-
'text-max-width': '
|
|
266
|
-
'width':
|
|
300
|
+
'text-max-width': '140px',
|
|
301
|
+
'width': 16, 'height': 16,
|
|
267
302
|
'border-width': 1.5,
|
|
268
303
|
'border-color': theme.nodeBorder,
|
|
269
304
|
'transition-property': 'background-color, border-color, width, height',
|
|
@@ -319,6 +354,8 @@
|
|
|
319
354
|
}
|
|
320
355
|
});
|
|
321
356
|
|
|
357
|
+
container.__obsidianLocalGraph = cy;
|
|
358
|
+
|
|
322
359
|
cy.on('tap', 'node', function (evt) {
|
|
323
360
|
var d = evt.target.data();
|
|
324
361
|
if (!d.url || d.broken) return;
|
|
@@ -336,12 +373,30 @@
|
|
|
336
373
|
evt.target.style('z-index', 99);
|
|
337
374
|
});
|
|
338
375
|
|
|
376
|
+
requestAnimationFrame(function () {
|
|
377
|
+
resizeGraph(container);
|
|
378
|
+
});
|
|
379
|
+
|
|
339
380
|
return cy;
|
|
340
381
|
}
|
|
341
382
|
|
|
342
383
|
function init() {
|
|
343
384
|
var container = document.getElementById(CONTAINER_ID);
|
|
344
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
|
+
|
|
345
400
|
var depth = parseInt(container.getAttribute('data-depth') || '1', 10);
|
|
346
401
|
if (!isFinite(depth) || depth < 1) depth = 1;
|
|
347
402
|
var indexUrl = container.getAttribute('data-index-url') ||
|
|
@@ -352,25 +407,22 @@
|
|
|
352
407
|
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
|
|
353
408
|
.then(function (data) {
|
|
354
409
|
var entries = Array.isArray(data && data.entries) ? data.entries : [];
|
|
355
|
-
if (!entries.length) { container
|
|
410
|
+
if (!entries.length) { setPanelAvailable(container, false); return; }
|
|
356
411
|
var lookup = buildLookup(entries);
|
|
357
412
|
var current = findCurrentEntry(lookup);
|
|
358
|
-
if (!current) { container
|
|
413
|
+
if (!current) { setPanelAvailable(container, false); return; }
|
|
359
414
|
var elements = buildSubgraph(entries, lookup, current, depth);
|
|
360
|
-
// If the current page has no neighbors, show a tiny "no links yet"
|
|
361
|
-
// hint instead of a single dot.
|
|
362
|
-
if (elements.length <= 1) {
|
|
363
|
-
container.style.display = 'none';
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
415
|
loadCytoscape(function () {
|
|
367
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);
|
|
368
420
|
});
|
|
369
421
|
})
|
|
370
422
|
.catch(function (err) {
|
|
371
|
-
// Sidebar
|
|
423
|
+
// Sidebar panel failing is non-fatal — hide and stay quiet.
|
|
372
424
|
console.warn('[obsidian-local-graph] init failed:', err);
|
|
373
|
-
container
|
|
425
|
+
setPanelAvailable(container, false);
|
|
374
426
|
});
|
|
375
427
|
}
|
|
376
428
|
|
|
@@ -26,11 +26,28 @@
|
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
var RESOLVER_SCRIPT_PATH = 'assets/js/obsidian-wiki-links.js';
|
|
30
|
+
var OBSIDIAN_CONFIG = window.OBSIDIAN_CONFIG || {};
|
|
31
|
+
|
|
32
|
+
function trimTrailingSlash(value) {
|
|
33
|
+
return (value === null || value === undefined ? '' : String(value)).replace(/\/$/, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assetPath(path) {
|
|
37
|
+
var script = document.currentScript || document.querySelector('script[src*="obsidian-wiki-links.js"]');
|
|
38
|
+
var src = script && script.getAttribute('src');
|
|
39
|
+
var escapedScriptPath = RESOLVER_SCRIPT_PATH.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
40
|
+
var match = src && src.match(new RegExp('^(.*?)' + escapedScriptPath + '(?:[?#].*)?$'));
|
|
41
|
+
if (match) return trimTrailingSlash(match[1]) + path;
|
|
42
|
+
|
|
43
|
+
var baseHref = (document.querySelector('base') || {}).href;
|
|
44
|
+
return trimTrailingSlash(baseHref) + path;
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
var CONFIG = {
|
|
30
|
-
indexUrl:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
tagBase: window.OBSIDIAN_TAG_BASE || '/tags/',
|
|
48
|
+
indexUrl: OBSIDIAN_CONFIG.wikiIndexUrl || window.OBSIDIAN_WIKI_INDEX_URL || assetPath('/assets/data/wiki-index.json'),
|
|
49
|
+
attachmentsPath: OBSIDIAN_CONFIG.attachmentsPath || window.OBSIDIAN_ATTACHMENTS_PATH || assetPath('/assets/images/notes'),
|
|
50
|
+
tagBase: OBSIDIAN_CONFIG.tagBase || window.OBSIDIAN_TAG_BASE || assetPath('/tags/'),
|
|
34
51
|
wikiLinkClass: 'wiki-link',
|
|
35
52
|
brokenLinkClass: 'wiki-link wiki-link-broken'
|
|
36
53
|
};
|
data/scripts/README.md
CHANGED
|
@@ -8,6 +8,7 @@ This directory contains automation scripts for managing the `jekyll-theme-zer0`
|
|
|
8
8
|
scripts/
|
|
9
9
|
├── bin/ # Entry point commands (use these!)
|
|
10
10
|
│ ├── build # Build gem without releasing
|
|
11
|
+
│ ├── validate # Preflight checks for local/CI validation
|
|
11
12
|
│ ├── release # Full release workflow
|
|
12
13
|
│ └── test # Run all test suites
|
|
13
14
|
├── lib/ # Shared libraries (sourced, not executed)
|
|
@@ -39,6 +40,10 @@ scripts/
|
|
|
39
40
|
# Build gem
|
|
40
41
|
./scripts/bin/build
|
|
41
42
|
|
|
43
|
+
# Preflight validation
|
|
44
|
+
./scripts/bin/validate --quick
|
|
45
|
+
./scripts/bin/validate --start-docker
|
|
46
|
+
|
|
42
47
|
# Full release workflow
|
|
43
48
|
./scripts/bin/release patch # or minor/major
|
|
44
49
|
|
|
@@ -57,6 +62,23 @@ Build the gem without the full release workflow.
|
|
|
57
62
|
./scripts/bin/build [--dry-run] [--verbose]
|
|
58
63
|
```
|
|
59
64
|
|
|
65
|
+
#### `bin/validate`
|
|
66
|
+
Run preflight validation before refactors, pull requests, and releases. The
|
|
67
|
+
quick path validates repository files, version consistency, YAML parsing, active
|
|
68
|
+
configuration contracts, config-file classification, and navigation data before
|
|
69
|
+
the Docker/local build stages run.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
./scripts/bin/validate [options]
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
--quick Host-only checks for CI fast feedback
|
|
76
|
+
--full Include tests, Obsidian tests, and HTMLProofer
|
|
77
|
+
--start-docker Start the jekyll Docker Compose service if needed
|
|
78
|
+
--docker Require Docker Compose for Jekyll commands
|
|
79
|
+
--local Require local bundle exec for Jekyll commands
|
|
80
|
+
```
|
|
81
|
+
|
|
60
82
|
#### `bin/release`
|
|
61
83
|
Full release workflow with changelog, version bump, and publishing.
|
|
62
84
|
|
data/scripts/bin/test
CHANGED
|
@@ -12,6 +12,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
12
12
|
SCRIPTS_ROOT="$SCRIPT_DIR/.."
|
|
13
13
|
LIB_DIR="$SCRIPTS_ROOT/lib"
|
|
14
14
|
TEST_DIR="$SCRIPTS_ROOT/test"
|
|
15
|
+
REPO_ROOT="$(cd "$SCRIPTS_ROOT/.." && pwd)"
|
|
16
|
+
THEME_TEST_RUNNER="$REPO_ROOT/test/test_runner.sh"
|
|
15
17
|
|
|
16
18
|
# Source common library for logging
|
|
17
19
|
source "$LIB_DIR/common.sh"
|
|
@@ -37,6 +39,10 @@ TEST_SUITES:
|
|
|
37
39
|
integration Run integration tests only
|
|
38
40
|
install Run installer e2e suites only (test/test_install_*.sh)
|
|
39
41
|
|
|
42
|
+
THEME SUITES:
|
|
43
|
+
core, deployment, quality, installation, site_generation, styling,
|
|
44
|
+
visual, full, or --suites <list> are forwarded to test/test_runner.sh.
|
|
45
|
+
|
|
40
46
|
OPTIONS:
|
|
41
47
|
--verbose, -v Show detailed test output
|
|
42
48
|
--dry-run Preview what tests would run
|
|
@@ -54,6 +60,62 @@ TEST LOCATIONS:
|
|
|
54
60
|
EOF
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
# Pass the repository-level theme runner's interface through this canonical
|
|
64
|
+
# command so CI, docs, and local users can converge on scripts/bin/test without
|
|
65
|
+
# breaking existing --suites calls.
|
|
66
|
+
maybe_delegate_to_theme_runner() {
|
|
67
|
+
[[ -f "$THEME_TEST_RUNNER" ]] || return 0
|
|
68
|
+
|
|
69
|
+
local should_delegate=false
|
|
70
|
+
local positional_suites=""
|
|
71
|
+
local delegated_args=()
|
|
72
|
+
|
|
73
|
+
while [[ $# -gt 0 ]]; do
|
|
74
|
+
case "$1" in
|
|
75
|
+
--suites|-s)
|
|
76
|
+
should_delegate=true
|
|
77
|
+
delegated_args+=("$1")
|
|
78
|
+
shift
|
|
79
|
+
[[ $# -gt 0 ]] || error "--suites requires a value"
|
|
80
|
+
delegated_args+=("$1")
|
|
81
|
+
;;
|
|
82
|
+
core|deployment|quality|installation|site_generation|styling|visual|full)
|
|
83
|
+
should_delegate=true
|
|
84
|
+
if [[ -n "$positional_suites" ]]; then
|
|
85
|
+
positional_suites="${positional_suites},$1"
|
|
86
|
+
else
|
|
87
|
+
positional_suites="$1"
|
|
88
|
+
fi
|
|
89
|
+
;;
|
|
90
|
+
--skip-docker|--skip-remote|--coverage|-c|--parallel|-p|--retry-failed|-r|--fail-fast|--baseline-compare)
|
|
91
|
+
should_delegate=true
|
|
92
|
+
delegated_args+=("$1")
|
|
93
|
+
;;
|
|
94
|
+
--format|-f|--timeout|-t|--environment|-e)
|
|
95
|
+
should_delegate=true
|
|
96
|
+
local option_name="$1"
|
|
97
|
+
delegated_args+=("$1")
|
|
98
|
+
shift
|
|
99
|
+
[[ $# -gt 0 ]] || error "$option_name requires a value"
|
|
100
|
+
delegated_args+=("$1")
|
|
101
|
+
;;
|
|
102
|
+
--verbose|-v)
|
|
103
|
+
delegated_args+=("$1")
|
|
104
|
+
;;
|
|
105
|
+
esac
|
|
106
|
+
shift
|
|
107
|
+
done
|
|
108
|
+
|
|
109
|
+
if [[ "$should_delegate" == "true" ]]; then
|
|
110
|
+
if [[ -n "$positional_suites" ]]; then
|
|
111
|
+
delegated_args+=("--suites" "$positional_suites")
|
|
112
|
+
fi
|
|
113
|
+
exec "$THEME_TEST_RUNNER" "${delegated_args[@]}"
|
|
114
|
+
fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
maybe_delegate_to_theme_runner "$@"
|
|
118
|
+
|
|
57
119
|
# Parse arguments
|
|
58
120
|
TEST_SUITE="all"
|
|
59
121
|
for arg in "$@"; do
|