jekyll-theme-zer0 1.3.0 → 1.4.1
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 +41 -0
- data/README.md +4 -4
- data/_includes/components/cookie-consent.html +2 -3
- data/_includes/components/info-section.html +9 -4
- data/_includes/components/js-cdn.html +8 -1
- data/_includes/content/backlinks.html +4 -0
- data/_includes/core/footer.html +9 -8
- data/_includes/navigation/local-graph.html +55 -20
- data/_includes/navigation/sidebar-left.html +0 -6
- data/_includes/obsidian/full-graph.html +170 -0
- data/_layouts/default.html +104 -100
- data/_plugins/obsidian_links.rb +5 -0
- data/_plugins/preview_image_generator.rb +82 -25
- data/_sass/core/_obsidian.scss +77 -4
- data/_sass/core/_offcanvas-panels.scss +8 -4
- data/assets/data/wiki-index.json +44 -6
- data/assets/js/obsidian-graph.js +0 -1
- data/assets/js/obsidian-local-graph.js +72 -21
- 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 +5 -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
|
}
|
|
@@ -242,7 +277,6 @@
|
|
|
242
277
|
var cy = window.cytoscape({
|
|
243
278
|
container: container,
|
|
244
279
|
elements: elements,
|
|
245
|
-
wheelSensitivity: 0.2,
|
|
246
280
|
minZoom: 0.3,
|
|
247
281
|
maxZoom: 3,
|
|
248
282
|
autoungrabify: false,
|
|
@@ -252,7 +286,7 @@
|
|
|
252
286
|
style: {
|
|
253
287
|
'background-color': 'data(color)',
|
|
254
288
|
'label': 'data(label)',
|
|
255
|
-
'font-size': '
|
|
289
|
+
'font-size': '11px',
|
|
256
290
|
'font-weight': 500,
|
|
257
291
|
'color': theme.label,
|
|
258
292
|
'text-outline-color': theme.labelOutline,
|
|
@@ -262,8 +296,8 @@
|
|
|
262
296
|
'text-valign': 'bottom',
|
|
263
297
|
'text-margin-y': 4,
|
|
264
298
|
'text-wrap': 'ellipsis',
|
|
265
|
-
'text-max-width': '
|
|
266
|
-
'width':
|
|
299
|
+
'text-max-width': '140px',
|
|
300
|
+
'width': 16, 'height': 16,
|
|
267
301
|
'border-width': 1.5,
|
|
268
302
|
'border-color': theme.nodeBorder,
|
|
269
303
|
'transition-property': 'background-color, border-color, width, height',
|
|
@@ -319,6 +353,8 @@
|
|
|
319
353
|
}
|
|
320
354
|
});
|
|
321
355
|
|
|
356
|
+
container.__obsidianLocalGraph = cy;
|
|
357
|
+
|
|
322
358
|
cy.on('tap', 'node', function (evt) {
|
|
323
359
|
var d = evt.target.data();
|
|
324
360
|
if (!d.url || d.broken) return;
|
|
@@ -336,12 +372,30 @@
|
|
|
336
372
|
evt.target.style('z-index', 99);
|
|
337
373
|
});
|
|
338
374
|
|
|
375
|
+
requestAnimationFrame(function () {
|
|
376
|
+
resizeGraph(container);
|
|
377
|
+
});
|
|
378
|
+
|
|
339
379
|
return cy;
|
|
340
380
|
}
|
|
341
381
|
|
|
342
382
|
function init() {
|
|
343
383
|
var container = document.getElementById(CONTAINER_ID);
|
|
344
384
|
if (!container) return;
|
|
385
|
+
setPanelAvailable(container, true);
|
|
386
|
+
setStatus(container, 'Loading graph...', false);
|
|
387
|
+
|
|
388
|
+
var panel = container.closest(PANEL_SELECTOR);
|
|
389
|
+
if (panel) {
|
|
390
|
+
panel.addEventListener('shown.bs.offcanvas', function () {
|
|
391
|
+
resizeGraph(container);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
window.addEventListener('resize', function () {
|
|
396
|
+
resizeGraph(container);
|
|
397
|
+
});
|
|
398
|
+
|
|
345
399
|
var depth = parseInt(container.getAttribute('data-depth') || '1', 10);
|
|
346
400
|
if (!isFinite(depth) || depth < 1) depth = 1;
|
|
347
401
|
var indexUrl = container.getAttribute('data-index-url') ||
|
|
@@ -352,25 +406,22 @@
|
|
|
352
406
|
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
|
|
353
407
|
.then(function (data) {
|
|
354
408
|
var entries = Array.isArray(data && data.entries) ? data.entries : [];
|
|
355
|
-
if (!entries.length) { container
|
|
409
|
+
if (!entries.length) { setPanelAvailable(container, false); return; }
|
|
356
410
|
var lookup = buildLookup(entries);
|
|
357
411
|
var current = findCurrentEntry(lookup);
|
|
358
|
-
if (!current) { container
|
|
412
|
+
if (!current) { setPanelAvailable(container, false); return; }
|
|
359
413
|
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
414
|
loadCytoscape(function () {
|
|
367
415
|
render(container, elements, current.url);
|
|
416
|
+
var nodeCount = elements.filter(function (element) { return element.group === 'nodes'; }).length;
|
|
417
|
+
var edgeCount = elements.filter(function (element) { return element.group === 'edges'; }).length;
|
|
418
|
+
setStatus(container, nodeCount + ' pages · ' + edgeCount + ' links', false);
|
|
368
419
|
});
|
|
369
420
|
})
|
|
370
421
|
.catch(function (err) {
|
|
371
|
-
// Sidebar
|
|
422
|
+
// Sidebar panel failing is non-fatal — hide and stay quiet.
|
|
372
423
|
console.warn('[obsidian-local-graph] init failed:', err);
|
|
373
|
-
container
|
|
424
|
+
setPanelAvailable(container, false);
|
|
374
425
|
});
|
|
375
426
|
}
|
|
376
427
|
|
|
@@ -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
|